使用動態代理只代理接口(非實現類)

假設如今咱們有一個已知的算法,咱們須要寫任意一個接口打上咱們特有的標籤,那麼這個接口的方法均可以執行這個算法,比如Mybatis的Dao,或者Feign的接口。如今假設咱們這個特有的標籤以下:算法

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

已知的算法爲打印方法的全部參數。spring

@Override
public Object invoke(Object[] argv) throws Throwable {
    Stream.of(argv).sequential().forEach(System.out::println);
    return null;
}

-------------------------------------------------------------緩存

如今需求清楚了,咱們來隨意寫個接口,並打上該標籤。ide

@ProxyVersion
public interface ProxyTest {
    String find(String a, Integer b);
}

先寫一個動態代理類post

@AllArgsConstructor
public class ProxyInvocationHandler implements InvocationHandler {
    private Map<Method,MethodHandler> dispatch;

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        return dispatch.get(method).invoke(args);
    }
}

其中MethodHandler爲方法處理器接口,定義以下測試

public interface MethodHandler {
    Object invoke(Object[] argv) throws Throwable;
}

而後寫一個方法處理器接口的實現類,它包含了咱們固定要實現的算法。ui

public class DefaultMethodHandler implements MethodHandler {

    @Override
    public Object invoke(Object[] argv) throws Throwable {
        Stream.of(argv).sequential().forEach(System.out::println);
        return null;
    }
}

咱們首先寫一個目標類,由於咱們不知道咱們要代理的是啥接口,因此使用泛型,而且包含一個該泛型的Class屬性type。spa

@Data
@AllArgsConstructor
public class Target<T> {
    private Class<T> type;
}

而後爲來建立該目標類,寫一個目標工廠類,從該目標工廠類去搜索包下的全部類,並獲取帶有@ProxyVersion標籤的接口放入咱們的目標對象屬性中。這裏咱們作了簡化處理,只取第一個接口。.net

public class TargetFactory {
    public static Target createTarget() {
        Set<Class<?>> classes = ClassUtil.getClassSet("com.guanjian.demo.proxytest");
        List<Class<?>> collect = classes.stream()
                .filter(Class::isInterface)
                .filter(clazz -> clazz.isAnnotationPresent(ProxyVersion.class))
                .collect(Collectors.toList());
        return new Target(collect.get(0));
    }
}

ClassUtil代碼請參考@Compenent,@Autowired,@PostConstruct自實現 prototype

如今咱們要調用動態代理類,這裏咱們也作了簡化處理,只取第一個方法。最終返回咱們代理的接口實例

public class ProxyBean {
    public Object proxyTest() {
        Map<Method,MethodHandler> methodToHandler = new HashMap<>();
        //獲取目標對象
        Target target = TargetFactory.createTarget();
        //將目標對象的方法以及方法處理器(方法處理器包含咱們須要的固定算法)放入映射中
        methodToHandler.put(target.getType().getMethods()[0],new DefaultMethodHandler());
        //構建動態代理對象
        InvocationHandler handler = new ProxyInvocationHandler(methodToHandler);
        //返回動態代理代理的接口實例
        return Proxy.newProxyInstance(target.getType().getClassLoader(), new Class[]{target.getType()}, handler);
    }
}

再加一個ProxyBean的工廠

public class ProxyBeanFactory {
    public static ProxyBean createProxyBean() {
        return new ProxyBean();
    }
}

最後寫測試

public class ProxyMain {

    public static void main(String[] args) {
        ProxyTest test = (ProxyTest)ProxyBeanFactory.createProxyBean().proxyTest();
        test.find("aaa",233);
    }
}

運行結果

aaa
233

若是咱們換一個接口替換ProxyTest也是同樣,隨意定義接口,均可以獲取執行的結果。

因爲咱們在使用Mybatis或者Feign的時候都是使用依賴注入的,因此咱們如今要將該例子修改成Spring依賴注入的形式。在此要感謝個人好兄弟雄爺(李少雄)提供的支持。

要實現Spring依賴注入,咱們須要修改一部分代碼。

首先要將TargetFactory修改以下,再也不使用靜態方法,而修改爲單例模式,便於獲取接口的類型。

public class TargetFactory {
    private Set<Class<?>> classes = ClassUtil.getClassSet("com.guanjian.demo.proxytest");

    private static final TargetFactory instance = new TargetFactory();

    public static TargetFactory getInstance(){
        return instance;
    }

    private TargetFactory() {}

    public Target createTarget() {
        return new Target(getNeedProxyClass());
    }
    
    public Class<?> getNeedProxyClass() {
        List<Class<?>> collect = classes.stream()
                .filter(Class::isInterface)
                .filter(clazz -> clazz.isAnnotationPresent(ProxyVersion.class))
                .collect(Collectors.toList());
        return collect.get(0);
    }
}

因此ProxyBean在獲取target目標對象的時候須要由單例來獲取,而且proxyTest方法返回改成泛型。

public class ProxyBean<T> {
    public T proxyTest() {
        Map<Method,MethodHandler> methodToHandler = new HashMap<>();
        //獲取目標對象
        Target target = TargetFactory.getInstance().createTarget();
        //將目標對象的方法以及方法處理器(方法處理器包含咱們須要的固定算法)放入映射中
        methodToHandler.put(target.getType().getMethods()[0],new DefaultMethodHandler());
        //構建動態代理對象
        InvocationHandler handler = new ProxyInvocationHandler(methodToHandler);
        //返回動態代理代理的實例
        return (T) Proxy.newProxyInstance(target.getType().getClassLoader(), new Class[]{target.getType()}, handler);
    }
}

ProxyBean工廠須要實現FactoryBean接口,該接口爲Spring環境的接口(org.springframework.beans.factory.FactoryBean)

@AllArgsConstructor
public class ProxyBeanFactory<T> implements FactoryBean<T> {
    private Class<T> interfaceType;

    /**
     * 返回由FactoryBean建立的bean實例,若是isSingleton()返回true,則該實例會放到Spring容器中單實例緩存池中。
     * @return
     * @throws Exception
     */
    @Override
    public T getObject() throws Exception {
        ProxyBean<T> proxyBean = new ProxyBean<>();
        return proxyBean.proxyTest();
    }

    /**
     * 返回FactoryBean建立的bean類型。
     * @return
     */
    @Override
    public Class<T> getObjectType() {
        return interfaceType;
    }

    /**
     * 返回由FactoryBean建立的bean實例的做用域是singleton仍是prototype。
     * @return
     */
    @Override
    public boolean isSingleton() {
        return true;
    }

}

而後咱們須要對接口以及動態代理代理的接口實例在Spring環境中進行bean的動態註冊,其實不管是Mybatis的Dao仍是Feign的接口都是這麼處理的。

/**
 * 實現BeanDefinitionRegistryPostProcessor接口來動態生成bean
 */
@Component
public class DynamicProxyServiceBeanRegister implements BeanDefinitionRegistryPostProcessor {

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        //獲取咱們須要的接口類型
        Class<?> beanClazz = TargetFactory.getInstance().getNeedProxyClass();
        //根據接口類生成一個bean的建造器
        BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(beanClazz);
        //根據該建造起獲取bean的定義器
        GenericBeanDefinition definition = (GenericBeanDefinition) builder.getRawBeanDefinition();
        //定義該接口類型爲該bean的匹配類型
        //如使用SpringbootApplication.getBean(匹配類型.class)
        definition.getConstructorArgumentValues().addGenericArgumentValue(beanClazz);
        //定義bean工廠中的動態代理代理的接口實例爲bean的對象
        //如 bean對象=SpringbootApplication.getBean(匹配類型.class)
        definition.setBeanClass(ProxyBeanFactory.class);
        //定義@Autowired的裝配方式,這裏用默認裝配方式便可
//        definition.setAutowireMode(GenericBeanDefinition.AUTOWIRE_BY_TYPE);
        //註冊該定義器,並把接口名作爲註冊的標識
        registry.registerBeanDefinition(beanClazz.getSimpleName(), definition);
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {

    }

}

最後進行測試

@Component
public class ProxyMain {
    @Autowired
    private ProxyTest proxyTest;

    @PostConstruct
    public void action() {
        proxyTest.find("aaa",233);
    }
}

啓動Springboot,運行結果以下

2019-11-27 22:48:09.419  INFO 535 --- [           main] com.guanjian.demo.DyimportApplication    : Starting DyimportApplication on admindeMacBook-Pro.local with PID 535 (/Users/admin/Downloads/dyimport/target/classes started by admin in /Users/admin/Downloads/dyimport) 2019-11-27 22:48:09.422  INFO 535 --- [           main] com.guanjian.demo.DyimportApplication    : No active profile set, falling back to default profiles: default aaa 233 2019-11-27 22:48:10.025  INFO 535 --- [           main] com.guanjian.demo.DyimportApplication    : Started DyimportApplication in 15.783 seconds (JVM running for 26.124)

相關文章
相關標籤/搜索