本文所介绍的三个Java底层技术,有着逐渐递进的特点,Java注解中使用了JDK动态代理,而JDK动态代理中运用了Java反射。
Java注解
当我们阅读框架源码时,会看到其中包含着大量的注解,注解被广泛使用的原因在于,可以生成一些通用的“模板化”代码,来避免重复性的工作。使用注解的工作模式是,通过注解来描述我们的意图,然后用注解解析工具对注解进行解析。
【一】实验:自定义注解
首先我们通过 @interface关键字定义一个注解@Tree,定义注解时,需要定义两个内容:元注解,注解属性。
@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)public @interface Tree { String name() default "tree";}
元注解:
可以看到我在上面还添加了@Target和@Retention,这个是元注解,也就是添加到注解之上的注解,元注解有5种:
- @Retention:声明注解的的存活时间
RetentionPolicy.SOURCE 注解只在源码阶段保留,在编译器进行编译时它将被丢弃忽视。RetentionPolicy.CLASS 注解只被保留到编译进行的时候,它并不会被加载到 JVM 中。RetentionPolicy.RUNTIME 注解可以保留到程序运行的时候,它会被加载进入到 JVM 中,所以在程序运行时可以获取到它们。
- @Target:声明注解运用的地方
ElementType.ANNOTATION_TYPE 应用到注解ElementType.CONSTRUCTOR 应用到构造方法ElementType.FIELD 应用到属性ElementType.LOCAL_VARIABLE 应用到局部变量ElementType.METHOD 应用到方法ElementType.PACKAGE 应用到包ElementType.PARAMETER 应用到方法内的参数ElementType.TYPE 应用到类型(类、接口、枚举)
- @Documented:将注解中的元素包含到 Javadoc 中
- @Inherited:使用了这个注解的子类,就继承了该注解
- @Repeatable:Java1.8新增特性,应用于注解的值可以取多个的场景
注解属性:
可以类比为普通类中的成员变量,注解中只有成员变量没有方法,在使用该注解时为该属性赋值,也可以在定义时赋默认值。
【注解处理器】
在注解处理器中,我们可以为注解定义逻辑,例如在下面的例子中,就是调用AnnotationClient类中所有方法,把@Tree中name属性值注入到方法中。
public class TreeProcessor { public void parseMethod(final Class> clazz) throws Exception { final Object obj = clazz.getConstructor(new Class[] {}).newInstance(new Object[] {}); final Method[] methods = clazz.getDeclaredMethods(); for (final Method method : methods) { final Tree my = method.getAnnotation(Tree.class); if (null != my) { method.invoke(obj, my.name()); } } }}
接下来做一下测试:
public class AnnotationClient { @Tree public static void sayHello(final String name) { System.out.println("==>> Hi, " + name + " [sayHello]"); } @Tree(name = "Someone") public static void sayHelloToSomeone(final String name) { System.out.println("==>> Hi, " + name + " [sayHelloToSomeone]"); } public static void main(final String[] args) throws Exception { final TreeProcessor treeProcessor = new TreeProcessor(); treeProcessor.parseMethod(AnnotationClient.class); }}
【二】深入理解注解
如果换一个角度理解注解:它的本质是一个继承了Annotation接口的接口
当我们通过getAnnotation()方法获取一个注解的时候,JDK会通过动态代理生成注解的代理类$Proxy1,这个代理类代理了Tree中的所有方法,其实本质上还是通过反射来实现的,但是我们逐步递进的分析,先研究注解,下一步研究JDK动态代理,最后才能到达反射。
JAVA 中专门用于处理注解的 Handler:
sun.reflect.annotation.AnnotationInvocationHandler
AnnotationInvocationHandler有如下几个属性:
private final Class extends Annotation> type;private final Map memberValues;private transient volatile Method[] memberMethods = null;
其中memberValues在初始化后,key是注解的属性值,value是我们为属性的赋值,可能你已经忘了我们在程序中是怎么做的了 : @Tree(name = "Someone")
所有动态代理类生成的方法,都会走这个invoke()方法。
而这个invoke方法做的事情,概括起来就是:通过方法名获取属性值。
public Object invoke(Object var1, Method var2, Object[] var3) { String var4 = var2.getName(); Class[] var5 = var2.getParameterTypes(); if (var4.equals("equals") && var5.length == 1 && var5[0] == Object.class) { return this.equalsImpl(var3[0]); } else if (var5.length != 0) { throw new AssertionError("Too many parameters for an annotation method"); } else { byte var7 = -1; switch(var4.hashCode()) { case -1776922004: if (var4.equals("toString")) { var7 = 0; } break; case 147696667: if (var4.equals("hashCode")) { var7 = 1; } break; case 1444986633: if (var4.equals("annotationType")) { var7 = 2; } } switch(var7) { case 0: return this.toStringImpl(); case 1: return this.hashCodeImpl(); case 2: return this.type; default: Object var6 = this.memberValues.get(var4); if (var6 == null) { throw new IncompleteAnnotationException(this.type, var4); } else if (var6 instanceof ExceptionProxy) { throw ((ExceptionProxy)var6).generateException(); } else { if (var6.getClass().isArray() && Array.getLength(var6) != 0) { var6 = this.cloneArray(var6); } return var6; } } } }
从代码中我们可以看到,如果匹配为toString(),hashCode(),annotationType,会专门返回相应的实现,否则就返回memberValues对应key中相应的value,这样看起来还是比较清晰的。
于是,在分析完注解的实现后,我们同时也有了下一步的研究目标:JDK动态代理的实现。
【三】servlet 3.0引入的新注解
此篇文章的缘起,是由于在阅读SpringBoot源码时看到了@HandlesTypes注解,这是Tomcat的SCI机制用到的一个注解,被它标明的类需要作为参数值传入到onStartup方法。这是Servlet3.0新增的特性,所以在这里也列举一下Servlet3.0中新增的一些注解:
- HandlesTypes –该注解用来表示一组传递给ServletContainerInitializer的应用类。
- HttpConstraint – 该注解代表所有HTTP方法的应用请求的安全约束,和ServletSecurity注释中定义的HttpMethodConstraint安全约束不同。
- HttpMethodConstraint – 指明不同类型请求的安全约束,和ServletSecurity 注解中描述HTTP协议方法类型的注释不同。
- MultipartConfig –该注解标注在Servlet上面,表示该Servlet希望处理的请求的 MIME 类型是 multipart/form-data。
- ServletSecurity 该注解标注在Servlet继承类上面,强制该HTTP协议请求遵循安全约束。
- WebFilter – 该注解用来声明一个Server过滤器;
- WebInitParam – 该注解用来声明Servlet或是过滤器的中的初始化参数,通常配合 @WebServlet 或者 @WebFilter 使用。
- WebListener –该注解为Web应用程序上下文中不同类型的事件声明监听器。
- WebServlet –该注解用来声明一个Servlet的配置。
JDK动态代理
【一】实验:实现基于JDK的动态代理
【第一步】定义一个接口和实现类
public interface IPerson { void sayHello();}@Servicepublic class PersonServiceImpl implements IPerson { @Override public void sayHello() { System.out.println("Hello~~~"); }}public class PersonInvocationHandler implements InvocationHandler { private Object target; public PersonInvocationHandler(Object target){ this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("------前置逻辑-------------"); // 执行相应的目标方法 Object result = method.invoke(target,args); System.out.println("------后置逻辑-------------"); return result; }}
【第二步】两种使用JDK动态代理的方式
方式一:分为五个步骤
- 实现InvocationHandler接口
- 获得动态代理类:Proxy.getProxyClass
- 获得代理类的构造方法:getConstructor(InvocationHandler.class)
- 获得代理对象,传入自定义的InvocationHandler
- 通过代理对象调用目标方法
方式二 - 提供了一种封装好的方法:java.lang.reflect.Proxy#newProxyInstance(ClassLoader loader, Class>[] interfaces, InvocationHandler h)三个参数代表的含义分别是:
ClassLoader loader:接口的类加载器
Class>[] interfaces:接口(可以是一组接口)
InvocationHandler h:自定义的InvocationHandler
下面是两种方式的示例:
public class DynamicProxyClient { public static void main(String[] args) throws Exception{ // 方式一 System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles