Spring之AnnotationUtils

举报
赵KK日常技术记录 发表于 2023/06/30 16:11:20 2023/06/30
【摘要】 在前段时间的策略模式中应用到了枚举动态维护实现类来应对N个对同一功能的实现,但枚举维护也有缺点,随着业务的变更,大概率像这种业务会不断增加,每增加一个业务类型则需要维护一个枚举,虽说是一行代码的事,但能不能再进行优化呢?当一个登陆接口随着密码登陆,验证码登陆,人脸识别,微信登录等逻辑增加,如果能用注解驱动,那岂不是更方便?在往期文中为了快速写完demo,并没有细节,在实际应用时发现许多忽略的...

在前段时间的策略模式中应用到了枚举动态维护实现类来应对N个对同一功能的实现,但枚举维护也有缺点,随着业务的变更,大概率像这种业务会不断增加,每增加一个业务类型则需要维护一个枚举,虽说是一行代码的事,但能不能再进行优化呢?当一个登陆接口随着密码登陆,验证码登陆,人脸识别,微信登录等逻辑增加,如果能用注解驱动,那岂不是更方便?

在往期文中为了快速写完demo,并没有细节,在实际应用时发现许多忽略的细节,在此重新补充一下。往期参考炫技?No.

SpringFactoriesLoader

源码如下

org.springframework.core.io.support.SpringFactoriesLoader#loadFactories
版本为spring-core-5.1.4.RELEASE.jar
SpringFactoriesLoader类的主要作用是通过类路径下的META-INF/spring.factories文件获取工厂类接口的实现类。

public static <T> List<T> loadFactories(Class<T> factoryClass, @Nullable ClassLoader classLoader) {
Assert.notNull(factoryClass, “‘factoryClass’ must not be null”);
ClassLoader classLoaderToUse = classLoader;
if (classLoaderToUse == null) {
classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
}
List<String> factoryNames = loadFactoryNames(factoryClass, classLoaderToUse);
if (logger.isTraceEnabled()) {
logger.trace(“Loaded [” + factoryClass.getName() + "] names: " + factoryNames);
}
List<T> result = new ArrayList<>(factoryNames.size());
for (String factoryName : factoryNames) {
result.add(instantiateFactory(factoryName, factoryClass, classLoaderToUse));
}
AnnotationAwareOrderComparator.sort(result);
return result;
}
路径在项目中为\src\main\resources\META-INF\spring.factories,注意需要遵守Properties的语法。

在实际应用中代码如下:

List<xxxService> dynamicBinds = SpringFactoriesLoader.loadFactories(xxxService.class, ClassUtils.getDefaultClassLoader());
注意事项:

在实际项目中由于获取实现类为ClassLoader.getClassLoader()方式获取,所以其底层是通过反射的方式获取的,相当于对service对象通过new来生成的,所以当实现类内存在Spring管理的Bean时,应通过aop的方式获取。

因为写demo我就只配了一个实现类所以list中只有一个

图片

图片

在项目中我是通过枚举绑定实现类的方式来实现的,但是随着业务的增长,接口如果每变化一次就新增一个枚举来维护的话,虽说只是一行代码,但我们也要尽量优化。那么能不能只用一个注解就标识了这个类的维护关系呢?之后我们只需要新增类上标识这个注解就好了。

注解驱动AnnotationUtils

新建注解驱动

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DynamicHandlerType {
DynamicEnum handler();
}
注解方法返回值随自己来定义,但建议定义为枚举,方便以后的业务区分。在实现类型标识此注解维护关系。

@PostConstruct
public void init(){
System.out.println(“LazyPostConstructAnnotation init”);
}

@Slf4j
@Service("xxxService")
@DynamicHandlerType(handler = DynamicEnum.xxx)
public class xxxServiceImpl implements xxxService {

    @Autowired
    private xxxMapper xxxMapper;

    @Override
    public Integer xxx(String x,String y) {
        return xxxMapper.xxx(x,y);
    }
}

@Autowired注释在方法

图片

通常我们在注入Bean会使用@Autowired/@Resource方式来注入,其实在源码中是明确指出@Autowired是可以直接应用到集合类或者方法上的。

当作用在方法上是会查询容器中否有此参数,并在注入的时候调用该方法,并对参数进行装配。

预先装配Bean的map关系,在调用时直接调用方法即可,一次维护处处方便,以后只维护注解即可。

private Map<xxxEnum, xxxService> xxxMap;

@Autowired
public void setxxxMap(List<xxxService> list) {
    xxxMap = list.stream()
            .collect(Collectors.toMap(
                    handler-> AnnotationUtils.findAnnotation(枚举定义方法.getClass(), 注解类.class).注解方法(),
                    handler-> 实现类));
}

AnnotationUtils & AnnocationElementUtils
图片

图片

就拿本次的findAmmotation方法构造函数就有6种,其中常用方法为第一个,第二和第五个的Set集合表示已经查找过的元素。

@Nullable
public static A findAnnotation(Class<?> clazz, Class annotationType) {
return findAnnotation(clazz, annotationType, true);
}
@Nullable
private static
A findAnnotation(
Class<?> clazz, @Nullable Class
annotationType, boolean synthesize) {

    Assert.notNull(clazz, "Class must not be null");
    if (annotationType == null) {
        return null;
    }

    AnnotationCacheKey cacheKey = new AnnotationCacheKey(clazz, annotationType);
    A result = (A) findAnnotationCache.get(cacheKey);
    if (result == null) {
        result = findAnnotation(clazz, annotationType, new HashSet<>());
        if (result != null && synthesize) {
            result = synthesizeAnnotation(result, clazz);
            findAnnotationCache.put(cacheKey, result);
        }
    }
    return result;
}

在调用findAnnotation方法中可看见调用的为声明式元注解,默认为true表示已经被代理过的对象,还可用于获取类上的注解

Mapper mapperAn= AnnotationUtils.findAnnotation(xxxService.class, Mapper.class);
返回为null表示该类上没有此注解。

图片

// 获取Class上面标注的注解
public static
A findAnnotation(Class<?> clazz, @Nullable Class annotationType)

// 获取Method上面的注解信息
public static <A extends Annotation> A findAnnotation(Method method, @Nullable Class<A> annotationType)

图片

当在@service注解上未标识value获取则为空,在AnnotationUtils中用于处理注解,处理元注解,桥接方法(编译器为通用声明生成)以及超级方法(用于可选注解继承)的常规实用程序方法,回顾下元注解:

在JDK 1.5中提供了4个标准的用来对注解类型进行注解的注解类,我们称之为 meta-annotation(元注解),他们分别是:
@Target
@Retention
@Documented
@Inherited
AnnocationElementUtils
用于在AnnotatedElements上查找注解,元注解和可重复注解的常规使用程序方法。AnnotatedElementUtils为Spring的元注解编程模型定义了公共API,并支持注解属性覆盖。如果您不需要支持注解属性覆盖,请考虑使用AnnotationUtils。请注意,JDK的内省工具本身不提供此类的功能

图片

https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/core/annotation/AnnotatedElementUtils.html
图片

我们看一个demo

其中父类有注解,子类没有注解,则控制台只输出了父类的注解信息,反之也是一样的,

public class ChildeMetaController extends xxxController {

public static void main(String[] args) {
System.out.println("getAnnotation @RequestMapping: " + AnnotationUtils.getAnnotation(DriverEntruckingController.class, RequestMapping.class));
System.out.println("getAnnotation @RequestMapping: " +AnnotationUtils.getAnnotation(ChildeMetaController.class, RequestMapping.class));
System.out.println();
}
}

控制台输出
getAnnotation @RequestMapping: @org.springframework.web.bind.annotation.RequestMapping(name=, value=[], method=[], params=[], path=[], produces=[application/json;charset=UTF-8], headers=[], consumes=[])
getAnnotation @RequestMapping: null
属性覆盖
属性覆盖指的是注解的一个成员覆盖另一个成员,最后两者成员属性值一致。

属性覆盖可以分为三类:

1.隐式覆盖(Implicit Overrides)
2.显式覆盖(Explicit Overrides)
3.传递式显式覆盖(Transitive Explicit Overrides)
至于覆盖注解的形式,我理解为注解传递,本应注解指定的value为parent,在注解内通过别名指定为child,则此时实际调用的是child的value。大多应用与组合注解的方式,在Spring当中也是应用非常广泛,比如RestContoller就同时包含controller和responseBody

public class SynthesizedAnnotationTest {

@Target({ ANNOTATION_TYPE, FIELD, TYPE })
@Retention(RUNTIME)
@interface Test1 {
    String test1() default "test1";
}

@Target({ ANNOTATION_TYPE, FIELD, TYPE })
@Retention(RUNTIME)
@interface Test2 {
    String test2() default "test2";
}

@Target({ ANNOTATION_TYPE, FIELD, TYPE })
@Retention(RUNTIME)
@Test2
@interface Test3 {
    /**
     * AliasFor注解用来表示要覆盖Test2注解中的test2()属性方法,
     * annotation属性声明的注解类必须存在于该注解的元注解上
     * attribute属性声明的值必须存在于Test2注解属性方法中(即Test2注解的test2方法)
     */
    @AliasFor(annotation = Test2.class, attribute = "test2")
    String test3() default "test3";
}

/**
 * 只有@Test3注解,但是Test3注解上组合了@Test2注解,并将该注解的test3方法值用来覆盖Test2注解中的test2方法
 * 即更低层次声明的覆盖规则,会覆盖更高层次的属性方法值,即调用高层次的注解方法值实际显示的是低层所赋的值
 * 当然也可以将组合注解作用于更高层次,如Test3组合Test2,Test2组合Test1,然后将Test3作用于元素,通过工具类获取Test1注解覆盖的属性值
 */
@Test3(test3 = "覆盖Test2属性中的test2方法")
static class Element {}

public static void main(String[] args) {
    Test2 test2 = AnnotatedElementUtils.getMergedAnnotation(Element.class, Test2.class);
    // 虽然调用了Test2注解的test2方法,但是实际显示的是Test3注解中的test3属性声明的值
    // 则说明Test2的test2属性被覆盖了 
    System.out.println(test2.test2());
}
// out '覆盖Test2属性中的test2方法'

}
版权声明:本文为CSDN博主「guyue35」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/guyue35/article/details/106691883

    重新整理思路是想要用于技术分享的,自己在尝试优化的过程遇到了很多问题,一一整理后又学到了新东西。
【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。