具有接口类型和 InjectionResolver 的 HK2 工厂

HK2 Factory with Interface Type and InjectionResolver

在我的用例中,我需要开发一个自定义注释,通过它我可以实例化 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(); 

    }

}

所以根据我们之前的聊天,下面是我试图传达的想法。基本上,您会添加一个 Feature,用户可以在其中将 IDao 实现 class 传递给。在 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 是球衣 class,它可以让你得到 ContainerRequest,你几乎可以得到任何你能从 HttpServletRequest[=28= 得到的东西]

public static class DummyEntityManagerFactory 
        extends AbstractContainerRequestValueFactory<DummyEntityManager> {

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

在摘要IDaoclass中,可以注入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 
     */
    @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.Whosebug.dao.WebXmlCustomDaoTest$DaoFeature")
                .initParam("dao.implementations",
                        "com.Whosebug.dao.WebXmlCustomDaoTest$DaoImplOne,"
                        + "com.Whosebug.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();
    }
}