像mybatis一样,Spring启动时为接口创建代理对象并自动注入

有些时候,我们需要为一些接口创建代理对象,并放入Spring的IOC容器中,比如,当我们需要构建一个RPC框架客户端程序时,客户端肯定只有服务的接口,并没有具体的实现,实现在远程服务器,这个时候,我们就可以为这些服务接口创建代理对象,并将代理对象放入IOC容器中,当我们需要调用服务时,通过接口请求服务,最终由代理对象发起网络请求,将服务请求发送到远程服务器,远程服务器执行后,再将结果返回到客户端,代理对象收到远程执行结果后,最终将执行结果返回到服务调用者。

第一种方式:通过Spring给我们提供的factoryBean接口手工注册

两个测试接口:

  1. package com.mtl.itf;
  2.  
  3. /**
  4. * 说明:用户服务测试
  5. *
  6. * @作者 莫天龙
  7. * @时间 2019/04/30 10:00
  8. */
  9. public interface UserService {
  10. public void save(String user);
  11. }
  1. package com.mtl.itf;
  2.  
  3. /**
  4. * 说明:测试服务接口
  5. *
  6. * @作者 莫天龙
  7. * @时间 2019/04/29 17:48
  8. */
  9. public interface Service {
  10. public void test(String s);
  11. }

factoryBean类:

  1. package com.mtl.interfaceProxy;
  2.  
  3. import org.springframework.beans.factory.FactoryBean;
  4.  
  5. import java.lang.reflect.InvocationHandler;
  6. import java.lang.reflect.Method;
  7. import java.lang.reflect.Proxy;
  8. import java.util.Arrays;
  9.  
  10. /**
  11. * 说明:代理对象的FactoryBean,继承至Spring FactoryBean,通过调用getBean获取代理对象
  12. *
  13. * @作者 莫天龙
  14. * @时间 2019/04/29 17:33
  15. */
  16. public class ProxyFactoryBean<T> implements FactoryBean {
  17. //被代理的接口Class对象
  18. private Class<T> interfaceClass;
  19.  
  20. public ProxyFactoryBean(Class<T> interfaceClass) {
  21. this.interfaceClass = interfaceClass;
  22. }
  23.  
  24.  
  25. @Override
  26. public T getObject() throws Exception {
  27. //通过JDK动态代理创建代理类
  28. return (T)Proxy.newProxyInstance(
  29. interfaceClass.getClassLoader(), new Class[]{interfaceClass},
  30. new InvocationHandler() {
  31. @Override
  32. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  33. //实现业务逻辑,比如发起网络连接,执行远程调用,获取到结果,并返回
  34. System.out.println(method.getName()+" method invoked ! param: "+ Arrays.toString(args));
  35. return null;
  36. }
  37. });
  38. }
  39.  
  40. @Override
  41. public Class<?> getObjectType() {
  42. return interfaceClass;
  43. }
  44. }

xml配置信息:

  1. <bean id="service" class="com.mtl.interfaceProxy.ProxyFactoryBean">
  2. <constructor-arg name="interfaceClass" type="java.lang.Class" value="com.mtl.itf.Service"/>
  3. </bean>
  4. <bean id="userService" class="com.mtl.interfaceProxy.ProxyFactoryBean">
  5. <constructor-arg name="interfaceClass" type="java.lang.Class" value="com.mtl.itf.UserService"/>
  6. </bean>

测试类:

  1. public static void main(String[] args){
  2. ClassPathXmlApplicationContext applicationContext=new ClassPathXmlApplicationContext("bean*.xml");
  3. Service bean = applicationContext.getBean(Service.class);
  4. bean.test("222");
  5. UserService userService = applicationContext.getBean(UserService.class);
  6. userService.save("user");
  7. applicationContext.close();
  8. }

测试结果:

如期的达到了目的,但是这样配置,有点麻烦,因为没一个接口都需要在spring的配置文件里面配置一下,所以我们想mybatis也是为接口创建了代理对象,但是我们并没有把每个Mapper接口配置到xml中,而是配置了一个basePackge(包名),就可以为包名下的接口创建代理对象,这个是怎么实现的呢?

先来认识一下Spring为我们提供的几个类和接口

org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor

根据文档说明:允许在常规的BeanFactoryPostProcessor检测开始之前注册更多的bean定义。其中有个方法postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry),文档说明:在应用程序上下文的标准初始化之后修改其内部bean定义注册表。所有常规bean定义都已加载,但还没有实例化bean。这允许在下一个后处理阶段开始之前添加更多的bean定义。说明BeanDefinitionRegistryPostProcessor这个接口对应的实现类,通过postProcessBeanDefinitionRegistry这个方法来实现在容器标准初始化之后可以添加更多的bean定义,通过查看原代码,BeanDefinitionRegistry对象有如下常用的方法:

void registerBeanDefinition(String beanName, BeanDefinition beanDefinition);//注册一个bean定义

void removeBeanDefinition(String beanName);//删除一个已有的bean定义

还有一些其他方法,比如查看获取某个bean的定义,查看某个beanName是否已经定义,查看spring IOC容器中一共定义了多个个bean(也就是Spring IOC容器中有多少个实例)等等,读者可以查看源代码查看了解其他方法,通过查看这些方法,似乎了解到我们可以在spring 容器启动中,可以管理这些bean。我们发现注册一个bean定义,需要一个BeanDefinition对象,我们来看一下这个对象是什么。

org.springframework.beans.factory.config.BeanDefinition

查看文档说明:bean定义描述了一个bean实例,它具有属性值、构造函数参数值和由具体实现提供的进一步信息。

所以,自定义注册一个bean,就是构建一个BeanDefinition对象,当然spring也为我们提供了创建方法:

通过org.springframework.beans.factory.support.BeanDefinitionBuilder类

上代码,测试一下吧!

有一个Person类:

  1. package com.mtl.beanDefine.test;
  2.  
  3. /**
  4. * 说明:
  5. *
  6. * @作者 莫天龙
  7. * @时间 2019/04/30 9:30
  8. */
  9. public class Person {
  10. private String name;
  11. private int age;
  12.  
  13. public String getName() {
  14. return name;
  15. }
  16.  
  17. public void setName(String name) {
  18. this.name = name;
  19. }
  20.  
  21. public int getAge() {
  22. return age;
  23. }
  24.  
  25. public void setAge(int age) {
  26. this.age = age;
  27. }
  28.  
  29. @Override
  30. public String toString() {
  31. return "Person{" +
  32. "name='" + name + '\'' +
  33. ", age=" + age +
  34. '}';
  35. }
  36. }

自己实现一个BeanDefinitionRegistryPostProcessor

  1. package com.mtl.beanDefine.test;
  2.  
  3. import org.springframework.beans.BeansException;
  4. import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
  5. import org.springframework.beans.factory.support.*;
  6.  
  7. /**
  8. * 说明:测试BeanDefinitionRegistryPostProcessor接口,自定义添加bean到容器
  9. *
  10. * @作者 莫天龙
  11. * @时间 2019/04/30 9:29
  12. */
  13. public class BeanDefineTest implements BeanDefinitionRegistryPostProcessor {
  14. @Override
  15. public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
  16. //创建beanDefinition构建器
  17. BeanDefinitionBuilder beanDefinitionBuilder=BeanDefinitionBuilder.genericBeanDefinition(Person.class);
  18. //获取到创建beanDefinition
  19. GenericBeanDefinition beanDefinition = (GenericBeanDefinition) beanDefinitionBuilder.getBeanDefinition();
  20. //通过此方法可以将age属性的值设置为23,就像xml配置中的property标签
  21. beanDefinition.getPropertyValues().add("age", new Integer(23));
  22. beanDefinition.getPropertyValues().add("name", "Mike");
  23. //设置bean的主动注入类型为根据type注入
  24. beanDefinition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
  25. //最后调用注入方法
  26. registry.registerBeanDefinition("person", beanDefinition);
  27. }
  28.  
  29. @Override
  30. public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
  31. //容器后处理器,暂不处理
  32. }
  33. }

将我们实现的BeanDefinitionRegistryPostProcessor类配置到spring中,这样spring在容器启动中会自动执行该类的postProcessBeanDefinitionRegistry方法,就像FactroyBean、bean后处理器、容器后处理器一样。

注意,只配置了这个类,不配置Person类。

<bean class="com.mtl.beanDefine.test.BeanDefineTest"/>

编写测试类

  1. public class MainTest {
  2. public static void main(String[] args){
  3. ClassPathXmlApplicationContext applicationContext=new ClassPathXmlApplicationContext("test.xml");
  4. Person per = applicationContext.getBean(Person.class);
  5. System.out.println(per);
  6. applicationContext.close();
  7. }
  8. }

测试结果:

通过测试结果发现,我们参与了容器启动过程,并将Person对象注入到了Spring中。

所以,我们可以通过这个方法动态的将接口生成的代理对象注入到Spring中。

第二种方法:通过BeanDefinitionRegistryPostProcessor接口动态注入。

现在还有一个问题,Mybatis是如何扫描到某个包下的接口的呢?

认识一下这个类:org.springframework.context.annotation.ClassPathBeanDefinitionScanner

文档说明:一个bean定义扫描器,它检测类路径上的bean候选项,并向给定的注册表(BeanFactory或ApplicationContext)注册相应的bean定义。通过可配置的类型过滤器检测候选类。默认过滤器包括用Spring的@Component、@Repository、@Service或@Controller原型注解的类。

重写一下这个类:

  1. package com.mtl.interfaceProxy;
  2.  
  3. import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
  4. import org.springframework.beans.factory.config.BeanDefinitionHolder;
  5. import org.springframework.beans.factory.support.BeanDefinitionRegistry;
  6. import org.springframework.beans.factory.support.GenericBeanDefinition;
  7. import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;
  8. import org.springframework.core.type.AnnotationMetadata;
  9. import org.springframework.core.type.classreading.MetadataReader;
  10. import org.springframework.core.type.classreading.MetadataReaderFactory;
  11. import org.springframework.core.type.filter.TypeFilter;
  12.  
  13. import java.io.IOException;
  14. import java.util.Set;
  15.  
  16. /**
  17. * 说明:用于扫描给定包名下的接口
  18. *
  19. * @作者 莫天龙
  20. * @时间 2019/04/29 16:34
  21. */
  22. public class InterfaceScanner extends ClassPathBeanDefinitionScanner {
  23. public InterfaceScanner(BeanDefinitionRegistry registry) {
  24. //registry是Spring的Bean注册中心
  25. // false表示不使用ClassPathBeanDefinitionScanner默认的TypeFilter
  26. // 默认的TypeFilter只会扫描带有@Service,@Controller,@Repository,@Component注解的类
  27. super(registry,false);
  28. }
  29.  
  30. //调用父类执行扫描
  31. @Override
  32. protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
  33. addFilter();
  34. Set<BeanDefinitionHolder> beanDefinitionHolders = super.doScan(basePackages);
  35. if (beanDefinitionHolders.isEmpty()){
  36. System.err.println("No Interface Found!");
  37. }else{
  38. //创建代理对象
  39. createBeanDefinition(beanDefinitionHolders);
  40. }
  41. return beanDefinitionHolders;
  42. }
  43.  
  44. //只扫描顶级接口
  45. @Override
  46. protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
  47. AnnotationMetadata metadata = beanDefinition.getMetadata();
  48. return metadata.isInterface()&&metadata.isIndependent();
  49. }
  50.  
  51. /**
  52. * 扫描所有类
  53. */
  54. private void addFilter(){
  55. addIncludeFilter(new TypeFilter() {
  56. @Override
  57. public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
  58. return true;
  59. }
  60. });
  61. }
  62.  
  63. /**
  64. * 为扫描到的接口创建代理对象
  65. * @param beanDefinitionHolders
  66. */
  67. private void createBeanDefinition(Set<BeanDefinitionHolder> beanDefinitionHolders){
  68. for (BeanDefinitionHolder beanDefinitionHolder : beanDefinitionHolders) {
  69. GenericBeanDefinition beanDefinition=((GenericBeanDefinition) beanDefinitionHolder.getBeanDefinition());
  70. //将bean的真实类型改变为FactoryBean
  71. beanDefinition.getConstructorArgumentValues().
  72. addGenericArgumentValue(beanDefinition.getBeanClassName());
  73. beanDefinition.setBeanClass(ProxyFactoryBean.class);
  74. beanDefinition.setAutowireMode(GenericBeanDefinition.AUTOWIRE_BY_TYPE);
  75. }
  76. }
  77. }

重写doScan方法,因为我们需要将扫描到的接口指定FactroyBean为我们创建代理对象,父类的doScan方法,默认扫描的是带有@Service,@Controller,@Repository,@Component注解的类,而不是接口。

重写isCandidateComponent方法,因为父类默认将接口忽略掉的,而我们恰恰只扫描接口。

再看一下父类的doScan方法,返回的是Set<BeanDefinitionHolder>,而BeanDefinitionHolder可以获取到BeanDefinition,所以私有方法createBeanDefinition,就是在处理BeanDefinition,可以看到createBeanDefinition方法并没有调用registry.registerBeanDefinition方法,为什么?

ClassPathBeanDefinitionScanner类文档说清楚了:

他是一个bean定义扫描器,它检测类路径上的bean候选项,并向给定的注册表(BeanFactory或ApplicationContext)注册相应的bean定义。

会自己会为我们注册,所以我们不需要手工调用,从构造方法也可以看出,传入了一个BeanDefinitionRegistry对象。

测试一下:

BeanDefinitionRegistryPostProcessor实现
  1. package com.mtl.interfaceProxy;
  2.  
  3. import org.springframework.beans.BeansException;
  4. import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
  5. import org.springframework.beans.factory.support.BeanDefinitionRegistry;
  6. import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
  7.  
  8. /**
  9. * 说明:接口代理对象配置类
  10. * BeanDefinitionRegistryPostProcessor允许在常规的BeanFactoryPostProcessor检测开始之前注册更多的bean定义。
  11. *
  12. * @作者 莫天龙
  13. * @时间 2019/04/29 17:43
  14. */
  15. public class InterfaceProxyconfigure implements BeanDefinitionRegistryPostProcessor {
  16. private String basePackge;
  17.  
  18. public void setBasePackge(String basePackge) {
  19. this.basePackge = basePackge;
  20. }
  21.  
  22. /**
  23. * 在应用程序上下文的标准初始化之后修改其内部bean定义注册表。所有常规bean定义都已加载,但还没有实例化bean。这允许在下一个后处理阶段开始之前添加更多的bean定义
  24. * @param registry
  25. * @throws BeansException
  26. */
  27. @Override
  28. public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
  29. InterfaceScanner scanner=new InterfaceScanner(registry);
  30. scanner.scan(basePackge);
  31. }
  32.  
  33. @Override
  34. public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
  35.  
  36. }
  37. }

xml配置:

  1. <bean class="com.mtl.interfaceProxy.InterfaceProxyconfigure">
  2. <property name="basePackge" value="com.mtl.itf"/>
  3. </bean>

测试类:

  1. public static void main(String[] args){
  2. ClassPathXmlApplicationContext applicationContext=new ClassPathXmlApplicationContext("app*.xml");
  3. Service service = applicationContext.getBean(Service.class);
  4. UserService UserService = applicationContext.getBean(UserService.class);
  5. service.test("123");
  6. UserService.save("user");
  7. applicationContext.close();
  8. }

执行结果:

 

完整实例代码:github

转自 https://blog.csdn.net/u014022405/article/details/89703609

posted @ 2021-01-01 09:10  甜菜波波  阅读(1111)  评论(0编辑  收藏  举报