前言
日常开发中用到了各式各样的注解,常用的注解@Override、@param、@Autowired、@Service等等,这些都是JDK或者Spring这类框架自带。在类,方法,变量,参数,包都可以用注解来注释。很多小伙伴可能还停留在使用层面,知道怎么用,但并不知道实现原理,更没亲自写过自定义注解运用在实际项目中解决问题。
接下来聊聊注解的基础,再聊聊自定义注解在实际项目中的使用。
注解作用
1、生成文档,早期最常见的@return,@param
2、在编译时进行检查,例如@Override,检查是否重写父类
3、简化配置文件,使得代码更清晰
什么是内置注解,元注解,自定义注解?
内置注解
@Override:作用在方法上,声明重写父类的方法
@Deprecated:作用方法,属性,或者类上,标识已过时
@SuppressWarings:用于抑制编译器警告,告知编译器,忽略它们产生了特殊警告
@SafeVarargs:是jdk1.7引入的注解,作用抑制在使用泛型和可变参数搭配使用产生的编译器告警
元注解:
元注解是由JDK5.0开始提供的,不能更改,用于定义其他注解,也就是对我们自定义注解进行定义
@Target:声明注解的作用范围可以是类,方法,方法参数变量等,也可以通过枚举类ElementType表达作用类型(可以查看源码)
@Retention:声明注解保留时长的作用域,可以理解为运行环境,
SOURCE(在源文件中有效)
CLASS(源文件编译成Class类文件中有效)
RUNTIME(在运行时有效)
@Documented:文档化注解,作用可以被javadoc此类工具文档化
@Inherited:声明此类能否被继承
自定义注解
/**
* 元注解
* public @interface 注解名称 {
* 类型 属性名() default 默认值
* }
*/
//用来声明自定义注解作用范围,变量、方法、类、包...
@Target({ElementType.METHOD, ElementType.TYPE})
//声明注解在运行时有效
@Retention(RetentionPolicy.RUNTIME)
//文档化
@Documented
//此类能否被继承
@Inherited
public @interface TestAnnotation {
String value() default "";
}
自定义注解的简单使用
/**
* 定义用户注解
*/
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface UserAnnontion {
String name();
int age() default 18;
String[] familyMembers() default {};
}
/**
* 方法注解
*/
@User(name="阿杰",age = 18,familyMembers = {"xxx","xxx","xxx"})
public void getUser() {
}
通过反射获取注解信息
public class TestAnnotationReflex {
public static void main(String[] args) {
try {
Class stuClass = Class.forName("com.example.demo.User");
Method method = stuClass.getMethod("getUser");
if(method.isAnnotationPresent(UserAnnontion.class)){
UserAnnontion userAnnontion = method.getAnnotation(UserAnnontion.class);
System.out.println("name: " + userAnnontion.name()
+ ", age: " + userAnnontion.age() + ", familyMembers: " + userAnnontion.familyMembers()[0]);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
以上的只是基础的知识,可能很多人还没搞明白自定义注解到底有什么作用,好像只是简单的注释说明,或者是通过反射获取注解信息。
接下来,使用自定义注解结合Spring AOP来讲解一下平时工作中使用的场景,Spring AOP常用于拦截器,事务,日志,权限,AOP就不再讲解了,举个例子讲解一下日志记录。在实际项目中,在调试或者排查异常信息时,日志是否记录着请求入参信息,是否都是使用log.info(“请求参数”)这样的写法记录日志,如果每次都需要这样手写,很容易遗漏,并且重复代码也比较多,在这种场景下,可以做一个统一日志处理的方案,通过使用自定义注解和切面来实现这个功能。
自定义注解+切面实现统一日志处理
自定义日志注解
/**
* 自定义操作日志注解
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface OptLog {
/**
* 业务
* @return
*/
String business();
/**
* 操作类型,增删改查
* @return
*/
OptType optType();
}
声明日志切面组件
import com.alibaba.fastjson.JSONObject;
import com.example.demo.annotation.OptLog;
import com.example.demo.annotation.OptType;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
@Aspect
@Component
public class OptLogAspect {
private static final Logger LOG = LoggerFactory.getLogger(OptLogAspect.class);
/**
* 声明切入点,凡是使用该注解都经过拦截
*/
@Pointcut("@annotation(com.example.demo.annotation.OptLog)")
public void OptLog() {
}
@Before("OptLog()")
public void doOptLogBefore(JoinPoint proceedingJoinPoint) {
LOG.info("前置通知, 在方法执行之前执行...");
}
@After("OptLog()")
public void doOptLogAfter(JoinPoint proceedingJoinPoint) {
LOG.info("后置通知, 在方法执行之后执行...");
}
@AfterReturning("OptLog()")
public void doOptLogAfterReturning(JoinPoint proceedingJoinPoint) {
LOG.info("返回通知, 在方法返回结果之后执行...");
}
@AfterThrowing("OptLog()")
public void doOptLogAfterThrowing(JoinPoint proceedingJoinPoint) {
LOG.info("异常通知, 在方法抛出异常之后执行...");
}
/**
* 设置环绕通知,围绕着方法执行
*
* @param proceedingJoinPoint
* @return
*/
@Around("OptLog()")
public Object optLogAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
Method method = ((MethodSignature) proceedingJoinPoint.getSignature()).getMethod();
if (method == null) {
return null;
}
// 获取方法名称
String methodName = proceedingJoinPoint.getSignature().getName();
LocalVariableTableParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer();
// 请求参数名称
String[] parameterNames = discoverer.getParameterNames(method);
// 请求参数值
Object[] paramValues = proceedingJoinPoint.getArgs();
OptLog optLog = method.getAnnotation(OptLog.class);
this.handle(optLog.optType(), optLog.business(), methodName, parameterNames, paramValues);
return proceedingJoinPoint.proceed();
}
/**
* 日志处理
*
* @param optType
* @param business
* @param methodName
* @param parameterNames
* @param paramValues
*/
public void handle(OptType optType, String business, String methodName,
String[] parameterNames, Object[] paramValues) {
JSONObject jsonObject = new JSONObject();
if (parameterNames != null && parameterNames.length > 0) {
for (int i = 0; i < parameterNames.length; i++) {
jsonObject.put(parameterNames[i], paramValues[i]);
}
}
LOG.info("optType:" + optType + ",business:" + business + ", methodName:" + methodName + ", params:" + jsonObject);
}
}
控制层运行结果
@RestController
@RequestMapping("/user/")
public class UserController {
@OptLog(optType = OptType.CREATE,business = "用户信息")
@RequestMapping("create")
public String createUser(String userName,int age,String address) {
System.out.println("方法执行中...");
return "success";
}
}
运行结果
15:32:49.494 [http-nio-8080-exec-2] INFO c.e.d.a.OptLogAspect - [handle,91] - optType:CREATE,business:用户信息, methodName:createUser, params:{"address":"广州市","userName":"阿杰","age":18}
15:32:49.494 [http-nio-8080-exec-2] INFO c.e.d.a.OptLogAspect - [doOptLogBefore,32] - 前置通知, 在方法执行之前执行...
方法执行中...
15:32:49.495 [http-nio-8080-exec-2] INFO c.e.d.a.OptLogAspect - [doOptLogAfterReturning,42] - 返回通知, 在方法返回结果之后执行...
15:32:49.495 [http-nio-8080-exec-2] INFO c.e.d.a.OptLogAspect - [doOptLogAfter,37] - 后置通知, 在方法执行之后执行...