Как создать динамический прокси с помощью Spring и Java

У меня такая ситуация: у меня есть один интерфейс Service, который агрегирует все сервисные интерфейсы. Так, например, если у меня есть два интерфейса ILoginService1 и ILoginService2, интерфейс службы выглядит так:

Service extends ILoginService1,ILoginService2. 

Мне нужно, чтобы этот интерфейс был доступен в данном контексте, например:

service.login();

Это мое решение (что-то похожее на http://artofsoftwarereuse.com/tag/dynamic-proxy/):

Я создаю одну аннотацию ServiceFacade, которую помещаю в интерфейс службы, затем у меня есть BeanPostProcessor, в котором я создаю DynamicProxy для интерфейса службы. Но проблема в том, что интерфейс службы не извлекается из сканирования компонентов Spring, даже если я помещаю на него @Component, но другие компоненты помещаются в контейнер Spring.

Как я могу исправить свое решение, или я что-то упустил, или есть другие решения? Вот исходный код: applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">

    <context:annotation-config/>    
    <context:component-scan base-package="org.finki.auction.ui.application"/>
    <context:component-scan base-package="org.finki.auction.services"/>

</beans>

Аннотация:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ServiceFacade{}

Обработчик вызовов для динамического прокси:

/**
 * 
 */
package org.finki.auction.services;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;

/**
 * 
 */
@Component("serviceLayer")
public class ServiceLayer implements InvocationHandler, ApplicationContextAware
{

    private static ApplicationContext applicationContext = null;
    private static Map<String, String> serviceMap = new HashMap<>();

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
    {
        Object result;
        try
        {
            String searchKey = method.getName();
            String beanName = serviceMap.get(searchKey);
            Object methodObject = applicationContext.getBean(beanName);
            result = method.invoke(methodObject, args);
        } catch (InvocationTargetException e)
        {
            throw e.getTargetException();
        } catch (Exception e)
        {
            throw new RuntimeException("unexpected invocation exception: " + e.getMessage());
        }
        return result;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException
    {
        ServiceLayer.applicationContext = applicationContext;

        Map<String, Object> beans = applicationContext.getBeansWithAnnotation(Service.class);
        for (Map.Entry<String, Object> entryBean : beans.entrySet())
        {
            String beanName = entryBean.getKey();
            Object beanObject = entryBean.getValue();
            Method[] beanMethods = beanObject.getClass().getDeclaredMethods();
            for (Method bMethod : beanMethods)
            {
                serviceMap.put(bMethod.getName(), beanName);
            }
        }
    }

}

Класс BeanPostProcessor:

/**
 * 
 */
package org.finki.auction.services.annotation;

import java.lang.reflect.Proxy;
import java.util.Arrays;

import org.finki.auction.services.Service;
import org.finki.auction.services.ServiceLayer;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

/**
 * 
 */
@Component("serviceFacadeProcessor")
public class ServiceFacadeProcessor implements BeanPostProcessor, ApplicationContextAware
{

    private static ApplicationContext applicationContext = null;

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException
    {
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException
    {
        Class<?> clz = bean.getClass();
        Class<?>[] tmpInterfaces = clz.getInterfaces();
        System.out.println("ServiceFacadeProcessor : " + bean);
        if (tmpInterfaces != null && tmpInterfaces.length == 1
                && tmpInterfaces[0].isAnnotationPresent(ServiceFacade.class))
        {

            System.out.println("Find serviceFacade >>>>");
            Class<?>[] interfaces = Arrays.copyOf(tmpInterfaces, tmpInterfaces.length + 1);

            interfaces[tmpInterfaces.length] = Service.class;
            ClassLoader cl = bean.getClass().getClassLoader();
            ServiceLayer serviceLayerBean = applicationContext.getBean("serviceLayer", ServiceLayer.class);
            Object t = Proxy.newProxyInstance(cl, interfaces, serviceLayerBean);
            System.out.println("Find serviceFacade <<<<");
            return t;
        }

        return bean;

    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException
    {
        ServiceFacadeProcessor.applicationContext = applicationContext;
    }

}

Итак, моя проблема не в конфигурации, моя проблема в том, как прикрепить интерфейс службы к контейнеру Spring, чтобы BeanPostProcessor мог его поймать и создать для него динамический прокси. Пока это мое решение, может быть, я что-то упускаю, но если у кого-то есть лучший способ сделать это, просто дайте мне сейчас. заранее спасибо

Решение:

/**
 * 
 */
package org.finki.auction.services.annotation;

import java.lang.reflect.Proxy;
import java.util.Arrays;

import org.finki.auction.services.Service;
import org.finki.auction.services.ServiceLayer;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

/**
 * @author
 * 
 */
@Component
public class ServiceFactoryBean implements FactoryBean<Service>, ApplicationContextAware
{

    private static ApplicationContext applicationContext = null;

    @Override
    public Service getObject() throws Exception
    {

        Class<?>[] tmpInterfaces = Service.class.getInterfaces();
        Class<?>[] interfaces = Arrays.copyOf(tmpInterfaces, tmpInterfaces.length + 1);
        interfaces[tmpInterfaces.length] = Service.class;
        ServiceLayer serviceLayerBean = applicationContext.getBean("serviceLayer", ServiceLayer.class);
        ClassLoader cl = serviceLayerBean.getClass().getClassLoader();
        Object t = Proxy.newProxyInstance(cl, interfaces, serviceLayerBean);
        return (Service) t;
    }

    @Override
    public Class<?> getObjectType()
    {
        return Service.class;
    }

    @Override
    public boolean isSingleton()
    {
        return true;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException
    {
        ServiceFactoryBean.applicationContext = applicationContext;
    }

}

Также необходимо удалить BeanPostProcessor и аннотацию.


person Xoke    schedule 27.09.2012    source источник
comment
Пожалуйста, опубликуйте код: объявления классов, включая аннотации, файл конфигурации вашего контекста (если вы его используете). и вызов, загружающий контекст.   -  person jalynn2    schedule 27.09.2012
comment
Это меня смущает: вы говорите, что интерфейс службы выглядит так, а затем Service implements ILoginService1. Интерфейсы не могут реализовывать другие интерфейсы, они могут только расширять их. Является ли Service действительно интерфейсом? Мне он кажется class.   -  person Aaron Digulla    schedule 27.09.2012


Ответы (1)


Я столкнулся с чем-то подобным и считаю, что вы можете заставить свой сценарий работать, используя функцию Spring Java Configuration.

@Configuration
public class ServiceConfiguration {

    // you can wire your service1 and service2 here

    @Bean
    Service service() {
         // create and return dynamic proxy here
    }
}

Таким образом, вы получите компонент типа «Сервис» и имя «Сервис», который будет вашим динамическим прокси с обработчиком вызовов и т. д.

Я уверен, что Конфигурация Java не ограничит вас описанным выше подходом (где вы подключаете свой сервис1 и сервис2 к конфигурации) - мне кажется, это деталь реализации.

person finrod    schedule 07.05.2013