Java 中的注解 Annotation

前言:

在看到网络上对注释的解释之后,就觉得很专业,有所感悟,然后就想着记录下来。

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

注解(Annotation)是在 Java SE 5.0 版本中开始引入的概念,同 class 和 interface 一样,也属于一种类型。就在最开始的时候我还认为注解的地位不高,但是最终发现不是这样子的。像 @Transaction、@Service、@RestController、@RequestMapping、@CrossOrigin 等等这些注解的使用频率在我后面的学习当中,越来越高。

为什么要使用注解呢?

让我们从另外一个问题说起。

“跨域” 这两个字就像一块狗皮膏药粘在每一个开发者的身上;而对于蚊子这样一个小菜鸡来说,更是如此。

跨域问题的出现,源于浏览器的同源策略—-限制一个源加载的脚本去访问另一个源的资源,可有效地隔离潜在的恶意文件,是一种重要的安全机制。

跨域问题的解决方案也有很多,比方说:

1)JSONP

2)Nginx 代理

3)“跨域资源共享”(Cross-origin resource sharing),简称 CORS,可以说是处理跨域问题的标准做法。

记得第一次遇到跨域问题的时候,我特意向学长请教了一个解决方案,他告诉我的答案如下:

第一步,在 web.xml 添加 filter。

1
2
3
4
5
6
7
8
<filter>
<filter-name>contextfilter</filter-name>
<filter-class>com.mosquito.filter.WebContextFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>contextfilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

第二步,实现 WebContextFilter 类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class WebContextFilter implements Filter {

@Override
public void destroy() {
}

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
httpServletResponse.setHeader("Access-Control-Allow-Origin", "*");
httpServletResponse.setHeader("Access-Control-Allow-Headers", "accept,content-type");
httpServletResponse.setHeader("Access-Control-Allow-Methods", "OPTIONS,GET,POST,DELETE,PUT");
chain.doFilter(request, httpServletResponse);

}

@Override
public void init(FilterConfig arg0) throws ServletException {
}

}

看到这样的解决方案,我其实蛮头痛的,因为这样的一个跨域问题,居然需要这么多的代码,头痛.jpg

我对这样的解决方案并不是很满意。于是决定好好的研究一下,然后花费了大概一天多的时间吧,我终于搞清楚了 “跨域” 问题,以及它的标准解决返港 CORS。并且找到了一个简介的解决方案----@CrossOrigin,只要在 Controller 类上加上这个注解,就可以轻松的解决跨域问题。

代码如下:

1
2
3
4
5
@RestController
@RequestMapping("course")
@CrossOrigin
public class CourseController {
}

如果没有找到 @CrossOrigin 这个注解,我估计就真的会按照学长的方案去解决这个跨域问题。但那样做的话,就感觉很繁琐、冗杂。

这也正是我第一个体会到的,为什么要使用注解的原因:它会让我们的代码看起来更加简洁、更有时代的进步感。

该如何定义注解呢?

注解需要通过 @interface 关键字(形式和接口非常的相似,只是前面多了一个 @ )进行定义。我们可以打开 @CrossOrigin 的源码来看一下。

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
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CrossOrigin {
/**
* List of allowed origins, e.g. {@code "http://domain1.com"}.
* <p>These values are placed in the {@code Access-Control-Allow-Origin}
* header of both the pre-flight response and the actual response.
* {@code "*"} means that all origins are allowed.
* <p>If undefined, all origins are allowed.
* @see #value
*/
@AliasFor("value")
String[] origins() default {};
/**
* List of request headers that can be used during the actual request.
* <p>This property controls the value of the pre-flight response's
* {@code Access-Control-Allow-Headers} header.
* {@code "*"} means that all headers requested by the client are allowed.
* <p>If undefined, all requested headers are allowed.
*/
String[] allowedHeaders() default {};
/**
* List of supported HTTP request methods, e.g.
* {@code "{RequestMethod.GET, RequestMethod.POST}"}.
* <p>Methods specified here override those specified via {@code RequestMapping}.
* <p>If undefined, methods defined by {@link RequestMapping} annotation
* are used.
*/
RequestMethod[] methods() default {};
}

从上面的代码可以看出来,“注解” 真的很 “注解”,除了注释多和 “元注解” 多之外,真没别的了。

“元注解”?什么是 “元注解” 呢?

“元注解” 是用来注解(动词)注解(名词)的注解(名词)。emmmmmm~ 这个说法我自己都很汗颜。。。。。。

而 @Target、@Retention、@Documented 就是所谓的元注解。

1)@Tartget

Target 是目标的意思,@Target 指定了注解运用的场景。

那么都有哪些场景值呢?

  • ElementType.ANNOTATION_TYPE:可以给注解进行注解
  • ElementType.CONSTRUCTOR:可以给构造方法进行注解
  • ElementType.FIELD:可以给字段进行注解
  • ElementType.LOCAL_VARIABLE:可以给局部变量进行注解
  • ElementType.METHOD:可以给方法进行注解
  • ElementType.PACKAGE:可以给包进行注解
  • ElementType.PARAMETER:可以给方法内的参数进行注解
  • ElementType.TYPE:可以给类型进行注解,比如类、接口和枚举

2)@Retention

Retention 这个单词的意思为保留期。也就是说,当 @Retention 应用到一个注解上的时候,它解释说明了这个注解的存活时间。来看它的取值范围。

  • RetentionPolicy.SOURCE:注解只在源码阶段保留,在编译器进行编译时它会将被丢弃忽视。
  • RetentionPolicy.CLASS:注解只被保留到编译进行的时候,并不会加载到 JVM 中。
  • RetentionPolicy.RUNTIME:注解可以保留到程序运行的时候,它会被加载进入到 JVM 中,所以在程序运行时可以获取到它们。

3)@Documented

Documented 就比较容易理解,它和文档有关。作用就是能够将注解中的元素包含到 Javadoc 中。

当我们了解了元注解的概念之后,再回头看 @CorssOrigin 的源码,就会觉得清晰了许多。

如果能够细致的读一读源码中的注释,就会看到 WebContextFilter 类中出现的关键字,比如 Access-Control-Allow-Origin、Access-Control-Allow-Headers、Access-Control-Methods。也就是说,当我们通过 @CrossOrigin 对 Controller 类注解后,Spring MVC 就能够在运行时对这个类自动加上解决跨域问题的过滤器。

注解可以反射吗?

注释是可以通过反射获取的。

1)可以通过 Class 对象的 isAnnotationPresent() 方法判断该类是否应用了某个指定的注解。

2)通过 getAnnotation() 方法来获取注解对象。

3)当获取到注解对象后,就可以获取使用注解时定义的属性值。

实例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@CrossOrigin(origins = "https://www.baidu.com", allowedHeaders = "accept,content-type", methods = { RequestMethod.GET, RequestMethod.POST })
public class TestController {
public static void main(String[] args) {
Class c = TestController.class;

if (c.isAnnotationPresent(CrossOrigin.class)) {
CrossOrigin crossOrigin = (CrossOrigin) c.getAnnotation(CrossOrigin.class);

System.out.println(Arrays.asList(crossOrigin.allowedHeaders()));
System.out.println(Arrays.asList(crossOrigin.methods()));
System.out.println(Arrays.asList(crossOrigin.origins()));
}

}
}

// 输出:[accept,content-type]
// [GET, POST]
// [https://www.baidu.com]

注解经常用在哪里呢?

1)@Transactional:Spring 为事务管理提供的功能支持。

2)@Service:Spring 在进行包扫描的时候,会自动将这个类注册到 Spring 容器中。

3)@RestController:是在 @ResponseBody 和 @Controller 的组合注解。

也就是说,下面这段代码与之后的一段代码等同:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@RestController
public class HelloController {

@RequestMapping(value="hello")
public String sayHello(){
return "hello";
}
}

@Controller
@ResponseBody
public class HelloController {

@RequestMapping(value="hello")
public String sayHello(){
return "hello";
}
}

4)@ResquestMapping:Spring Web 应用程序中常用的注解之一,将 HTTP 请求映射到 MVC 和 REST 控制器的处理方法上。

5)@Select:MyBatis 提供的查询语句注解。示例如下:

1
2
@Select("select * from city")
List<City> getCitys();

。。。。后面还有很多,就不一一举例了

最后,注解还有很多用处,主要有:

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