简体   繁体   中英

How to create dynamic proxy with Spring and Java

I have this situation: I have one interface Service which aggregates all service interfaces. So for example if I have two interfaces ILoginService1 and ILoginService2 the Service interface looks like this

Service extends ILoginService1,ILoginService2. 

I need this interface to be accessible in a given context like this:

service.login();

This is my solution (something similar to http://artofsoftwarereuse.com/tag/dynamic-proxy/ ):

I create one annotation ServiceFacade, which I put on Service interface, then I have BeanPostProcessor in which I create DynamicProxy for the Service interface. But the problem is that Service interface isn't pick up from spring component scan, even in the case I put @Component on it, but other components are put in Spring container.

How can I fix my solution so far or I'm missing something or is there other solutions? Here is source code: 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>

Annotation:

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

Invocation Handler for Dynamic Proxy:

/**
 * 
 */
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 class:

/**
 * 
 */
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;
    }

}

So, my problem is not the configuration, my problem is how to attach Service interface to spring container in order to be caught by BeanPostProcessor and create dynamic proxy for it. It's is my solution so far maybe I'm missing something, but if someone have better way doing it, just let me now. Thanks in advance

Solution:

/**
 * 
 */
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;
    }

}

Also need to delete BeanPostProcessor and annotation.

I run into something similar and believe you can get your scenario working using Spring's Java Configuration feature.

@Configuration
public class ServiceConfiguration {

    // you can wire your service1 and service2 here

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

This way you will end up with a bean of type 'Service' and name 'service' which will be your dynamic proxy with invocation handler etc.

I'm sure Java Configuration will not limit you to the approach outlined above (where you wire your service1 and service2 into the config) - methinks that is implementation detail.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM