像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;
-
}
-
-
-
-
public T getObject() throws Exception {
-
//通过JDK动态代理创建代理类
-
return (T)Proxy.newProxyInstance(
-
interfaceClass.getClassLoader(), new Class[]{interfaceClass},
-
new InvocationHandler() {
-
-
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
-
//实现业务逻辑,比如发起网络连接,执行远程调用,获取到结果,并返回
-
System.out.println(method.getName()+" method invoked ! param: "+ Arrays.toString(args));
-
return null;
-
}
-
});
-
}
-
-
-
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;
-
}
-
-
-
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 {
-
-
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);
-
}
-
-
-
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);
-
}
-
-
//调用父类执行扫描
-
-
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;
-
}
-
-
//只扫描顶级接口
-
-
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
-
AnnotationMetadata metadata = beanDefinition.getMetadata();
-
return metadata.isInterface()&&metadata.isIndependent();
-
}
-
-
/**
-
* 扫描所有类
-
*/
-
private void addFilter(){
-
addIncludeFilter(new TypeFilter() {
-
-
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
-
*/
-
-
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
-
InterfaceScanner scanner=new InterfaceScanner(registry);
-
scanner.scan(basePackge);
-
}
-
-
-
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
转自 https://blog.csdn.net/u014022405/article/details/89703609