标注在注解上的常用注解
最重要的两个注解:@Retention与@Target, 一般自定义注解上都需要加上这两个注解
- @Retention:用来指定注解的生命周期,可以指定的生命周期有:(RetentionPolicy枚举类的)SOURCE、CLASS、RUNTIME,如果不加此注解,默认生命周期为CLASS
- SOURCE:只在源代码上显示,编译后不保存。
- CLASS:编译后会留在class文件中,但运行程序时JVM不会保留该注解。
- RUNTIME:编译后留在class文件中,且运行程序时JVM会保留该注解。(当需要使用反射来获得类上的注解时,该注解的生命周期必须是RUNTIME)
- @Retention注解示例使用:
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
String value();
}
-
@Target:用来指定该注解可以标注在哪些地方:
- 用例:
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface MyAnnotation {
String value();
}
- ElementType枚举类的含义解析:
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解析时,会被保留下来
- @Inherited:表示修饰的注解将具有继承性(也就是子类可以继承父类上被标记了@Inherited注解的注解)
自定义注解的用途
创建了自定义注解后,标注在对应的地方,可以通过反射的方式查看注解,在程序中判断是否有指定注解,根据结果,对方法进行不同的操作(如通过拦截器Interceptor,对有注解/没注解的方法等进行前置处理)
用途一 作为AOP的切入点
比如想要在一些相似的方法的切面上插入一个固定方法(如想要在固定位置插入日志记录等),可以通过给那些方法上加一个辅助的自定义注解,然后通过Spring的AOP功能,完成切面编程。
例子
1、首先创建一个自定义注解
(这里的注解,如果只是用于作为AOP切入点,Retention生命周期为CLASS或RUNTIME都可以,但是不可以是SOURCE的)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface AOPAnnotation {
}
2、编写基于@Aspect注解的切面类(切面类是我自己的说法)
@Aspect // 开启切面功能
@Component // 切面类需要加入IOC容器
public class MyAspect {
// 先给出切点 也可以不事先给出 在后面直接用表达式指定切点
// 切点就是标注了@AOPAnnotation注解的方法
@Pointcut("@annotation(org.fall.springboot.annotation.AOPAnnotation)")
public void pointcut() {
}
// 后置通知
@AfterReturning("pointcut()")
public void doAfterReturn() {
System.out.println("在标注了@AOPAnnotation的方法执行完成后执行... ...");
}
// 前置通知
@Before("pointcut()")
public void doBefore() {
System.out.println("在标注了@AOPAnnotation的方法执行之前执行... ...");
}
}
3、编写标注@AOPAnnotation的方法,用来测试切面是否生效
@Service
public class MyService {
@AOPAnnotation // 标注
public void doService() {
System.out.println("service 方法执行... ...");
}
}
4、在测试类中测试
@SpringBootTest
class SpringBootAnnotationApplicationTests {
@Resource
private MyService myService;
@Test
void testAOP() {
myService.doService();
}
}
执行testAOP()
的结果:
可以发现标注了@AOPAnnotation注解后,可以基于该注解给对应方法加切面通知。
用途二 通过反射利用注解作为拦截器的标识
除了将自定义注解作为AOP的切点之外,还可以利用注解与拦截器结合,完成一些特定的功能(如最常用的利用注解进行权限认证)
例子
1、先创建一个自定义注解
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface AuthAnnotation {
}
2、编写拦截器类
public class AuthInterceptor implements HandlerInterceptor {
/**
* preHandle 在请求到达拦截器前触发
* 这里模拟:
* 在请求到达时,只放行未标注@AuthAnnotation注解的方法(返回true)
* 而不放行标注了@AuthAnnotation注解的方法(返回false)
* 如果需要更多的功能可以自行添加
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 先判断当前传入的是不是HandlerMethod的实例 防止类型转换的错误
if (!(handler instanceof HandlerMethod)) {
return true;
}
HandlerMethod handlerMethod = (HandlerMethod) handler;
// 这里先判断类上有没有@AuthAnnotation注解
AuthAnnotation authAnnotation = handlerMethod.getBean().getClass().getAnnotation(AuthAnnotation.class);
Method method = null;
// 如果类上没有@AuthAnnotation注解 再判断对应的方法上有没有该注解
if (authAnnotation == null) {
method = handlerMethod.getMethod();
authAnnotation = method.getAnnotation(AuthAnnotation.class);
}
if (authAnnotation == null) {
// 方法上没有注解时的操作
System.out.println("方法 " + method.getName() + " 上未标注@AuthAnnotation 直接放行");
return true;
} else {
// 方法上有注解时的操作
System.out.println("方法 "+ method.getName() +" 上标注了@AuthAnnotation 不允许放行");
return false;
}
}
// postHandle 在响应到达拦截器前触发
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
// 可以自行编写操作
}
// afterCompletion 在页面渲染完毕后触发
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// 可以自行编写操作
}
}
3、将拦截器注册入容器
@Configuration
public class MyWebMVCConfig implements WebMvcConfigurer {
// 重写WebMvcCOnfigurer的addInterceptors方法
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 注册AuthInterceptor拦截器
registry.addInterceptor(new AuthInterceptor());
}
}
4、编写Controller层的方法测试注解与拦截器是否生效了
@RestController
public class MyController {
// 该方法标注了@AuthAnnotation注解
@AuthAnnotation
@GetMapping("/with")
public String doMethodWithAuthAnnotation() {
System.out.println("标注了@AuthAnnotation注解的方法执行");
return "doMethodWithAuthAnnotation";
}
// 该方法未标注@AuthAnnotation注解
@GetMapping("/without")
public String doMethodWithoutAuthAnnotation() {
System.out.println("未标注@AuthAnnotation注解的方法执行");
return "doMethodWithoutAuthAnnotation";
}
}
5、此时启动项目,分别测试两个controller方法
- without:
- 浏览器访问:
- 后台输出:
- 浏览器访问:
- with:
- 浏览器访问:
- 后台输出:
可以发现未标注@AuthAnnotation的handler方法在被浏览器访问时,拦截器检测到并放行,执行了内部的代码;而标注了@AuthAnnotation的handler方法被拦截器检测到后,发现有指定注解,被拦截,内部代码没有执行,浏览器也没有输出任何文字。
- 浏览器访问: