簡述
我們都知道在使用 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地址