实现这样一种功能:
自定义了一个注解@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 某某方法的调用将直接触发代理类。