假設如今咱們有一個已知的算法,咱們須要寫任意一個接口打上咱們特有的標籤,那麼這個接口的方法均可以執行這個算法,比如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)