spring 自定义注解实现静态注入(@StaticAutowired,@StaticValue)
简述
我们都知道在使用 spring 框架时,我们无法通过 注解(@Autowired) 的方式自动注入静态属性,如果想注入静态属性只能通过其他方式实现,这里不讨论其他原生曲线实现方式,我就要用自动注入的注解,这样用起来方便,由此诞生了这篇文章
不过首先可以先说下,本篇文章的静态注入功能核心并非我从零开始实现,而是主要spring由实现的,没错,spring 的 @Autowired 和 @Value 其实是实现了静态注入的功能的,只是,spring 将他 "封印" 了,我这里只是将其"解封",并没有去编写一套复杂的机制,还有明确另一个问题,即 spring 不提供静态注入说明其本身是并不希望开发者使用spring管理静态对象的,所以希望使用注解静态注入的自己把握是否需要
使用方式及效果
为了简单性,易用性,最好和 @Autowired 和 @Value 使用方式一致,我自定义的注解是 @StaticAutowired,@StaticValue 分别对标上面两个注解,实现了 @Autowired 和 @Value 的所有功能,外加支持自动注入静态属性
使用上这里给个小示例吧,和使用 @Autowired 和 @Value没有任何区别
在 UserService 中静态注入 UserStatic
@Service
public class UserService {
@StaticAutowired
public static User user;
public static void test(){
System.out.println("UserService 里面的静态方法 name:"+user.getName()+" age:"+user.getAge());
}
}
当然使用前提和 @Autowired 是一样的 ,即被注入的类必须在容器中存在,下面是被注入的 User 类
@Data
public class User {
@StaticValue("${user.name}")
private static String name;
@Value("${user.age}")
private Integer age;
public User() {}
public String getName() {return name;}
在 User 类中,使用了 @StaticValue 注入了 静态属性 name,使用 @Value 注入了 普通属性 age ps:@StaticValue 也是可以注入普通属性的,即这里两个属性都可以用 @StaticValue
配置类
这里用 @Bean 的方式将 User 加入到容器中,在 User 类上 加类似的 @Component 注解也是可以的,我这里主要还需要引入 user.properties ,就写了一个 @Configuration 配置类,放一起了
@Configuration
@PropertySource(value = {"classpath:user.properties"})
public class SelfConfig {
@Bean
public User user(){
return new User();
}
}
user.properties 文件内容
user.name="properties config"
user.age=10
@Autowired 实现原理与被封印的静态注入
我既然想通过注解实现静态注入,肯定会参考 @Autowired 的实现,不参考不知道,一参考发现 其实
@Autowired 和 @Value 其实是实现了静态注入的功能,下面大略细说
spring 实现 @Autowired 是基于大名鼎鼎的 Bean 后置处理器实现的 ,该Bean 后置处理器是 AutowiredAnnotationBeanPostProcessor
,他的继承结构如下:
spring 中 Bean 的后置处理器的调用时机有很多, @Autowired 的Bean 后置处理器自动注入Bean的处理在这里
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#populateBean
点进去看具体方法
org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor#postProcessPropertyValues
通过 debug 调试会发现,InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);
是获取不到静态属性的,接着跟进:
会先从缓存中获取注解的解析结果,这里会直接从缓存获取,因为解析在之前进行过了,不过从代码中,我们也能看出来,实际的解析就是红线标出部分 metadata = buildAutowiringMetadata(clazz);
实际解析的时机是
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#applyMergedBeanDefinitionPostProcessors
AutowiredAnnotationBeanPostProcessor
类实现了 MergedBeanDefinitionPostProcessor
接口,会在上面的时机调用 postProcessMergedBeanDefinition 方法缓存要解析的注解结果,这里贴下调用链
因此我们重点关注 metadata = buildAutowiringMetadata(clazz);
方法
org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor#buildAutowiringMetadata
通过 debug 调试,会发现静态属性上的注解 spring 也做了解析,但是上面框住部分做了拦截,静态属性不会加入到解析缓存中,通过debug 动态调整数据,跳过静态拦截检查,会发现 @Autowired
正常注入了静态属性,因此我上面会说 spring 其实已经做了静态注入的所有准备,但是将该功能 "封印" 了,原因估计是 spring 一些设计理念的问题,因此实现 @StaticAutowired 无需我们做什么,只需 "解封" 即可
解封 @StaticValue 问题
跳过“封印”后,@StaticAutowired 没什么问题,但是自定义注解 @StaticValue 静态注入仍然有问题,这里就不赘述详细分析过程了,原因是在获取注解的spel表达式的值时,spring 框架用的是 ContextAnnotationAutowireCandidateResolver
,该类的继承结构如下图:
获取 注解值的方法是其父类 QualifierAnnotationAutowireCandidateResolver
的 findValue 方法
org.springframework.beans.factory.annotation.QualifierAnnotationAutowireCandidateResolver#findValue
/**
* Determine a suggested value from any of the given candidate annotations.
*/
@Nullable
protected Object findValue(Annotation[] annotationsToSearch) {
// qualifier annotations have to be local
if (annotationsToSearch.length > 0) {
AnnotationAttributes attr = AnnotatedElementUtils.getMergedAnnotationAttributes(
// this.valueAnnotationType 是写死在类上的成员变量 Value.class
// 即 private Class<? extends Annotation> valueAnnotationType = Value.class;
AnnotatedElementUtils.forAnnotations(annotationsToSearch), this.valueAnnotationType);
if (attr != null) {
return extractValue(attr);
}
}
return null;
}
经过上面的代码,是解析不出来 @StaticValue 的注解值的,只能解析出来 @Value 的
@StaticAutowired,@StaticValue 实现
上面分析的差不多了,下面开始代码修改,首先是两个自定义注解的定义
/**
* @Description: 注入静态属性
* @Author: ysx
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
@Documented
public @interface StaticAutowired {
boolean required() default true;
}
/**
* @Description: 注入静态属性值
* @Author: ysx
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
@Documented
public @interface StaticValue {
String value();
}
通过上面分析,为了能取到 @StaticValue 注解值,我们需要自己定义一个类来代替 ContextAnnotationAutowireCandidateResolver
,我们继承该类,重写方法即可,这样没有风险
/**
* @Description: spring 容器中默认的 ContextAnnotationAutowireCandidateResolver 写死只解析 @Value 注解 ,想解析自定义的 @StaticValue 注解 只能出此继承下策
* @Author: ysx
*/
public class ExContextAnnotationAutowireCandidateResolver extends ContextAnnotationAutowireCandidateResolver {
private Class<? extends Annotation> staticValueAnnotationType = StaticValue.class;
@Override
protected Object findValue(Annotation[] annotationsToSearch) {
// 父类 对 @Value 的 value 值解析出来
Object value = super.findValue(annotationsToSearch);
if(value!=null){
return value;
}
// 如果 无值 解析 @staticValue
if (annotationsToSearch.length > 0) {
AnnotationAttributes attr = AnnotatedElementUtils.getMergedAnnotationAttributes(
AnnotatedElementUtils.forAnnotations(annotationsToSearch), this.staticValueAnnotationType);
if (attr != null) {
return extractValue(attr);
}
}
return null;
}
}
下面就是 解析 StaticAutowired,StaticValue 的 bean 后置处理器了,该处理器直接参考 AutowiredAnnotationBeanPostProcessor
做一些修改即可,代码如下:
/**
* @Description: 解析 StaticAutowired,StaticValue 的 bean 后置处理器
* @Author: ysx
*/
@Component
public class StaticAutowiredAnnotationBeanPostProcessor extends InstantiationAwareBeanPostProcessorAdapter
implements MergedBeanDefinitionPostProcessor, PriorityOrdered, BeanFactoryAware {
private final Log logger = LogFactory.getLog(getClass());
private DefaultListableBeanFactory beanFactory;
private final Map<String, InjectionMetadata> injectionMetadataCache = new ConcurrentHashMap<>(256);
/**
* 支持的注解
*/
private final Set<Class<? extends Annotation>> autowiredAnnotationTypes = new LinkedHashSet<>(4);
private String requiredParameterName = "required";
private boolean requiredParameterValue = true;
private final ExContextAnnotationAutowireCandidateResolver exContextAnnotationAutowireCandidateResolver = new ExContextAnnotationAutowireCandidateResolver();
@SuppressWarnings("unchecked")
public StaticAutowiredAnnotationBeanPostProcessor() {
this.autowiredAnnotationTypes.add(StaticAutowired.class);
this.autowiredAnnotationTypes.add(StaticValue.class);
}
@Override
public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) throws BeansException {
InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);
AutowireCandidateResolver autowireCandidateResolver = beanFactory.getAutowireCandidateResolver();
// 为了 解析 @StaticValue 必须使用 自定义的 ExContextAnnotationAutowireCandidateResolver
boolean isExContextAnnotationAutowireCandidateResolver = autowireCandidateResolver instanceof ExContextAnnotationAutowireCandidateResolver;
try {
if (!isExContextAnnotationAutowireCandidateResolver) {
beanFactory.setAutowireCandidateResolver(exContextAnnotationAutowireCandidateResolver);
}
metadata.inject(bean, beanName, pvs);
}
catch (BeanCreationException ex) {
throw ex;
}
catch (Throwable ex) {
throw new BeanCreationException(beanName, "Injection of static autowired dependencies failed", ex);
}finally {
// 设置回原来的
if (!isExContextAnnotationAutowireCandidateResolver) {
beanFactory.setAutowireCandidateResolver(autowireCandidateResolver);
}
}
return pvs;
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
if (!(beanFactory instanceof DefaultListableBeanFactory)) {
throw new IllegalArgumentException(
"StaticAutowiredAnnotationBeanPostProcessor requires a DefaultListableBeanFactory: " + beanFactory);
}
this.beanFactory = (DefaultListableBeanFactory) beanFactory;
}
@Override
public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {
InjectionMetadata metadata = findAutowiringMetadata(beanName, beanType, null);
metadata.checkConfigMembers(beanDefinition);
}
@Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE - 2;
}
private InjectionMetadata findAutowiringMetadata(String beanName, Class<?> clazz, @Nullable PropertyValues pvs) {
// Fall back to class name as cache key, for backwards compatibility with custom callers.
String cacheKey = (StringUtils.hasLength(beanName) ? beanName : clazz.getName());
// Quick check on the concurrent map first, with minimal locking.
InjectionMetadata metadata = this.injectionMetadataCache.get(cacheKey);
if (InjectionMetadata.needsRefresh(metadata, clazz)) {
synchronized (this.injectionMetadataCache) {
metadata = this.injectionMetadataCache.get(cacheKey);
if (InjectionMetadata.needsRefresh(metadata, clazz)) {
if (metadata != null) {
metadata.clear(pvs);
}
metadata = buildAutowiringMetadata(clazz);
this.injectionMetadataCache.put(cacheKey, metadata);
}
}
}
return metadata;
}
private InjectionMetadata buildAutowiringMetadata(final Class<?> clazz) {
List<InjectionMetadata.InjectedElement> elements = new ArrayList<>();
Class<?> targetClass = clazz;
do {
final List<InjectionMetadata.InjectedElement> currElements = new ArrayList<>();
ReflectionUtils.doWithLocalFields(targetClass, field -> {
AnnotationAttributes ann = findAutowiredAnnotation(field);
if (ann != null) {
// 这里把不做静态检查 -- @Autowired 在这里做了静态校验,因此无法自动注入静态属性
boolean required = determineRequiredStatus(ann);
currElements.add(new StaticAutowiredAnnotationBeanPostProcessor.StaticAutowiredFieldElement(field, required));
}
});
elements.addAll(0, currElements);
targetClass = targetClass.getSuperclass();
}
while (targetClass != null && targetClass != Object.class);
return new InjectionMetadata(clazz, elements);
}
/**
* Determine if the annotated field or method requires its dependency.
* <p>A 'required' dependency means that autowiring should fail when no beans
* are found. Otherwise, the autowiring process will simply bypass the field
* or method when no beans are found.
* @param ann the Autowired annotation
* @return whether the annotation indicates that a dependency is required
*/
protected boolean determineRequiredStatus(AnnotationAttributes ann) {
return (!ann.containsKey(this.requiredParameterName) ||
this.requiredParameterValue == ann.getBoolean(this.requiredParameterName));
}
@Nullable
private AnnotationAttributes findAutowiredAnnotation(AccessibleObject ao) {
// autowiring annotations have to be local
if (ao.getAnnotations().length > 0) {
for (Class<? extends Annotation> type : this.autowiredAnnotationTypes) {
AnnotationAttributes attributes = AnnotatedElementUtils.getMergedAnnotationAttributes(ao, type);
if (attributes != null) {
return attributes;
}
}
}
return null;
}
/**
* Class representing injection information about an annotated field.
*/
private class StaticAutowiredFieldElement extends InjectionMetadata.InjectedElement {
private final boolean required;
private volatile boolean cached = false;
@Nullable
private volatile Object cachedFieldValue;
public StaticAutowiredFieldElement(Field field, boolean required) {
super(field, null);
this.required = required;
}
@Override
protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
Field field = (Field) this.member;
Object value;
if (this.cached) {
value = resolvedCachedArgument(beanName, this.cachedFieldValue);
}
else {
DependencyDescriptor desc = new DependencyDescriptor(field, this.required);
desc.setContainingClass(bean.getClass());
Set<String> autowiredBeanNames = new LinkedHashSet<>(1);
Assert.state(beanFactory != null, "No BeanFactory available");
TypeConverter typeConverter = beanFactory.getTypeConverter();
try {
value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);
}
catch (BeansException ex) {
throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(field), ex);
}
synchronized (this) {
if (!this.cached) {
if (value != null || this.required) {
this.cachedFieldValue = desc;
registerDependentBeans(beanName, autowiredBeanNames);
if (autowiredBeanNames.size() == 1) {
String autowiredBeanName = autowiredBeanNames.iterator().next();
if (beanFactory.containsBean(autowiredBeanName) &&
beanFactory.isTypeMatch(autowiredBeanName, field.getType())) {
this.cachedFieldValue = new StaticAutowiredAnnotationBeanPostProcessor.ShortcutDependencyDescriptor(
desc, autowiredBeanName, field.getType());
}
}
}
else {
this.cachedFieldValue = null;
}
this.cached = true;
}
}
}
if (value != null) {
ReflectionUtils.makeAccessible(field);
field.set(bean, value);
}
}
}
/**
* Register the specified bean as dependent on the staticAutowired beans.
*/
private void registerDependentBeans(@Nullable String beanName, Set<String> autowiredBeanNames) {
if (beanName != null) {
for (String autowiredBeanName : autowiredBeanNames) {
if (this.beanFactory != null && this.beanFactory.containsBean(autowiredBeanName)) {
this.beanFactory.registerDependentBean(autowiredBeanName, beanName);
}
if (logger.isTraceEnabled()) {
logger.trace("StaticAutowiring by type from bean name '" + beanName +
"' to bean named '" + autowiredBeanName + "'");
}
}
}
}
/**
* Resolve the specified cached method argument or field value.
*/
@Nullable
private Object resolvedCachedArgument(@Nullable String beanName, @Nullable Object cachedArgument) {
if (cachedArgument instanceof DependencyDescriptor) {
DependencyDescriptor descriptor = (DependencyDescriptor) cachedArgument;
Assert.state(this.beanFactory != null, "No BeanFactory available");
return this.beanFactory.resolveDependency(descriptor, beanName, null, null);
}
else {
return cachedArgument;
}
}
/**
* DependencyDescriptor variant with a pre-resolved target bean name.
*/
@SuppressWarnings("serial")
private static class ShortcutDependencyDescriptor extends DependencyDescriptor {
private final String shortcut;
private final Class<?> requiredType;
public ShortcutDependencyDescriptor(DependencyDescriptor original, String shortcut, Class<?> requiredType) {
super(original);
this.shortcut = shortcut;
this.requiredType = requiredType;
}
@Override
public Object resolveShortcut(BeanFactory beanFactory) {
return beanFactory.getBean(this.shortcut, this.requiredType);
}
}
}
修改点 主要是以下这些,这里也提下吧
- buildAutowiringMetadata() 方法去除静态校验,删除方法解析相关代码
- BeanFactoryAware 接口注入的容器声明为 DefaultListableBeanFactory
- 在 postProcessProperties() 方法中将我们自定义的的 ExContextAnnotationAutowireCandidateResolver 设置到 DefaultListableBeanFactory 中
最后点击访问 github地址