Annotation(注解)是JDK1.5及以后版本引入的。它可以创建文档,跟踪代码中的依赖性,甚至执行基本编译时检查。注解是以 @注解名称 在代码中存在的,根据注解参数的数量,可以将注解分为三类:标记注解、单值注解、完整注解三类。他们都不会直接影响到程序的语义,只是作为注解(标识)存在,我们可以通过反射机制编程实现对这些元数据(元数据:用来描述数据的数据)的访问。另外,你可以在编译时选择代码里的注解是否只存在于源代码级,或者它也能在class文件、运行时中出现(SOURCE/CLASS/RUNTIME)。
元注解(定义自己自定义的注解):java.lang.annotation中提供了元注解,可以使用这些注解来定义自己的注解。主要使用的是Target、Retention注解,和ElementType、RetentionPolicy枚举类。
注解处理类:既然上面定义了注解,那么就一定有办法拿到我们定义的注解。
java.lang.reflect.AnnotationElement接口则提供了该功能,注解的处理方式是通过java的反射来进行处理的,如下,反射的相关类都实现了AnnotationElement接口。
因此,只要我们通过反射拿到Class、Method、Field类,就能通过getAnnotation(Class)拿到我们想要的注解并取值。
Target:描述了注解修饰的对象范围,取值在java.lang.annotation.ElementType定义,常用的包括:
Retention:表示注解保留时间的长短,取值在java.lang.annotation.RetentionPolicy中,取值为:
只有定义为RetentionPolicy.RUNTIME时,我们才能通过注解反射获取到注解。所以,假设我们要自定义一个注解,它用在字段上,并且可以通过反射获取到,功能是用来描述字段的长度和作用。
先定义一个注解:
/**
* 自定义注解
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyField {
String description() default "这是一个注解";
int length();
}
通过反射获取注解信息:
public class ApplicationTest {
@MyField(description = "第一个注解", length = 12)
private String username;
@Test
public void testMyField(){
// 获取类模板
Class applicationTestClass = ApplicationTest.class;
// 获取所有字段
Field[] declaredFields = applicationTestClass.getDeclaredFields();
for (Field f : declaredFields){
// 判断这个字段是否有MyField注解
if(f.isAnnotationPresent(MyField.class)){
MyField annotation = f.getAnnotation(MyField.class);
System.out.println("字段:[" + f.getName() + "],描述:[" + annotation.description() + "],长度:[" + annotation.length() +"]");
}
}
}
}
自定义注解+拦截器 实现登录校验
我们使用SpringBoot拦截器实现这样一个功能,如果方法上加了@LoginRequired注解,则提示用户该接口需要登录才能访问,如果方法上没有该注解,则不需要提示。
1、定义一个LoginRequired自定义注解
/**
* 自定义注解:方法上添加了此注解,则提示登录后才可访问
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LoginRequired {
}
2、写俩个简单的接口,访问loginA和loginB资源
/**
* 测试 自定义注解+拦截器 实现登录校验 访问层
*/
@RestController
@RequestMapping("/method")
public class TestMethodController {
@GetMapping("/A")
public String loginA(){
return "登录A";
}
@GetMapping("/B")
@LoginRequired
public String loginB(){
return "登录B,需要登录校验";
}
}
3、实现Spring框架提供的HandlerInterceptor类,自定义拦截器
/**
* 实现Spring的HandlerInterceptor拦截器
*/
public class TestMethodInterceptor implements HandlerInterceptor {
// 前置处理
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("进入拦截器了...");
HandlerMethod handlerMethod = (HandlerMethod) handler;
// 获取请求方法上的@LoginRequired注解,判断是否存在
LoginRequired loginRequired = handlerMethod.getMethod().getAnnotation(LoginRequired.class);
if(loginRequired == null){
return true;
}
// 有@LoginRequired注解则表示需要登录,提示用户登录
response.setContentType("application/json; charset=utf-8");
response.getWriter().println("请先登录 才可以访问此接口 !");
return false;
}
// 后置处理
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
}
// 完成时处理
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
}
}
4、实现Spring提供的WebMvcConfigurer,创建配置类,把自定义的拦截器添加到拦截器链中
/**
* 实现spring类WebMvcConfigurer
* 创建配置类把拦截器添加到拦截器链中
*/
@Configuration
public class InterceptorTrainConfigurer implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new TestMethodInterceptor()).addPathPatterns("/**");
}
}
自定义注解+AOP 实现日志打印
1、先导入切面需要的依赖包
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
<version>3.0.4.RELEASE</version>
</dependency>
2、自定义一个注解(作为标识)
/**
* 自定义注解: AOP实现日志打印
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyLog {
}
3、定义一个切面类,实现对自定义注解的切入
/**
* 定义一个切面类
*/
@Aspect // 1、表示此类是一个切面类
@Component
public class MyLogAspect {
// 2、@Pointcut表示这是一个切点,@annotation表示这个切点切到一个注解上,后面带该注解的全类名
// 切面最主要的解释切点,所有的故事都是围绕切点展开的
// logPointCut代表切点名称
@Pointcut("@annotation(com.example.springbootdome.anntation.MyLog)")
public void logPointCut(){}
// 3、环绕通知
@Around("logPointCut()")
public Object logAround(ProceedingJoinPoint joinPoint){ // 参数:连接点
// 获取方法名称
String name = joinPoint.getSignature().getName();
// 获取入参
Object[] args = joinPoint.getArgs();
StringBuilder sb = new StringBuilder();
for(Object o : args){
sb.append(o + "; ");
}
System.out.println("进入[" + name + "]方法,参数为:" + sb.toString());
// 继续执行方法
try {
return joinPoint.proceed();
} catch (Throwable e) {
e.printStackTrace();
}
System.out.println(name + "方法执行结束");
return null;
}
}
4、给相应方法添加注解
@GetMapping("/C")
@MyLog
public String loginC(String arg){
return "登录C" + arg;
}