本片文章重在理解spring的扩展机制。理解了扩展机制。今后可以自行灵活对spring进行扩展。
带着上述的问题,我们来通过代码一步步的去实现一个feign,从而一步一步解开spring的真相。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(RestClientTest.class)
public static @interface EnableSongFeignClient {
}
有了上述这个注解,我们也可以像springBoot一样,把这个注解写到启动类的上面。
目前就是要@Import(RestClientTest.class),导入SongFeignClientTest.class这个类,只有这样写了,spring在启动的时候,才会去执行SongFeignClientTest该类。
那么该类又做了什么事情呢?
为什么要启动的时候,要去执行这个类,这个类又和我们实现Feign有啥关系呢?
正如上面所解释的内容一样,我们看看SongFeignClientTestRegistrar .class这个类又做了什么事情?
带着问题,我们继续往下看?
public class SongFeignClientTestRegistrar implements ImportBeanDefinitionRegistrar{
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
}
}
ImportBeanDefinitionRegistrar接口定义的类,其复写的方法只有在通过{@code @Import}方式注入时,才会被执行。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public static @interface SongFeignClient {
string baseUrl()
}
}
我们要实现的功能就是:凡是加了@SongFeignClient这个注解的接口,接口里面的方法,就会去帮我们调用方法指定的服务。类似于@Mapping,加了这个注解,mybatis,就会去执行sql,一个道理。
public static class SongClientFactoryBean implements FactoryBean<Object> {
private final Class<?> type ;
private final String baseUrl ;
public RestClientFactoryBean(Class<?> type, String baseUrl) {
this.type = type;
this.baseUrl = baseUrl;
}
@Override
public Object getObject() throws Exception {
return Proxy.newProxyInstance(
Thread.currentThread().getContextClassLoader(),
new Class[]{
type},
new SongClientImpl(baseUrl));
}
@Override
public Class<?> getObjectType() {
return type;
}
}
- FactoryBean,也是一个bean,这个bean是啥bean,取决于 该方法的getObject()方法的实现返回,该返回是啥就是啥bean。有了bean,就要想办法把bean交给spring。
- Proxy.newProxyInstance(params1,params2,params3) 这个方法就是jdk动态代理生成动态类的方式。我这里简单的件是下 3个参数分分别代表什么意思:
- Thread.currentThread().getContextClassLoader(): 表示jdk动态代理生成的类需要放在什么地方。
- new Class[]{type}: jdk动态代理是基于接口才能实现的,而java是多继承的,所以这个参数传入的是需要实现的接口。多继承,所以可能会有多个接口,这里接受的是数组
- InvocationHandler 第三个参数,是接口的方法,具体的实现内容。写的是我们的实现接口后要做的事情。因为方法名字森罗万象,所以必须要统一起来,统一的办法就是必须实现InvocationHandler的类才能传入。我这里实现InvocationHandler这个接口的对象叫:SongClientImpl :具体内容如下:
public static class SongClientImpl implements InvocationHandler{ private final RestTemplate restTemplate = new RestTemplate(); private final String baseUrl ; public RestClientImpl(String baseUrl){ this.baseUrl = baseUrl; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { GetMapping get = AnnotationUtils.findAnnotation(method, GetMapping.class); if (get != null) { String url = get.value()[0]; // 如果有ribbon,会在这里发挥作用 return restTemplate.getForObject(baseUrl + url, method.getReturnType()); } PostMapping post = AnnotationUtils.findAnnotation(method, PostMapping.class); if (post != null) { String url = post.value()[0]; return restTemplate.postForObject(baseUrl + url, args[0], method.getReturnType()); } return null; }
public class SongFeignClientTestRegistrar implements ImportBeanDefinitionRegistrar{
/*
*
* 交给spring的办法就是在这里,这个就是像的办法,把这个类通过register注册给spring
* 注册的思路:
* 1.通过扫描剋,把我们自己添加的注解扫描到。
* 2.扫描到之后,把扫描的添加的注解的 接口交给上面的 RestClientFactoryBean 让他帮忙生成 代理对象
* 3.把生成的代理对象交给spring管理
*
* */
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
// 扫描类
ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false){
// 将接口也扫进来
@Override
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
if (beanDefinition.getMetadata().isInterface()) {
return true ;
}
return super.isCandidateComponent(beanDefinition);
}
};
// 只扫描RestClient注解注释的接口
scanner.addIncludeFilter(new AnnotationTypeFilter(RestClient.class));
String scanPackage;
try {
// 设置扫描路径
scanPackage = Class.forName(importingClassMetadata.getClassName()).getPackage().getName();
} catch (ClassNotFoundException e1) {
throw new RuntimeException(e1);
}
for (BeanDefinition b : scanner.findCandidateComponents(scanPackage)) {
AnnotatedBeanDefinition abd = (AnnotatedBeanDefinition) b;
// 获取RestClient注解值
String baseUrl = (String) abd.getMetadata().getAnnotationAttributes(RestClient.class.getName(),true).get("baseUrl");
try {
// 动态定义bean
registry.registerBeanDefinition("rest-client--" + b.getBeanClassName(),
BeanDefinitionBuilder.genericBeanDefinition(RestClientFactoryBean.class)
.addConstructorArgValue(Class.forName(b.getBeanClassName()))
.addConstructorArgValue(baseUrl)
.getBeanDefinition());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
}
整理思路
- 通过扫描剋,把我们自己添加的注解扫描到。
- 扫描到之后,把扫描的添加的注解的 接口交给上面的 RestClientFactoryBean 让他帮忙生成 代理对象
- 把生成的代理对象交给spring管理
至此我们就完成了,一个基本原理的feign。
- ClassPathScanningCandidateComponentProvider类是spring中用来做类/包扫描的工具.
- 包括classpath下所有的包,比如jar中的。你平时使用的ComponentScan注解用的就是这个类处理的。
- 关键他会通过asm解析类,而不是通过动态加载解析类,这意味着类不会被初始化。
- 而且你可以自定义扫描规则,例如只返回指定注解的类等等。
public class ClassPathScanningCandidateComponentProvider类妙用 {
public static void main(String[] args) {
// new的参数设置false,表示不适用spring自己内置的规则,通常自定义规范的时候都不需要设置true。
ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false){
// 因为默认不回去返回接口,这样写可以将接口也扫进来
// 但是注意,这只能说是让接口作为候选类,并不一定会返回,关键还是要看规则是否匹配
@Override
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
if (beanDefinition.getMetadata().isInterface()) {
return true ;
}
return super.isCandidateComponent(beanDefinition);
}
};
// 自定义规则,扫描具有指定注解的类
scanner.addIncludeFilter(new AnnotationTypeFilter(RestClient.class));
scanner.findCandidateComponents("com.dragonsoft").forEach(System.out :: println);
}
如何看到这里,那么久在附送一个Javassist实现动态代理原理
public class JavassistTest {
public static void main(String[] args) throws Exception{
// 创建一个新的ClassPool,这样它可以被回收,同时它内部的CtClass也可以被回收,以避免内存溢出
ClassPool cp = new ClassPool(true);
CtClass cls = cp.makeClass("com.gframework.samplecode.BB");
cls.addInterface(cp.get(AA.class.getName()));
CtMethod f = new CtMethod(CtClass.voidType, "fun", null, cls);
f.setModifiers(Modifier.PUBLIC);
f.setBody("{"
+ "System.out.println(\"Hello\");"
+ "System.out.println(\"World\");"
+ "}");
cls.addMethod(f);
CtMethod f2 = new CtMethod(CtClass.voidType, "fun2", new CtClass[]{
cp.get("java.lang.String")}, cls);
f2.setModifiers(Modifier.PUBLIC);
f2.setBody("{"
+ "String ddd = $1;"
+ "System.out.println(\"Hello22\" + ddd + new java.util.Date());"
+ "System.out.println(\"World22\");"
+ "}");
cls.addMethod(f2);
AA a = (AA)cls.toClass().newInstance();
a.fun();
a.fun2("AAAAA");
}
}
interface AA{
public void fun();
public void fun2(String str);
}
***全是干货,不知道自己有没有说清楚,希望各位可以有所收获。期待下次更新,pice