注解提供了一种结构化的,并且具有类型检查能力的新途径,从而使得我们能够为代码加入元数据,而不会导致代码杂乱且难以阅读。使用注解能够帮助我们编写累赘的部署描述文件以及其他生成的文件。
一、注解定义
(和接口挺像)
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface MyAnnotation { public String info() default "MyAnnotation info"; //default后的是默认值 }
@Target用来定义注解可用在什么地方,可选值有(同时使用多各值用逗号分隔):
ElementType.Type 类型声明(类,接口,注解,枚举) ElementType.METHOD 方法声明; ElementType.FIELD 属性,包括枚举的值; ElementType.PARAMETER 参数(形参); ElementType.CONSTRUCTOR 构造器; ElementType.ANNOTATION_TYPE 注解定义; ElementType.PACKAGE 包; ElementType.LOCAL_VARIABLEnd;局部变量声明; ElementType.TYPE_PARAMETER; 类型参数声明;java 1.8引入 ElementType.TYPE_USE; 类型使用;java 1.8引入
@Retention 定义在哪一个级别可用,值有:
RetentionPolicy.SOURCE 注解只保留在源文件,当Java文件编译成class文件的时候,注解被遗弃
RetentionPolicy.CLASS 注解被保留到class文件,但jvm加载class文件时候被遗弃,这是默认的生命周期
RetentionPolicy.RUNTIME 注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在
1、没有元素的注解称为标记注解。
2、注解可使用的元素类型有:所有的基本类型和String, Class,enum, Annotation。不允许使用其他自定义类型或包装类型。
3、注解不能继承。
4、元注解,即java内置的用来创建注解的注解:
@Target 用来定义注解可用在什么地方
@Retention 定义在哪一个级别可用
@Documented 将此注解包含在javadoc中
@Inherited 允许子类继承父类的注解
同时Java还内置了三种标准注解
@Override 定义重写超类中的方法,如果拼写错了,或者方法签名对不上被覆盖的方法,编译器会报错
@Depreceted 弃用,如果使用了带有此注解的元素,编译器会发出警告;
@SuppressWarnings 关闭警告信息
二、注解处理器
大多数时候我们都需要定义自大的处理器来处理注解逻辑,如果注解没有相应的处理器,那么注解也就是个注释,没什么用。
主要利用反射机制,使用Class.getAnnotation(); Method.getAnnotation();等方法来获取对象上的注解,然后进行相应的操作;
如:
void myAnnotationHandler(Object o){ MyAnnotation anno = o.getClass().getAnnotation(MyAnnotation.class); if (anno != null) System.out.println(anno.info()); else System.out.println("this object has no MyAnnotation"); }
使用验证:
@MyAnnotation public static class Myclass{} public static void main(String[] args) { Myclass c = new Myclass(); myAnnotationHandler(c); myAnnotationHandler(1); }
结果:
MyAnnotation info
this object has no MyAnnotation
三、Spring注解处理器
现在更常用的Spring提供我们使用Aspect来实现AOP面向切面的注解处理器
@Aspect @Component public class AopAnnotationHandler { //要处理的切面,此处为要处理的注解MyAnnotation @Pointcut("@annotation(mypackage.springboot.MyAnnotation)") public void pointname(){ } //处理切面前的操作 @Before("pointname()") public void beforeAno(JoinPoint joinPoint){ System.out.println("befroe pointcut"); Class<?> clazz = joinPoint.getTarget().getClass(); System.out.println(clazz.getAnnotation(MyAnnotation.class).info()); } //处理切面后的操作 @After("pointname()") public void afterAno(JoinPoint joinPoint){ try { System.out.println("after pointcut"); //获取切面方法 Method method = joinPoint.getTarget().getClass() .getMethod(joinPoint.getSignature().getName(), ((MethodSignature)joinPoint.getSignature()).getParameterTypes()); } catch (NoSuchMethodException e) { e.printStackTrace(); } }
}
同时注意如果不加@Component注解需要配置<aop:aspectj-autoproxy proxy-target-class="true"/>
常用注解:
@Aspect 切面声明,标注在类、接口(包括注解类型)或枚举上,把当前类标识为一个切面供容器读取 @Pointcut 切入点声明,即切入到哪些目标类的目标方法,Pointcut是植入Advice的触发条件。每个Pointcut的定义包括2部分,一是表达式,二是方法签名。方法签名必须是 public及void型。可以将Pointcut中的方法看作是一个被Advice引用的助记符,因为表达式不直观,因此我们可以通过方法签名的方式为 此表达式命名。因此Pointcut中的方法只需要方法签名,而不需要在方法体内编写实际代码。 @Before 前置通知, 在目标方法(切入点)执行之前执行。value 属性绑定通知的切入点表达式,可以关联切入点声明,也可以直接设置切入点表达式。注意:如果在此回调方法中抛出异常,则目标方法不会再执行,会继续执行后置通知 -> 异常通知 @Around 环绕增强,相当于MethodInterceptor,目标方法执行前后分别执行一些代码,发生异常的时候执行另外一些代码 @AfterReturning 后置增强,相当于AfterReturningAdvice,方法正常退出时执行 @AfterThrowing 异常抛出增强,相当于ThrowsAdvice @After final增强,不管是抛出异常或者正常退出都会执行