Java与Spring注解篇

1-Java注解介绍和原理

从J2SE 5.0时代开始,注解一直是Java的重要组成部分。 在我们的应用程序代码中的某些地方,我们经常看到过类似@Override 和的注释 @Deprecated。

1.1-什么是注解?

用一个词就可以描述注解,那就是元数据,即一种描述数据的数据。所以,可以说注解就是源代码的元数据。

Annontation是Java5开始引入的新特征,中文名称叫注解。它提供了一种安全的类似注释的机制,用来将任何的信息或元数据(metadata)程序元素(类、方法、成员变量等)进行关联。为程序的元素(类、方法、成员变量)加上更直观更明了的说明,这些说明信息是与程序的业务逻辑无关,并且供指定的工具或框架使用。Annontation像一种修饰符一样,应用于包、类型、构造方法、方法、成员变量、参数及本地变量的声明语句中。

Java注解是附加在代码中的一些元信息,用于一些工具在编译、运行时进行解析和使用,起到说明、配置的功能。注解不会也不能影响代码的实际逻辑,仅仅起到辅助性的作用。包含在 java.lang.annotation 包中。

比如@Override注解是告诉编译器此方法是重写的方法,如果父类中不存在任何这样的方法,则编译器会抛出异常。现在,如果我犯了一个书写错误,我重写的方法名写错了,如果我没有加上注解,那么我的代码将成功编译并执行,但是却不是我想要的结果。如果我加上了注解,那么编译器就会提示我父类不存在复写的方法。

注解的本质就是一个继承了 Annotation 接口的接口。有关这一点,你可以去反编译任意一个注解类,你会得到结果的。

一个注解准确意义上来说,只不过是一种特殊的注释而已,如果没有解析它的代码,它可能连注释都不如。

而解析一个类或者方法的注解往往有两种形式,一种是编译期直接的扫描,一种是运行期反射。反射的事情我们待会说,而编译器的扫描指的是编译器在对 java 代码编译字节码的过程中会检测到某个类或者方法被一些注解修饰,这时它就会对于这些注解进行某些处理。

典型的就是注解 @Override,一旦编译器检测到某个方法被修饰了 @Override 注解,编译器就会检查当前方法的方法签名是否真正重写了父类的某个方法,也就是比较父类中是否具有一个同样的方法签名。

1.2-为什么要引入注解?

使用Annotation之前(甚至在使用之后),XML被广泛的应用于描述元数据。不知何时开始一些应用开发人员和架构师发现XML的维护越来越糟糕了。他们希望使用一些和代码紧耦合的东西,而不是像XML那样和代码是松耦合的(在某些情况下甚至是完全分离的)代码描述。如果你在Google中搜索“XML vs. annotations”,会看到许多关于这个问题的辩论。最有趣的是XML配置其实就是为了分离代码和配置而引入的。上述两种观点可能会让你很疑惑,两者观点似乎构成了一种循环,但各有利弊。下面我们通过一个例子来理解这两者的区别。

假如你想为应用设置很多的常量或参数,这种情况下,XML是一个很好的选择,因为它不会同特定的代码相连。如果你想把某个方法声明为服务,那么使用Annotation会更好一些,因为这种情况下需要注解和方法紧密耦合起来,开发人员也必须认识到这点。

另一个很重要的因素是Annotation定义了一种标准的描述元数据的方式。在这之前,开发人员通常使用他们自己的方式定义元数据。例如,使用标记interfaces,注释,transient关键字等等。每个程序员按照自己的方式定义元数据,而不像Annotation这种标准的方式。

目前,许多框架将XML和Annotation两种方式结合使用,平衡两者之间的利弊。

1.3-注解的实现原理

  • 从java源码到class字节码是由编译器完成的,编译器会对java源码进行解析并生成class文件,而注解也是在编译时由编译器进行处理,编译器会对注解符号处理并附加到class结构中,根据jvm规范,class文件结构是严格有序的格式,唯一可以附加信息到class结构中的方式就是保存到class结构的attributes属性中。我们知道对于类、字段、方法,在class结构中都有自己特定的表结构,而且各自都有自己的属性,而对于注解,作用的范围也可以不同,可以作用在类上,也可以作用在字段或方法上,这时编译器会对应将注解信息存放到类、字段、方法自己的属性上。

注解的本质就是一个继承Annotation接口的接口,当我们通过AnnotationTest.class.getAnnotation(Test.class)调用时,JDK会通过动态代理生成一个实现了Test接口的对象,并把将RuntimeVisibleAnnotations属性值设置进此对象中,此对象即为Test注解对象,通过它的value()方法就可以获取到注解值。

Java注解实现机制的整个过程如上面所示,它的实现需要编译器和JVM一起配合

  • 注解本质是一个继承了Annotation 的特殊接口,其具体实现类是Java 运行时生成的动态代理类。而我们通过反射获取注解时,返回的是Java 运行时生成的动态代理对象$Proxy1。通过代理对象调用自定义注解(接口)的方法,会最终调用AnnotationInvocationHandler 的invoke 方法。该方法会从memberValues 这个Map 中索引出对应的值。而memberValues 的来源是Java 常量池。

1.4-注解的用处

  • 生成文档。这是最常见的,也是java 最早提供的注解。常用的有@see @param @return 等;
  • 跟踪代码依赖性,实现替代配置文件功能。比较常见的是spring 2.5 开始的基于注解配置。作用就是减少配置。现在的框架基本都使用了这种配置来减少配置文件的数量;
  • 在编译时进行格式检查。如@Override放在方法前,如果你这个方法并不是覆盖了超类方法,则编译时就能检查出。

1.5-元注解

元注解是用于修饰注解的注解,通常用在注解的定义上,例如:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {

}

这是我们 @Override 注解的定义,你可以看到其中的 @Target@Retention 两个注解就是我们所谓的元注解,元注解一般用于指定某个注解生命周期以及作用目标等信息。

java.lang.annotation 提供了四种元注解,专门注解其他的注解(在自定义注解的时候,需要使用到元注解):

  • @Documented – 注解是否将包含在JavaDoc中
  • @Retention – 注解的生命周期(什么时候使用该注解)
  • @Target – 注解的作用目标(注解用于什么地方)
  • @Inherited – 是否允许子类继承该注解

1. @Target

用于指明被修饰的注解最终可以作用的目标是谁,也就是指明,你的注解到底是用来修饰方法的?修饰类的?还是用来修饰字段属性的,表示该注解用于什么地方。默认值为任何元素,表示该注解用于什么地方。

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
/**
* Returns an array of the kinds of elements an annotation type
* can be applied to.
* @return an array of the kinds of elements an annotation type
* can be applied to
*/
ElementType[] value();
}

我们可以通过以下的方式来为这个 value 传值:

@Target(value = {ElementType.FIELD})

其中,ElementType 是一个枚举类型,有以下一些值:

  • ElementType.TYPE:允许被修饰的注解作用在类、接口和枚举上
  • ElementType.FIELD:允许作用在属性字段上
  • ElementType.METHOD:允许作用在方法上
  • ElementType.PARAMETER:允许作用在方法参数上
  • ElementType.CONSTRUCTOR:允许作用在构造器上
  • ElementType.LOCAL_VARIABLE:允许作用在本地局部变量上
  • ElementType.ANNOTATION_TYPE:允许作用在注解上
  • ElementType.PACKAGE:允许作用在包上

2. @Retention

用于指明当前注解的生命周期

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
/**
* Returns the retention policy.
* @return the retention policy
*/
RetentionPolicy value();
}

同样的,它也有一个 value 属性:

@Retention(value = RetentionPolicy.RUNTIME

这里的 RetentionPolicy 依然是一个枚举类型,它有以下几个枚举值可取:

  • RetentionPolicy.SOURCE:当前注解编译期可见,不会写入 class 文件
  • RetentionPolicy.CLASS:类加载阶段丢弃,会写入 class 文件
  • RetentionPolicy.RUNTIME:永久保存,可以反射获取

@Retention 注解指定了被修饰的注解的生命周期,一种是只能在编译期可见,编译后会被丢弃,一种会被编译器编译进 class 文件中,无论是类或是方法,乃至字段,他们都是有属性表的,而 JAVA 虚拟机也定义了几种注解属性表用于存储注解信息,但是这种可见性不能带到方法区,类加载时会予以丢弃,最后一种则是永久存在的可见性。

3. @Documented

一个简单的Annotations 标记注解,表示是否将注解信息添加在java 文档中。

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Documented {
}

4. @Inherited

定义该注释和子类的关系

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Inherited {
}

@Inherited 元注解是一个标记注解,@Inherited 阐述了某个被标注的类型是被继承的。如果一个使用了@Inherited 修饰的annotation 类型被用于一个class,则这个annotation 将被用于该class 的子类。

1.6-Java中常见的注解

1. @Override

java.lang.Override 是一个标记类型注解,它被用作标注方法。它说明了被标注的方法重载了父类的方法,起到了断言的作用。如果我们使用了这种注解在一个没有覆盖父类方法的方法时,java 编译器将以一个编译错误来警示。

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

它没有任何的属性,所以并不能存储任何其他信息。它只能作用于方法之上,编译结束后将被丢弃。

所以你看,它就是一种典型的标记式注解,仅被编译器可知,编译器在对 java 文件进行编译成字节码的过程中,一旦检测到某个方法上被修饰了该注解,就会去匹对父类中是否具有一个同样方法签名的函数,如果不是,自然不能通过编译。

2. @Deprecated

@Deprecated 也是一种标记类型注解。当一个类型或者类型成员使用@Deprecated 修饰的话,编译器将不鼓励使用这个被标注的程序元素。所以使用这种修饰具有一定的“延续性”:如果我们在代码中通过继承或者覆盖的方式使用了这个过时的类型或者成员,虽然继承或者覆盖后的类型或者成员并不是被声明为@Deprecated,但编译器仍然要报警。

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
public @interface Deprecated {
}

依然是一种标记式注解,永久存在,可以修饰所有的类型,作用是,标记当前的类或者方法或者字段等已经不再被推荐使用了,可能下一次的 JDK 版本就会删除。

当然,编译器并不会强制要求你做什么,只是告诉你 JDK 已经不再推荐使用当前的方法或者类了,建议你使用某个替代者。

3. @SuppressWarnings

SuppressWarning 不是一个标记类型注解。它有一个类型为String[] 的成员,这个成员的值为被禁止的警告名。对于javac 编译器来讲,被-Xlint 选项有效的警告名也同样对@SuppressWarings 有效,同时编译器忽略掉无法识别的警告名。

@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
/**
* The set of warnings that are to be suppressed by the compiler in the
* annotated element. Duplicate names are permitted. The second and
* successive occurrences of a name are ignored. The presence of
* unrecognized warning names is <i>not</i> an error: Compilers must
* ignore any warning names they do not recognize. They are, however,
* free to emit a warning if an annotation contains an unrecognized
* warning name.
*
* <p> The string {@code "unchecked"} is used to suppress
* unchecked warnings. Compiler vendors should document the
* additional warning names they support in conjunction with this
* annotation type. They are encouraged to cooperate to ensure
* that the same names work across multiple compilers.
* @return the set of warnings to be suppressed
*/
String[] value();
}

它有一个 value 属性需要你主动的传值,这个 value 代表一个什么意思呢,这个 value 代表的就是需要被压制的警告类型。例如:

public static void main(String[] args) {
Date date = new Date(2018, 7, 11);
}

这么一段代码,程序启动时编译器会报一个警告。

Warning:(8, 21) java: java.util.Date 中的 Date(int,int,int) 已过时

而如果我们不希望程序启动时,编译器检查代码中过时的方法,就可以使用 @SuppressWarnings 注解并给它的 value 属性传入一个参数值来压制编译器的检查。

@SuppressWarning(value = "deprecated")
public static void main(String[] args) {
Date date = new Date(2020, 3, 01);
}

这样你就会发现,编译器不再检查 main 方法下是否有过时的方法调用,也就压制了编译器对于这种警告的检查。

当然,JAVA 中还有很多的警告类型,他们都会对应一个字符串,通过设置 value 属性的值即可压制对于这一类警告类型的检查。

1.7-自定义注解

自定义注解类编写的一些规则:

  1. Annotation 型定义为@interface, 所有的Annotation 会自动继承java.lang.Annotation这一接口,并且不能再去继承别的类或是接口.

  2. 参数成员只能用public 或默认(default) 这两个访问权修饰

  3. 参数成员只能用基本类型byte、short、char、int、long、float、double、boolean八种基本数据类型和String、Enum、Class、annotations等数据类型,以及这一些类型的数组.

  4. 要获取类方法和字段的注解信息,必须通过Java的反射技术来获取 Annotation 对象,因为你除此之外没有别的获取注解对象的方法

  5. 注解也可以没有定义成员,,不过这样注解就没啥用了

PS:自定义注解需要使用到元注解

//该注解用于方法声明
@Target(ElementType.METHOD)
//VM将在运行期也保留注释,因此可以通过反射机制读取注解的信息
@Retention(RetentionPolicy.RUNTIME)
//将此注解包含在javadoc中
@Documented
//允许子类继承父类中的注解
@Inherited
public @interface Test {
public int id();
public String description() default "no description";
}

使用:

public class TestAnnotation {
/**
* 被注释的三个方法
*/
@Test(id = 1, description = "hello method1")
public void method1() {
}

@Test(id = 2)
public void method2() {
}

/**
* 解析注释,将Test_1类所有被注解方法的信息打印出来
* @param args
*/
@Test(id = 3, description = "last method3")
public static void main(String[] args) {
Method[] methods = TestAnnotation.class.getDeclaredMethods();
for (Method method : methods) {
//判断方法中是否有指定注解类型的注解
boolean hasAnnotation = method.isAnnotationPresent(Test.class);
if (hasAnnotation) {
//根据注解类型返回方法的指定类型注解
Test annotation = method.getAnnotation(Test.class);
System.out.println("Test(method=" + method.getName() + ",id=" + annotation.id()
+ ",description=" + annotation.description() + ")");
}
}
}
}

1.8-注解与反射

上述内容我们介绍了注解使用上的细节,也简单提到,「注解的本质就是一个继承了 Annotation 接口的接口」,现在我们就来从虚拟机的层面看看,注解的本质到底是什么。

@Retention(RetentionPolicy.RUNTIME)
@Target(value={ElementType.FIELD,ElementType.METHOD})
public @interface Hello {
String value();
}

这里我们指定了 Hello 这个注解只能修饰字段和方法,并且该注解永久存活,以便我们反射获取。

之前我们说过,虚拟机规范定义了一系列和注解相关的属性表,也就是说,无论是字段、方法或是类本身,如果被注解修饰了,就可以被写进字节码文件。属性表有以下几种:

  • RuntimeVisibleAnnotations:运行时可见的注解
  • RuntimeInVisibleAnnotations:运行时不可见的注解
  • RuntimeVisibleParameterAnnotations:运行时可见的方法参数注解
  • RuntimeInVisibleParameterAnnotations:运行时不可见的方法参数注解
  • AnnotationDefault:注解类元素的默认值

给大家看虚拟机的这几个注解相关的属性表的目的在于,让大家从整体上构建一个基本的印象,注解在字节码文件中是如何存储的。

所以,对于一个类或者接口来说,Class 类中提供了以下一些方法用于反射注解。

  • getAnnotation:返回指定的注解
  • isAnnotationPresent:判定当前元素是否被指定注解修饰
  • getAnnotations:返回所有的注解
  • getDeclaredAnnotation:返回本元素的指定注解
  • getDeclaredAnnotations:返回本元素的所有注解,不包含父类继承而来的

方法、字段中相关反射注解的方法基本是类似的,这里不再赘述,我们下面看一个完整的例子。

首先,设置一个虚拟机启动参数,用于捕获 JDK 动态代理类。

-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true

然后写一个测试类:

public class Test {

@Hello("hello")
public static void main(String[] args) {
Class cls = Test.class;
Method method = cls.getMethod("main", String[].class);
Hello hello = method.getAnnotation(Hello.class);
}
}

注解本质上是继承了 Annotation 接口的接口,而当你通过反射,也就是我们这里的 getAnnotation 方法去获取一个注解类实例的时候,其实 JDK 是通过动态代理机制生成一个实现我们注解(接口)的代理类。

我们运行程序后,会看到输出目录里有这么一个代理类,反编译之后是这样的:

public final class $Proxy1 extends Proxy implements Hello {

public $Proxy1(InvocationHandler invocationHandler) {
super(invocationHandler);
}

public final String value() {
try {
return (String)super.h.invoke(this, m3, null);
} catch (Error_ex){

} catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}

}

代理类实现接口 Hello 并重写其所有方法,包括 value 方法以及接口 Hello 从 Annotation 接口继承而来的方法。

而这个关键的 InvocationHandler 实例是谁?

AnnotationInvocationHandler 是 JAVA 中专门用于处理注解的 Handler:

class AnnotationInvocationHandler implements InvocationHandler, Serializable {
private static final long serialVersionUID = 6182022883658399397L;
private final Class<? extends Annotation> type;
private final Map<String, Object> memberValues;
private transient volatile Method[] memberMethods = null;

AnnotationInvocationHandler(Class<? extends Annotation> var1, Map<String, Object> var2) {
Class[] var3 = var1.getInterfaces();
if (var1.isAnnotation() && var3.length == 1 && var3[0] == Annotation.class) {
this.type = var1;
this.memberValues = var2;
} else {
throw new AnnotationFormatError("Attempt to create proxy for a non-annotation type.");
}
}

这里有一个 memberValues,它是一个 Map 键值对,键是我们注解属性名称,值就是该属性当初被赋上的值。

public Object invoke(Object var1, Method var2, Object[] var3) {
String var4 = var2.getName();
Class[] var5 = var2.getParameterTypes();
if (var4.equals("equals") && var5.length == 1 && var5[0] == Object.class) {
return this.equalsImpl(var3[0]);
} else if (var5.length != 0) {
throw new AssertionError("Too many parameters for an annotation method");
} else {
byte var7 = -1;
switch(var4.hashCode()) {
case -1776922004:
if (var4.equals("toString")) {
var7 = 0;
}
break;
case 147696667:
if (var4.equals("hashCode")) {
var7 = 1;
}
break;
case 1444986633:
if (var4.equals("annotationType")) {
var7 = 2;
}
}

switch(var7) {
case 0:
return this.toStringImpl();
case 1:
return this.hashCodeImpl();
case 2:
return this.type;
default:
Object var6 = this.memberValues.get(var4);
if (var6 == null) {
throw new IncompleteAnnotationException(this.type, var4);
} else if (var6 instanceof ExceptionProxy) {
throw ((ExceptionProxy)var6).generateException();
} else {
if (var6.getClass().isArray() && Array.getLength(var6) != 0) {
var6 = this.cloneArray(var6);
}

return var6;
}
}
}
}

我们的代理类代理了 Hello 接口中所有的方法,所以对于代理类中任何方法的调用都会被转到这里来。

var2 指向被调用的方法实例,而这里首先用变量 var4 获取该方法的简明名称,接着 switch 结构判断当前的调用方法是谁,如果是 Annotation 中的四大方法,将 var7 赋上特定的值。

如果当前调用的方法是 toString,equals,hashCode,annotationType 的话,AnnotationInvocationHandler 实例中已经预定义好了这些方法的实现,直接调用即可。

那么假如 var7 没有匹配上这四种方法,说明当前的方法调用的是自定义注解字节声明的方法,例如我们 Hello 注解的 value 方法。这种情况下,将从我们的注解 map 中获取这个注解属性对应的值。

其实,JAVA 中的注解设计个人觉得有点反人类,明明是属性的操作,非要用方法来实现。当然,如果你有不同的见解,欢迎留言探讨。

最后我们再总结一下整个反射注解的工作原理:

首先,我们通过键值对的形式可以为注解属性赋值,像这样:@Hello(value = “hello”)。

接着,你用注解修饰某个元素,编译器将在编译期扫描每个类或者方法上的注解,会做一个基本的检查,你的这个注解是否允许作用在当前位置,最后会将注解信息写入元素的属性表。

然后,当你进行反射的时候,虚拟机将所有生命周期在 RUNTIME 的注解取出来放到一个 map 中,并创建一个 AnnotationInvocationHandler 实例,把这个 map 传递给它。

最后,虚拟机将采用 JDK 动态代理机制生成一个目标注解的代理类,并初始化好处理器。

那么这样,一个注解的实例就创建出来了,它本质上就是一个代理类,你应当去理解好 AnnotationInvocationHandler 中 invoke 方法的实现逻辑,这是核心。一句话概括就是,通过方法名返回注解属性值

2-Spring注解篇

2.1-XML和注解

2.1.1-XML

  • XML优点

    1. xml是集中式的元数据,不需要和代码绑定的;

      在我们开发中,xml配置文件和代码类是区分开的,不需要绑定到代码中。

    2. 使用xml配置可以让软件更具有扩展性;

      比如,我们在spring中,我们不想使用接口而是想用接口的实现类,这个时候只需要修改xml配置中bean的class值就可以了。

    3. 对象之间的关系一目了然;

    4. 类与类间的松藕合,容易扩展、更换;

    5. xml定义:可扩展标记语言,标准通用标记语言的子集,简称XML。从这个定义中我们可以发现,xml最大的优势就在于,开发者(程序员)能够为软件量身定做使用的标记,使得xml更通俗易懂;

    6. 成熟的校验机制,来保证正确。可以使用Schema或者是DTD来对xml的正确性进行校验;

    7. 基于xml配置的时候,只需要修改xml即可,不需要对现有的程序进行修改;

    8. 容易与其他系统进行数据交互。数据共享方便。

  • XML缺点

    1. 应用程序中如果使用了xml配置,需要解析xml的工具或者是是第三方类库的支持;

    2. 解析xml的时候必然会占用资源,势必会影响到应用程序的性能;

      以java为例,无论是将xml一次性装置到内存中,还是一行一行读取解析的,都会占用资源的。

    3. xml配置文件过多,会导致维护变得困难;

    4. 在程序编译期间无法对其配置项的正确性进行验证,只能在运行期发现;

    5. 出错后,排错变得困难。往往在配置的时候,一个手误就会出现莫名其妙的错误(虽然事出必有妖,但是排查真难);

      比如,xml配置bean信息的时候,如果class的值带有空格,这种不好检查的,是比较麻烦的。排查起来很费事。

    6. 开发的时候,既要维护代码又要维护配置文件,使得开发的效率降低;

    7. 代码与配置项之间有时候会存在很多“潜规则”.改变了任意一方,都有可能影响到另一方的使用。这是个大坑

      比如:自定义的标记,如果其他开发不清楚这些的话,修改了无论是代码还是xml的配置,都会导致程序不能正常运行。

    8. 开发工具对xml的验证支持的不是很好。

      比如idea,对xml正确性,如果是自定义的,验证就不是很好。

2.1.2-注解

  • 注解优点
    1. 简化配置;
    2. 注解的解析可以不依赖于第三方库,可以之间使用Java自带的反射;
    3. 注解和代码在一起的,之间在类上,降低了维护两个地方的成本;
    4. 注解如果有问题,在编译期间,就可以验证正确性,如果出错更容易找;
    5. 使用注解开发能够提高开发效率。不用多个地方维护,不用考虑是否存在“潜规则”;
    6. 类型安全。
  • 注解缺点
    1. 修改的话比较麻烦。如果需要对注解进行修改的话,就需要对整个项目重新编译;
    2. 处理业务类之间的复杂关系,不然xml那样容易修改,也不及xml那样明了;
    3. 在程序中注解太多的话,会影响代码质量,代码简洁会有影响;
    4. 如果后来的人对注解不了解,会给维护带来成本;
    5. 注解功能没有xml配置齐全

2.2-Spring常用注解大全

2.2.1-Spring部分

1. 声明bean的注解
  • @Component:表示一个带注释的类是一个“组件”,成为Spring管理的Bean。当使用基于注解的配置和类路径扫描时,这些类被视为自动检测的候选对象。同时@Component还是一个元注解;
  • @Service:组合注解(组合了@Component注解),应用在service层(业务逻辑层);
  • @Controller:组合注解(组合了@Component注解),应用在MVC层(控制层),DispatcherServlet会自动扫描注解了此注解的类,然后将web请求映射到注解了@RequestMapping的方法上;
  • @Reponsitory:组合注解(组合了@Component注解),应用在dao层(数据访问层);
  • @RestController:Spring4之后加入的注解,原来在@Controller中返回json需要@ResponseBody来配合,如果直接用@RestController替代@Controller就不需要再配置@ResponseBody,默认返回json格式。
2. 注入bean的注解
  • @Autowired:Spring提供的工具(由Spring的依赖注入工具(BeanPostProcessor、BeanFactoryPostProcessor)自动注入);

  • @Resource:JSR-250提供的注解;

  • @Inject:JSR-330提供的注解;

  • @Qualifier:此注解是和@Autowired一起使用的。使用此注解可以让你对注入的过程有更多的控制。

    @Qualifier可以被用在单个构造器或者方法的参数上。当上下文有几个相同类型的bean, 使用@Autowired则无法区分要绑定的bean,此时可以使用@Qualifier来指定名称;

    @Component
    publicclass User {
    @Autowired
    @Qualifier("address1")
    private Address address;
    ...
    }
  • @Required:此注解用于bean的setter方法上。表示此属性是必须的,必须在配置阶段注入,否则会抛出BeanInitializationExcepion;

  • @Lazy:此注解使用在Spring的组件类上。默认的,Spring中Bean的依赖一开始就被创建和配置。如果想要延迟初始化一个bean,那么可以在此类上使用Lazy注解,表示此bean只有在第一次被使用的时候才会被创建和初始化。此注解也可以使用在被@Configuration注解的类上,表示其中所有被@Bean注解的方法都会延迟初始化。

2.1. @Autowired,@Resource和@Inject区别与实现原理
  • @Autowired

    • @Autowired是spring框架提供的实现依赖注入的注解,主要支持在set方法,field,构造函数中完成bean注入,注入方式为通过类型查找bean,即byType的,如果存在多个同一类型的bean,则使用@Qualifier来指定注入哪个beanName的bean。
    • Spring提供的注解,功能与@Inject相似,也是通过AutowiredAnnotationBeanPostProcessor处理,处理的顺序同样是:
      • 基于类型
      • 基于@Qualifier
      • 基于名称
  • @Resource

    • 与JDK的@Resource的区别:@Resource是基于bean的名字,即beanName,来从spring的IOC容器查找bean注入的,而@Autowried是基于类型byType来查找bean注入的。
    • JDK默认提供的注解,属于JSR-250规范的一部分(其他的还有@PostConstruce/@PreDestroy等),可以标记在属性或者Setter上,Spring通过CommonAnnotationBeanPostProcessor来处理该注解,在实现依赖注入的时候的匹配顺序是:
      • 基于名称
      • 基于类型
      • 基于@Qualifier
  • @Inject

    • 与JDK的@Inject的区别:@Inject也是基于类型来查找bean注入的,如果需要指定名称beanName,则可以结合使用@Named注解,而@Autowired是结合@Qualifier注解来指定名称beanName。

    • 属于JSR-330提供的注解,该规范主要提供Java注入相关的注解,需要手动引入:

      <dependency>
      <groupId>javax.inject</groupId>
      <artifactId>javax.inject</artifactId>
      <version>1</version>
      </dependency>

      Spring通过AutowiredAnnotationBeanPostProcessor来处理该注解,处理顺序是:

      • 基于类型
      • 基于@Qualifier
      • 基于名称和@Named
2.2. spring依赖注入注解的实现原理
  • 注解处理器

    • 在spring框架内部实现当中,注解实现注入主要是通过bean后置处理器BeanPostProcessor接口的实现类来生效的。BeanPostProcessor后置处理器是在spring容器启动时,创建bean对象实例后,马上执行的,对bean对象实例进行加工处理。
    • @Autowired是通过BeanPostProcessor接口的实现类AutowiredAnnotationBeanPostProcessor来实现对bean对象对其他bean对象的依赖注入的;
    • @Resource和@Inject是通过BeanPostProcessor接口的实现类CommonAnnotationBeanPostProcessor来实现的,其中如名字所述,即公共注解CommonAnotation,CommonAnnotationBeanPostProcessor是spring中统一处理JDK中定义的注解的一个BeanPostProcessor。该类会处理的注解还包括@PostConstruct,@PreDestroy等。
  • 注解处理器的激活条件

    • AutowiredAnnotationBeanPostProcessor和CommonAnnotationBeanPostProcessor添加到spring容器的BeanPostProcessor的条件,即激活这些处理器的条件如下:
    1. 基于xml的spring配置
    • 在对应的spring容器的配置xml文件中,如applicationContext.xml,添加<context:annotation-config />和<context:component-scan />,或者只使用<context:component-scan />。
    • 两者的区别是<context:annotation-config />只查找并激活已经存在的bean,如通过xml文件的bean标签生成加载到spring容器的,而不会去扫描如@Controller等注解的bean,查找到之后进行注入;而<context:component-scan />除了具有<context:annotation-config />的功能之外,还会去加载通过basePackages属性指定的包下面的,默认为扫描@Controller,@Service,@Component,@Repository注解的类。不指定basePackages则是类路径下面,或者如果使用注解@ComponentScan方式,则是当前类所在包及其子包下面。

    2.基于配置类的spring配置

    • 如果是基于配置类而不是基于applicationContext.xml来对spring进行配置,如SpringBoot,则在内部使用的IOC容器实现为AnnotationConfigApplicationContext或者其派生类,在AnnotationConfigApplicationContext内部会自动创建和激活以上的BeanPostProcessor。
    • 如果同时存在基于xml的配置和配置类的配置,而在注入时间方面,基于注解的注入先于基于XML的注入,所以基于XML的注入会覆盖基于注解的注入。
2.3. 总结
  • @Autowired是Spring自带的,@Inject和@Resource都是JDK提供的,其中@Inject是JSR330规范实现的,@Resource是JSR250规范实现的,而Spring通过BeanPostProcessor来提供对JDK规范的支持。
  • @Autowired、@Inject用法基本一样,不同之处为@Autowired有一个required属性,表示该注入是否是必须的,即如果为必须的,则如果找不到对应的bean,就无法注入,无法创建当前bean。
  • @Autowired、@Inject是默认按照类型匹配的,@Resource是按照名称匹配的。如在spring-boot-data项目中自动生成的redisTemplate的bean,是需要通过byName来注入的。如果需要注入该默认的,则需要使用@Resource来注入,而不是@Autowired。
  • 对于@Autowire和@Inject,如果同一类型存在多个bean实例,则需要指定注入的beanName。@Autowired和@Qualifier一起使用,@Inject和@Name一起使用。
3. java配置类相关注解
  • @Configuration :声明当前类是一个配置类(相当于一个Spring配置的xml文件),其中内部组合了@Component注解,表明这个类是一个bean(类上);
  • @Bean:注解在方法上,声明当前方法的返回值为一个Bean。返回的Bean对应的类中可以定义init()方法和destroy()方法,然后在@Bean(initMethod=”init”,destroyMethod=”destroy”)定义,在构造之后执行init,在销毁之前执行destroy;
  • @ComponentScan:自动扫描指定包下所有使用@Service,@Component,@Controller,@Repository的类并注册,相当于xml中的ComponentScan;
  • @WishlyConfiguration :@Configuration与@ComponentScan的组合注解,可以替代这两个注解。
4. 切面(AOP)相关注解
  • @Aspect:声明一个切面(类上);

    使用@After、@Before、@Around定义建言(advice),可直接将拦截规则(切点)作为参数。

  • @After:后置建言(advice),在方法执行之后执行(方法上);

  • @Before :前置建言(advice),在方法执行之前执行(方法上);

  • @Around:环绕建言(advice),在方法执行之前与之后执行(方法上);

  • @PointCut :声明切点,即定义拦截规则,确定有哪些方法会被切入,在java配置类中使用@EnableAspectJAutoProxy注解开启Spring对AspectJ代理的支持(类上);

  • @EnableAspectJAutoProxy:开启Spring对AspectJ的支持。

5. @Bean的属性支持
  • @Scope:设置Spring容器如何新建Bean实例(方法上,得有@Bean);

    其设置类型包括:

    • Singleton (单例,一个Spring容器中只有一个bean实例,默认模式),
    • Protetype (每次调用新建一个bean),
    • Request (web项目中,给每个http request新建一个bean),
    • Session (web项目中,给每个http session新建一个bean),
    • GlobalSession(给每一个 global http session新建一个Bean实例)。
  • @StepScope:在Spring Batch中还有涉及;

  • @PostConstruct :由JSR-250提供,在构造函数执行完之后执行,等价于xml配置文件中bean的initMethod;

  • @PreDestory:由JSR-250提供,在Bean销毁之前执行,等价于xml配置文件中bean的destroyMethod。

6. @Value和@ConfigurationProperties
6.1. @Value

@Value 为属性注入值(属性上),此注解使用在字段、构造器参数和方法参数上。@Value可以指定属性取值的表达式,支持通过#{}使用SpringEL来取值,也支持使用${}来将属性来源中(Properties文件、本地环境变量、系统属性等)的值注入到bean的属性中。此注解值的注入发生在AutowiredAnnotationBeanPostProcessor类中。

  • 注入普通字符

    @Value("Kobe")
    String name;
  • 注入操作系统属性

    @Value("#{systemProperties['os.name']}")
    String osName;
  • 注入表达式结果

    @Value("#{ T(java.lang.Math).random() * 100 }")
    String randomNumber;
  • 注入其它bean属性

    @Value("#{User.name}")
    String userName;
  • 注入文件资源

    @Value("classpath:com/fangpeng/hello/test.txt")
    Resource file;
  • 注入网站资源

    @Value("http://www.google.com")
    Resource url;
  • 注入配置文件

    @Value("${book.name}")
    String bookName;

注入配置使用方法:

通过@Value将外部配置文件的值动态注入到Bean中。配置文件主要有两类:

  • application.properties。application.properties在spring boot启动时默认加载此文件
  • 自定义属性文件。自定义属性文件通过@PropertySource加载。@PropertySource可以同时加载多个文件,也可以加载单个文件。如果相同第一个属性文件和第二属性文件存在相同key,则最后一个属性文件里的key启作用。
  1. 编写配置文件(test.properties)

    book.name=Java编程思想
  2. @PropertySource 加载配置文件(类上)

    @PropertySource("classpath:com/fangpeng/hello/test.properties")
6.2. @ConfigurationProperties

将属性文件与一个Java类绑定,属性文件中的变量与Java类中的成员变量一一对应,无需完全一致。

如需将 @ConfigurationProperties 注解的目标类添加到Spring IOC容器中,可以

  • 使用 @Component 注解目标类,这样项目启动时会自动将该类添加到容器中。
  • 使用 @EnableConfigurationProperties 间接的将 @ConfigurationProperties 注解的目标类添加到容器中。讲的详细点就是,使用 @ConfigurationProperties 注解 类A,使用 @EnableConfigurationProperties(value = 类A.class) 注解类B,那么容器在加载类B的时候,会先加载类A到容器中,实现了间接的加载。

具体使用:

首先在配置文件里面添加配置信息:

connection.username=admin
connection.password=kyjufskifas2jsfs
connection.remoteAddress=192.168.1.1

定义一个实体类在装载配置文件信息:

@Component
@ConfigurationProperties(prefix = "connection")
public class ConnectionSettings {

private String username;
private String remoteAddress;
private String password ;

public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getRemoteAddress() {
return remoteAddress;
}
public void setRemoteAddress(String remoteAddress) {
this.remoteAddress = remoteAddress;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}

}

还可以把@ConfigurationProperties还可以直接定义在@bean的注解上,这是bean实体类就不用@Component和@ConfigurationProperties了:

@SpringBootApplication
public class DemoApplication{

@Bean
@ConfigurationProperties(prefix = "connection")
public ConnectionSettings connectionSettings(){
return new ConnectionSettings();

}

public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}

需要使用的时候就直接这样子注入:

@RestController
@RequestMapping("/task")
public class TaskController {

@Autowired
ConnectionSettings conn;

@RequestMapping(value = {"/", ""})
public String hellTask(){
String userName = conn.getUsername();
return "hello task !!";
}

}
6.3-区别
header 1@ConfigurationProperties@Value
功能批量注入配置文件中的属性一个个制定
松散绑定(松散语法)支持不支持
SpEL不支持支持
JSR303数据校验支持不支持
复杂类型封装支持不支持

配置文件yml还是properties他们都能获取到值;

  • 松散语法
    1. person.firstName: 使用标准模式
    2. person.first-name: 大写用-
    3. person.first_name: 大写用_
    4. PERSON_FIRST_NAME: 推荐属性使用这种写法

如果说,我们只是在某个业务逻辑中需要获取一下配置文件中的某项值,使用@Value;

如果说,我们专门编写了一个javaBean来和配置文件进行映射,我们就直接使用@ConfigurationProperties;

7. 环境切换
  • @Profile:表示当一个或多个指定的文件是活动的时,一个组件是有资格注册的。使用@Profile注解类或者方法,达到在不同情况下选择实例化不同的Bean。@Profile(“dev”)表示为dev时实例化,过设定Environment的ActiveProfiles来设定当前context需要使用的配置环境。(类或方法上)

  • @Conditional:Spring4中可以使用此注解定义条件话的bean,通过实现Condition接口,并重写matches方法,从而决定该bean是否被实例化。(方法上)

    作用:必须是@Conditional指定的条件成立,才给容器中添加组件,配置配里面的所有内容才生效;

    @Conditional派生注解作用(判断是否满足当前条件)
    @@Conditional派生注解系统的java版本是否符合要求
    @ConditionalOnBean容器中存在指定Bean
    @ConditionalOnMissingBean容器中不存在指定Bean
    @ConditionalOnExpression满足SpEL表达式指定
    @ConditionalOnClass系统中有指定的类
    @ConditionalOnMissingClass系统中没有指定的类
    @ConditionalOnSingleCandidate容器中只有一个指定的Bean,或者这个Bean是首选Bean
    @ConditionalOnProperty系统中指定的属性是否有指定的值
    @ConditionalOnResource类路径下是否存在指定资源文件
    @ConditionalOnWebApplication当前是web环境
    @ConditionalOnNotWebApplication当前不是web环境
    @ConditionalOnJndiNDI存在指定项
8. 异步相关
  • @EnableAsync:配置类中,通过此注解开启对异步任务的支持,叙事性AsyncConfigurer接口(类上);
  • @Async:注解在方法上标示这是一个异步方法,在类上标示这个类所有的方法都是异步方法;
9. 定时任务相关
  • @EnableScheduling:注解在配置类上,开启对计划任务的支持;
  • @Scheduled:注解在方法上,声明该方法是计划任务。支持多种类型的计划任务:cron,fixDelay,fixRate,(需先开启计划任务的支持);
10. @Enable*注解

通过简单的@Enable来开启一项功能的支持。所有@Enable注解都有一个@Import注解,@Import是用来导入配置类的,这也就意味着这些自动开启的实现其实是导入了一些自动配置的Bean:

  1. 直接导入配置类

  2. 依据条件选择配置类

  3. 动态注册配置类

  • @EnableAspectJAutoProxy 开启对AspectJ自动代理的支持
  • @EnableAsync 开启异步方法的支持
  • @EnableScheduling 开启计划任务的支持
  • @EnableWebMvc 开启Web MVC的配置支持
  • @EnableConfigurationProperties 开启对@ConfigurationProperties注解配置Bean的支持
  • @EnableJpaRepositories 开启对SpringData JPA Repository的支持
  • @EnableTransactionManagement 开启注解式事务的支持
  • @EnableTransactionManagement 开启注解式事务的支持
  • @EnableCaching 开启注解式的缓存支持
11. 测试相关注解
  • @RunWith:运行器,Spring中通常用于对JUnit的支持,是Junit的注解,一般在测试类里使用:@RunWith(SpringJUnit4ClassRunner.class) — SpringJUnit4ClassRunner在JUnit环境下提供Sprng TestContext Framework的功能;
  • @ContextConfiguration:用来加载配置ApplicationContext,其中classes属性用来加载配置类:@ContextConfiguration(classes = {TestConfig.class(自定义的一个配置类)});
12. 事务相关注解

事务管理一般有编程式和声明式两种,编程式是直接在代码中进行编写事物处理过程,而声名式则是通过注解方式或者是在xml文件中进行配置,相对编程式很方便。

而注解方式通过@Transactional 是常见的。我们可以使用@EnableTransactionManagement 注解来启用事务管理功能,该注解可以加在启动类上或者单独加个配置类来处理。

  • @Transactional

    当标于类前时, 标示类中所有方法都进行事物处理;标记在方法上,则表示当前方法是一个事务方法;当类级别配置了@Transactional,方法级别也配置了@Transactional,应用程序会以方法级别的事务属性信息来管理事务,即方法级别的事务属性信息会覆盖类级别的相关配置信息。

    Transactional 注解的属性

    • name 当在配置文件中有多个 TransactionManager , 可以用该属性指定选择哪个事务管理器。
    • propagation 事务的传播行为,默认值为 REQUIRED。
    • isolation 事务的隔离度,默认值采用 DEFAULT。
    • timeout 事务的超时时间,默认值为-1。如果超过该时间限制但事务还没有完成,则自动回滚事务。
    • read-only 指定事务是否为只读事务,默认值为 false;为了忽略那些不需要事务的方法,比如读取数据,可以设置 read-only 为 true。
    • rollback-for 用于指定能够触发事务回滚的异常类型,如果有多个异常类型需要指定,各类型之间可以通过逗号分隔。
    • no-rollback- for 抛出 no-rollback-for 指定的异常类型,不回滚事务。

    propagation 属性(事务传播性)

    • REQUIRED 支持当前已经存在的事务,如果还没有事务,就创建一个新事务。
    • MANDATORY 支持当前已经存在的事务,如果还没有事务,就抛出一个异常。
    • NESTED 在当前事务中创建一个嵌套事务,如果还没有事务,那么就简单地创建一个新事务。
    • REQUIRES_NEW 挂起当前事务,创建一个新事务,如果还没有事务,就简单地创建一个新事务。
    • NEVER 强制要求不在事务中运行,如果当前存在一个事务,则抛出异常。
    • NOT_SUPPORTED 强制不在事务中运行,如果当前存在一个事务,则挂起该事务。
    • SUPPORTS 支持当前事务,如果没有事务那么就不在事务中运行。
  • @Transactional工作原理

    声明式事务管理包含三个组成部分:

    • 事务的切面
    • 事务管理器
    • EntityManager Proxy本身

    事务的切面

    事务的切面是一个“around(环绕)”切面,在注解的业务方法前后都可以被调用。实现切面的具体类是TransactionInterceptor。事务的切面有两个主要职责:

    • 在’before’时,切面提供一个调用点,来决定被调用业务方法应该在正在进行事务的范围内运行,还是开始一个新的独立事务。
    • 在’after’时,切面需要确定事务被提交,回滚或者继续运行。

    在’before’时,事务切面自身不包含任何决策逻辑,是否开始新事务的决策委派给事务管理器完成。

    事务管理器

    事务管理器需要解决下面两个问题:

    • 新的Entity Manager是否应该被创建?
    • 是否应该开始新的事务?

    这些需要事务切面’before’逻辑被调用时决定。事务管理器的决策基于以下两点:

    • 事务是否正在进行
    • 事务方法的propagation属性(比如REQUIRES_NEW总要开始新事务)

    如果事务管理器确定要创建新事务,那么将:

    • 创建一个新的entity manager
    • entity manager绑定到当前线程
    • 从数据库连接池中获取连接
    • 将连接绑定到当前线程

    使用ThreadLocal变量将entity manager和数据库连接都绑定到当前线程。事务运行时他们存储在线程中,当它们不再被使用时,事务管理器决定是否将他们清除。程序的任何部分如果需要当前的entity manager和数据库连接都可以从线程中获取。

    EntityManager proxy

    当业务方法调用类似**entityManager.persist()**方法时,这不是由entity manager直接调用的,而是业务方法调用代理,因为事物管理器将entity manage绑定到了线程上,代理从线程获取当前的entity manager。

2.2.2-Spring MVC部分

  • @EnableWebMvc

    在配置类中开启Web MVC的配置支持,如一些ViewResolver或者MessageConverter等,若无此句,重写WebMvcConfigurerAdapter方法(用于对SpringMVC的配置);

  • @Controller

    声明该类为SpringMVC中的Controller;

  • @RequestMapping

    用于映射Web请求,包括访问路径和参数(类或方法上);

    @RequestMapping注解的主要用途是将Web请求与请求处理类中的方法进行映射。Spring MVC和Spring WebFlux都通过RquestMappingHandlerMapping和RequestMappingHndlerAdapter两个类来提供对@RequestMapping注解的支持。

    @RequestMapping注解对请求处理类中的请求处理方法进行标注;@RequestMapping注解拥有以下的六个配置属性:

    • value:映射的请求URL或者其别名
    • method:兼容HTTP的方法名
    • params:根据HTTP参数的存在、缺省或值对请求进行过滤
    • header:根据HTTP Header的存在、缺省或值对请求进行过滤
    • consume:设定在HTTP请求正文中允许使用的媒体类型
    • product:在HTTP响应体中允许使用的媒体类型

    提示:在使用@RequestMapping之前,请求处理类还需要使用@Controller或@RestController进行标记

    @Controller
    @RequestMapping("/users")
    publicclass UserController {
    @RequestMapping(method = RequestMethod.GET)
    public String getUserList() {
    return"users";
    }
    }

    此外,Spring4.3之后引入了一系列@RequestMapping的变种。如下:

    • @GetMapping
    • @PostMapping
    • @PutMapping
    • @PatchMapping
    • @DeleteMapping

    分别对应了相应method的RequestMapping配置。

    @RestController
    @RequestMapping(value = "/users") // 通过这里配置使下面的映射都在/users下
    public class UserController {

    // 创建线程安全的Map,模拟users信息的存储
    static Map<Long, User> users = Collections.synchronizedMap(new HashMap<Long, User>());

    /**
    * 处理"/users/"的GET请求,用来获取用户列表
    *
    * @return
    */
    @GetMapping("/")
    public List<User> getUserList() {
    // 还可以通过@RequestParam从页面中传递参数来进行查询条件或者翻页信息的传递
    List<User> r = new ArrayList<User>(users.values());
    return r;
    }

    /**
    * 处理"/users/"的POST请求,用来创建User
    *
    * @param user
    * @return
    */
    @PostMapping("/")
    public String postUser(@RequestBody User user) {
    // @RequestBody注解用来绑定通过http请求中application/json类型上传的数据
    users.put(user.getId(), user);
    return "success";
    }

    /**
    * 处理"/users/{id}"的GET请求,用来获取url中id值的User信息
    *
    * @param id
    * @return
    */
    @GetMapping("/{id}")
    public User getUser(@PathVariable Long id) {
    // url中的id可通过@PathVariable绑定到函数的参数中
    return users.get(id);
    }

    /**
    * 处理"/users/{id}"的PUT请求,用来更新User信息
    *
    * @param id
    * @param user
    * @return
    */
    @PutMapping("/{id}")
    public String putUser(@PathVariable Long id, @RequestBody User user) {
    User u = users.get(id);
    u.setName(user.getName());
    u.setAge(user.getAge());
    users.put(id, u);
    return "success";
    }

    /**
    * 处理"/users/{id}"的DELETE请求,用来删除User
    *
    * @param id
    * @return
    */
    @DeleteMapping("/{id}")
    public String deleteUser(@PathVariable Long id) {
    users.remove(id);
    return "success";
    }

    }
  • @RequestBody

    允许request的参数在request体中,而不是在直接链接在地址的后面。此注解放置在参数前,@RequestBody在处理请求方法的参数列表中使用,它可以将请求主体中的参数绑定到一个对象中,请求主体参数是通过HttpMessageConverter传递的,根据请求主体中的参数名与对象的属性名进行匹配并绑定值;

  • @ResponseBody

    支持将返回值放在response内,而不是一个页面,通常用户返回json数据(返回值旁或方法上);

  • @CookieValue

    此注解用在@RequestMapping声明的方法的参数上,可以把HTTP cookie中相应名称的cookie绑定上去;

    @ReuestMapping("/cookieValue")
    public void getCookieValue(@CookieValue("JSESSIONID") String cookie){

    }

    cookie即http请求中name为JSESSIONID的cookie值。

  • @CrossOrigin

    此注解用在class和method上用来支持跨域请求,是Spring 4.2后引入的。

    @CrossOrigin(maxAge = 3600)
    @RestController
    @RequestMapping("/users")
    publicclass AccountController {
    @CrossOrigin(origins = "http://xx.com")
    @RequestMapping("/login")
    public Result userLogin() {
    // ...
    }
    }
  • @PathVariable

    放置在参数前,用来接受路径参数,比如@RequestMapping(“/hello/{name}”)申明的路径,将注解放在参数中前,即可获取该值,通常作为Restful的接口实现方法。

    @PathVariable注解是将方法中的参数绑定到请求URI中的模板变量上。可以通过@RequestMapping注解来指定URI的模板变量,然后使用@PathVariable注解将方法中的参数绑定到模板变量上。特别地,@PathVariable注解允许我们使用value或name属性来给参数取一个别名。下面是使用此注解的一个示例:

    PathVariable

  • @RestController

    该注解为一个组合注解,相当于@Controller和@ResponseBody的组合,注解在类上,意味着,该Controller的所有方法都默认加上了@ResponseBody;

  • @ControllerAdvice

    @ControllerAdvice是@Component注解的一个延伸注解,Spring会自动扫描并检测被@ControllerAdvice所标注的类。@ControllerAdvice需要和@ExceptionHandler、@InitBinder以及@ModelAttribute注解搭配使用,主要是用来处理控制器所抛出的异常信息。首先,我们需要定义一个被@ControllerAdvice所标注的类,在该类中,定义一个用于处理具体异常的方法,并使用@ExceptionHandler注解进行标记。

    此外,在有必要的时候,可以使用@InitBinder在类中进行全局的配置,还可以使用@ModelAttribute配置与视图相关的参数。使用@ControllerAdvice注解,就可以快速的创建统一的,自定义的异常处理类。下面是一个使用@ControllerAdvice的示例代码:

  • @ExceptionHandler

    @ExceptionHander注解用于标注处理特定类型异常类所抛出异常的方法。当控制器中的方法抛出异常时,Spring会自动捕获异常,并将捕获的异常信息传递给被@ExceptionHandler标注的方法。下面是使用该注解的一个示例:

  • @ResponseStatus

    @ResponseStatus注解可以标注请求处理方法。使用此注解,可以指定响应所需要的HTTP STATUS。特别地,我们可以使用HttpStauts类对该注解的value属性进行赋值。下面是使用@ResponseStatus注解的一个示例:

  • @InitBinder

    此注解使用在方法上,声明对WebDataBinder的初始化(绑定请求参数到JavaBean上的DataBinder)。在controller上使用此注解可以自定义请求参数的绑定。

  • @MatrixVariable

    此注解使用在请求handler方法的参数上,Spring可以注入matrix url中相关的值。这里的矩阵变量可以出现在url中的任何地方,变量之间用;分隔。如下:

    // GET /pets/42;q=11;r=22
    @RequestMapping(value = "/pets/{petId}")
    public void findPet(@PathVariable String petId, @MatrixVariable int q) {
    // petId == 42
    // q == 11
    }

    需要注意的是默认Spring mvc是不支持矩阵变量的,需要开启。

    <mvc:annotation-driven enable-matrix-variables="true" />

    注解配置则需要如下开启:

    @Configuration
    publicclass WebConfig extends WebMvcConfigurerAdapter {

    @Override
    public void configurePathMatch(PathMatchConfigurer configurer) {
    UrlPathHelper urlPathHelper = new UrlPathHelper();
    urlPathHelper.setRemoveSemicolonContent(false);
    configurer.setUrlPathHelper(urlPathHelper);
    }
    }
  • @RequestHeader

    此注解用在请求handler方法的参数上,用于将http请求头部的值绑定到参数上。

  • @RequestParam

    @RequestParam注解用于将方法的参数与Web请求的传递的参数进行绑定。使用@RequestParam可以轻松的访问HTTP请求参数的值。下面是使用该注解的代码示例:

  • @SessionAttribute

    此注解用于方法的参数上,用于将session中的属性绑定到参数。

  • @SessionAttributes

    此注解用于type级别,用于将JavaBean对象存储到session中。一般和@ModelAttribute注解一起使用。如下:

    @ModelAttribute("user")

    public PUser getUser() {}

    // controller和上面的代码在同一controller中
    @Controller
    @SeesionAttributes(value = "user", types = {
    User.class
    })

    publicclass UserController {}
  • @RestControllerAdvice

    此注解用于class上,同时引入了@ControllerAdvice和@ResponseBody两个注解。

2.2.3-Spring Boot注解

  • @SpringBootApplication

    @SpringBootApplication注解是一个快捷的配置注解,在被它标注的类中,可以定义一个或多个Bean,并自动触发自动配置Bean和自动扫描组件。此注解相当于@Configuration、@EnableAutoConfiguration和@ComponentScan的组合。在Spring Boot应用程序的主类中,就使用了此注解。示例代码如下:

    @SpringBootApplication
    public class Application{
    public static void main(String [] args){
    SpringApplication.run(Application.class,args);
    }
    }
  • @EnableAutoConfiguration

    @EnableAutoConfiguration注解用于通知Spring,根据当前类路径下引入的依赖包,自动配置与这些依赖包相关的配置项。

  • @ConditionalOnClass与@ConditionalOnMissingClass

    这两个注解属于类条件注解,它们根据是否存在某个类作为判断依据来决定是否要执行某些配置。下面是一个简单的示例代码:

    @Configuration
    @ConditionalOnClass(DataSource.class)
    class MySQLAutoConfiguration {
    //...
    }
  • @ConditionalOnBean与@ConditionalOnMissingBean

    这两个注解属于对象条件注解,根据是否存在某个对象作为依据来决定是否要执行某些配置方法。示例代码如下:

    @Bean
    @ConditionalOnBean(name="dataSource")
    LocalContainerEntityManagerFactoryBean entityManagerFactory(){
    //...
    }
    @Bean
    @ConditionalOnMissingBean
    public MyBean myBean(){
    //...
    }
  • @ConditionalOnProperty

    @ConditionalOnProperty注解会根据Spring配置文件中的配置项是否满足配置要求,从而决定是否要执行被其标注的方法。示例代码如下:

    @Bean
    @ConditionalOnProperty(name="alipay",havingValue="on")
    Alipay alipay(){
    return new Alipay();
    }
  • @ConditionalOnResource

    此注解用于检测当某个配置文件存在使,则触发被其标注的方法,下面是使用此注解的代码示例:

    @ConditionalOnResource(resources = "classpath:website.properties")
    Properties addWebsiteProperties(){
    //...
    }
  • @ConditionalOnWebApplication与@ConditionalOnNotWebApplication

    这两个注解用于判断当前的应用程序是否是Web应用程序。如果当前应用是Web应用程序,则使用Spring WebApplicationContext,并定义其会话的生命周期。下面是一个简单的示例:

    @ConditionalOnWebApplication
    HealthCheckController healthCheckController(){
    //...
    }
  • @ConditionalExpression

    此注解可以让我们控制更细粒度的基于表达式的配置条件限制。当表达式满足某个条件或者表达式为真的时候,将会执行被此注解标注的方法。

    @Bean
    @ConditionalException("${localstore} && ${local == 'true'}")
    LocalFileStore store(){
    //...
    }
  • @ImportResource:导入Spring的配置文件,让配置文件里面的内容生效(xml);