一、什么是注解
java中,注解分两种,元注解和自定义注解。
我们常用的一些注解,如:@Autowired、@Override等都是自定义注解。
二、java的元注解
可以理解为描述注解的注解,除了这几个元注解,所有注解都是自定义注解。
- @Document:表示是否将注解信息添加在java文档中
-
@Target:表示注解用于什么地方。
- ElementType.CONSTRUCTOR: 用于描述构造器
- ElementType.FIELD: 成员变量、对象、属性(包括enum实例)
- ElementType.LOCAL_VARIABLE: 用于描述局部变量
- ElementType.METHOD: 用于描述方法
- ElementType.PACKAGE: 用于描述包
- ElementType.PARAMETER: 用于描述参数
- ElementType.TYPE: 用于描述类、接口(包括注解类型) 或enum声明
-
@Retention:定义该注解的生命周期
- RetentionPolicy.SOURCE:在编译阶段丢弃。不写入字节码。如:@Override,@SuppressWarnings都属于这类注解。
- RetentionPolicy.CLASS:在类加载的时候丢弃。在字节码文件的处理中有用。注解默认使用这种方式
- RetentionPolicy.RUNTIME:始终不会丢弃,运行期也保留该注解,因此可以使用反射机制读取该注解信息。
-
@Inherited:定义该注释和子类的关系
- 是一个标记注解,阐述了某个被标注的类型是被继承的。如果一个使用了@Inherited修饰的annotation类型被用于一个class,则这个annotation将被用于该class的子类。
三、自定义注解
自定义注解编写规则:
- Annotation型定义为@interface,所有的Annotation会自动继承java.lang.Annotation这一接口,并且不能再去继承别的类或是接口。
- 参数成员只能用public或默认(default)这两个反问权修饰
- 参数成员只能用八种基本数据类型和String、Enum、Class、annotations等数据类型,以及这些类型的数组。
- 要获取类方法和字段的注解信息,必须通过java的反射技术来获取Annotation对象,除此之外没有别的获取注解对象的方法
四、注解实现:记录访问日志
4.1 pom文件
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.9</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>30.1-jre</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
4.2 自定义一个注解
/**
* @Author: KD
* @Date: 2021/1/1 1:02 下午
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface TheLog {
/**
* 业务对象名称
*
* @return
*/
public String name();
/**
* 描述了如何获取id的表达式
*
* @return
*/
public String idExpression();
}
这个类中定义了注解的两个属性,第一个是name,第二个是一个Spel表达式。
4.3 切面
/**
* @Author: KD
* @Date: 2021/1/1 1:02 下午
*/
@Aspect
@Component
public class TheAspect {
private static final Logger LOGGER = LoggerFactory.getLogger(TheAspect.class);
@Autowired
HttpServletRequest request;
@Around("@annotation(com.example.zhujie.anno.TheLog)")//对标注了TheLog注解的方法设置切面
public Object log(ProceedingJoinPoint pjp) throws Exception {
Method method = ((MethodSignature)pjp.getSignature()).getMethod();
TheLog theLog = method.getAnnotation(TheLog.class);
Object response = null;
try {
// 目标方法执行
response = pjp.proceed();
} catch (Throwable throwable) {
throw new Exception(throwable);
}
if (StringUtils.isNotEmpty(theLog.idExpression())) {
SpelExpressionParser parser = new SpelExpressionParser();
Expression expression = parser.parseExpression(theLog.idExpression());
EvaluationContext context = new StandardEvaluationContext();
// 获取参数值
Object[] args = pjp.getArgs();
// 获取运行时参数的名称
LocalVariableTableParameterNameDiscoverer discoverer
= new LocalVariableTableParameterNameDiscoverer();
String[] parameterNames = discoverer.getParameterNames(method);
// 将参数绑定到context中
if (parameterNames != null) {
for (int i = 0; i < parameterNames.length; i++) {
context.setVariable(parameterNames[i], args[i]);
}
}
// 将方法的resp当做变量放到context中,变量名称为该类名转化为小写字母开头的驼峰形式
if (response != null) {
context.setVariable(
CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_CAMEL, response.getClass().getSimpleName()),
response);
}
// 解析表达式,获取结果
String itemId = String.valueOf(expression.getValue(context));
// 执行日志记录
handle(theLog.name(), itemId);
}
return response;
}
private void handle(String name, String theId) {
// 通过日志打印输出
LOGGER.info("theType = " + name + ",theId = " + theId);
}
}
这里主要是通过aspectj中的类库来实现反射,获取注解属性,然后解析Spel表达式,最后在handle方法中实现日志的打印输出。
4.4 使用注解
/**
* @Author: KD
* @Date: 2021/1/1 12:41 下午
*/
@Controller
public class TestController {
@GetMapping("test")
@ResponseBody
@TheLog(name="test1",idExpression="#id")
public String test(String id){
return id;
}
}
其中idExpression是Spel表达式,"#id"表示获取方法中名为id的局部变量。
访问url:
localhost:8080/test?id=123123
日志打印如下:
2021-01-01 13:43:38.157 INFO 52624 --- [nio-8080-exec-1] com.example.zhujie.anno.TheAspect : theType = test1,theId = 123123