深入理解Spring注解机制:合并注解的合成

深入理解Spring注解机制:合并注解的合成,第1张

众所周知, spring 从 25 版本以后开始支持使用注解代替繁琐的 xml 配置,到了 springboot 更是全面拥抱了注解式配置。平时在使用的时候,点开一些常见的等注解,会发现往往在一个注解上总会出现一些其他的注解,比如 @Service :

大部分情况下,我们可以将 @Service 注解等同于 @Component 注解使用,则是因为 spring 基于其 JDK 对 元注解的机制 进行了扩展。

在 java 中,元注解是指可以注解在其他注解上的注解,spring 中通过对这个机制进行了扩展,实现了一些原生 JDK 不支持的功能,比如允许在注解中让两个属性互为别名,或者将一个带有元注解的子注解直接作为元注解看待,或者在这个基础上,通过 @AliasFor 或者同名策略让子注解的值覆盖元注解的值。

本文将基于 spring 源码 52x 分支,解析 spring 如何实现这套功能的。

这是系列的第三篇文章,将详细介绍 Spring 是如何在经过搜索与属性映射后,将处理后的注解合成为合并注解的。

相关文章:

我们在前文了解用于搜索注解的合并注解聚合 MergedAnnotations 与用于完成注解属性映射的 AnnotationTypeMappings 和 AnnotationTypeMapping ,现在我们需要知道在 MergedAnnotations 这个容器中, AnnotationTypeMappings 和 AnnotationTypeMapping 是如何转为一个我们所需要的合并注解 MergedAnnotation 的。

与前文一样,我们以 AnnotatedElementUtilsfindMergedAnnotations 方法作为入口:

我们在上文顺着 MergedAnnotationsget 一路找到 TypeMappedAnnotationsMergedAnnotationFinder 的 process 方法,在这里我们目睹了一个普通的注解的元注解被解析为 AnnotationTypeMappings 与 AnnotationTypeMapping 的过程:

该方法是 AnnotationTypeMapping 转为 MergedAnnotation 的关键。

TypeMappedAnnotation 是 MergedAnnotation 一个通用实现,在大部分情况下,我们所说的合并注解其实指的就是这个类。

通过它的构造方法我们得以了解其创建过程:

可以看得出, TypeMappedAnnotation 基本可以认为是 AnnotationTypeMapping 的包装类,它以一个 AnnotationTypeMapping 实例作为数据源,从而提供一些关于映射后的属性的相关功能。

回到 AnnotatedElementUtilsfindMergedAnnotations ,我们可以看到,在通过 MergedAnnotations 获得了一个 MergedAnnotation 对象——实际上是 TypeMappedAnnotation 对象——之后,又调用了 MergedAnnotationsynthesize 方法,将 MergedAnnotation 转成了一个调用方指定类型的注解对象。

该方法先调用了 AbstractMergedAnnotation 的 synthesize 方法:

随后再调用了实现类 TypeMappedAnnotation 的 synthesize 方法:

继续点开 createSynthesized :

而 SynthesizedMergedAnnotationInvocationHandler 是一个用于 JDK 动态代理的 InvocationHandler ,我们不需要完全站看,仅需看看它的构造函数与 InvocationHandlerinvoke 就能明白它的运作机制了:

至此,合并注解的合成机制已经很明确了:

承接上文,当我们使用 MergedAnnotationsynthesize 方法时,我们可能会得到两种对象:

而通过注解代理对象取值时,这些方法会被代理到 SynthesizedMergedAnnotationInvocationHandler 中存放的 MergedAnnotation 对象上,从而让这个代理对象通过原始注解的属性,获得与原始注解不一样的属性值。

当我们调用代理对象的属性值时,它会在 SynthesizedMergedAnnotationInvocationHandler 中,通过 invoke 代理到对应的方法上:

这里我们代理对象是如何获取注解属性值的:

这里的 MergedAnnotationgetValue 最终在经过多次跳转后,调到 TypeMappedAnnotationgetAttributeValue 上:

而这边的 getValue 方法就是真正要获取属性值的地方。

这一步有点复杂,主要是根据不同的情况,通过 AnnotationTypeMapping 中的几个属性映射数组,包括 aliasMappings 、 conventionMappings , annotationValueMappings 与 annotationValueSource 来确定最终用于取值的 AnnotationTypeMapping 对象与调用的方法在 AttributeMethods 中的下标:

至此,获取属性值的方法流程也走完了。

在这一章,我们了解了当通过 MergedAnnotations 获得注解并解析得到 AnnotationTypeMapping 后, AnnotationTypeMapping 是如何再转为我们所需的 MergedAnnotation ,以及在此之后, MergedAnnotation 又是如何生成我们最终所需要的代理注解的。

简而言之,当解析注解的元注解获得所需的 AnnotationTypeMapping 后, MergedAnnotation 会判断 AnnotationTypeMapping 是否发生过属性映射,如果没有则返回该映射对象对应的原始注解,否则就通过 SynthesizedMergedAnnotationInvocationHandler 生成一个对应类型的 JDK 动态代理对象。

当我们通过代理对象去调用注解的方法,获取注解的属性的时候, SynthesizedMergedAnnotationInvocationHandler 会把方法代理到对应的内部方法中,而获取属性时,还会通过 MergedAnnotationgetValue ,最终绕到 AnnotationTypeMapping 中获取被映射后的属性值。

jackson中,指定包含哪些属性、忽略哪些属性的注解:

用于标记忽略一个或多个属性。可以注解在类上、构造函数、方法、字段上。

@JsonIgnore注解用于在字段级别标记要忽略的属性。注意:系列化和反系列化时都会被忽略。

1、bean

2、测试

3、控制台输出

反系列化时,json数据中明明包含了category值,但是最后Article对象的category属性值依然为null。就是因为使用了JsonIgnore。

我们可以使用@JsonInclude排除某些empty、null、默认值的属性。

2、controller

没有在Result类上加 @JsonInclude(JsonIncludeIncludeNON_NULL) 注解时,返回的响应:

加了之后的返回的响应:

默认情况下,jackson获取public权限的字段进行系列化和反系列化。如果没有public权限的字段,就会去获取public修饰的getter/setter。使用 JsonAutoDetect注解,我们就可以修改默认的行为。比如,下面的案例,即使字段都是private的、也没有getter/setter,也照样获取字段。

1、bean

首先,我们定义一个属性id/name都私有的而且没有getter/setter的一个bean

2、反系列化测试:

报错:

3、系列化测试

报错:

4、bean上加上注解

5、再次分别测试反系列化、系列化

都运行正常,分别打印:

1 使用Spring注解来注入属性

11 使用注解以前我们是怎样注入属性的

类的实现:

Java代码

public class UserManagerImpl implements UserManager {

private UserDao userDao;

public void setUserDao(UserDao userDao) {

thisuserDao = userDao;

}

}

[java] view plain copy

public class UserManagerImpl implements UserManager {

private UserDao userDao;

public void setUserDao(UserDao userDao) {

thisuserDao = userDao;

}

}

配置文件:

Java代码

<bean id="userManagerImpl" class="comkedacomspringannotationserviceUserManagerImpl">

<property name="userDao" ref="userDao" />

</bean>

<bean id="userDao" class="comkedacomspringannotationpersistenceUserDaoImpl">

<property name="sessionFactory" ref="mySessionFactory" />

</bean>

[java] view plain copy

<bean id="userManagerImpl" class="comkedacomspringannotationserviceUserManagerImpl">

<property name="userDao" ref="userDao" />

</bean>

<bean id="userDao" class="comkedacomspringannotationpersistenceUserDaoImpl">

<property name="sessionFactory" ref="mySessionFactory" />

</bean>

12 引入@Autowired注解(不推荐使用,建议使用@Resource)

类的实现(对成员变量进行标注)

Java代码

public class UserManagerImpl implements UserManager {

@Autowired

private UserDao userDao;

}

[java] view plain copy

public class UserManagerImpl implements UserManager {

@Autowired

private UserDao userDao;

}

或者(对方法进行标注)

Java代码

public class UserManagerImpl implements UserManager {

private UserDao userDao;

@Autowired

public void setUserDao(UserDao userDao) {

thisuserDao = userDao;

}

}

[java] view plain copy

public class UserManagerImpl implements UserManager {

private UserDao userDao;

@Autowired

public void setUserDao(UserDao userDao) {

thisuserDao = userDao;

}

}

配置文件

Java代码

<bean id="userManagerImpl" class="comkedacomspringannotationserviceUserManagerImpl" />

<bean id="userDao" class="comkedacomspringannotationpersistenceUserDaoImpl">

<property name="sessionFactory" ref="mySessionFactory" />

</bean>

[java] view plain copy

<bean id="userManagerImpl" class="comkedacomspringannotationserviceUserManagerImpl" />

<bean id="userDao" class="comkedacomspringannotationpersistenceUserDaoImpl">

<property name="sessionFactory" ref="mySessionFactory" />

</bean>

@Autowired可以对成员变量、方法和构造函数进行标注,来完成自动装配的工作。以上两种不同实现方式中,@Autowired的标

注位置不同,它们都会在Spring在初始化userManagerImpl这个bean时,自动装配userDao这个属性,区别是:第一种实现

中,Spring会直接将UserDao类型的唯一一个bean赋值给userDao这个成员变量;第二种实现中,Spring会调用

setUserDao方法来将UserDao类型的唯一一个bean装配到userDao这个属性。

13 让@Autowired工作起来

要使@Autowired能够工作,还需要在配置文件中加入以下代码

Java代码

<bean class="orgspringframeworkbeansfactoryannotationAutowiredAnnotationBeanPostProcessor" />

[java] view plain copy

<bean class="orgspringframeworkbeansfactoryannotationAutowiredAnnotationBeanPostProcessor" />

14 @Qualifier

@Autowired是根据类型进行自动装配的。在上面的例子中,如果当Spring上

下文中存在不止一个UserDao类型的bean时,就会抛出BeanCreationException异常;如果Spring上下文中不存在

UserDao类型的bean,也会抛出BeanCreationException异常。我们可以使用@Qualifier配合@Autowired来

解决这些问题。

1 可能存在多个UserDao实例

Java代码

@Autowired

public void setUserDao(@Qualifier("userDao") UserDao userDao) {

thisuserDao = userDao;

}

[java] view plain copy

@Autowired

public void setUserDao(@Qualifier("userDao") UserDao userDao) {

thisuserDao = userDao;

}

这样,Spring会找到id为userDao的bean进行装配。

2 可能不存在UserDao实例

Java代码

@Autowired(required = false)

public void setUserDao(UserDao userDao) {

thisuserDao = userDao;

}

[java] view plain copy

@Autowired(required = false)

public void setUserDao(UserDao userDao) {

thisuserDao = userDao;

}

15 @Resource(JSR-250标准注解,推荐使用它来代替Spring专有的@Autowired注解)

Spring 不但支持自己定义的@Autowired注解,还支持几个由JSR-250规范定义的注解,它们分别是@Resource、@PostConstruct以及@PreDestroy。

@Resource

的作用相当于@Autowired,只不过@Autowired按byType自动注入,而@Resource默认按byName自动注入罢了。

@Resource有两个属性是比较重要的,分别是name和type,Spring将@Resource注解的name属性解析为bean的名字,而

type属性则解析为bean的类型。所以如果使用name属性,则使用byName的自动注入策略,而使用type属性时则使用byType自动注入策

略。如果既不指定name也不指定type属性,这时将通过反射机制使用byName自动注入策略。

@Resource装配顺序

如果同时指定了name和type,则从Spring上下文中找到唯一匹配的bean进行装配,找不到则抛出异常

如果指定了name,则从上下文中查找名称(id)匹配的bean进行装配,找不到则抛出异常

如果指定了type,则从上下文中找到类型匹配的唯一bean进行装配,找不到或者找到多个,都会抛出异常

如果既没有指定name,又没有指定type,则自动按照byName方式进行装配(见2);如果没有匹配,则回退为一个原始类型(UserDao)进行匹配,如果匹配则自动装配;

16 @PostConstruct(JSR-250)

在方法上加上注解@PostConstruct,这个方法就会在Bean初始化之后被Spring容器执行(注:Bean初始化包括,实例化Bean,并装配Bean的属性(依赖注入))。

它的一个典型的应用场景是,当你需要往Bean里注入一个其父类中定义的属性,而你又无法复写父类的属性或属性的setter方法时,如:

Java代码

public class UserDaoImpl extends HibernateDaoSupport implements UserDao {

private SessionFactory mySessionFacotry;

@Resource

public void setMySessionFacotry(SessionFactory sessionFacotry) {

thismySessionFacotry = sessionFacotry;

}

@PostConstruct

public void injectSessionFactory() {

supersetSessionFactory(mySessionFacotry);

}

}

[java] view plain copy

public class UserDaoImpl extends HibernateDaoSupport implements UserDao {

private SessionFactory mySessionFacotry;

@Resource

public void setMySessionFacotry(SessionFactory sessionFacotry) {

thismySessionFacotry = sessionFacotry;

}

@PostConstruct

public void injectSessionFactory() {

supersetSessionFactory(mySessionFacotry);

}

}

这里通过@PostConstruct,为UserDaoImpl的父类里定义的一个sessionFactory私有属性,注入了我们自

己定义的sessionFactory(父类的setSessionFactory方法为final,不可复写),之后我们就可以通过调用

supergetSessionFactory()来访问该属性了。

17 @PreDestroy(JSR-250)

在方法上加上注解@PreDestroy,这个方法就会在Bean初始化之后被Spring容器执行。由于我们当前还没有需要用到它的场景,这里不不去演示。其用法同@PostConstruct。

18 使用<context:annotation-config />简化配置

Spring21

添加了一个新的context的Schema命名空间,该命名空间对注释驱动、属性文件引入、加载期织入等功能提供了便捷的配置。我们知道注释本身是不会

做任何事情的,它仅提供元数据信息。要使元数据信息真正起作用,必须让负责处理这些元数据的处理器工作起来。

AutowiredAnnotationBeanPostProcessor

和CommonAnnotationBeanPostProcessor就是处理这些注释元数据的处理器。但是直接在Spring配置文件中定义这些

Bean显得比较笨拙。Spring为我们提供了一种方便的注册这些BeanPostProcessor的方式,这就

是<context:annotation-config />:

Java代码

<beans xmlns=">

Java注解(Annotation)就是一种java标注,并且能够携带数据, 是在JDK50被引入的。

Java的注解可以标注Java语言中的类、变量、方法、参数、包等等。

值得注意的是: 上面所说的Java注解只是一种标注,所以注解需要配合反射来使用才能发挥出强大作用。

注解的成员变量只支持 八种基本数据类型(byte、short、int、long、float、double、char、boolean)、String、Class、Enum、Annotation

还有的就是,所有的注解都是Annotation接口的实现类,可以把Annotation接口看成是所有注解的超类

上面就是定义了一个可以标注在类或者注解以及方法上的,保留到运行期的注解。 但是也仅仅是定义了一个注解而已,一个注解要发挥它自己的作用,还需要反射的配合。

反射是java中的一种机制,通过这种机制我们能够在运行时获取到一个类的一切信息(继承的类、实现的接口、属性、方法等), 以及注解信息和注解所携带的数据

通过获取到的类信息,我们可以构造一个新的对象、获取到某个对象的属性值、执行某个对象的方法等。

定义value注解

定义pojo User类

测试并使用注解

注解的使用一般是与java的反射一起使用,下面是一个例子

注解相当于一种标记,在程序中加了注解就等于为程序打上了某种标记,没加,则等于没有某种标记,以后,javac编译器,开发工具和其他程序可以用反射来了解你的类及各种元素上有无何种标记,看你有什么标记,就去干相应的事。标记可以加在包,类,字段,方法,方法的参数以及局部变量上。

自定义注解及其应用

1)、定义一个最简单的注解

public @interface MyAnnotation {

//

}

2)、把注解加在某个类上:

@MyAnnotation

public class AnnotationTest{

//

}

以下为模拟案例

自定义注解@MyAnnotation

1 package comljqtest;

2

3 import javalangannotationElementType;

4 import javalangannotationRetention;

5 import javalangannotationRetentionPolicy;

6 import javalangannotationTarget;

7

8 /

9 定义一个注解

10

11

12 @author jiqinlin

13

14 /

15 //Java中提供了四种元注解,专门负责注解其他的注解,分别如下

16

17 //@Retention元注解,表示需要在什么级别保存该注释信息(生命周期)。可选的RetentionPoicy参数包括:

18 //RetentionPolicySOURCE: 停留在java源文件,编译器被丢掉

19 //RetentionPolicyCLASS:停留在class文件中,但会被VM丢弃(默认)

20 //RetentionPolicyRUNTIME:内存中的字节码,VM将在运行时也保留注解,因此可以通过反射机制读取注解的信息

21

22 //@Target元注解,默认值为任何元素,表示该注解用于什么地方。可用的ElementType参数包括

23 //ElementTypeCONSTRUCTOR: 构造器声明

24 //ElementTypeFIELD: 成员变量、对象、属性(包括enum实例)

25 //ElementTypeLOCAL_VARIABLE: 局部变量声明

26 //ElementTypeMETHOD: 方法声明

27 //ElementTypePACKAGE: 包声明

28 //ElementTypePARAMETER: 参数声明

29 //ElementTypeTYPE: 类、接口(包括注解类型)或enum声明

30

31 //@Documented将注解包含在JavaDoc中

32

33 //@Inheried允许子类继承父类中的注解

34

35

36 @Retention(RetentionPolicyRUNTIME)

37 @Target({ElementTypeMETHOD, ElementTypeTYPE})

38 public @interface MyAnnotation {

39 //为注解添加属性

40 String color();

41 String value() default "我是林计钦"; //为属性提供默认值

42 int[] array() default {1, 2, 3};

43 Gender gender() default GenderMAN; //添加一个枚举

44 MetaAnnotation metaAnnotation() default @MetaAnnotation(birthday="我的出身日期为1988-2-18");

45 //添加枚举属性

46

47 }

注解测试类AnnotationTest

1 package comljqtest;

2

3 /

4 注解测试类

5

6

7 @author jiqinlin

8

9 /

10 //调用注解并赋值

11 @MyAnnotation(metaAnnotation=@MetaAnnotation(birthday = "我的出身日期为1988-2-18"),color="red", array={23, 26})

12 public class AnnotationTest {

13

14 public static void main(String[] args) {

15 //检查类AnnotationTest是否含有@MyAnnotation注解

16 if(AnnotationTestclassisAnnotationPresent(MyAnnotationclass)){

17 //若存在就获取注解

18 MyAnnotation annotation=(MyAnnotation)AnnotationTestclassgetAnnotation(MyAnnotationclass);

19 Systemoutprintln(annotation);

20 //获取注解属性

21 Systemoutprintln(annotationcolor());

22 Systemoutprintln(annotationvalue());

23 //数组

24 int[] arrs=annotationarray();

25 for(int arr:arrs){

26 Systemoutprintln(arr);

27 }

28 //枚举

29 Gender gender=annotationgender();

30 Systemoutprintln("性别为:"+gender);

31 //获取注解属性

32 MetaAnnotation meta=annotationmetaAnnotation();

33 Systemoutprintln(metabirthday());

34 }

35 }

36 }

枚举类Gender,模拟注解中添加枚举属性

1 package comljqtest;

2 /

3 枚举,模拟注解中添加枚举属性

4

5 @author jiqinlin

6

7 /

8 public enum Gender {

9 MAN{

10 public String getName(){return "男";}

11 },

12 WOMEN{

13 public String getName(){return "女";}

14 }; //记得有“;”

15 public abstract String getName();

16 }

注解类MetaAnnotation,模拟注解中添加注解属性

1 package comljqtest;

2

3 /

4 定义一个注解,模拟注解中添加注解属性

5

6 @author jiqinlin

7

8 /

9 public @interface MetaAnnotation {

10 String birthday();

11 }

@ModelAttribute可以用于注解方法和参数。

@ModelAttribute可以用于注解方法和参数。

1、注解Controller中的方法时,返回的参数是一个属性值,@ModelAttribute注解的方法在Controller中每个URL处理方法调用之前,都会按照先后顺序执行。

2、注解Controller方法的参数,用于从model、Form表单或者URL请求参数中获取属性值。

例子如下,已在Spring30中验证通过。

@Controller

public class TestAction {

/---------------------@ModelAttribute注解一个方法---------------------/

/方法返回值为:void,没有什么意义/

@ModelAttribute

public void populateModel(ModelMap model) {

Systemoutprintln("---populateModel---");

modeladdAttribute("attributeName", "111");

}

/不指定指定属性名称,方法返回一个对象,相当于modeladdAttribute("user", user)/

@ModelAttribute

public User addUser() {

Systemoutprintln("---addUser---");

User user = new User();

usersetId(1);

usersetUsername("alan");

usersetPassword("1234");

return user;

}

/指定属性名称,方法返回一个字符串,相当于modeladdAttribute("string1-key", "string1-value")/

@ModelAttribute("string1-key")

public String addString() {

Systemoutprintln("---addString---");

return "string1-value";

}

/返回一个model属性,而不是视图名称/

@RequestMapping(value = "helloWorld1")

@ModelAttribute("attributeName")

public String helloWorld1() {

Systemoutprintln("---helloWorld1---");

return "hi";

}

/---------------------@ModelAttribute注解方法的参数---------------------/

/从模型中获取一个属性值,将其转换到对应类型的变量中/

@RequestMapping(value = "helloWorld")

public String helloWorld(@ModelAttribute("user") User user,

@ModelAttribute("attributeName") String aName,

@ModelAttribute("string1-value") String svalue) {

Systemoutprintln("---helloWorld---"+usergetUsername()+", "+usergetPassword());

Systemoutprintln("aName="+aName+", svalue="+svalue);

return "helloWorld";

}

/从Form表单或者URL参数中获取属性参数值,放到对应类型的参数中,注意:此时表单中的组件名和参数属性名称一致,如User对象有两个属性,分别为username,password,则表单中input的名称必须为username,password,才能实现属性值注入。

注意这个User类必须要有无参数的构造函数或者是setter方法

此时@ModelAttribute可以不用显式写/

@RequestMapping(value = "helloWorld2")

public String helloWorld2(@ModelAttribute("user") User user) {

Systemoutprintln("---helloWorld---"+usergetUsername()+", "+usergetPassword());

return "helloWorld";

}

}

1使用 @Value("${}") 方式:

可以获取属性文件中对应的值(如果属性文件中没有这个属性,则会报错。可以通过赋予默认值解决这个问题,如@Value("${attr:127001}"))

2使用 @Value("#{}")方式:

3在@Value()中 ${…}和#{…}混合使用

注意必须#{}外面, {}是时机要早于#{}

applicationproperties属性文件:

参考网址: >

以上就是关于深入理解Spring注解机制:合并注解的合成全部的内容,包括:深入理解Spring注解机制:合并注解的合成、详解jackson注解(三)jackson包含属性、忽略属性的注解、spring 注解注入怎么注入属性等相关内容解答,如果想了解更多相关内容,可以关注我们,你们的支持是我们更新的动力!

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/web/9480300.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-04-28
下一篇 2023-04-28

发表评论

登录后才能评论

评论列表(0条)

保存