目录

Java 注解

Java Annotation

Java 注解用于为 Java 代码提供元数据。作为元数据,注解不直接影响你的代码执行,但也有一些类型的注解实际上可以用于这一目的。Java 注解是从 Java5 开始添加到 Java 的。

注解是一系列元数据,它提供数据用来解释程序代码,但是注解并非是所解释的代码本身的一部分。注解对于代码的运行效果没有直接影响。

  • 提供信息给编译器: 编译器可以利用注解来探测错误和警告信息
  • 编译阶段时的处理: 软件工具可以用来利用注解信息来生成代码、Html文档或者做其它相应处理。
  • 运行时的处理: 某些注解可以在程序运行的时候接受代码的提取

当开发者使用了Annotation 修饰了类、方法、Field 等成员之后,这些 Annotation 不会自己生效,必须由开发者提供相应的代码来提取并处理 Annotation 信息。这些处理提取和处理 Annotation 的代码统称为 APT(Annotation Processing Tool)。

现在,我们可以给自己答案了,注解有什么用?给谁用?给 编译器或者 APT 用的。

java.lang.annotation.Annotation

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
/**
 * @author  Josh Bloch
 * @since   1.5
 */
public interface Annotation {    
    boolean equals(Object obj);
    int hashCode();
    String toString();
    Class<? extends Annotation> annotationType();
}

注解通过 @interface关键字进行定义。

1
2
3
4
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
  • 元注解:可以注解到别的注解上的注解。
  • 组合注解:被注解的注解我们就称之为组合注解。

元注解

元注解——是定义注解的注解,可以注解到注解上的注解。

@Retention@Documented@Target@Inherited@Repeatable 共5 种。

@Retention

保留期,用于提示注解被保留多长时间,有三种取值

  • RetentionPolicy.SOURCE 保留在源码级别,被编译器抛弃;
  • RetentionPolicy.CLASS 被编译器保留在编译后的类文件级别,但是被虚拟机(JVM)丢弃;
  • RetentionPolicy.RUNTIME 保留至运行时,它会被加载进入到 JVM 中,可以被反射读取。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/**
 * Indicates how long annotations with the annotated type are to
 * be retained.  If no Retention annotation is present on
 * an annotation type declaration, the retention policy defaults to
 * RetentionPolicy.CLASS.
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
    /**
     * Returns the retention policy.
     * @return the retention policy
     */
    RetentionPolicy value();
}

public enum RetentionPolicy {
    /**
     * Annotations are to be discarded by the compiler.
     */
    SOURCE,
    /**
     * Annotations are to be recorded in the class file by the compiler
     * but need not be retained by the VM at run time.  This is the default
     * behavior.
     */
    CLASS,
    /**
     * Annotations are to be recorded in the class file by the compiler and
     * retained by the VM at run time, so they may be read reflectively.
     *
     * @see java.lang.reflect.AnnotatedElement
     */
    RUNTIME
}

@Target

用于提示该注解使用的地方。

  • TYPE 类、接口(包含注解类型)、枚举
  • FIELD, 属性(包含枚举常量)
  • METHOD 方法
  • PARAMETER 方法内的参数
  • CONSTRUCTOR 构造方法
  • LOCAL_VARIABLE 局部变量
  • ANNOTATION_TYPE 注解类型
  • PACKAGE 包
  • TYPE_PARAMETER Type parameter declaration @since 1.8
  • TYPE_USE Use of a type @since 1.8

在Java8中 ElementType 新增两个枚举成员,TYPE_PARAMETER 和 TYPE_USE ,在Java8前注解只能标注在一个声明(如字段、类、方法)上,Java8后,新增的TYPE_PARAMETER可以用于标注类型参数,而TYPE_USE则可以用于标注任意类型(不包括class)。如下所示

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//TYPE_PARAMETER 标注在类型参数上
class D<@Parameter T> { }

//TYPE_USE则可以用于标注任意类型(不包括class)
//用于父类或者接口
class Image implements @Rectangular Shape { }

//用于构造函数
new @Path String("/usr/bin")

//用于强制转换和instanceof检查,注意这些注解中用于外部工具,它们不会对类型转换或者instanceof的检查行为带来任何影响。
String path=(@Path String)input;
if(input instanceof @Path String)

//用于指定异常
public Person read() throws @Localized IOException.

//用于通配符绑定
List<@ReadOnly ? extends Person>
List<? extends @ReadOnly Person>

@NotNull String.class //非法,不能标注class
import java.lang.@NotNull String //非法,不能标注import
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package java.lang.annotation;

/**
 * Indicates the contexts in which an annotation type is applicable. The
 * declaration contexts and type contexts in which an annotation type may be
 * applicable are specified in JLS 9.6.4.1, and denoted in source code by enum
 * constants of java.lang.annotation.ElementType
 * @since 1.5
 * @jls 9.6.4.1 @Target
 * @jls 9.7.4 Where Annotations May Appear
 */
@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();
}

@Target,取值有:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public enum ElementType {
    /** Class, interface (including annotation type), or enum declaration */
    TYPE,
    /** Field declaration (includes enum constants) */
    FIELD,
    /** Method declaration */
    METHOD,
    /** Formal parameter declaration */
    PARAMETER,
    /** Constructor declaration */
    CONSTRUCTOR,
    /** Local variable declaration */
    LOCAL_VARIABLE,
    /** Annotation type declaration */
    ANNOTATION_TYPE,
    /** Package declaration */
    PACKAGE,
    /**
     * Type parameter declaration
     * @since 1.8
     */
    TYPE_PARAMETER,
    /**
     * Use of a type
     * @since 1.8
     */
    TYPE_USE
}

@Documented

表示注解是否能被 javadoc 处理并保留在文档中。

/**
 * Indicates that annotations with a type are to be documented by javadoc
 * and similar tools by default.  This type should be used to annotate the
 * declarations of types whose annotations affect the use of annotated
 * elements by their clients.  If a type declaration is annotated with
 * Documented, its annotations become part of the public API
 * of the annotated elements.
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Documented {
}

@Inherited

Inherited 是继承的意思,但是它并不是说注解本身可以继承,而是说如果一个超类被 @Inherited 注解过的注解进行注解的话,那么如果它的子类没有被任何注解应用的话,那么这个子类就继承了超类的注解。 说的比较抽象,代码来解释。

1
2
3
4
5
6
7
8
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@interface Test {}

@Test
public class A {}

public class B extends A {}

注解 Test 被 @Inherited 修饰,之后类 A 被 Test 注解,类 B 继承 A,类 B 也拥有 Test 这个注解。

使用@Inherited,可以让子类Class对象使用getAnnotations()获取父类被@Inherited修饰的注解。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
T[] result = getDeclaredAnnotationsByType(annotationClass);

//判断当前类获取到的注解数组是否为0
if (result.length == 0 && this instanceof Class && 
//判断定义注解上是否使用了@Inherited元注解 
 AnnotationType.getInstance(annotationClass).isInherited()) { // Inheritable
        //从父类获取
       Class<?> superClass = ((Class<?>) this).getSuperclass();
   if (superClass != null) {
      result = superClass.getAnnotationsByType(annotationClass);
       }
   }

   return result;
}

@Repeatable

Repeatable 自然是可重复的意思。@Repeatable 是 Java 1.8 才加进来的,所以算是一个新的特性。

什么样的注解会多次应用呢?通常是注解的值可以同时取多个。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
@interface Persons {
	Person[] value();
}

@Repeatable(Persons.class)
@interface Person{
	String role default "";
}

@Person(role="artist")
@Person(role="coder")
@Person(role="PM")
public class SuperMan{
	
}

@Repeatable 后面括号中的 Persons 类相当于一个容器注解。属性类型是一个被 @Repeatable 注解过的注解的数组。

注解的属性

注解的属性也叫做成员变量。注解只有成员变量,没有方法。注解的成员变量在注解的定义中以“无形参的方法”形式来声明,其方法名定义了该成员变量的名字,其返回值定义了该成员变量的类型。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {	
	int id() default -1;	
	String msg();
}

@TestAnnotation(id=3, msg="hello annotation")
public class Test {
}

@TestAnnotation(msg="hello annotation again")
public class Test1 {
}

在注解中定义属性时它的类型必须是 8 种基本数据类型外加 类、接口、注解及它们的数组。

  • 所有基本类型(int,float,boolean,byte,double,char,long,short)
  • String
  • Class
  • enum
  • Annotation
  • 上述类型的数组

注意,声明注解元素时可以使用基本类型但不允许使用任何包装类型

如果一个注解内仅仅只有一个名字为 value 的属性时,应用这个注解时可以直接接属性值填写到括号内。

一个注解没有任何属性,应用这个注解的时候,括号都可以省略。eg @Override

Java 预置的注解

@Deprecated

这个元素是用来标记过时的元素,想必大家在日常开发中经常碰到。编译器在编译阶段遇到这个注解时会发出提醒警告,告诉开发者正在调用一个过时的元素比如过时的方法、过时的类、过时的成员变量。

@Override

提示编译器,使用了@Override注解的方法必须override父类或者java.lang.Object中的一个同名方法。

@SuppressWarnings 阻止警告的意思。之前说过调用被 @Deprecated 注解的方法后,编译器会警告提醒,而有时候开发者会忽略这种警告,可以在调用的地方通过 @SuppressWarnings 达到目的。

用于有选择的关闭编译器对类、方法、成员变量、变量初始化的警告,其实现源码如下:

1
2
3
4
5
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
    String[] value();
}

其内部有一个String数组,主要接收值如下:

deprecation:使用了不赞成使用的类或方法时的警告; unchecked:执行了未检查的转换时的警告,例如当使用集合时没有用泛型 (Generics) 来指定集合保存的类型; fallthrough:当 Switch 程序块直接通往下一种情况而没有 Break 时的警告; path:在类路径、源文件路径等中有不存在的路径时的警告; serial:当在可序列化的类上缺少 serialVersionUID 定义时的警告; finally:任何 finally 子句不能正常完成时的警告; all:关于以上所有情况的警告。

@FunctionalInterface

函数式接口注解,Java 1.8 版本引入的新特性。

1
2
3
4
@FunctionalInterface
public interface Runnable {
    public abstract void run();
}

线程开发中常用的 Runnable 就是一个典型的函数式接口,函数式接口可以很容易转换为 Lambda 表达式。

常用工具注解

JUnit

@Test

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
@RunWith(SpringRunner.class)
@SpringBootTest(classes = SpringBootCrudRestApplication.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class SpringBootCrudRestApplicationTests {

    @Autowired
    private TestRestTemplate restTemplate;

    @LocalServerPort
    private int port;

    private String getRootUrl() {
        return "http://localhost:" + port;
    }

    @Test
    public void contextLoads() {

    }

    @Test
    public void testGetAllUsers() {
         HttpHeaders headers = new HttpHeaders();
         HttpEntity<String> entity = new HttpEntity<String>(null, headers);

         ResponseEntity<String> response = restTemplate.exchange(getRootUrl() + "/users",
         HttpMethod.GET, entity, String.class);
  
         assertNotNull(response.getBody());
    }

    @Test
    public void testGetUserById() {
        User user = restTemplate.getForObject(getRootUrl() + "/users/1", User.class);
        System.out.println(user.getFirstName());
        assertNotNull(user);
    }
}

jackson

@JsonProperty

1
2
3
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.4</version>

jackson 依赖 jackson-annotations jackson-core

注解的提取

注解与反射

经过反编译后,可以看到 Java 所有注解都继承了Annotation接口。

Java使用Annotation接口代表注解元素,该接口是所有Annotation类型的父接口。同时为了运行时能准确获取到注解的相关信息,Java在java.lang.reflect 反射包下新增了AnnotatedElement接口,它主要用于表示目前正在 VM 中运行的程序中已使用注解的元素,通过该接口提供的方法可以利用反射技术地读取注解的信息,如反射包的Constructor类、Field类、Method类、Package类和Class类都实现了AnnotatedElement接口,

Class Method Field 等都实现了 AnnotatedElement 接口

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
public interface AnnotatedElement {
    /**
     * 是否应用了某个注解    
     * @since 1.5
     */
    default boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) {
        return getAnnotation(annotationClass) != null;
    }

   /**
     * 返回注解类型,如果不存在返回 null
     */
    <T extends Annotation> T getAnnotation(Class<T> annotationClass);

    /**
     * 返回元素上的所有注解
     */
    Annotation[] getAnnotations();

    /**
     * Returns annotations that are <em>associated</em> with this element.    
     * @since 1.8
     */
    default <T extends Annotation> T[] getAnnotationsByType(Class<T> annotationClass) {
         /*
          * Definition of associated: directly or indirectly present OR
          * neither directly nor indirectly present AND the element is
          * a Class, the annotation type is inheritable, and the
          * annotation type is associated with the superclass of the
          * element.
          */
         T[] result = getDeclaredAnnotationsByType(annotationClass);

         if (result.length == 0 && // Neither directly nor indirectly present
             this instanceof Class && // the element is a class
             AnnotationType.getInstance(annotationClass).isInherited()) { // Inheritable
             Class<?> superClass = ((Class<?>) this).getSuperclass();
             if (superClass != null) {
                 // Determine if the annotation is associated with the
                 // superclass
                 result = superClass.getAnnotationsByType(annotationClass);
             }
         }

         return result;
     }

    /**
     * Returns this element's annotation for the specified type if
     * such an annotation is <em>directly present</em>, else null.
     * @since 1.8
     */
    default <T extends Annotation> T getDeclaredAnnotation(Class<T> annotationClass) {
         Objects.requireNonNull(annotationClass);
         // Loop over all directly-present annotations looking for a matching one
         for (Annotation annotation : getDeclaredAnnotations()) {
             if (annotationClass.equals(annotation.annotationType())) {
                 // More robust to do a dynamic cast at runtime instead
                 // of compile-time only.
                 return annotationClass.cast(annotation);
             }
         }
         return null;
     }

    /**
     * Returns this element's annotation(s) for the specified type if
     * such annotations are either <em>directly present</em> or
     * <em>indirectly present</em>. This method ignores inherited
     * annotations.  
     * @since 1.8
     */
    default <T extends Annotation> T[] getDeclaredAnnotationsByType(Class<T> annotationClass) {
        Objects.requireNonNull(annotationClass);
        return AnnotationSupport.
            getDirectlyAndIndirectlyPresent(Arrays.stream(getDeclaredAnnotations()).
                                            collect(Collectors.toMap(Annotation::annotationType,
                                                                     Function.identity(),
                                                                     ((first,second) -> first),
                                                                     LinkedHashMap::new)),
                                            annotationClass);
    }

    /**
     * Returns annotations that are <em>directly present</em> on this element.
     * This method ignores inherited annotations.
     * @since 1.5
     */
    Annotation[] getDeclaredAnnotations();
}

getDeclaredMethod(s)

返回该类本身的所有方法(包括私有方法),但不包括继承的方法。 返回数组中的元素没有排序,也没有任何特定的顺序。如果该类或接口不声明任何方法,或此Class对象表示一个基本类型、数组类型或void,则此方法返回一个长度为0的数组。

类初始化方法不包含在返回数组中。

该方法返回所有重载的方法。

getMethod(s)

返回某个类的所有public(包括继承来public方法)。

如果此Class对象表示基本类型或void,则此方法返回长度为0的数组。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
 public static void main(String[] args) {
        try {
            // 使用类加载器加载类
            Class c = Class.forName("com.test.Child");
            // 找到类上面的注解
            boolean isExist = c.isAnnotationPresent(Description.class);
            // 上面的这个方法是用这个类来判断这个类是否存在Description这样的一个注解
            if (isExist) {
                // 拿到注解实例,解析类上面的注解
                Description d = (Description) c.getAnnotation(Description.class);
                System.out.println(d.value());
            }
          
          	//获取所有的方法
            Method[] ms = c.getMethods();
            // 遍历所有的方法
            for (Method m : ms) {
                boolean isExist1 = m.isAnnotationPresent(Description.class);
                if (isExist1) {
                    Description d1=m.getAnnotation(Description.class);
                    System.out.println(d1.value());
                }
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

如果一个注解要在运行时被成功提取,那么 @Retention(RetentionPolicy.RUNTIME) 是必须的。

JSR-303 标准

1
2
3
4
5
6
				<!-- Hibernate Validator -->
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-validator</artifactId>
            <version>5.4.1.Final<[表情]ersion>
        </dependency>

JSR 303 - Bean Validation

Constraint 详细信息
@Null 被注释的元素必须为 null
@NotNull 被注释的元素必须不为 null
@AssertTrue 被注释的元素必须为 true
@AssertFalse 被注释的元素必须为 false
@Min(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值
@Max(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值
@DecimalMin(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值
@DecimalMax(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值
@Size(max, min) 被注释的元素的大小必须在指定的范围内
@Digits (integer, fraction) 被注释的元素必须是一个数字,其值必须在可接受的范围内
@Past 被注释的元素必须是一个过去的日期
@Future 被注释的元素必须是一个将来的日期
@Pattern(value) 被注释的元素必须符合指定的正则表达式

Hibernate Validator 附加的 constraint

Constraint 详细信息
@Email 被注释的元素必须是电子邮箱地址
@Length 被注释的字符串的大小必须在指定的范围内
@NotEmpty 被注释的字符串的必须非空
@Range 被注释的元素必须在合适的范围内

JSR-330 标准

从Spring 3.0开始,Spring开始支持JSR-330标准的注解(依赖注入)。

javax.inject.* 中的注解 (@Inject, @Named, @Qualifier, @Provider, @Scope, @Singleton).

这些注解和Spring注解扫描的方式是一样的,开发者只需要在classpath中配置相关的jar包即可。

1
2
3
4
5
<dependency>
    <groupId>javax.inject</groupId>
    <artifactId>javax.inject</artifactId>
    <version>1</version>
</dependency>

JSR-330中,@javax.inject.Inject和Spring中的@Autowired的职责相同

JSR-330中,@javax.inject.Named和Spring中的@Component的职责类似

当使用JSR-330标准的注解时,了解其和Spring注解的不同点也是十分必要的,参考如下表:

Spring javax.inject.* javax.inject 限制
@Autowired @Inject @Inject注解没有required属性,但是可以通过Java 8的Optional取代
@Component @Named JSR_330标准并没有提供复合的模型,只有一种方式来识别组件
@Scope(“singleton”) @Singleton JSR-330默认的作用域类似Spring的prototype,然而,为何和Spring的默认保持一致,JSR-330标准中的Bean在Spring中默认也是单例的。如果要使用非单例的作用域,开发者应该使用Spring的@Scope注解。java.inject也提供一个@Scope注解,然而,这个注解仅仅可以用来创建自定义的作用域时才能使用。
@Qualifier @Qualifier/@Named javax.inject.Qualifier仅仅是一个元注解,用来构建自定义限定符的。而String的限定符(比如Spring中的@Qualifier)可以通过javax.inject.Named来实现
@Value - 不等价
@Required - 不等价
@Lazy - 不等价
ObjectFactory Provider javax.inject.Provider是SpringObjectFactory的另一个选择,通过get()方法来代理,Provider可以和Spring的@Autowired组合使用

@Inject就相当于@Autowired, @Named 就相当于 @Qualifier, 另外 @Named 用在类上还有 @Component的功能。

JSR-250 标准

相关的注解全部在 javax.annotationjavax.annotation.security 包中,资源定义和权限控制。

javax.annotation 中包含一下几个注解:

  • @Generated:生成资源的注解,通过该项标记产生的实例是一个资源。类似于Spring中的@Bean注解,用于生成一向资源。
  • @PostConstruct 创造资源之后的回调处理,Spring已经实现了这个注解。
  • @PreDestroy 销毁资源之前的回调处理,Spring同样实现了这个注解。
  • @Resource 标记使用资源的位置,Spring同样实现了这个注解的功能。功能上有些类似于@Autowired、@Inject,但是两者有不少的差别。
  • @Resources 标记使用多项资源的位置,类似于使用@Autowired向一个列表装载数据。

javax.annotation.security 包中有以下内容:

  • @DeclareRoles 声明角色
  • @DenyAll 拒绝所有角色
  • @PermitAll 授权所有惧色
  • @RolesAllowed 角色授权
  • @RunAs 运行模式

Spring 中的注解

Bean 容器相关的注解

将被依赖的bean注入进入

@Required

注解适用于bean属性setter方法,并表示受影响的bean属性必须在XML配置文件在配置时进行填充。否则,容器会抛出一个BeanInitializationException异常。已经废弃

1
2
3
4
5
@Deprecated
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Required {
}

@Autowired

使用得最多的注解,其实就是 autowire=byType 就是根据类型的自动注入依赖(基于注解的依赖注入),可以被使用再属性域,方法,构造函数上。

使用@Autowired注解,必须事先在Spring容器中声明AutowiredAnnotationBeanPostProcessor的Bean

1
<bean class="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor "/>

也可以通过下面的配置实现

1
<context:annotation-config/>

@Qualifier

就是 autowire=byName, @Autowired注解判断多个bean类型相同时,就需要使用 @Qualifier(“xxBean”) 来指定依赖的bean的id:

1
2
3
4
5
6
7
@Controller
@RequestMapping("/user")
public class HelloController {
    @Autowired
    @Qualifier("userService")
    private UserService userService;
}

@Resource

Spring 实现了 JSR250标 的 @PostConstruct、@PreDestroy和@Resource。

  • @Autowired默认按照byType方式进行bean匹配,@Resource默认按照byName方式进行bean匹配

  • @Autowired是Spring的注解,@Resource是J2EE的注解,这个看一下导入注解的时候这两个注解的包名就一清二楚了

Spring 中 @Resource 的实现是在 CommonAnnotationBeanPostProcessor 而@Autowired 是在 AutowiredAnnotationBeanPostProcessor,但是实际上两者的功能是重叠的,或者说@Resource的提供的功能是@Autowired的子集。

@Value

为属性注入值

1
2
3
4
5
@Value("Michael Jackson")
String name;
// 注入配置
@Value("${book.name}")
String bookName;

@PostConstruct @PreDestroy

不是用于依赖注入,而是bean 的生命周期。

Spring会在Bean的依赖注入完成后回调这个@PostConstruct方法。

@PreDestroy 标注的方法,在bean销毁之前调用。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
@Component
public class DatabaseInitiaizer {

    private List < User > listOfUsers = new ArrayList < > ();

    @PostConstruct
    public void init() {
        User user = new User(1, "User");
        User user1 = new User(2, "Admin");
        User user2 = new User(3, "SuperAdmin");

        listOfUsers.add(user);
        listOfUsers.add(user1);
        listOfUsers.add(user2);
        System.out.println("-----------List of users added in init() method ------------");
        for (Iterator < User > iterator = listOfUsers.iterator(); iterator.hasNext();) {
            User user3 = (User) iterator.next();
            System.out.println(user3.toString());
        }
        // save to database

    }

    @PreDestroy
    public void destroy() {
        // Delete from database
        listOfUsers.clear();
        System.out.println("-----------After of users removed from List in destroy() method ------------");
        for (Iterator < User > iterator = listOfUsers.iterator(); iterator.hasNext();) {
            User user3 = (User) iterator.next();
            System.out.println(user3.toString());
        }
    }
}

@Configuration
@ComponentScan(basePackages = "com.ynthm.test")
public class AppConfig {
 
}

类似于 init-method(InitializeingBean) destory-method(DisposableBean)

继承org.springframework.beans.factory.InitializingBean接口,然后实现 afterPropertiesSet方法。

1
2
3
4
public class A implements InitializingBean {
    public void afterPropertiesSet(){
    }
}
1
<bean id="a" class="x.y.A" />

或在Bean的XML配置上使用init-method属性来制定要调用的初始化:

1
<bean id="a" class="x.y.A" init-method="init" />

声明 Bean 的注解

用于标注Spring要进行实例化的类

作用都是生产bean, 这些注解都是注解在类上,将类注解成spring的bean工厂中一个一个的bean。@Controller, @Service, @Repository基本就是语义更加细化的@Component。

1
package org.springframework.stereotype

@Component

组件,没有明确的角色

@Service

组合注解(组合了@Component注解)在业务逻辑层使用(service层)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Service {
    @AliasFor(
        annotation = Component.class
    )
    String value() default "";
}

@Repository

在数据访问层使用(dao层)

@Controller

在展现层使用,控制器的声明

表明该类会作为与前端作交互的控制层组件,通过服务接口定义的提供访问应用程序的一种行为,解释用户的输入,将其转换成一个模型然后将试图呈献给用户。

1
package org.springframework.context.annotation

@Bean

注解在方法上,声明当前方法的返回值为一个Bean。返回的Bean对应的类中可以定义init()方法和destroy()方法,然后在@Bean(initMethod=“init”,destroyMethod=“destroy”)定义,在构造之后执行init,在销毁之前执行destroy。

@ComponentScan

用于对Component进行扫描,相当于

1
<context:component-scan base-package="com.ynthm.test" />

@Conditional

根据满足某一特定条件创建特定的Bean

通过实现Condition接口,并重写matches方法,从而决定该bean是否被实例化。

@Configuration

声明当前类是一个配置类(相当于一个Spring配置的xml文件)

@Import

@Lazy

用于指定该类是否取消预初始化。指定参数boolean型true即可

@Primary

@Profile

通过设定Environment的ActiveProfiles来设定当前context需要使用的配置环境。

@Scope

用来配置 spring bean 的作用域,它标识 bean 的作用域。 默认值是单例

  1. singleton:单例模式,全局有且仅有一个实例
  2. prototype:原型模式,每次获取Bean的时候会有一个新的实例
  3. request:request表示该针对每一次HTTP请求都会产生一个新的bean,同时该bean仅在当前HTTP request内有效
  4. session:session作用域表示该针对每一次HTTP请求都会产生一个新的bean,同时该bean仅在当前HTTP session内有效
  5. global session:只在portal应用中有用,给每一个 global http session 新建一个Bean实例。

@Scope(“prototype”):默认的情况下是产生单例对象。

Spring MVC 相关的注解

@RequestMapping

用于将url映射到整个处理类或者特定的处理请求的方法。可以只用通配符

@GetMapping @PostMapping @PutMapping @DeleteMapping @PatchMapping

@RequestParam

将请求的参数绑定到方法中的参数上,有required参数,默认情况下,required=true,也就是改参数必须要传。如果改参数可以传可不传,可以配置required=false。

@PathVariable

用于方法修饰方法参数,会将修饰的方法参数变为可供使用的uri变量(可用于动态绑定)。

1
2
3
4
@RequestMapping(value="/happy/{dayid}",method=RequestMethod.GET)
public String findPet(@PathVariable String dayid, Model mode) {
//使用@PathVariable注解绑定 {dayid} 到String dayid
}

Spring会自动将其转换成合适的类型或者抛出 TypeMismatchException异常

支持使用正则表达式,这就决定了它的超强大属性,它能在路径模板中使用占位符,可以设定特定的前缀匹配,后缀匹配等自定义格式。

@RequestBody

方法参数应该被绑定到HTTP请求Body上

@ResponseBody

@ResponseBody与@RequestBody类似,它的作用是将返回类型直接输入到HTTP response body中。 @ResponseBody在输出JSON格式的数据时,会经常用到。

作用:

​ 该注解用于将Controller的方法返回的对象,通过适当的HttpMessageConverter转换为指定格式后,写入到Response对象的body数据区。

使用时机:

​ 返回的数据不是html标签的页面,而是其他某种格式的数据时(如json、xml等)使用;

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
@RequestMapping("/login")
@ResponseBody
public User login(User user){
  return user;
}

// 效果等同于如下代码:
@RequestMapping("/login")
public void login(User user, HttpServletResponse response){
   response.getWriter.write(JSONObject.fromObject(user).toString());
}

@ResponseStatus

返回 HTTP 状态码

1
2
3
4
5
6
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<?> resourceNotFoundException(ResourceNotFoundException ex, WebRequest request) {
  ErrorDetails errorDetails = new ErrorDetails(new Date(), ex.getMessage(), request.getDescription(false));
  return new ResponseEntity<>(errorDetails, HttpStatus.NOT_FOUND);
}

@RestController

控制器实现了REST的API,只为服务于JSON,XML或其它自定义的类型内容,@RestController用来创建REST类型的控制器,与@Controller类型。@RestController就是这样一种类型,它避免了你重复的写@RequestMapping与@ResponseBody。

用在类上,声明一个控制器建言,它也组合了@Component注解,会自动注册为Spring的Bean。

@ExceptionHandler

用在方法上定义全局处理,通过他的value属性可以过滤拦截的条件:@ExceptionHandler(value=Exception.class)–表示拦截所有的Exception。

@InitBinder

用来设置WebDataBinder,WebDataBinder用来自动绑定前台请求参数到Model中。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@Controller
@RequestMapping("/customer")
public class CustomerController {

    // add an initbinder ... to convert trim input strings
    // remove leading and trailing whitespace
    // resolve issue for our validation

    @InitBinder
    public void initBinder(WebDataBinder dataBinder) {

        StringTrimmerEditor stringTrimmerEditor = new StringTrimmerEditor(true);

        dataBinder.registerCustomEditor(String.class, stringTrimmerEditor);
    }


    @RequestMapping("/showForm")
    public String showForm(Model theModel) {

        theModel.addAttribute("customer", new Customer());

        return "customer-form";
    }
}

@ControllerAdvice

@ModelAttribute

@ModelAttribute可以作用在方法或方法参数上,当它作用在方法上时,标明该方法的目的是添加一个或多个模型属性(model attributes)。 该方法支持与@RequestMapping一样的参数类型,但并不能直接映射成请求。控制器中的@ModelAttribute方法会在@RequestMapping方法调用之前而调用。

两种风格:一种是添加隐形属性并返回它。另一种是该方法接受一个模型并添加任意数量的模型属性。用户可以根据自己的需要选择对应的风格

@SessionAttributes

默认情况下Spring MVC将模型中的数据存储到request域中。当一个请求结束后,数据就失效了。如果要跨页面使用。那么需要使用到session。而@SessionAttributes注解就可以使得模型中的数据存储一份到session域中。

@CrossOrigin

跨域支持

@Enable 注解

早在Spring 3框架就引入了这些注释,用这些注释替代XML配置文件。

  • @EnableTransactionManagement注释,它能够声明事务管理;
  • @EnableWebMvc注释,它能启用Spring MVC;
  • @EnableScheduling注释,它可以初始化一个调度器。

Spirng 事务注解

@Transactional

声明事务 spring boot 会自动配置一个 DataSourceTransactionManager,我们只需在方法(或者类)加上 @Transactional 注解,就自动纳入 Spring 的事务管理了。

Spring AOP 相关注解

@PointCut 声明切点 在java配置类中使用@EnableAspectJAutoProxy注解开启Spring对AspectJ代理的支持

@Aspect 声明一个切面(就是说这是一个额外功能) @After 后置建言(advice),在原方法前执行。 @Before 前置建言(advice),在原方法后执行。 @Around 环绕建言(advice),在原方法执行前执行,在原方法执行后再执行(@Around可以实现其他两种advice) @PointCut 声明切点,即定义拦截规则,确定有哪些方法会被切入

异步相关

@EnableAsync 配置类中,通过此注解开启对异步任务的支持,叙事性AsyncConfigurer接口(类上)

@Async 在实际执行的bean方法使用该注解来申明其是一个异步任务(方法上或类上所有的方法都将异步,需要@EnableAsync开启异步任务)

定时任务

@EnableScheduling 在配置类上使用,开启计划任务的支持

@Scheduled 来申明这是一个任务,包括cron,fixDelay,fixRate等类型

缓存相关

spring-context-5.1.9.RELEASE.jar

package org.springframework.cache.annotation;

@EnableCaching

开启缓存支持

@Cacheable

对于一个支持缓存的方法,Spring会在其被调用后将其返回值缓存起来,以保证下次利用同样的参数来执行该方法时可以直接从缓存中获取结果,而不需要再次执行该方法。Spring在缓存方法的返回值时是以键值对进行缓存的,值就是方法的返回结果,至于键的话,Spring又支持两种策略,默认策略和自定义策略。

参数 解释 example
value 缓存的名称 在 spring 配置文件中定义,必须指定至少一个 例如:@Cacheable(value=”mycache”)
@Cacheable(value={”cache1”,”cache2”}
key 缓存的 key 可以为空,如果指定要按照 SpEL 表达式编写,如果不指定,则缺省按照方法的所有参数进行组合 @Cacheable(value=”testcache”,key=”#userName”)
condition 缓存的条件 可以为空,使用 SpEL 编写,返回 true 或者 false,只有为 true 才进行缓存 @Cacheable(value=”testcache”,condition=”#userName.length()>2”)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

/**
* key 是指传入时的参数
*
*/
   @Cacheable(value="users", key="#id")
   public User find(Integer id) {
      return null;
   }
// 表示第一个参数
  @Cacheable(value="users", key="#p0")
   public User find(Integer id) {
      return null;
   }
// 表示User中的id值
   @Cacheable(value="users", key="#user.id")
   public User find(User user) {
      return null;
   }
 // 表示第一个参数里的id属性值
   @Cacheable(value="users", key="#p0.id")
   public User find(User user) {
      return null;
   }

@CachePut

会把方法的返回值放入缓存中, 主要用于数据新增和修改方法。

Spring在每次执行前都会检查Cache中是否存在相同key的缓存元素,如果存在就不再执行该方法,而是直接从缓存中获取结果进行返回,否则才会执行并将返回结果存入指定的缓存中。@CachePut也可以声明一个方法支持缓存功能。与@Cacheable不同的是使用@CachePut标注的方法在执行前不会去检查缓存中是否存在之前执行过的结果,而是每次都会执行该方法

@CacheEvict

用来标注在需要清除缓存元素的方法或类上的。当标记在一个类上时表示其中所有的方法的执行都会触发缓存的清除操作。@CacheEvict可以指定的属性有value、key、condition、allEntries和beforeInvocation。其中value、key和condition的语义与@Cacheable对应的属性类似。即value表示清除操作是发生在哪些Cache上的(对应Cache的名称);key表示需要清除的是哪个key,如未指定则会使用默认策略生成的key;condition表示清除操作发生的条件。

@Caching

可以让我们在一个方法或者类上同时指定多个Spring Cache相关的注解。其拥有三个属性:cacheable、put和evict,分别用于指定@Cacheable、@CachePut和@CacheEvict。

1
2
3
4
5
@Caching(cacheable = @Cacheable("users"), evict = { @CacheEvict("cache2"),
   @CacheEvict(value = "cache3", allEntries = true) })
   public User find(Integer id) {
      return null;
}

使用Redis做缓存

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
@CacheConfig
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport{

    @Bean
    public KeyGenerator keyGenerator() {
        return new KeyGenerator() {
            //为给定的方法及其参数生成一个键
            //格式为:com.frog.mvcdemo.controller.FrogTestController-show-[params]
            @Override
            public Object generate(Object target, Method method, Object... params) {
                StringBuffer sb = new StringBuffer();
                sb.append(target.getClass().getName());//类名
                sb.append("-");
                sb.append(method.getName());//方法名
                sb.append("-");
                for (Object param: params ) {
                    sb.append(param.toString());//参数
                }
                return sb.toString();
            }
        };
    }

    @Bean
    public CacheManager cacheManager(RedisTemplate redisTemplate) {
        RedisCacheManager rcm = new RedisCacheManager(redisTemplate);
        //设置默认的过期时间(以秒为单位)
        rcm.setDefaultExpiration(600);
        //rcm.setExpires();设置缓存区域(按key)的过期时间(以秒为单位)
        return rcm;
    }

    @Bean
    public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory factory) {
        StringRedisTemplate template = new StringRedisTemplate(factory);
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        template.setValueSerializer(jackson2JsonRedisSerializer);
        template.afterPropertiesSet();
        return template;
    }
}

Spring 中注解的处理

Spring 中注解的处理基本都是通过实现接口 BeanPostProcessor 来进行的:

public interface BeanPostProcessor {
    Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;
    Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;
}

相关的处理类有: AutowiredAnnotationBeanPostProcessor,CommonAnnotationBeanPostProcessor,PersistenceAnnotationBeanPostProcessor, RequiredAnnotationBeanPostProcessor

这些处理类,可以通过 <context:annotation-config/> 配置隐式的配置进spring容器。这些都是依赖注入的处理,还有生产bean的注解(@Component, @Controller, @Service, @Repository)的处理:

<context:component-scan base-package="net.aazj.service,net.aazj.aop" />

这些都是通过指定扫描的基包路径来进行的,将他们扫描进spring的bean容器。注意 context:component-scan 也会默认将 AutowiredAnnotationBeanPostProcessor,CommonAnnotationBeanPostProcessor 配置进来。所以<context:annotation-config/> 是可以省略的。另外 context:component-scan 也可以扫描@Aspect风格的AOP注解,但是需要在配置文件中加入 <aop:aspectj-autoproxy/> 进行配合。

1
2
<context:component-scan> 
<!--<context:component-scan>的使用,是默认激活<context:annotation-config>功能-->

Spring注解和JSR-330标准注解的区别

https://images0.cnblogs.com/blog2015/699877/201505/251128132561369.png

@Repository、@Service、@Controller 和 @Component 将类标识为Bean spring 自 2.0 版本开始,陆续引入了一些注解用于简化 Spring 的开发。@Repository注解便属于最先引入的一批,它用于将数据访问层 (DAO 层 ) 的类标识为 Spring Bean。具体只需将该注解标注在 DAO类上即可。同时,为了让 Spring 能够扫描类路径中的类并识别出 @Repository 注解,需要在 XML 配置文件中启用Bean 的自动扫描功能,这可以通过context:component-scan/实现。如下所示:

// 首先使用 @Repository 将 DAO 类声明为 Bean package bookstore.dao; @Repository public class UserDaoImpl implements UserDao{ …… }

// 其次,在 XML 配置文件中启动 Spring 的自动扫描功能 <beans … > ​ …… <context:component-scan base-package=”bookstore.dao” /> ……

如此,我们就不再需要在 XML 中显式使用 进行Bean 的配置。Spring 在容器初始化时将自动扫描 base-package 指定的包及其子包下的所有 class文件,所有标注了 @Repository 的类都将被注册为 Spring Bean。

为什么 @Repository 只能标注在 DAO 类上呢?这是因为该注解的作用不只是将类识别为Bean,同时它还能将所标注的类中抛出的数据访问异常封装为 Spring 的数据访问异常类型。 Spring本身提供了一个丰富的并且是与具体的数据访问技术无关的数据访问异常结构,用于封装不同的持久层框架抛出的异常,使得异常独立于底层的框架。

Spring 2.5 在 @Repository的基础上增加了功能类似的额外三个注解:@Component、@Service、@Constroller,它们分别用于软件系统的不同层次:

@Component 是一个泛化的概念,仅仅表示一个组件 (Bean) ,可以作用在任何层次。 @Service 通常作用在业务层,但是目前该功能与 @Component 相同。 @Constroller 通常作用在控制层,但是目前该功能与 @Component 相同。 通过在类上使用 @Repository、@Component、@Service 和 @Constroller 注解,Spring会自动创建相应的 BeanDefinition 对象,并注册到 ApplicationContext 中。这些类就成了 Spring受管组件。这三个注解除了作用于不同软件层次的类,其使用方式与 @Repository 是完全相同的。

另外,除了上面的四个注解外,用户可以创建自定义的注解,然后在注解上标注 @Component,那么,该自定义注解便具有了与所@Component 相同的功能。不过这个功能并不常用。

当一个 Bean 被自动检测到时,会根据那个扫描器的 BeanNameGenerator 策略生成它的 bean名称。默认情况下,对于包含 name 属性的 @Component、@Repository、 @Service 和@Controller,会把 name 取值作为 Bean 的名字。如果这个注解不包含 name值或是其他被自定义过滤器发现的组件,默认 Bean 名称会是小写开头的非限定类名。如果你不想使用默认 bean命名策略,可以提供一个自定义的命名策略。首先实现 BeanNameGenerator接口,确认包含了一个默认的无参数构造方法。然后在配置扫描器时提供一个全限定类名,如下所示:

<beans …> <context:component-scan ​ base-package=“a.b” name-generator=“a.SimpleNameGenerator”/>

与通过 XML 配置的 Spring Bean 一样,通过上述注解标识的Bean,其默认作用域是"singleton",为了配合这四个注解,在标注 Bean 的同时能够指定 Bean 的作用域,Spring2.5 引入了 @Scope 注解。使用该注解时只需提供作用域的名称就行了,如下所示:

@Scope(“prototype”) @Repository public class Demo { … }

如果你想提供一个自定义的作用域解析策略而不使用基于注解的方法,只需实现 ScopeMetadataResolver接口,确认包含一个默认的没有参数的构造方法。然后在配置扫描器时提供全限定类名:

<context:component-scan base-package=“a.b” scope-resolver=“footmark.SimpleScopeResolver” />

作用是向Spring容器注册以下四个BeanPostProcessor:

  • AutowiredAnnotationBeanPostProcessor
  • CommonAnnotationBeanPostProcessor
  • PersistenceAnnotationBeanPostProcessor
  • RequiredAnnotationBeanPostProcessor

推荐配置扫描包路径选项,会覆盖上面的配置

1
<context:component-scan base-package="com.ynthm.hotel" />
1
<mvc:annotation-driven/>

会自动注册DefaultAnnotationHandlerMapping与AnnotationMethodHandlerAdapter 两个bean,是Spring MVC为@Controller分发请求所必须的。

AnnotationMethodHandlerAdapter将会初始化7个转换器

1
2
3
4
5
6
7
ByteArrayHttpMessageConverter
StringHttpMessageConverter
ResourceHttpMessageConverter
SourceHttpMessageConverter
XmlAwareFormHttpMessageConverter
Jaxb2RootElementHttpMessageConverter
MappingJacksonHttpMessageConverter

context:component-scan/标签是告诉Spring 来扫描指定包下的类,并注册被@Component,@Controller,@Service,@Repository等注解标记的组件。

自定义注解

有了元注解,那么我就可以使用它来自定义我们需要的注解。结合自定义注解和AOP或者过滤器,是一种十分强大的武器。比如可以使用注解来实现权限的细粒度的控制——在类或者方法上使用权限注解,然后在AOP或者过滤器中进行拦截处理。下面是一个关于登录的权限的注解的实现

1
2
3
4
5
6
7
8
/**
 * 不需要登录注解
 */
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface NoLogin {
}

我们自定义了一个注解 @NoLogin, 可以被用于 方法 和 类 上,注解一直保留到运行期,可以被反射读取到。该注解的含义是:被 @NoLogin 注解的类或者方法,即使用户没有登录,也是可以访问的。下面就是对注解进行处理了:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
/**
 * 检查登录拦截器
 * 如不需要检查登录可在方法或者controller上加上@NoLogin
 */
@Component
public class CheckLoginInterceptor implements HandlerInterceptor {
    private static final Logger logger = Logger.getLogger(CheckLoginInterceptor.class);

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
                             Object handler) throws Exception {
        if (!(handler instanceof HandlerMethod)) {
            logger.warn("当前操作handler不为HandlerMethod=" + handler.getClass().getName() + ",req="
                        + request.getQueryString());
            return true;
        }
        HandlerMethod handlerMethod = (HandlerMethod) handler;
        String methodName = handlerMethod.getMethod().getName();
        // 判断是否需要检查登录
        NoLogin noLogin = handlerMethod.getMethod().getAnnotation(NoLogin.class);
        if (null != noLogin) {
            if (logger.isDebugEnabled()) {
                logger.debug("当前操作methodName=" + methodName + "不需要检查登录情况");
            }
            return true;
        }
        noLogin = handlerMethod.getMethod().getDeclaringClass().getAnnotation(NoLogin.class);
        if (null != noLogin) {
            if (logger.isDebugEnabled()) {
                logger.debug("当前操作methodName=" + methodName + "不需要检查登录情况");
            }
            return true;
        }
        if (null == request.getSession().getAttribute(CommonConstants.SESSION_KEY_USER)) {
            logger.warn("当前操作" + methodName + "用户未登录,ip=" + request.getRemoteAddr());
            response.getWriter().write(JsonConvertor.convertFailResult(ErrorCodeEnum.NOT_LOGIN).toString()); // 返回错误信息
            return false;
        }
        return true;
    }
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response,
                           Object handler, ModelAndView modelAndView) throws Exception {
    }
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
                                Object handler, Exception ex) throws Exception {
    }
}

Spring AOP 方式自定义注解

1
2
3
4
5
6
7
8
<dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
</dependency>
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AnalysisActuator {
    String note() default "";
}

@Aspect
@Component
public class AnalysisActuatorAspect {
    final static Logger log = LoggerFactory.getLogger(AnalysisActuatorAspect.class);

    ThreadLocal<Long> beginTime = new ThreadLocal<>();

    @Pointcut("@annotation(analysisActuator)")
    public void serviceStatistics(AnalysisActuator analysisActuator) {
    }

    @Before("serviceStatistics(analysisActuator)")
    public void doBefore(JoinPoint joinPoint, AnalysisActuator analysisActuator) {
        // 记录请求到达时间
        beginTime.set(System.currentTimeMillis());
        log.info("note:{}", analysisActuator.note());
    }

    @After("serviceStatistics(analysisActuator)")
    public void doAfter(AnalysisActuator analysisActuator) {
        log.info("statistic time:{}, note:{}", System.currentTimeMillis() - beginTime.get(), analysisActuator.note());
    }
}
1
2
3
4
5
6
7
8
@Service
public class HelloWorldServiceImpl implements IHelloWorldService {

    @AnalysisActuator(note = "获取聊天信息方法")
    public String getHelloMessage(String name) {
        return "Hello " + Optional.ofNullable(name).orElse("World!");
    }
}