像mybatis一样,Spring启动时为接口创建代理对象并自动注入 - Go语言中文社区

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


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

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

两个测试接口:

package com.mtl.itf;

/**
 * 说明:用户服务测试
 *
 * @作者 莫天龙
 * @时间 2019/04/30 10:00
 */
public interface UserService {
    public void save(String user);
}
package com.mtl.itf;

/**
 * 说明:测试服务接口
 *
 * @作者 莫天龙
 * @时间 2019/04/29 17:48
 */
public interface Service {
    public void test(String s);
}

factoryBean类:

package com.mtl.interfaceProxy;

import org.springframework.beans.factory.FactoryBean;

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

/**
 * 说明:代理对象的FactoryBean,继承至Spring FactoryBean,通过调用getBean获取代理对象
 *
 * @作者 莫天龙
 * @时间 2019/04/29 17:33
 */
public class ProxyFactoryBean<T> implements FactoryBean {
    //被代理的接口Class对象
    private Class<T> interfaceClass;

    public ProxyFactoryBean(Class<T> interfaceClass) {
        this.interfaceClass = interfaceClass;
    }


    @Override
    public T getObject() throws Exception {
        //通过JDK动态代理创建代理类
        return (T)Proxy.newProxyInstance(
                interfaceClass.getClassLoader(), new Class[]{interfaceClass},
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        //实现业务逻辑,比如发起网络连接,执行远程调用,获取到结果,并返回
                        System.out.println(method.getName()+" method invoked ! param: "+ Arrays.toString(args));
                        return null;
                    }
                });
    }

    @Override
    public Class<?> getObjectType() {
        return interfaceClass;
    }
}

xml配置信息:

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

测试类:

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

测试结果:

如期的达到了目的,但是这样配置,有点麻烦,因为没一个接口都需要在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类:

package com.mtl.beanDefine.test;

/**
 * 说明:
 *
 * @作者 莫天龙
 * @时间 2019/04/30 9:30
 */
public class Person {
    private String name;
    private int age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + ''' +
                ", age=" + age +
                '}';
    }
}

自己实现一个BeanDefinitionRegistryPostProcessor

package com.mtl.beanDefine.test;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.*;

/**
 * 说明:测试BeanDefinitionRegistryPostProcessor接口,自定义添加bean到容器
 *
 * @作者 莫天龙
 * @时间 2019/04/30 9:29
 */
public class BeanDefineTest implements BeanDefinitionRegistryPostProcessor {
    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        //创建beanDefinition构建器
        BeanDefinitionBuilder beanDefinitionBuilder=BeanDefinitionBuilder.genericBeanDefinition(Person.class);
        //获取到创建beanDefinition
        GenericBeanDefinition beanDefinition = (GenericBeanDefinition) beanDefinitionBuilder.getBeanDefinition();
        //通过此方法可以将age属性的值设置为23,就像xml配置中的property标签
        beanDefinition.getPropertyValues().add("age", new Integer(23));
        beanDefinition.getPropertyValues().add("name", "Mike");
        //设置bean的主动注入类型为根据type注入
        beanDefinition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
        //最后调用注入方法
        registry.registerBeanDefinition("person", beanDefinition);
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        //容器后处理器,暂不处理
    }
}

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

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

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

编写测试类

public class MainTest {
    public static void main(String[] args){
        ClassPathXmlApplicationContext applicationContext=new ClassPathXmlApplicationContext("test.xml");
        Person per = applicationContext.getBean(Person.class);
        System.out.println(per);
        applicationContext.close();
    }
}

测试结果:

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

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

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

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

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

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

重写一下这个类:

package com.mtl.interfaceProxy;

import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.filter.TypeFilter;

import java.io.IOException;
import java.util.Set;

/**
 * 说明:用于扫描给定包名下的接口
 *
 * @作者 莫天龙
 * @时间 2019/04/29 16:34
 */
public class InterfaceScanner extends ClassPathBeanDefinitionScanner {
    public InterfaceScanner(BeanDefinitionRegistry registry) {
        //registry是Spring的Bean注册中心
        // false表示不使用ClassPathBeanDefinitionScanner默认的TypeFilter
        // 默认的TypeFilter只会扫描带有@Service,@Controller,@Repository,@Component注解的类
        super(registry,false);
    }

    //调用父类执行扫描
    @Override
    protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
        addFilter();
        Set<BeanDefinitionHolder> beanDefinitionHolders = super.doScan(basePackages);
        if (beanDefinitionHolders.isEmpty()){
            System.err.println("No Interface Found!");
        }else{
            //创建代理对象
            createBeanDefinition(beanDefinitionHolders);
        }
        return beanDefinitionHolders;
    }

    //只扫描顶级接口
    @Override
    protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
        AnnotationMetadata metadata = beanDefinition.getMetadata();
        return metadata.isInterface()&&metadata.isIndependent();
    }

    /**
     * 扫描所有类
     */
    private void addFilter(){
        addIncludeFilter(new TypeFilter() {
            @Override
            public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
                return true;
            }
        });
    }

    /**
     * 为扫描到的接口创建代理对象
     * @param beanDefinitionHolders
     */
    private void createBeanDefinition(Set<BeanDefinitionHolder> beanDefinitionHolders){
        for (BeanDefinitionHolder beanDefinitionHolder : beanDefinitionHolders) {
            GenericBeanDefinition beanDefinition=((GenericBeanDefinition) beanDefinitionHolder.getBeanDefinition());
            //将bean的真实类型改变为FactoryBean
            beanDefinition.getConstructorArgumentValues().
                    addGenericArgumentValue(beanDefinition.getBeanClassName());
            beanDefinition.setBeanClass(ProxyFactoryBean.class);
            beanDefinition.setAutowireMode(GenericBeanDefinition.AUTOWIRE_BY_TYPE);
        }
    }
}

重写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实现
package com.mtl.interfaceProxy;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;

/**
 * 说明:接口代理对象配置类
 * BeanDefinitionRegistryPostProcessor允许在常规的BeanFactoryPostProcessor检测开始之前注册更多的bean定义。
 *
 * @作者 莫天龙
 * @时间 2019/04/29 17:43
 */
public class InterfaceProxyconfigure implements BeanDefinitionRegistryPostProcessor {
    private String basePackge;

    public void setBasePackge(String basePackge) {
        this.basePackge = basePackge;
    }

    /**
     * 在应用程序上下文的标准初始化之后修改其内部bean定义注册表。所有常规bean定义都已加载,但还没有实例化bean。这允许在下一个后处理阶段开始之前添加更多的bean定义
     * @param registry
     * @throws BeansException
     */
    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        InterfaceScanner scanner=new InterfaceScanner(registry);
        scanner.scan(basePackge);
    }

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

    }
}

xml配置:

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

测试类:

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

执行结果:

 

完整实例代码:github

版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/u014022405/article/details/89703609
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。
  • 发表于 2020-03-01 22:46:09
  • 阅读 ( 1105 )
  • 分类:

0 条评论

请先 登录 后评论

官方社群

GO教程

猜你喜欢