实现动态代理并将其注入IOC容器
前言
今天重构公司平台某模块的功能,在理顺了业务逻辑后,开始构思如何编写代码时,正好想到最近研究的关于FeignClient的底层实现机制,发现正好适用于当前场景,遂模仿FeignClient源码,利用动态代理并将代理类注入到IOC容器的思想来实现此业务场景,特此记录下来,方便参考与复盘。
场景
思考下,在比如FeignClient、Mybatis 这些场景下,我们需要根据一定的规则去实现某些操作,比如FeignClient的HTTP请求,Mybatis的数据库操作等,它们大部分操作都是相同,只是不同调用方导致的参数不一致,如果我们为每一种操作都写代码实现,就会造成大量冗余,何不对不同操作的共性进行抽取,通过动态代理的方式进行简化,既可以使代码优雅美观,也可以减轻大家的负担。
这就是本文要说的动态代理接口,并将其注入到IOC容器的思想。
实现
DynamicProxied
- 首先我们编写注解,用于标记需要被代理的类:
import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * @author chenjun * @description DynamicProxied * @date 2021/7/9 11:47 */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface DynamicProxied { String value() default ""; }
DefaultHandler
- 有了注解标记接口,我们就能(反射)获取所有被这个注解标记的接口,也就是需要被代理的接口类,既然是动态代理,所以我们要实现
InvocationHandler
:import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; /** * @author chenjun * @description DefaultHandler * @date 2021/7/9 15:48 */ public class DefaultHandler implements InvocationHandler { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // Object 方法,走原生方法,比如hashCode() if (Object.class.equals(method.getDeclaringClass())) { return method.invoke(this,args); } //这里方便演示,我们就只打印接口第一个入参 return args[0]; } } ```
DynamicProxiedFactoryBean
- 现在我们有了代理类的
invoke()
方法,我们就得生成代理类,参考Feign的FeignClientFactoryBean
的实现,我们也利用FactoryBean<T>
接口来实现:
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import java.lang.reflect.Proxy;
/**
* @author chenjun
* @description DynamicProxiedFactoryBean
* @date 2021/7/9 15:35
*/
public class DynamicProxiedFactoryBean implements FactoryBean<Object>, InitializingBean, ApplicationContextAware {
private ApplicationContext applicationContext;
private Class<?> type;
private String name;
private String contextId;
//重点:生成我们需要的代理对象
@Override
public Object getObject() throws Exception {
return Proxy.newProxyInstance(getObjectType().getClassLoader(), new Class[]{getObjectType()},
new DefaultHandler());
}
//返回Bean的类型
@Override
public Class<?> getObjectType() {
return getType();
}
@Override
public void afterPropertiesSet() throws Exception {
//用不上,空实现就可
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
//设置Bean单例
@Override
public boolean isSingleton() {
return true;
}
public Class<?> getType() {
return type;
}
public void setType(Class<?> type) {
this.type = type;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getContextId() {
return contextId;
}
public void setContextId(String contextId) {
this.contextId = contextId;
}
}
FactoryBean
是一种特殊的对象,它是一个工厂Bean,它返回的不是某一个实例,而是一类对象,它最大的作用是能让我们自定义Bean的创建过程,注意与BeanFactory
区分,理解FactoryBean
更有助于我们理解Spring的编程思想。
思考EnableFeignClient实现
- 现在有了
@DynamicProxied
、DefaultHandler
、DynamicProxiedFactoryBean
,我们基本完成了动态代理的主要实现,现在需要考虑的是怎么利用注解@DynamicProxied
搜集到所有需要被代理的接口,并为每种类型生成代理对象,为了方便理解,我们还是借鉴Feign。
在我们使用Feign时,会需要用到@EnableFeignClient
添加到springboot启动类来开启Feign功能,那是不是假如不添加这个注解,springboot就找不到FeignClient呢?
答案显然是的。
在
@EnableFeignClient
中,它利用@Import(FeignClientsRegistrar.class)
来进行装配的,而FeignClientsRegistrar
是依靠实现ImportBeanDefinitionRegistrar
来动态注册bean的
EnableToBeProxy
- 相应的我们添加
@EnableToBeProxy
注解:
import org.springframework.context.annotation.Import;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
1. @author chenjun
2. @description EnableToBeProxy
3. @date 2021/7/9 14:46
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(DynamicProxiedRegister.class)
public @interface EnableToBeProxy {
String value() default "";
}
DynamicProxiedRegister(关键点)
- 相应的新建
DynamicProxiedRegister
来动态注册bean:
registerBeanDefinitions
是其中最主要的方法,主要利用ClassPathScanningCandidateComponentProvider
来扫描所有被标记了DynamicProxied
的接口,接下来利用BeanDefinitionBuilder
手动将DynamicProxiedFactoryBean
注入容器,设置属性后获取BeanDefinition
,这一步很关键,代表我们真正获取到了动态代理类的bean定义,在此基础上才能通过BeanDefinitionReaderUtils
注入代理类到容器,其中,需要注意的是注入的bean的name,这里取的是接口类名的方式。
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.env.Environment;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.util.ClassUtils;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
/**
* @author chenjun
* @description DynamicProxiedRegister
* @date 2021/7/9 14:47
*/
public class DynamicProxiedRegister implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {
private Environment environment;
private ResourceLoader resourceLoader;
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
LinkedHashSet<BeanDefinition> candidateComponents = new LinkedHashSet<>();
ClassPathScanningCandidateComponentProvider scanner = getScanner();
scanner.setResourceLoader(this.resourceLoader);
scanner.addIncludeFilter(new AnnotationTypeFilter(DynamicProxied.class));
Set<String> basePackages = getBasePackages(metadata);
for (String basePackage : basePackages) {
candidateComponents.addAll(scanner.findCandidateComponents(basePackage));
}
for (BeanDefinition candidateComponent : candidateComponents) {
if (candidateComponent instanceof AnnotatedBeanDefinition) {
// verify annotated class is an interface
AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
Map<String, Object> attributes = annotationMetadata
.getAnnotationAttributes(DynamicProxied.class.getCanonicalName());
try {
register(registry, annotationMetadata, attributes);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
}
private String getName(AnnotationMetadata annotationMetadata) throws ClassNotFoundException {
String name = annotationMetadata.getClassName();
Class<?> aClass = Class.forName(name);
String simpleName = aClass.getSimpleName();
simpleName = simpleName.substring(0, 1).toLowerCase() + simpleName.substring(1);
return simpleName;
}
private void register(BeanDefinitionRegistry registry,
AnnotationMetadata annotationMetadata, Map<String, Object> attributes) throws ClassNotFoundException {
String className = annotationMetadata.getClassName();
BeanDefinitionBuilder definition = BeanDefinitionBuilder
.genericBeanDefinition(DynamicProxiedFactoryBean.class);
String name = getName(annotationMetadata);
definition.addPropertyValue("name", name);
definition.addPropertyValue("contextId", name);
definition.addPropertyValue("type", className);
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
String alias = name + "needProxy";
AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
beanDefinition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE, className);
beanDefinition.setPrimary(true);
BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
new String[] { alias });
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}
protected Set<String> getBasePackages(AnnotationMetadata importingClassMetadata) {
Set<String> basePackages = new HashSet<>();
basePackages.add(
ClassUtils.getPackageName(importingClassMetadata.getClassName()));
return basePackages;
}
protected ClassPathScanningCandidateComponentProvider getScanner() {
return new ClassPathScanningCandidateComponentProvider(false, this.environment) {
@Override
protected boolean isCandidateComponent(
AnnotatedBeanDefinition beanDefinition) {
boolean isCandidate = false;
if (beanDefinition.getMetadata().isIndependent()) {
if (!beanDefinition.getMetadata().isAnnotation()) {
isCandidate = true;
}
}
return isCandidate;
}
};
}
@Override
public void setEnvironment(Environment environment) {
this.environment = environment;
}
@Override
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
}
MyInterface 和 MyController
- 准备工作做好后,我们新建
MyInterface
和MyController
来看看效果,当然,启动类不要忘记了@EnableToBeProxy
:
理论上,在myFunction
上会打印我们请求参数message
MyInterface:
/**
* @author chenjun
* @description MyInterface
* @date 2021/7/9 14:23
*/
@DynamicProxied
public interface MyInterface {
String print(String message);
}
MyController:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Objects;
/**
* @author chenjun
* @description MyController
* @date 2021/7/9 14:24
*/
@RestController
@RequestMapping("my")
public class MyController {
@Autowired
private MyInterface myInterface;
@RequestMapping("/proxy/myFunction/{message}")
public String myFunction(@PathVariable String message){
Objects.requireNonNull(myInterface, "佛祖保佑,永无bug");
System.out.println("测试 getClass() = " + myInterface.getClass());
System.out.println("测试 hashCode() = " + myInterface.hashCode());
String result = myInterface.print(message);
return result;
}
}
启动类:
import com.study.proxy.dynamic.EnableToBeProxy;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@EnableToBeProxy
public class ProxyApplication {
public static void main(String[] args) {
SpringApplication.run(ProxyApplication.class, args);
}
}
实现效果
- 最后,浏览器访问
http://localhost:8080/my/proxy/myFunction/666
,我们来看看返回结果:
跟我们预期返回结果一样。
总结
可以看到,实现过程中,其实我们并没有编写难以理解的代码,大部分都是借助Spring已有的工具和实现方式,重要的是理解Bean的生命周期以及各种注入方式,而对于使用注解的人来说,重复性的工作已经减少了大部分,真正要做的只是Autowired
对应的接口而已,而这也正是我们的目的。
反思
有时候,基础知识的熟练掌握与运用,比新技术的学习,对于个人甚至团队的提升更大。