Spring元编程
最近读了一些Spring关于注解的代码,提了两个比较有趣的问题:Q1: 在Spring里面@Controller
, @Service
, @Repository
等与@Component
是等价的,如何实现的?Q2: @AliasFor
如何实现的?我们来聊聊这些问题。
Q1: 在Spring里面@Controller
, @Service
, @Repository
等与@Component
是等价的,如何实现的?
实现的原理很简单,就是注解的注解。
如果查看@Controller
、@Service
、@Repository
等注解的源码,能发现在他们至上都有@Component
注解。
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Service {
// ...
}
org.springframework.stereotype.Service#L47
如果查看AnnotationUtils
类中的getAnnotation
方法,可以发现其实算法非常简单(5.0版本开始已经重写了这个方法,但原理应该是一样的):
public static <A extends Annotation> A getAnnotation(AnnotatedElement annotatedElement, Class<A> annotationType) {
try {
// 获取类型上的注解
A annotation = annotatedElement.getAnnotation(annotationType);
// 如果没有找到的话
if (annotation == null) {
// 从类型所有的注解上
for (Annotation metaAnn : annotatedElement.getAnnotations()) {
// 找注解的注解
annotation = metaAnn.annotationType().getAnnotation(annotationType);
if (annotation != null) {
break;
}
}
}
return synthesizeAnnotation(annotation, annotatedElement);
}
catch (Throwable ex) {
handleIntrospectionFailure(annotatedElement, ex);
return null;
}
}
org.springframework.core.annotation.AnnotationUtils#L180
所以我们是不是可以这样理解,@Service
注解“扩展”自@Component
注解,是@Component
注解的“子类”注解。
但是我要说的是,当Spring容器去扫描Bean的时候,算法比上面的更复杂一点。
Spring去scan component是由ClassPathBeanDefinitionScanner
实现的:
org.springframework.context.annotation.ClassPathBeanDefinitionScanner
而对各对象进行过滤则是有AnnotationTypeFilter
负责的:
org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider#L184
更深入阅读源码会发现这个复杂的算法核心是AnnotatedElementUtils
中的searchWithGetSemantics
方法实现的,原理还是一样只是这个方法是个稍复杂的递归:
org.springframework.core.annotation.AnnotatedElementUtils#923
另外,你会发现这个方法传入的不是Annotation
类型,而是字符串,我觉得可能是为了解决跨ClassLoader
类不相等的问题。(猜测)
另外的另外,从5.0版本开始这里的代码好像都重构过了,虽然我还没有仔细看,但我觉得原理应该是一样的,只是使用了更时髦的方式去写。(也有肯能为了能够配合Kotlin)
Q2: @AliasFor
如何实现的?
众所周知,在使用@RequestMapping
注解的时候,如下两种使用方法是一样的:
@RequestMapping("/path/to/endpoint")
Public HttpEntity<?> someEndpoint() {
// ...
}
@RequestMapping(path = "/path/to/endpoint")
Public HttpEntity<?> someEndpoint() {
// ...
}
初看起来这好像没有什么问题,意图很好理解,但是自己写过注解的同学肯定有点细思极恐的感觉。好吧,肯定得多个value()
和path()
的值的判断,二者取其一。那么每个注解进行反射处理的时候是不是都要多写一大堆判断的代码呢?
打开Spring源码,我们发现:
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping
public @interface RequestMapping {
@AliasFor("path")
String[] value() default {};
@AliasFor("value")
String[] path() default {};
// ...
}
hummm,好吧又是黑科技。然后更黑的科技是,如下两种使用效果是一样的:
@RequestMapping(path = "/path/to/endpoint", method = GET)
Public HttpEntity<?> someEndpoint() {
// ...
}
@GetMapping("/path/to/endpoint")
Public HttpEntity<?> someEndpoint() {
// ...
}
如果你打开@GetMapping
注解的源代码,就会看到:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@RequestMapping(method = RequestMethod.GET)
public @interface GetMapping {
@AliasFor(annotation = RequestMapping.class)
String[] value() default {};
@AliasFor(annotation = RequestMapping.class)
String[] path() default {};
// ...
}
在前面一个问题中,我们已经回答了注解的注解是如何实现的。所以@GetMapping
注解上的@RequestMapping
是可以理解的,说明@GetMapping
是“扩展”自@RequestMapping
的。
然后我们可以猜测一下@AliasFor
注解的意思,它字面上的意思是“为…的别名”。所以@GetMapping#value
为@RequestMapping#value
的别名,@GetMapping#path
为@RequestMapping#path
的别名。OK,字面上很好理解,那么问题来了,它是怎么实现的呢?
诀窍就在getAnnotation
方法里调用的synthesizeAnnotation
方法里面。当Spring发现对象注解含有@AliasFor
时,或者对象注解中的注解含有@AliasFor
时,Spring会返回这个注解的“合成注解”,简单来说就是把这个注解的Annotation
对象包裹在一个动态代理里面,这样当你获取这个对象的被@AliasFor
所注释的属性时,所有必须的信息就可以通过这个动态代理来获得。
也许我说的比较复杂,还是来看源码吧。
@SuppressWarnings("unchecked")
static <A extends Annotation> A synthesizeAnnotation(A annotation, Object annotatedElement) {
if (annotation == null) {
return null;
}
if (annotation instanceof SynthesizedAnnotation) {
return annotation;
}
Class<? extends Annotation> annotationType = annotation.annotationType();
if (!isSynthesizable(annotationType)) {
return annotation;
}
DefaultAnnotationAttributeExtractor attributeExtractor =
new DefaultAnnotationAttributeExtractor(annotation, annotatedElement);
InvocationHandler handler = new SynthesizedAnnotationInvocationHandler(attributeExtractor);
// Can always expose Spring's SynthesizedAnnotation marker since we explicitly check for a
// synthesizable annotation before (which needs to declare @AliasFor from the same package)
Class<?>[] exposedInterfaces = new Class<?>[] {annotationType, SynthesizedAnnotation.class};
return (A) Proxy.newProxyInstance(annotation.getClass().getClassLoader(), exposedInterfaces, handler);
}
org.springframework.core.annotation.AnnotationUtils#L1504
另外要说一下的是,从5.0版本开始这一块的代码已经被全部重构了。
Q3:高级玩法
有了这些关于元编程的知识再加上我之前那篇关于注解切面的实现的文章,我觉得可以玩出很多花样。至于什么样的花样,hummm,再让我好好想想。
- 完 -