自定义元注解需重点关注两点:1)注解的数据类型;2)反射获取注解的方法。首先,注解中的方法并不支持所有的数据类型,仅支持八种基本数据类型、String、Class、enum、Annotation和它们的数组。比如以下代码会产生编译时错误:
@Documented @Inherited @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface AnnotationTest { // 1. 注解数据类型不能是Object;2. 默认值不能为null Object value() default null; // 支持的定义方式 String value() default ""; }其次,上节中提到的反射相关类(Class、Constructor、Method和Field)和Package均实现了AnnotatedElement接口,该接口定义了访问反射信息的方法,主要如下:
// 获取指定注解类型 getAnnotation(Class<T>):T; // 获取所有注解,包括从父类继承的 getAnnotations():Annotation[]; // 获取指定注解类型,不包括从父类继承的 getDeclaredAnnotation(Class<T>):T // 获取所有注解,不包括从父类继承的 getDeclaredAnnotations():Annotation[]; // 判断是否存在指定注解 isAnnotationPresent(Class<? extends Annotation>:boolean当使用上例中的AnnotationTest 标注某个类后,便可在运行时通过该类的反射方法访问注解信息了。
@AnnotationTest("yhthu") public class AnnotationReflection { public static void main(String[] args) { AnnotationReflection ar = new AnnotationReflection(); Class clazz = ar.getClass(); // 判断是否存在指定注解 if (clazz.isAnnotationPresent(AnnotationTest.class)) { // 获取指定注解类型 Annotation annotation = clazz.getAnnotation(AnnotationTest.class); // 获取该注解的值 System.out.println(((AnnotationTest) annotation).value()); } } }当自定义注解只有一个方法value()时,使用注解可只写值,例如:@AnnotationTest("yhthu")
三、动态代理代理是一种结构型设计模式,当无法或不想直接访问某个对象,或者访问某个对象比较复杂的时候,可以通过一个代理对象来间接访问,代理对象向客户端提供和真实对象同样的接口功能。经典设计模式中,代理模式有四种角色:
Subject抽象主题类——申明代理对象和真实对象共同的接口方法;
RealSubject真实主题类——实现了Subject接口,真实执行业务逻辑的地方;
ProxySubject代理类——实现了Subject接口,持有对RealSubject的引用,在实现的接口方法中调用RealSubject中相应的方法执行;
Cliect客户端类——使用代理对象的类。
在实现上,代理模式分为静态代理和动态代理,静态代理的代理类二进制文件是在编译时生成的,而动态代理的代理类二进制文件是在运行时生成并加载到虚拟机环境的。JDK提供了对动态代理接口的支持,开源的动态代理库(Cglib、Javassist和Byte Buddy)提供了对接口和类的代理支持,本节将简单比较JDK和Cglib实现动态代理的异同,后续章节会对Java字节码编程做详细分析。
3.1 JDK动态代理接口JDK实现动态代理是通过Proxy类的newProxyInstance方法实现的,该方法的三个入参分别表示:
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)ClassLoader loader,定义代理生成的类的加载器,可以自定义类加载器,也可以复用当前Class的类加载器;
Class<?>[] interfaces,定义代理对象需要实现的接口;
InvocationHandler h,定义代理对象调用方法的处理,其invoke方法中的Object proxy表示生成的代理对象,Method表示代理方法, Object[]表示方法的参数。
通常的使用方法如下:
private Object getProxy() { return Proxy.newProxyInstance(JDKProxyTest.class.getClassLoader(), new Class<?>[]{Subject.class}, new MyInvocationHandler(new RealSubject())); } private static class MyInvocationHandler implements InvocationHandler { private Object realSubject; public MyInvocationHandler(Object realSubject) { this.realSubject = realSubject; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("Some thing before method invoke"); Object result = method.invoke(realSubject, args); System.out.println("Some thing after method invoke"); return result; } }