實現這樣一種功能:
自定義了一個註解@MyReference,
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyReference{
}
被它註解的字段是就是我們要代理的類,希望在Spring啓動時將代理類注入到這些被該註解標識的字段。
如何實現:
1,如何實現在Spring啓動後將被@MyReference
標註的類的替換 ?
將代理類的beanDefination註冊到容器中,並將其與委託類在容器中的name關聯,這樣容器注入的就是我們的代理類了。
利用 BeanDefinitionRegistryPostProcessor
來實現。
這裏我們代理類的功能是一樣的,都是獲取委託類的信息來向遠端發送請求再獲得結果,所以利用FactoryBean
與InitializingBean
,創建一個叫 ProxyFactoryBean 的類實現它們,內部聲明一個成員變量用來標識委託類的類型,afterPropertiesSet方法中根據類型創建對應代理類,該方法在初始化bean時被調用;對於每個標註類都會創建一個 ProxyFactoryBean 類型的beanDefination。
這樣我們項目下所有被標註的類,其在Spring容器中的BeanDefination實質上是個FactoryBean,而Spring在獲取一個Bean實例時若發現其是FactoryBean,則調用它的getObject,由於我們實現了InitializingBean,在bean初始化時afterPropertiesSet被調用,我們在該方法里根據類型創建該標註類的代理類對象,getObject返回的也是它。具體代碼實現在下面。
2,我們使用的是SpringBoot,上述功能作爲一個獨立的模塊,在項目中進行依賴,需要在容器啓動時進行觸發,如何觸發?利用Spring Boot的Spring Factories機制來實現。
關於Spring Factories推薦兩篇文章:
Spring Boot的擴展機制之Spring Factories
EnableAutoConfiguration註解的工作原理
功能模塊的resources目錄下 META-INF/spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.miao.client.MyAutoConfiguration
MyAutoConfiguration
@Configuration
@ConditionalOnMissingBean(ProxyFactoryBeanRegistry.class)
@EnableConfigurationProperties(ClientProperties.class)
public class MyAutoConfiguration{
@Autowired
private ClientProperties properties;
// 爲什麼爲static? 因爲下面的proxyFactoryBeanRegistry方法中要用到client
// 而該方法必須是static的,因爲在@Configuration的類裏,若@Bean標註的方法的
// 返回類型是BeanDefinitionRegistryPostProcessor,則該方法必須是static的
// https://github.com/ulisesbocchio/jasypt-spring-boot/issues/45
//https://stackoverflow.com/questions/41939494/springboot-cannot-enhance-configuration-bean-definition-beannameplaceholderreg
private static Client client = new Client();
@Bean
public Client client() {
log.info("初始化Client設置discovery");
ServiceDiscovery discovery = ......
client.setDiscovery(discovery);
client.init();
return client;
}
/**
* Cannot enhance @Configuration bean definition 'com.miao.rpc.client.RpcClientAutoConfiguration'
* since its singleton instance has been created too early.
* The typical cause is a non-static @Bean method with a BeanDefinitionRegistryPostProcessor return type:
* Consider declaring such methods as 'static'.
*
* 在@Configuration的類裏,若@Bean標註的方法的返回類型是BeanDefinitionRegistryPostProcessor,則該方法必須是static的
* 原因可能是:
* Having a BeanFactoryPostProcessor in a @Configuration class breaks the default post-processing
* of that @Configuration class.
*/
@Bean
public static ProxyFactoryBeanRegistry proxyFactoryBeanRegistry(){
// 這裏只能直接去property文件中獲取,因爲此時配置文件對象還未注入
String basePackage = PropertityUtil.getProperty("rpc.serverBasePackage");
return new ProxyFactoryBeanRegistry(basePackage, client);
}
接下來我們利用 BeanDefinitionRegistryPostProcessor 在bean初始化前將委託類的beanDefinition替換爲代理類的beanDefinition,委託類指的是被@MyReference標註,將其實現指向我們的proxy代理類,對它們的調用實際是對我們代理類的調用。
/**
* 實現後置處理器BeanDefinitionRegistryPostProcessor,該類繼承自BeanFactoryPostProcessor
* BeanFactoryPostProcessor在容器實例化任何bean之前讀取bean的定義(配置元數據),並可以修改它。
* BeanDefinitionRegistryPostProcessor可以讓我們註冊bean到容器中
*
* 這裏我的目的就是生成代理類的beanDefinition,將其與委託類在容器中的名字關聯,
* 這樣@Autowired注入的就是我們實現的代理類了
*/
@Slf4j
public class ProxyFactoryBeanRegistry implements BeanDefinitionRegistryPostProcessor {
private String basePackage;
private Client client;
public ProxyFactoryBeanRegistry(String basePackage, Client client) {
this.basePackage = basePackage;
this.client = client;
}
/**
* 掃描指定路徑下的所有類,得到其class對象,查詢class對象中是否有@RpcReference註解的字段,
* 爲該字段生成RpcProxyFactoryBean類型的beanDefinition,使其與字段的beanName關聯,
* RpcProxyFactoryBean是個FactoryBean,該類初始化時返回的是getObject方法的對象的bean,
* 而我們的RpcProxyFactoryBean同樣實現了InitializingBean,初始化時會先調用它的afterPropertiesSet方法,
* 在該方法中利用反射創建代理類對象,這樣在客戶端代碼中@Autowired便將代理類注入到了@RpcReference註解的字段,
* 客戶端對服務接口方法的調用,實際上觸發了代理類的invoke方法,在該方法中收集信息,如類名,方法名,參數
* 再向服務端發出請求
*/
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {
log.info("正在添加動態代理類的FactoryBean");
// 掃描工具類
ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false);
scanner.addIncludeFilter((metadataReader, metadataReaderFactory) -> true); // 設置過濾條件,這裏掃描所有
Set<BeanDefinition> beanDefinitionSet = scanner.findCandidateComponents(basePackage); // 掃描指定路徑下的類
for (BeanDefinition beanDefinition : beanDefinitionSet) {
log.info("掃描到的類的名稱{}", beanDefinition.getBeanClassName());
String beanClassName = beanDefinition.getBeanClassName(); // 得到class name
Class<?> beanClass = null;
try {
beanClass = Class.forName(beanClassName); // 得到Class對象
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
Field[] fields = beanClass.getDeclaredFields(); // 獲得該Class的多有field
for (Field field : fields) {
if (!field.isAccessible()) {
field.setAccessible(true);
}
// @MyReference註解標識
MyReference reference = field.getAnnotation(MyReference.class);
Class<?> fieldClass = field.getType(); // 獲取該標識下的類的類型,用於生成相應proxy
if (reference != null) {
log.info("創建" + fieldClass.getName() + "的動態代理");
BeanDefinitionHolder holder = createBeanDefinition(fieldClass);
log.info("創建成功");
// 將代理類的beanDefination註冊到容器中
BeanDefinitionReaderUtils.registerBeanDefinition(holder, beanDefinitionRegistry);
}
}
}
}
/**
* 生成fieldClass類型的BeanDefinition
* @return
*/
private BeanDefinitionHolder createBeanDefinition(Class<?> fieldClass) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(ProxyFactoryBean.class);
String className = fieldClass.getName();
// bean的name首字母小寫,spring通過它來注入
String beanName = StringUtils.uncapitalize(className.substring(className.lastIndexOf('.')+1));
// 給ProxyFactoryBean字段賦值
builder.addPropertyValue("interfaceClass", fieldClass);
builder.addPropertyValue("client", client);
return new BeanDefinitionHolder(builder.getBeanDefinition(), beanName);
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
}
}
這裏對代理類的要求相同,都是獲取委託類的類名,請求方法名,請求參數信息等,發送到遠端,所以這裏利用 FactoryBean 來返回統一的代理類
/**
* InitializingBean:初始化時afterPropertiesSet被調用,生成interfaceClass類型的代理類對象
* 在上面的ProxyFactoryBeanRegistry#createBeanDefinition方法中會創建
* 該類的BeanDefination,並給interfaceClass,client字段注入值
*/
@Slf4j
public class ProxyFactoryBean implements FactoryBean<Object>, InitializingBean {
private Client client;
private Class<?> interfaceClass; // 要生成的代理的類型
private Object proxy;
@Override
public Object getObject() throws Exception {
return proxy;
}
@Override
public Class<?> getObjectType() {
return interfaceClass;
}
@Override
public boolean isSingleton() {
return true; // 單例
}
@Override
public void afterPropertiesSet() throws Exception {
this.proxy = Proxy.newProxyInstance(
interfaceClass.getClassLoader(),
new Class<?>[]{interfaceClass},
(proxy, method, args) -> {
......
});
}
// 下面兩個setxxx方法用於容器的注入使用
public void setClient(RpcClient client) {
this.client = client;
}
public void setInterfaceClass(Class<?> interfaceClass) {
this.interfaceClass = interfaceClass;
}
}
接下來我們只需要在項目代碼中像下面這樣直接注入即可
@Autowired
@MyReference
HelloService helloService;
對 helloService#xxx 某某方法的調用將直接觸發代理類。