Фабрика HK2 с типом интерфейса и 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(); 

    }

}

person Alex    schedule 09.10.2015    source источник
comment
ну конечно да... моя проблема заключается в методе предоставления. Хорошо для IDAO RequestScoped ... это была моя ошибка (я отредактировал и исправил). В любом случае, вы говорите, что мне нужно связать все типы, которые я хочу ввести? В этом смысле мне нужно разработать независимый от типов фреймворк. То, как я могу внедрить свой собственный дао, просто реализовав интерфейс IDAO. Является ли это возможным?   -  person Alex    schedule 09.10.2015
comment
Если вы говорите, что просто реализуя интерфейс, вы хотите, чтобы реализация автоматически регистрировалась, я не знаю, как это будет работать.   -  person Paul Samsotha    schedule 09.10.2015
comment
о, хорошо ... да, у меня есть несколько реализаций. Цель такова: если кто-то реализует свой собственный IDAO, он может это сделать, и в итоге он сможет получить свой собственный экземпляр через аннотацию. Я отредактировал свой пост. Фабричный метод предоставления должен знать об аннотированном типе и, таким образом, иметь возможность выбрать правильную реализацию для инстанцирования.   -  person Alex    schedule 09.10.2015
comment
Разрешите чат   -  person Paul Samsotha    schedule 11.10.2015
comment
Вы, ребята, поняли это?   -  person jwells131313    schedule 12.10.2015
comment
На данный момент это своего рода обходной путь, но... достаточно хороший. Он использует не фабрику, а преобразователь инъекций для создания нового экземпляра и closeableservice для удаления ресурсов.   -  person Alex    schedule 12.10.2015
comment
@ jwells131313 jwells131313 Проблема, с которой я столкнулся, пытаясь понять это, заключается в том, что Алексу нужно внедрять реализации, а не интерфейс, но в то же время нужно использовать Factory. Его текущее решение/обходной путь состоит в том, чтобы использовать отражение для newInstance реализации изнутри InjectionResolver. Другая проблема заключается в том, что реализации неизвестны заранее. Это была проблема, с которой я столкнулся. Причина, по которой ОП хотел использовать Factory, заключается в том, что ему нужно было получить доступ к HttpServletRequest. У вас есть другая идея?   -  person Paul Samsotha    schedule 13.10.2015
comment
Да, peeskillet хорошо сказал... фабрика нужна была и для метода dispose. В любом случае добавление в closeableservice работает хорошо   -  person Alex    schedule 13.10.2015
comment
Я думал, что единственный другой вариант, который может помочь, — это использовать JustInTimeInjectionResolver (hk2.java.net/2.4.0-b32/apidocs/org/glassfish/hk2/api/) и в реализации добавить дескриптор, который мог бы создать/уничтожить сервис   -  person jwells131313    schedule 14.10.2015


Ответы (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, который я использовал, - это класс Джерси, который позволяет вам получить 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();
    }
}
person Paul Samsotha    schedule 24.10.2015
comment
действительно спасибо @peesKillet ... ваш пример довольно интересен. Что касается того, о чем мы говорили в нашем чате... я хочу сказать, что .register(new DaoFeature(DaoImplOne.class, DaoImplTwo.class)); заставить разработчика написать свой собственный класс конфигурации ресурсов. Но этот класс должен быть неприкасаемым, связанным со встроенной импортированной структурой, над которой я работаю. можно ли зарегистрировать daoimpl.class снаружи... в web.xml? - person Alex; 24.10.2015
comment
См. обновление с помощью web.xml. Прокрутите вниз, пока не увидите комментарии Javadoc. - person Paul Samsotha; 24.10.2015
comment
Замечательно! Таким образом, разработчик может написать свою собственную реализацию dao и зарегистрировать ее без повторной компиляции фреймворка. Лучше всего избегать дополнительных операций пользователя и оставлять ему только работу по использованию аннотаций и расширению реферата. Но дальнейшая регистрация в web.xml должна быть приемлемым компромиссом, в надежде, что hk2 улучшит эту функцию. Спасибо! - person Alex; 24.10.2015
comment
Вы также можете зарегистрировать эту функцию самостоятельно и оставить пользователю только настройку классов с параметром инициализации. Это на одну конфигурацию меньше. - person Paul Samsotha; 25.10.2015
comment
Ммм, как я могу их зарегистрировать, если я не знаю реализованных классов на уровне фреймворка? - person Alex; 25.10.2015
comment
Функция получает классы из init-param, в которые загружаются из свойств конфигурации Джерси. Если вы просмотрите весь код, нигде не используется DaoImplOne или DaoImplTwo. Только в ресурсном методе. Везде просто используется тип IDao. Фактические Classes получаются Class.forName, затем этот класс регистрируется. Таким образом, фреймворку ни в коем случае не нужно знать о реализациях. - person Paul Samsotha; 25.10.2015
comment
Ах, хорошо, да... действительно, из-за этого я спросил, можно ли использовать web.xml... да, еще раз спасибо - person Alex; 25.10.2015