有些時候,我們需要為一些接口創建代理對象,並放入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