注解的使用真的很神奇,加一个注解就能实现想要的功能,很好奇,也想自己根据需要写一些自己实现的自定义注解。问题来了,自定义注解到底是什么?其实注解一点也不神奇,注解是一种能被添加到java源代码中的元数据,单独使用注解,就相当于在类、方法、参数和包上加上一个装饰,什么功能也没有,仅仅是一个标志,然后这个标志可以加上一些自己定义的参数。就像下面这样,创建一个@interface的注解,然后就可以使用这个注解了,加在我们需要装饰的方法上,但是什么功能也没有。
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface ValidateToken {
String value() default"";
}
我们上面的创建的注解ValidateToken 上面还有几个注解(@Target、@Retention、@Inherited、@Documented),这四个注解就是元注解,元注解的作用就是负责注解其他注解。Java5.0定义了4个标准的元注解类型,它们被用来提供对其它 注解类型作标志操作(可以理解为最小的注解,基础注解)
Target类型 | 描述 |
---|---|
ElementType.TYPE | 应用于类、接口(包括注解类型)、枚举 |
ElementType.FIELD | 应用于属性(包括枚举中的常量) |
ElementType.METHOD | 应用于方法 |
ElementType.PARAMETER | 应用于方法的形参 |
ElementType.CONSTRUCTOR | 应用于构造函数 |
ElementType.LOCAL_VARIABLE | 应用于局部变量 |
ElementType.ANNOTATION_TYPE | 应用于注解类型 |
ElementType.PACKAGE | 应用于包 |
备注:例如@Target(ElementType.METHOD),标志的注解使用在方法上,但是我们在这个注解标志在类上,就会报错
生命周期类型 | 描述 |
---|---|
RetentionPolicy.SOURCE | 编译时被丢弃,不包含在类文件中 |
RetentionPolicy.CLASS | JVM加载时被丢弃,包含在类文件中,默认值 |
RetentionPolicy.RUNTIME | 由JVM 加载,包含在类文件中,在运行时可以被获取到 |
@Inherited阐述了某个被标注的类型是被继承的。如果一个使用了@Inherited修饰的annotation类型被用于一个class,则这个annotation将被用于该class的子类。
AOP (Aspect Orient Programming),直译过来就是 面向切面编程。AOP 是一种编程思想,是面向对象编程(OOP)的一种补充。面向对象编程将程序抽象成各个层次的对象,而面向切面编程是将程序抽象成各个切面,是Spring的核心思想之一。
AOP 要达到的效果是,保证开发者不修改源代码的前提下,去为系统中的业务组件添加某种通用功能。AOP 的本质是由 AOP 框架修改业务组件的多个方法的源代码,按照 AOP 框架修改源代码的时机,可以将其分为两类:
我们知道,spring中的aop是通过动态代理实现的,那么他具体是如何实现的呢?spring通过一个切面类,在他的类上加入@Aspect注解,定义一个Pointcut方法,最后定义一系列的增强方法。这样就完成一个对象的切面操作。
那么思考一下,按照上述的基础,要实现我们的aop,大致有以下思路:
1.找到所有的切面类
2.解析出所有的advice并保存
3.创建一个动态代理类
4.调用被代理类的方法时,找到他的所有增强器,并增强当前的方法
bean表达式一般应用于类级别,实现粗粒度的切入点定义,案例分析:
within表达式应用于类级别,实现粗粒度的切入点表达式定义,案例分析:
execution表达式应用于方法级别,实现细粒度的切入点表达式定义,案例分析:
语法:execution(返回值类型 包名.类名.方法名(参数列表))。
@annotaion表达式应用于方法级别,实现细粒度的切入点表达式定义,案例分析
自定义注解使用范围:
上面总结的注解的定义,但是创建这样一个注解,仅仅是一个标志,装饰类、方法、属性的,并没有功能,要想实现功能,需要我们通过拦截器、AOP切面这些地方获取注解标志,然后实现我们的功能。一般我们可以通过注解来实现一些重复的逻辑,就像封装了的一个方法,可以用在一些权限校验、字段校验、字段属性注入、保存日志、缓存。
/**
* @Pointcut 注解通过切入点表达式定义切入点,例如
* bean表达式:bean(bean的名称)
*/
//@Pointcut("bean(sysUserServiceImpl)")
@Pointcut("@annotation(com.cy.pj.common.annotation.RequiredLog)")
public void doLogPointCut() {
//方法体不需要写任何内容
}
@Around("doLogPointCut()")
public Object around(ProceedingJoinPoint jp)throws Throwable{
long t1=System.currentTimeMillis();
log.info("start:{}",t1);//{}在这里表示占位符
try {
Object result=jp.proceed();//调用本类内部切入点对应其它通知或其它切面或目标方法。
long t2=System.currentTimeMillis();
log.info("end: {}",t2);
saveLog(jp,(t2-t1));//记录正常行为日志
return result;
}catch (Throwable e) {
log.error("error end: {}",e.getMessage());
//可以在此位置记录异常行为日志
//return null;
throw e;
}
}
private void saveLog(ProceedingJoinPoint jp,long time) throws NoSuchMethodException, SecurityException, JsonProcessingException {
//1.获取用户行为信息
//1.1获取目标方法对象
Method targetMethod=getTargetMethod(jp);
//1.2获取目标方法的方法名信息
String targetMethodName=
targetMethod.getDeclaringClass().getName()+"."+targetMethod.getName();
//1.3获取目标方法上的操作名
String operation = getOperation(targetMethod);
//1.4 目标方法参数(转换为字符串)
//String params=Arrays.toString(jp.getArgs());
String params=
//将参数对象转换为json格式字符串
new ObjectMapper().writeValueAsString(jp.getArgs());
//2.封装用户行为信息
SysLog log=new SysLog();
log.setIp(IPUtils.getIpAddr());
log.setUsername(ShiroUtils.getUsername());//后续做完登陆以后,为登陆用户名
log.setOperation(operation);
log.setMethod(targetMethodName);
log.setParams(params);
log.setTime(time);
log.setCreatedTime(new Date());
//3.保存用户行为信息
sysLogService.saveObject(log);
}
private Method getTargetMethod(ProceedingJoinPoint jp) throws NoSuchMethodException, SecurityException {
Class<?> targetCls=jp.getTarget().getClass();
MethodSignature ms=(MethodSignature)jp.getSignature();
//获取目标方法
Method targetMethod=//目标方法(类全名+方法名)
targetCls.getDeclaredMethod(ms.getName(),ms.getParameterTypes());
return targetMethod;
}
private String getOperation(Method targetMethod) {
String operation="operation";
RequiredLog requiredLog=
targetMethod.getAnnotation(RequiredLog.class);
if(requiredLog!=null) {
operation=requiredLog.value();
}
return operation;
}