文章目录
1. 自定义注解【Annotation】
1.1. 元注解
1.1.1. @Retention
1.1.2. @Target
1.1.3. @Document
1.1.4. @Inherited
1.2. 关于注解的反射方法
1.3. 五种通知
1.4. JoinPoint 对象
1.5. 实战
1.5.1. 日志
1.5.2. 性能监控
1.5.3. 输出错误日志到文件中
1.6. 参考文章
自定义注解【Annotation】
元注解
@Retention
- 表示需要在什么级别保存该注解信息 。分为如下三类:
@Retention(RetentionPolicy.SOURCE)
:注解仅存在于源码中,在class字节码文件中不包含@Retention(RetentionPolicy.CLASS)
:默认的保留策略,注解会在class字节码文件中存在,但运行时无法获得@Retention(RetentionPolicy.RUNTIME)
: 注解会在class字节码文件中存在,在运行时可以通过反射获取到
@Target
- 注解的作用目标
取值 | 作用域 |
---|---|
@Target(ElementType.TYPE) | 接口、类、枚举、注解 |
@Target(ElementType.FIELD) | 字段、枚举的常量 |
@Target(ElementType.METHOD) | 方法 |
@Target(ElementType.PARAMETER) | 方法参数 |
@Target(ElementType.CONSTRUCTOR) | 构造函数 |
@Target(ElementType.LOCAL_VARIABLE) | 局部变量 |
@Target(ElementType.ANNOTATION_TYPE) | 注解 |
@Target(ElementType.PACKAGE) | 包 |
@Document
- 注解包含在javadoc中
@Inherited
- 注解可以被继承
关于注解的反射方法
代码语言:javascript复制// 获取某个类型的注解
public <A extends Annotation> A getAnnotation(Class<A> annotationClass);
// 获取所有注解(包括父类中被Inherited修饰的注解)
public Annotation[] getAnnotations();
// 获取声明的注解(但是不包括父类中被Inherited修饰的注解)
public Annotation[] getDeclaredAnnotations();
// 判断某个对象上是否被某个注解进行标注
public boolean isAnnotationPresent(Class<? extends Annotation> annotationClass)
// 获取某个类声明的所有字段
public Field[] getDeclaredFields() throws SecurityException;
// 获取某个方法
public Method getMethod(String name, Class<?>... parameterTypes);
五种通知
@Before
:前置通知,在调用目标方法之前执行通知定义的任务@After
:后置通知,在目标方法执行结束后,无论执行结果如何(异常或者正常执行)都执行通知定义的任务@After-returning
:后置通知,在目标方法执行结束后,如果执行成功(没有异常),则执行通知定义的任务@After-throwing
:异常通知,如果目标方法执行过程中抛出异常,则执行通知定义的任务@Around
:环绕通知,在目标方法执行前和执行后,都需要执行通知定义的任务
JoinPoint 对象
方法名 | 功能 |
---|---|
Signature getSignature(); | 获取封装了署名信息的对象,在该对象中可以获取到目标方法名,所属类的Class等信息 |
Object[] getArgs(); | 获取传入目标方法的参数对象 |
Object getTarget(); | 获取被代理的对象 |
Object getThis(); | 获取代理对象 |
实战
- 使用springBoot,需要 添加aop相关的依赖,如下:
<!-- springBoot的aop功能启动器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
日志
- 实际的生产环境中会对每个都记录日志,比如xxx用户执行了xxx操作,这些日志如果都使用一个方法的话,未免太不雅,此时我们可以定义一个注解,使用spring中的aop,在方法执行成功之后记录日志信息,步骤如下:
- 定义一个注解,使用
@Interface
import java.lang.annotation.*;
/**
* 定义日志的注解,作用在方法上
* @author 陈加兵
* @since 2018年11月3日 上午11:53:59
*/
@Target({ElementType.PARAMETER, ElementType.METHOD}) //作用在方法上面
@Retention(RetentionPolicy.RUNTIME) //程序运行
@Documented
public @interface InsertLog {
String name() default ""; //用户名
String operation() default ""; //操作
}
- 定义一个注解的实现类,使用aop实现该注解
@Component //注入
@Aspect //切面
public class InsertLogAnnotation {
@Resource
private LogService logService; //日志的service,用来记录日志
/**
* 1、@pointCut:定义切入点,其中支持多种表达式来匹配切入点,这里的annotation是用来匹配注解的
* 2、@annotation的参数必须和这个方法的参数字段相同,因为这里表示的扫描的哪个注解
* 3、这个切入点的 意思:只要被`@InsertLog`这个注解标注的都会被扫描到成为切入点
* @param log 注解的对象
*/
@Pointcut("@annotation(log)")
public void insertLog(InsertLog log){}
/**
* `@AfterReturning`:定义后置通知,只有程序执行成功才会调用该注解,用来添加操作的日志
* `insertLog(log)`: 这里的log一定要和上面定义切入点(@Pointcut)中的参数字段一样
* @param point JoinPoint对象,可以获取一些切面信息,比如调用的类,调用的方法名称
* @param log 该注解的对象,可以获取注解中参数的内容
* @throws Exception
*/
@AfterReturning("insertLog(log)")
public void SystemLog(JoinPoint point,InsertLog log) throws Exception{
//获取注解中的参数内容
String name=log.name(); //姓名
String operation=log.operation(); //操作
Log log2=new Log();
log2.setName(name);
log2.setOperation(operation);
logService.addLog(log2); //添加到日志中
}
- 此时就已经完成了,我们只需要将该注解添加到需要记录日志的方法上即可,如下:
@InsertLog(name="陈加兵",operation="删除用户") //使用日志注解,在程序执行成功之后记录日志
public Object deleteUserById(Integer userId) {
System.out.println("删除成功");
return null;
}
- 完整的项目截图如下:
性能监控
- 可以定义一个注解实现性能监控,设置一个环绕通知即可,在程序执行开始和结束之后统计时间即可
- 定义一个注解,如下:
import java.lang.annotation.*;
/**
* 性能监控的注解
* @author 陈加兵
* @since 2018年11月3日 下午1:08:30
*/
@Target({ElementType.PARAMETER, ElementType.METHOD}) //作用在方法上面
@Retention(RetentionPolicy.RUNTIME) //程序运行
@Documented
public @interface CapabilityMonitor {
}
- 定义注解的实现类,使用切面的环绕通知
/**
* 性能监控注解的实现类
* @author 陈加兵
* @since 2018年11月3日 下午1:09:40
*/
@Aspect //切面
@Component //注入
public class CapabilityMonitorAnnotationImpl {
private Logger logger=LoggerFactory.getLogger(CapabilityMonitorAnnotationImpl.class); //selfj的日志信息
/**
* 定义切入点,只要方法体上有这个注解
* @param capabilityMonitor 注解的对象
*/
@Pointcut("@annotation(capabilityMonitor)")
public void capabilityMonitor(CapabilityMonitor capabilityMonitor){}
/**
* 环绕通知,在方法执行之前和之后都执行
* capabilityMonitor(capabilityMonitor):这里的参数一定要和切入点(`@Pointcut("@annotation(capabilityMonitor)")`)的参数相同
* @param point
* @param capabilityMonitor 注解的对象
* @throws Throwable
*/
@Around("capabilityMonitor(capabilityMonitor)")
public void execute(ProceedingJoinPoint point,CapabilityMonitor capabilityMonitor) throws Throwable{
Long startTime=System.currentTimeMillis(); //开始时间
Object[] args=point.getArgs(); //获取目标方法执行的参数数组
Object returnValues=point.proceed(args); //执行目标方法
Long endTime=System.currentTimeMillis(); //结束时间
logger.info("程序执行的时间:" ((endTime-startTime)/1000.0)); //输出程序执行的时间,秒位单位
}
}
输出错误日志到文件中
- 定义日志注解,如下:
/**
* 输出日志信息到日志文件的注解
* @author 陈加兵
* @since 2018年11月7日 下午5:52:55
*/
@Target({ElementType.PARAMETER, ElementType.METHOD}) //作用在方法上面
@Retention(RetentionPolicy.RUNTIME) //程序运行
@Documented
public @interface PrintLog {
}
- 注解的实现类,结合aop实现
@Component
@Aspect
public class PrintLogAnnotationImpl {
/**
* 定义切入点,凡是方法体上标注@PrintLog这个注解都会被增强
* @param printLog
*/
@Pointcut("@annotation(printLog)")
public void printLog(PrintLog printLog){}
/**
* 在程序之后执行并且在出现异常的时候才会执行
* @param point JoinPoint对象,用于获取切入点的信息,比如被增加的类,被增强的方法名称、方法传入的参数等信息
* @param printLog 注解的接口信息,可以获取接口的信息
* @param message 异常信息
*/
@AfterThrowing(value="printLog(printLog)",throwing="message")
public void excute(JoinPoint point,PrintLog printLog,Throwable message){
Class targetCls=point.getTarget().getClass(); //获取目标类
Logger logger = LoggerFactory.getLogger(targetCls);
logger.error("异常信息:",message);
}
}
- 只需要在方法上标注这个注解,只要是遇到异常信息,就会自动写入日志文件中
参考文章
- aop的相关知识点
- spring aop