越不懂的越爱装
大家都同等:IT世界没有难不难,只有是否了解过
挑战目录
为什么可以使用注解进行代码调试
- 因为研究源码的方法里面有一个重要的方法是:为每个方法写上描述方法作用的log,然后运行时会打印这些方法的运行过程。那么流程就一目了然了。
- 静态代理(Aspectd):我们可以在编译时改变编译的代码(需要特殊处理的编译器, Aspectd的编译器 ),去代理目标方法,在目标方法前后织入我们的代码段。
- 动态代理(Spring AOP): 不会去修改字节码,而是在内存中临时为方法生成一个AOP对象,这个AOP对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法
注解的实现
-
元注解:为注解的注解,用于定义注解
分别为@Retention、 @Target、 @Document、 @Inherited和@Repeatable(JDK1.8加入)五种
-
@Retention 注解的作用时间:源码(编译期阶段),class(类加载阶段),运行时(JVM阶段)
- @Retention(RetentionPolicy.SOURCE) 在源码,不在class,不在运行时
- @Retention(RetentionPolicy.CLASS) 在源码,在class,不在运行时 (默认的)
- @Retention(RetentionPolicy.RUNTIME) 在源码,在class,在运行时
-
@Target 注解的作用范围:接口、类、枚举、注解、枚举的常量、方法、方法参数、属性字段、构造函数、局部变量、泛型方法、泛型类、泛型接口 、任意类型除了 class
- @Target(ElementType.TYPE) 作用接口、类、枚举、注解
- @Target(ElementType.FIELD) 作用属性字段、枚举的常量
- @Target(ElementType.METHOD) 作用方法
- @Target(ElementType.PARAMETER) 作用方法参数
- @Target(ElementType.CONSTRUCTOR) 作用构造函数
- @Target(ElementType.LOCAL_VARIABLE)作用局部变量
- @Target(ElementType.ANNOTATION_TYPE)作用于注解(@Retention注解中就使用该属性)
- @Target(ElementType.PACKAGE) 作用于包
- @Target(ElementType.TYPE_PARAMETER) 作用于类型泛型,即泛型方法、泛型类、泛型接口 (jdk1.8加入)
- @Target(ElementType.TYPE_USE) 类型使用.可以用于标注任意类型除了 class (jdk1.8加入)
@Document 注解的Javadoc文档
@Inherited 注解作用与类的继承:如果java父类使用了该注解,当子类没有使用注解时。默认也使用该注解。
@Repeatable 可以使用多次:可以以不同的作用多次作用目标
-
-
注解的属性:为注解的属性,用于定义注解里面的变量
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.METHOD)
public @interface TestAnnotation {
String methodName() default ""; //属性一
String desc() default ""; //属性二
}
获取注解属性:根据@Retention的描述,知道可能有三种方式
-
源码(编译期阶段)
-
使用注解处理器 Annotation Processing Tool 简称APT
-
实现注解处理器(继承 AbstractProcessor 抽象类)
public class TestProcessor extends AbstractProcessor { @Override public SourceVersion getSupportedSourceVersion() { //处理器支持的版本 return SourceVersion.RELEASE_8; } @Override public Set<String> getSupportedAnnotationTypes() { Set<String> 处理器支持处理的注解集合 = new HashSet<>(); 处理器支持处理的注解集合.add(Test.class.toString()); return 处理器支持处理的注解集合; } /** * @param processingEnvironment */ @Override public synchronized void init(ProcessingEnvironment processingEnvironment){ super.init(processingEnvironment); } @Override public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) { Set<? extends Element> es = roundEnvironment.getElementsAnnotatedWith(Test.class);//获取被Test注解标记的元素集合 es.forEach(e -> { //e 标识被标注的对象,相当于反射获取的那个 //获取注解标注在哪 e.getKind().isClass(); e.getKind().isField(); e.getKind().isInterface(); //获取被标注的地方的名字 e.getSimpleName(); //获取使用的注解对象 Test test = e.getAnnotation(Test.class); //获取对象属性 test.name(); //使用反射api对e和test对象的操作 ... }); return false; } }
-
声明注解处理器
创建resources/META-INF/services/javax.annotation.processing.Process文件
在文件中添加注解处理器的全类名(一行只能写一个)如果是作为第三方jar包提供给别人 : 需要在maven打包时增加如下配置,主要也是把javax. annotation.processing.Processor文件打包到对应目录:
<build> <resources> <resource> <directory>src/main/resources</directory> <excludes> <exclude>META-INF/**/*</exclude> </excludes> </resource> </resources> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-resources-plugin</artifactId> <version>2.6</version> <executions> <execution> <id>process-META</id> <phase>prepare-package</phase> <goals> <goal>copy-resources</goal> </goals> <configuration> <outputDirectory>target/classes</outputDirectory> <resources> <resource> <directory>${basedir}/src/main/resources/</directory> <includes> <include>**/*</include> </includes> </resource> </resources> </configuration> </execution> </executions> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>1.8</source> <target>1.8</target> </configuration> </plugin> </plugins> </build>
-
-
-
class(类加载阶段)
-
运行时(JVM阶段)
-
使用反射
反射提供的支持方法有:
是否存在注解对象:isAnnotationPresent(Class<? extends Annotation> annotationClass) )
获取指定注解对象:getAnnotation(Class<A> annotationClass)
获取所有注解对象数组:getAnnotations()
-
示例
/** * 获取类注解属性 */ Class<C> c = C.class; boolean exist = c.isAnnotationPresent(A.class); if(exist){ A a = c.getAnnotation(A.class); System.out.println(a.name());//注解A中定义了name属性 } try { /** * 获取变量注解属性 */ Field name = c.getDeclaredField("name"); boolean nameAExist = name.isAnnotationPresent(Name.class); if(nameAExist){ Name nameA = name.getAnnotation(Name.class); System.out.println(nameA.value()); } /** * 获取方法注解属性 */ Method setName = c.class.getDeclaredMethod("setName"); if (setName!=null){ A a = setName.getAnnotation(A.class); A[] as = a.value(); for (A a : as) { System.out.println(a.value()); } } } catch (NoSuchFieldException e) { e.printStackTrace(); }
-
使用静态代理(Aspectd)实现方法日志打印
-
定义注解
@Retention(RetentionPolicy.SOURCE) @Target(ElementType.METHOD) public @interface TestAnnotation { String methodName() default ""; String desc() default ""; }
-
实现处理
import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.reflect.MethodSignature; import java.lang.reflect.Method; @Aspect public class mp { @Around("@annotation(TestAnnotation)") public Object beforeReturnValue(ProceedingJoinPoint point) throws Throwable { Method methodSignart = ((MethodSignature) point.getSignature()).getMethod(); TestAnnotation methodAnnotation = methodSignart.getAnnotation(TestAnnotation.class); if (methodAnnotation != null) { String methodName = methodAnnotation.methodName(); String desc = methodAnnotation.desc(); System.out.println("----------方法开始执行----------" + methodName); System.out.println("----------方法描述----------" + desc); Object proceed = point.proceed(); System.out.println("----------方法执行结束----------" + methodName); return proceed; } return point.proceed(); } }
-
编写测试类
public class TestMain { public static void main(String[] args) { new Test().test("x"); } } public class Test { @TestAnnotation String test(String name){ System.out.println("exe test"); return name; } }
使用动态代理(Spring AOP)实现方法日志打印
略,以后写Spring AOP实现原理的时候在写