注解的介绍
http://note.youdao.com/noteshare?id=cec5c547392eae6304916c849f8ae2de
要用好注解,必须熟悉java 的反射机制,从上面的例子可以看出,注解的解析完全依赖于反射。
不要滥用注解,平常我们编程过程很少接触和使用注解,只有做设计,且不想让设计有过多的配置时
注解的应用
一、参数校验
方式一:利用“注解”+“反射”校验参数
步骤大概为:
1、注解定义
2、提供静态工具类方法,该方法利用反射遍历一个实体对象的所有字段,判断字段是否使用了前面定义的注解,如果使用了注解,则取出注解的变量(也就是校验的规则,可以有多个,使用注解的时候传入的)和实体类该字段的值,然后校验,校验结果可以通过返回或者抛异常方式
3、需要校验参数的时候,显式调用工具类的静态方法,把要校验的实体对象作为参数传进去
方式二:通过ConstraintValidator,该方式简单易用
实际上是提供注解的一个实现类,通过该实现类的方法做校验,需要引入如下依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
或
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
参考资料:
https://www.jianshu.com/p/4688c55e0d64
该方式校验不通过会抛出异常BindException,我们需要在全局异常中来处理这个异常
@ControllerAdvice
public class ControllerExceptionHandler {
@ResponseBody
@ExceptionHandler(Exception.class)
public MyObject handleException(Exception e) {
log.error(e.getMessage(), e);
if (e instanceof BindException) {
BindException ex = (BindException) e;
List<ObjectError> allErrors = ex.getAllErrors();
ObjectError error = allErrors.get(0);
String defaultMessage = error.getDefaultMessage();
log.error(defaultMessage);
return myObject;
} else {
return myObject;
}
}
二、结合AOP
适用的场景有很多,如接口限流、响应时间统计、权限验证,嵌入通用逻辑等
AOP可以对某个包下所有/指定的类中的全部/指定方法切入,切入点可以指定包名、类名或方法名,如:
@Pointcut("execution(public * com.xxx.xxxx.controller..*.*(..))")
对controller包下所有的类(包括子包)的全部方法做切入,如果我们只想对某个类切入,则进一步指定类名:
@Pointcut("execution(public * com.xxx.xxxx.controller.MyController.*(..))")
如果只想对其中的某个方法切入,则再进一步指定方法名:
@Pointcut("execution(public * com.xxx.xxxx.controller.MyController.methodName(..))")
这种方式非常适用于对整个类的方法做切入,但有以下两个不足:
1、如果被切入的方法比较分散,则需要为每个方法指定一个切入点,当方法的路径(包类名或方法名)发生改变时,要同步修改切入点,维护困难
2、只能对被外部调用的方法做切入,类内部之间调用的方法无法切入
如,假设类A有两个方法method1和method2,类B调用了类A的method1方法,method1间接调用了method2方法,可以对method1切入,但无法对method2切入
可以结合注解灵活对某个方法切入
首先定义注解:
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface InRange {
String[] ranges() default{};//取值范围
}
编写该注解的AOP
@Component
@Aspect
public class InRangeAnnotationAop {
@Pointcut("@annotation(com.wdy.annotation.InRange)")
public void rangeCutPoint(){
//定义切点
}
@Before("rangeCutPoint()")
public void before(JoinPoint joinPoint){
//通过joinPoint获取切入的方法对象
Signature signature = joinPoint.getSignature();
MethodSignature methodSignature = (MethodSignature)signature;
Method method = methodSignature.getMethod();
//通过joinPoint获取切入的方法入参
Object[] objects = joinPoint.getArgs();
String param1 = objects[0].toString();
//通过方法获取注解对象
InRange inRange = method.getDeclaredAnnotation(InRange.class);
//通过注解对象获取注解的成员变量,如果没有给注解传入值的话,就不需要这两步了
String[] ranges = inRange.ranges();
//TODO 判断param1是否在ranges里面,不在的话抛出异常
}
@AfterReturning(returning = "ret" , pointcut = "rangeCutPoint()")
public void after(JoinPoint joinPoint,Object ret){
//
}
}
则可以在需要对方法第一个参数做取值范围校验的方法前面使用注解@InRange了,还可以用更多应用场景,不再一一举例了