【问题标题】:HK2 Factory with Interface Type and InjectionResolver具有接口类型和 InjectionResolver 的 HK2 工厂
【发布时间】:2015-10-09 11:26:28
【问题描述】:

在我的用例中,我需要开发一个自定义注释,以便我可以实例化 DAO 的实现。

所以我有了界面:

public interface IDAO{
     public void method1();
     public void method2();
}

以及资源配置实现:

public class JAXRSConfig extends ResourceConfig {

    public JAXRSConfig() {
        register(new AbstractBinder() {
            @Override
            protected void configure() {
                /*Factory Classes Binding*/
                bindFactory(DaoFactory.class).to(IDAO.class).in(RequestScoped.class);

            /*Injection Resolver Binding*/
                bind(CustomContextInjectionResolver.class).to(new TypeLiteral<InjectionResolver<CustomContext>>(){}).in(Singleton.class);
        }
    });
}

我坚持使用工厂实现:

public class DaoFactory implements Factory<IDAO>{

    private final HttpServletRequest request;

    @Inject
    public DaoFactory(HttpServletRequest request) {
        this.request = request;
    }

    @Override
    public  IDAO  provide() {

        IDAO dao = null;
        try {

            ???????????

        } catch (InstantiationException | IllegalAccessException e) {
            e.printStackTrace();
        }
        return dao;
    }

    @Override
    public void dispose( IDAO  mud) {   
    }
}

这里当然有我的 IDAO 实现:

public class DAOImplementation implements IDAO {
    public void method1(){
        //do some stuff
    }

    public void method2(){
       //do some stuff
    }

    public MyEntity getEntity(){
     //get my entity 
    }
}

我想要得到的结果是:

@Path("/myResource")
public class myService(){

      @CustomContext
      DAOImplementation myDao;

    public String myService(){
       MyEntity entity =  myDao.getEntity(); 
    }

}

有没有办法将工厂连接到注入解析器,我可以获得真正的实现来提供? hk2 是否提供任何方法来做到这一点?

已编辑 我可以有多个 IDAO 接口实现...例如,如果我有:

public class DAOImplementation2 implements IDAO {
    public void method1(){
        //do some stuff
    }

    public void method2(){
       //do some stuff
    }

    public MyEntity2 getEntity2(){
     //get my entity 
    }
}

我应该能够像这样获得第二个实现:

 @Path("/myResource")
 public class myService(){

      @CustomContext
      DAOImplementation myDao;

      @CustomContext
      DAOImplementation2 mySecondDao;


    public String myService(){
       MyEntity entity =  myDao.getEntity(); 
       MyEntity2 entity =  mySecondDao.getEntity2(); 

    }

}

【问题讨论】:

  • 当然是的...我的问题是提供方法。好的 RequestScoped IDAO ...是我的错误(我已编辑和更正)。无论如何,您是说我应该绑定所有要注入的类型吗?从这个意义上说,我需要开发一个与类型无关的框架。我可以简单地实现 IDAO 接口注入我自己的自定义 dao 的方式。有可能吗?
  • 如果你说只是通过实现接口,你希望实现自动注册,我不知道它是如何工作的。
  • 哦,好吧...是的,我有多个实现。目的是:如果有人要实现自己的 IDAO,他可以做到,并且最终他可以通过注释获得自己的实例。我已经编辑了我的帖子。工厂提供方法应该知道注解的类型,以便能够选择正确的实现来实例化。
  • 让chat
  • 你们解决了吗?

标签: java dependency-injection jersey jersey-2.0 hk2


【解决方案1】:

因此,根据我们之前的聊天,以下是我试图传达的想法。基本上,您将添加一个Feature,用户可以将IDao 实现类传递给其中。在Feature 中可以按名称绑定它们

public static class DaoFeature implements Feature {
    
    private final Class<? extends IDao>[] daoClasses;
    
    public DaoFeature(Class<? extends IDao> ... daoClasses) {
        this.daoClasses = daoClasses;
    }

    @Override
    public boolean configure(FeatureContext context) {
        context.register(new Binder());
        return true;
    } 
    
    private class Binder extends AbstractBinder {
        @Override
        protected void configure() {
            ...
            for (Class<? extends IDao> daoClass: daoClasses) {
                bind(daoClass).to(IDao.class)
                        .named(daoClass.getCanonicalName()).in(RequestScoped.class);
            }
        }    
    }
}

然后在InjectionResolver 中,您可以按名称查找然后将其添加到CloseableService。所有这些都不需要任何丑陋的反射。

public static class CustomContextResolver 
        implements InjectionResolver<CustomContext> {
    
    @Inject
    private ServiceLocator locator;
    
    @Inject
    private IDaoProviders daoClasses;
    
    @Inject
    private CloseableService closeableService;

    @Override
    public Object resolve(Injectee injectee, ServiceHandle<?> root) {
        Type requiredType = injectee.getRequiredType();
        for (Class type: daoClasses.getDaoClasses()) {
            if (requiredType == type) {
                IDao dao = locator.getService(IDao.class, type.getCanonicalName());
                addToCloseableService(dao);
                return type.cast(dao);
            }
        }
        return null;
    }
    ...
}

EntityManager 将由Factory 处理。我使用的Factory 是一个Jersey 类,它可以让你获得ContainerRequest,你几乎可以从HttpServletRequest 获得任何你能得到的东西

public static class DummyEntityManagerFactory 
        extends AbstractContainerRequestValueFactory<DummyEntityManager> {

    @Override
    public DummyEntityManager provide() {
        ContainerRequest request = getContainerRequest();
        // get some condition for EntityManager
        return new DummyEntityManager();
    }
}

在抽象的IDao类中,可以注入EntityManager,自行处理和处置资源。

public static abstract class IDao {
    
    @Inject
    private DummyEntityManager em;
    
    protected abstract String getData();
    
    public void close() {
        em.close();
    }
    
    protected DummyEntityManager getEntityManager() {
        return em;
    }
}

这是完整的测试用例

import java.io.Closeable;
import java.io.IOException;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.inject.Inject;
import javax.inject.Singleton;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.core.Feature;
import javax.ws.rs.core.FeatureContext;
import javax.ws.rs.core.Response;
import org.glassfish.hk2.api.Injectee;
import org.glassfish.hk2.api.InjectionResolver;
import org.glassfish.hk2.api.ServiceHandle;
import org.glassfish.hk2.api.ServiceLocator;
import org.glassfish.hk2.api.TypeLiteral;
import org.glassfish.hk2.utilities.binding.AbstractBinder;
import org.glassfish.jersey.process.internal.RequestScoped;
import org.glassfish.jersey.server.CloseableService;
import org.glassfish.jersey.server.ContainerRequest;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.server.internal.inject.AbstractContainerRequestValueFactory;
import org.glassfish.jersey.test.JerseyTest;
import static org.junit.Assert.assertEquals;
import org.junit.Test;

public class CustomDaoTest extends JerseyTest {
    
    public static class DummyEntityManager {
        String findByDaoClass(Class cls) {
            return "Data from " + cls.getSimpleName();
        }
        public void close() { /* noop */ }
    }
    
    public static abstract class IDao {
        
        private static final Logger LOG = Logger.getLogger(IDao.class.getName());
        
        @Inject
        private DummyEntityManager em;
        
        protected abstract String getData();
        
        public void close() {
            LOG.log(Level.INFO, "Closing IDAO: {0}", this.getClass().getName());
            em.close();
        }
        
        protected DummyEntityManager getEntityManager() {
            return em;
        }
    }
    
    public static class DaoImplOne extends IDao {
        @Override
        public String getData() {
            return getEntityManager().findByDaoClass(this.getClass());
        }
    }
    
    public static class DaoImplTwo extends IDao {
        @Override
        protected String getData() {
            return getEntityManager().findByDaoClass(this.getClass());
        }   
    }
    
    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.FIELD, ElementType.CONSTRUCTOR})
    public static @interface CustomContext{}
    
    public static class CustomContextResolver 
            implements InjectionResolver<CustomContext> {
        
        @Inject
        private ServiceLocator locator;
        
        @Inject
        private IDaoProviders daoClasses;
        
        @Inject
        private CloseableService closeableService;

        @Override
        public Object resolve(Injectee injectee, ServiceHandle<?> root) {
            Type requiredType = injectee.getRequiredType();
            for (Class type: daoClasses.getDaoClasses()) {
                if (requiredType == type) {
                    IDao dao = locator.getService(IDao.class, type.getCanonicalName());
                    addToCloseableService(dao);
                    return type.cast(dao);
                }
            }
            return null;
        }
        
        private void addToCloseableService(final IDao idao) {
            closeableService.add(new Closeable(){
                @Override
                public void close() throws IOException {
                    idao.close();
                }
            });
        }

        @Override
        public boolean isConstructorParameterIndicator() {
            return false;
        }

        @Override
        public boolean isMethodParameterIndicator() {
            return false;
        } 
    }
    
    public static class DummyEntityManagerFactory 
            extends AbstractContainerRequestValueFactory<DummyEntityManager> {

        @Override
        public DummyEntityManager provide() {
            ContainerRequest request = getContainerRequest();
            // get some condition for EntityManager
            return new DummyEntityManager();
        }
    }
    
    public static class IDaoProviders {
        
        private final List<Class<? extends IDao>> daoClasses;
        
        public IDaoProviders(Class<? extends IDao> ... daoClasses) {
            this.daoClasses = new ArrayList<>(Arrays.asList(daoClasses));
        }
        
        public List<Class<? extends IDao>> getDaoClasses() {
            return daoClasses;
        }
    }
    
    public static class DaoFeature implements Feature {
        
        private final Class<? extends IDao>[] daoClasses;
        
        public DaoFeature(Class<? extends IDao> ... daoClasses) {
            this.daoClasses = daoClasses;
        }

        @Override
        public boolean configure(FeatureContext context) {
            context.register(new Binder());
            return true;
        } 
        
        private class Binder extends AbstractBinder {
            @Override
            protected void configure() {
                bind(CustomContextResolver.class)
                        .to(new TypeLiteral<InjectionResolver<CustomContext>>(){})
                        .in(Singleton.class);
                bindFactory(DummyEntityManagerFactory.class)
                        .to(DummyEntityManager.class)
                        .in(RequestScoped.class);
                
                for (Class<? extends IDao> daoClass: daoClasses) {
                    bind(daoClass).to(IDao.class)
                            .named(daoClass.getCanonicalName()).in(RequestScoped.class);
                }
                
                IDaoProviders daoProviders = new IDaoProviders(daoClasses);
                bind(daoProviders).to(IDaoProviders.class);
            }    
        }
    }
    
    @Path("dao")
    public static class DaoResource {
        
        @CustomContext
        private DaoImplOne daoOne;
        
        @CustomContext
        private DaoImplTwo daoTwo;
        
        @GET
        @Path("one")
        public String getOne() {
            return daoOne.getData();
        }
        
        @GET
        @Path("two")
        public String getTwo() {
            return daoTwo.getData();
        }
    }
    
    @Override
    public ResourceConfig configure() {
        return new ResourceConfig(DaoResource.class)
                .register(new DaoFeature(DaoImplOne.class, DaoImplTwo.class));
    }
    
    @Test
    public void should_return_dao_one_data() {
        Response response = target("dao/one").request().get();
        assertEquals(200, response.getStatus());
        assertEquals("Data from DaoImplOne", response.readEntity(String.class));
        response.close();
    }
    
    @Test
    public void should_return_dao_two_data() {
        Response response = target("dao/two").request().get();
        assertEquals(200, response.getStatus());
        assertEquals("Data from DaoImplTwo", response.readEntity(String.class));
        response.close();
    }
}

更新

使用 web.xml(无 ResourceConfig)

import java.io.Closeable;
import java.io.IOException;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.inject.Inject;
import javax.inject.Singleton;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.core.Feature;
import javax.ws.rs.core.FeatureContext;
import javax.ws.rs.core.Response;
import org.glassfish.hk2.api.Injectee;
import org.glassfish.hk2.api.InjectionResolver;
import org.glassfish.hk2.api.ServiceHandle;
import org.glassfish.hk2.api.ServiceLocator;
import org.glassfish.hk2.api.TypeLiteral;
import org.glassfish.hk2.utilities.binding.AbstractBinder;
import org.glassfish.jersey.internal.util.PropertiesHelper;
import org.glassfish.jersey.process.internal.RequestScoped;
import org.glassfish.jersey.server.CloseableService;
import org.glassfish.jersey.server.ContainerRequest;
import org.glassfish.jersey.server.internal.inject.AbstractContainerRequestValueFactory;
import org.glassfish.jersey.servlet.ServletContainer;
import org.glassfish.jersey.test.DeploymentContext;
import org.glassfish.jersey.test.JerseyTest;
import org.glassfish.jersey.test.ServletDeploymentContext;
import org.glassfish.jersey.test.grizzly.GrizzlyWebTestContainerFactory;
import org.glassfish.jersey.test.spi.TestContainerFactory;
import static org.junit.Assert.assertEquals;
import org.junit.Test;

public class WebXmlCustomDaoTest extends JerseyTest {

    public static class DummyEntityManager {

        String findByDaoClass(Class cls) {
            return "Data from " + cls.getSimpleName();
        }

        public void close() { /* noop */ }
    }

    public static abstract class IDao {

        private static final Logger LOG = Logger.getLogger(IDao.class.getName());

        @Inject
        private DummyEntityManager em;

        protected abstract String getData();

        public void close() {
            LOG.log(Level.INFO, "Closing IDAO: {0}", this.getClass().getName());
            em.close();
        }

        protected DummyEntityManager getEntityManager() {
            return em;
        }
    }

    public static class DaoImplOne extends IDao {

        @Override
        public String getData() {
            return getEntityManager().findByDaoClass(this.getClass());
        }
    }

    public static class DaoImplTwo extends IDao {

        @Override
        protected String getData() {
            return getEntityManager().findByDaoClass(this.getClass());
        }
    }

    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.FIELD, ElementType.CONSTRUCTOR})
    public static @interface CustomContext {
    }

    public static class CustomContextResolver
            implements InjectionResolver<CustomContext> {

        @Inject
        private ServiceLocator locator;

        @Inject
        private IDaoProviders daoClasses;

        @Inject
        private CloseableService closeableService;

        @Override
        public Object resolve(Injectee injectee, ServiceHandle<?> root) {
            Type requiredType = injectee.getRequiredType();
            for (Class type : daoClasses.getDaoClasses()) {
                if (requiredType == type) {
                    IDao dao = locator.getService(IDao.class, type.getCanonicalName());
                    addToCloseableService(dao);
                    return type.cast(dao);
                }
            }
            return null;
        }

        private void addToCloseableService(final IDao idao) {
            closeableService.add(new Closeable() {
                @Override
                public void close() throws IOException {
                    idao.close();
                }
            });
        }

        @Override
        public boolean isConstructorParameterIndicator() {
            return false;
        }

        @Override
        public boolean isMethodParameterIndicator() {
            return false;
        }
    }

    public static class DummyEntityManagerFactory
            extends AbstractContainerRequestValueFactory<DummyEntityManager> {

        @Override
        public DummyEntityManager provide() {
            ContainerRequest request = getContainerRequest();
            // get some condition for EntityManager
            return new DummyEntityManager();
        }
    }

    public static class IDaoProviders {

        private final List<Class<? extends IDao>> daoClasses;

        public IDaoProviders(Class<? extends IDao>... daoClasses) {
            this.daoClasses = new ArrayList<>(Arrays.asList(daoClasses));
        }

        public List<Class<? extends IDao>> getDaoClasses() {
            return daoClasses;
        }
    }
    
    public static class DaoFeature implements Feature {

        public static final String DAO_IMPLEMENTATIONS = "dao.implementations";

        @Override
        public boolean configure(FeatureContext context) {
            Map<String, Object> props = context.getConfiguration().getProperties();
            String initParam = getValue(props, DAO_IMPLEMENTATIONS, String.class);
            context.register(new Binder(getFromStringParam(initParam)));
            return true;
        }
        
        private List<Class<? extends IDao>> getFromStringParam(String initParam) {
            String[] daoClassNames = initParam.split(",");
            List<Class<? extends IDao>> daoClasses = new ArrayList<>();
            for (int i = 0; i < daoClassNames.length; i++) {
                try {
                    String classname = daoClassNames[i].trim();
                    Class<?> cls = Class.forName(daoClassNames[i].trim());
                    if (IDao.class.isAssignableFrom(cls)) {
                        Class<? extends IDao> c = (Class<? extends IDao>)cls;
                        daoClasses.add(c);
                    }
                } catch (ClassNotFoundException ex) {
                    // noop - ignore non IDao classes.
                    System.out.println(ex.getMessage());
                }
            }
            return daoClasses;
        }

        public static <T> T getValue(Map<String, ?> properties, String key, Class<T> type) {
            return PropertiesHelper.getValue(properties, key, type, null);
        }

        private class Binder extends AbstractBinder {
            
            List<Class<? extends IDao>> daoClasses;
            
            public Binder(List<Class<? extends IDao>> daoClasses) {
                this.daoClasses = daoClasses;
            }

            @Override
            protected void configure() {
                bind(CustomContextResolver.class)
                        .to(new TypeLiteral<InjectionResolver<CustomContext>>() {
                        })
                        .in(Singleton.class);
                bindFactory(DummyEntityManagerFactory.class)
                        .to(DummyEntityManager.class)
                        .in(RequestScoped.class);

                for (Class<? extends IDao> daoClass : daoClasses) {
                    bind(daoClass).to(IDao.class)
                            .named(daoClass.getCanonicalName()).in(RequestScoped.class);
                }

                Class<? extends IDao>[] array = daoClasses.toArray(new Class[]{});
                IDaoProviders daoProviders = new IDaoProviders(array);
                bind(daoProviders).to(IDaoProviders.class);
            }
        }
    }

    @Path("dao")
    public static class DaoResource {

        @CustomContext
        private DaoImplOne daoOne;

        @CustomContext
        private DaoImplTwo daoTwo;

        @GET
        @Path("one")
        public String getOne() {
            return daoOne.getData();
        }

        @GET
        @Path("two")
        public String getTwo() {
            return daoTwo.getData();
        }
    }

    @Override
    protected TestContainerFactory getTestContainerFactory() {
        return new GrizzlyWebTestContainerFactory();
    }

    /**
     * 
     * This method is to configure a web deployment using a "web.xml".
     * 
     * The "dao.implementations" is a property from the `DaoFeature`
     * The user should list the `IDao` implementation classes separated
     * by a comma.
     *
     * The `DaoFeature` is register with the Jersey init-param
     * `jersey.config.server.provider.classnames`
     * 
     * The class names I listed use a `$` only because they are inner classes.
     * Normally you would not need that.
     * 
     * See http://stackoverflow.com/a/7007859/2587435
     */
    @Override
    protected DeploymentContext configureDeployment() {
        return ServletDeploymentContext
                .forServlet(ServletContainer.class)
                .initParam("jersey.config.server.provider.packages", 
                        this.getClass().getPackage().getName())
                .initParam("jersey.config.server.provider.classnames", 
                        "com.stackoverflow.dao.WebXmlCustomDaoTest$DaoFeature")
                .initParam("dao.implementations",
                        "com.stackoverflow.dao.WebXmlCustomDaoTest$DaoImplOne,"
                        + "com.stackoverflow.dao.WebXmlCustomDaoTest$DaoImplTwo")
                .build();
    }

    @Test
    public void should_return_dao_one_data() {
        Response response = target("dao/one").request().get();
        assertEquals(200, response.getStatus());
        assertEquals("Data from DaoImplOne", response.readEntity(String.class));
        response.close();
    }

    @Test
    public void should_return_dao_two_data() {
        Response response = target("dao/two").request().get();
        assertEquals(200, response.getStatus());
        assertEquals("Data from DaoImplTwo", response.readEntity(String.class));
        response.close();
    }
}

【讨论】:

  • 真的很感谢@peesKillet ...你的例子很有趣。对于我们在聊天中谈到的......我的观点是 .register(new DaoFeature(DaoImplOne.class, DaoImplTwo.class));强制开发人员编写他的自定义资源配置类。但是对于我正在处理的内置导入框架,这个类应该是不可触碰的。是否可以在 web.xml 之外注册 daoimpl.class?
  • 查看使用 web.xml 的更新。向下滚动直到看到 Javadoc cmets。
  • 太棒了!通过这种方式,开发人员可以编写自己的 dao 实现并注册它,而无需再次编译框架。最好的办法是避免用户进行额外的操作,而让他只做使用注释和扩展摘要的工作。但是在 web.xml 中进一步注册应该是一个可以接受的折衷方案,希望 hk2 能够增强此功能。谢谢!
  • 您也可以自己注册该功能,并让用户只使用 init-param 配置类。这样配置就少了。
  • 嗯,如果我不知道框架级别的实现类,我该如何注册它们?
猜你喜欢
  • 2020-12-25
  • 1970-01-01
  • 2023-03-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-05-12
相关资源
最近更新 更多