Java-反射&注解
标签(空格分隔): Java
反射机制允许我们在运行时发现和使用类的信息,反射机制是由Class对象与java.lang.reflect类库
进行支持的,Class对象获取类信息,reflect类库提供相应的操作。
1. Class对象
每个类都有一个Class对象
,Class对象包含了与类有关的信息,事实上Class对象就是用来创建类的所有常规对象的,在类加载的加载阶段时创建在方法区的对象。
1.1 Class对象的创建
1)Class.forName("类名"):
要求JVM查找并加载指定的类,需要捕获检查异常(ClassNotFoundException),并返回一个Class对象引用。
2)Object.getClass():
前者的方法可以不需要为了获得Class引用而要持有该类型的对象,但是如果你已经拥有了一个类型的对象(类肯定已经加载),就可以直接调用getClass方法获取Class对象引用。
3)类字面量常量:
用类字面常量,例如Toy.class获取Class对象,这样更安全,因为它在编译时就会受到检查(不需要捕获异常)。它还可以应用于接口、数组、基本数据类型,对于基本数据类型的包装类,就得用其标准字段TYPE来返回Class对象。
当使用字面量获取会加载类但不会自动地初始化该类,这一点和forName不同,为使用类而做的准备工作包括下面:
- 加载。类加载器执行,该步骤查找字节码文件并创建Class对象。
- 链接。验证字节码,为静态域分配存储空间,如果必须的话则会解析这个类创建的其他类的所有引用。
- 初始化。如果该类有超类,则自动对其初始化,执行静态初始化器和静态初始化块。
由步骤可以知道:初始化被延迟到了对静态方法或则非常数静态域进行首次引用才执行。这一点和forName立马加载并初始化不同。
1.2 泛化的Class对象
Class引用表示的就是它所指向的对象的确切类型,而该该对象便是Class类的一个对象。对Class引用所指向的Class对象的类型进行限定,用到了泛型语法来让编译器强制执行额外的类型检查
。
通配符:?
"?"表示任何事物,这和不添加任何泛型的作用是一致的,但它优于平凡的Class,因为平凡的不会报编译警告,而它的好处是会表示以防止你疏忽而使用一个非具体的类引用。
Class<Number> a = int.class //error
其中,Integer继承自Number,但它是错误的,因为Integer Class对象并不是Number Class对象的子类。
1.3 instanceof和isInstance
在查询类型信息时,以instanceof或isInstance形式和直接比较Class对象有一个很重要的差别:
instanceof和isInstance生成的结果完全一样,equals和==也一样,二者结论不一样:前者保持了类型的概念,后者则时比较实际的Class对象,没有考虑继承,是比较切确的类型。
2. 反射机制
Java不允许在运行时改变程序结构或类型变量的结构,但它允许在运行时去探知、加载、调用在编译期完全未知的class(在运行时打开和检查.class文件),可以在运行时加载该class,生成实例对象(instance object),调用method,或对field赋值。
reflect类库
提供了三个类来支持反射机制:
- Field :可以使用 get() 和 set() 方法读取和修改 Field 对象关联的字段;
- Constructor :可以用 Constructor 创建新的对象
- Method :可以使用 invoke() 方法调用与 Method 对象关联的方法;
2.1 反射创建对象的两种方式
1)Class对象的newInstance()
使用 Class 对象的 newInstance()方法来创建该 Class 对象对应类的实例,但是这种方法要求 该Class对象对应的类有默认的空构造器。
2)调用Constructor对象的newInstance()
先使用Class对象获取指定的Constructor对象,再调用Constructor对象的newInstance() 方法来创建 Class对象对应类的实例,通过这种方法可以选定构造方法创建实例。
//获取Person类的 Class对象 Class clazz=Class.forName("reflection.Person"); //使用.newInstane方法创建对象 Person p=(Person) clazz.newInstance(); //获取构造方法并创建对象 Constructor c=clazz.getDeclaredConstructor(String.class,String.class,int.class); //创建对象并设置属性 13/04/2018 Page 106 of 283 Person p1=(Person) c.newInstance("李四","男",20);
2.2 反射创建和new创建的区别
其中Constructor
构造对象的方法和new构造对象是有所差别的:
- 前者返回obejct类型引用(当然可以强制类型转换为指定类型),其作用是创建一个指定类的对象
- newInstance只能调用无参构造器(反射机制可以用任意的构造器来构造对象),而new可以随意调用.
- 前者是使用类加载机制来创建对象,后者是直接创建一个新对象。
3. 动态代理
代理一般充当中间人,为委托类进行代理的代理类,旨在提供额外或不同的操作而不用修改原有类。静态代理一般在编译时就确定,一般而言就是设计模式中的代理模式
;而动态代理在运行时根据代码的指示动态生成,一般有CGLIB动态字节码指令
和JDK动态代理
。
3.1 JDK动态代理
在java的java.lang.reflect包下提供了一个Proxy类和一个InvocationHandler接口,通过这个类和这个接口可以生成JDK动态代理类和动态代理对象。
3.1.1 创建动态代理类
每一个动态代理类都必须要实现InvocationHandler这个接口,并且每个代理类的实例都关联到了一个handler,当我们通过代理对象调用一个方法的时候,这个方法的调用就会被转发为由InvocationHandler这个接口的 invoke 方法来进行调用。
InvocationHandler只有唯一的一个invoke方法,其参数相应的作用:
Object invoke(Object proxy, Method method, Object[] args) throws Throwable //proxy: 指代我们所代理的那个真实对象 //method: 指代的是我们所要调用真实对象的某个方法的Method对象 //args: 指代的是调用真实对象某个方法时接受的参数 //Object返回值: 指代的是调用的目标方法的返回值
实例:
public interface Service { void speak(String str); int peek(); String reverse(String str); } /* * 委托类 */ public class ServiceImpl implements Service{ @Override public void speak(String str) { System.out.println(str); } @Override public int peek() { return -1; } } /* * 动态代理类 */ public class DynamicProxy implements InvocationHandler { private Service target; // 被代理(委托)对象 public DynamicProxy(Service target) { this.target = target; } /* * proxy: 动态代理对象 * method: 代理对象调用的方法 * args: 代理对象调用的方法的参数 * 返回值: 代理对象执行方法的返回值 */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Exception { System.out.println("方法执行前"); Object obj = method.invoke(this.target, args); // 反射调用被代理对象的方法 System.out.println("方法执行后"); return obj; } }
3.1.2 创建动态代理对象
Proxy这个类的作用就是用来动态创建一个代理对象的类,它提供了许多的方法,下面介绍通过Proxy创建动态对象的一般过程:
public class Client { public static void main(String[] args) { // 创建一个InvocationHandler对象 InvocationHandler serviceHandler = new DynamicProxy(new ServiceImpl()); // 使用Proxy的getProxyClass静态方法生成一个动态代理类Class对象serviceProxyClass Class<?> serviceProxyClass = Proxy.getProxyClass(Service.class.getClassLoader(), new Class<?>[] {Service.class}); // 获得serviceProxyClass中一个带InvocationHandler参数的构造器constructor Constructor<?> constructor = serviceProxyClass.getConstructor(InvocationHandler.class); // 通过构造器constructor来创建一个动态实例serviceProxy Service serviceProxy = (Service) constructor.newInstance(serviceHandler); } }
但是我们用的最多的是简化版本的newProxyInstance
方法:
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException // loader: 一个ClassLoader对象,定义了由哪个ClassLoader对象来对生成的代理对象进行加载 // interfaces: 一个Interface对象的数组,表示的是我将要给我需要代理的对象提供一组什么接口,如果我提供了一组接口给它,那么这个代理对象就宣称实现了该接口(多态),这样我就能调用这组接口中的方法了 // h: 一个InvocationHandler对象,表示的是当我这个动态代理对象在调用方法的时候,会关联到哪一个InvocationHandler对象上
创建动态对象的简化过程:
public class Client { public static void main(String[] args) { // 创建一个InvocationHandler对象 DynamicProxy dynamicProxy = new DynamicProxy(); Service service = (Service) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), dynamicProxy); } }
3.2 分析
Proxy类的newProxyInstance方法创建了一个动态代理对象,查看源码发现它封装了创建动态代理类的步骤:
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException { Objects.requireNonNull(h); final Class<?>[] intfs = interfaces.clone(); final SecurityManager sm = System.getSecurityManager(); if (sm != null) { checkProxyAccess(Reflection.getCallerClass(), loader, intfs); } /* * Look up or generate the designated proxy class. */ Class<?> cl = getProxyClass0(loader, intfs); /* * Invoke its constructor with the designated invocation handler. */ try { if (sm != null) { checkNewProxyPermission(Reflection.getCallerClass(), cl); } final Constructor<?> cons = cl.getConstructor(constructorParams); final InvocationHandler ih = h; if (!Modifier.isPublic(cl.getModifiers())) { AccessController.doPrivileged(new PrivilegedAction<Void>() { public Void run() { cons.setAccessible(true); return null; } }); } return cons.newInstance(new Object[]{h}); } catch (IllegalAccessException|InstantiationException e) { throw new InternalError(e.toString(), e); } catch (InvocationTargetException e) { Throwable t = e.getCause(); if (t instanceof RuntimeException) { throw (RuntimeException) t; } else { throw new InternalError(t.toString(), t); } } catch (NoSuchMethodException e) { throw new InternalError(e.toString(), e); } }
Class<?> cl = getProxyClass0(loader, intfs);
产生了代理类,后面代码中的构造器也是通过这里产生的类来获得,这个类的产生就是整个动态代理的关键,由于是动态生成的类文件,这个类文件是缓存在java虚拟机中的。
jdk为我们的生成了一个叫$Proxy0
(这个名字后面的0是编号,有多个代理类会一次递增)的代理类,这个类文件时放在内存中的,我们在创建代理对象时,就是通过反射获得这个类的构造方法,然后创建的代理实例。通过对这个生成的代理类源码的查看,我们很容易能看出,动态代理实现的具体过程。
我们可以对InvocationHandler看做一个中介类,中介类持有一个被代理对象,在invoke方法中调用了被代理对象的相应方法。通过聚合方式持有被代理对象的引用,把外部对invoke的调用最终都转为对被代理对象的调用。
3. 注解
3.1 概念
Annotation(注解)是 Java 提供的一种对元程序中元素关联信息和元数据(metadata)的途径 和方法。Annatation(注解)是一个接口,程序可以通过反射来获取指定程序中元素的 Annotation 对象,然后通过该Annotation对象来获取注解中的元数据信息。
注解的类型可以分为:Java自带的注解;标准元注解(定义注解的注解);自定义注解。
3.2 Java自带的注解
- @Override:标明重写某个方法
- @Deprecated:标明某个类或方法过时
- @SuppressWarnings:标明要忽略的警告,使用这些注解后编译器就会进行检查。
3.3 标准元注解
元注解的作用是负责注解其他注解。 Java5.0 定义了 4 个标准的 meta-annotation 类型,它们被 用来提供对其它 annotation类型作说明。
@Target修饰的对象范围
@Target 说明了Annotation所修饰的对象范围,在Annotation类型的声明中使用了target可更加明晰 其修饰的目标。 Annotation可被用于:
- packages
- types(类、 接口、枚举、Annotation 类型)
- 类型成员(方法、构造方法、成员变量、枚举值)
- 方法参数和本地变量(如循环变量、catch参数)
@Retention定义被保留的时间长短
@Retention 定义了该Annotation被保留的时间长短:表示需要在什么级别保存注解信息,用于描 述注解的生命周期(即:被描述的注解在什么范围内有效),取值(RetentionPoicy)由:
- SOURCE:在源文件中有效(即源文件保留)
- CLASS:在class文件中有效(即class保留)
- RUNTIME:在运行时有效(即运行时保留)
@Documented描述-javadoc
@Documented 用于描述其它类型的 annotation 应该被作为被标注的程序成员的公共 API,因 此可以被例如javadoc此类的工具文档化。
@Inherited阐述了某个被标注的类型是被继承的
@Inherited 元注解是一个标记注解,@Inherited 阐述了某个被标注的类型是被继承的。如果一 个使用了@Inherited 修饰的 annotation 类型被用于一个 class,则这个 annotation 将被用于该 class的子类。
4. 自定义注解
4.1 定义注解
定义注解:
/** * 定义一个可以注解在Class,interface,enum上的注解 */ @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface MyAnTargetType { /** * 定义注解的一个元素 并给定默认值 * @return */ String value() default "我是定义在类接口枚举类上的注解元素value的默认值"; }
/** * 定义一个可以注解在METHOD上的注解 */ @Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface MyAnTargetMethod { /** * 定义注解的一个元素 并给定默认值 * @return */ String value() default "我是定义在方法上的注解元素value的默认值"; }
/** * 定义一个可以注解在FIELD上的注解 */ @Target({ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) public @interface MyAnTargetField { /** * 定义注解的一个元素 并给定默认值 * @return */ String value() default "我是定义在字段上的注解元素value的默认值"; }
/** * 定义一个可以注解在PARAMETER上的注解 */ @Target({ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) public @interface MyAnTargetParameter { /** * 定义注解的一个元素 并给定默认值 * @return */ String value() default "我是定义在参数上的注解元素value的默认值"; }
注解的使用:
/** * 测试java注解类 */ @MyAnTargetType public class AnnotationTest { @MyAnTargetField private String field = "我是字段"; @MyAnTargetMethod("测试方法") public void test(@MyAnTargetParameter String args) { System.out.println("参数值 === "+args); } }
4.2 注解处理器
如果没有用来读取注解的方法和工作,那么注解也就不会比注释更有用处了。使用注解的过程中, 很重要的一部分就是创建于使用注解处理器。Java SE5扩展了反射机制的API,以帮助程序员快速 的构造自定义注解处理器。下面对上文的定义的注解自定义注解处理器。
/** * 测试java注解类 * * @author zhangqh * @date 2018年4月22日 */ @MyAnTargetType public class AnnotationTest { @MyAnTargetField private String field = "我是字段"; @MyAnTargetMethod("测试方法") public void test(@MyAnTargetParameter String args) { System.out.println("参数值 === "+args); } // 注解处理器 public static void main(String[] args) { // 获取类上的注解MyAnTargetType MyAnTargetType t = AnnotationTest.class.getAnnotation(MyAnTargetType.class); System.out.println("类上的注解值 === "+t.value()); MyAnTargetMethod tm = null; try { // 根据反射获取AnnotationTest类上的test方法 Method method = AnnotationTest.class.getDeclaredMethod("test",String.class); // 获取方法上的注解MyAnTargetMethod tm = method.getAnnotation(MyAnTargetMethod.class); System.out.println("方法上的注解值 === "+tm.value()); // 获取方法上的所有参数注解 循环所有注解找到MyAnTargetParameter注解 Annotation[][] annotations = method.getParameterAnnotations(); for(Annotation[] tt : annotations){ for(Annotation t1:tt){ if(t1 instanceof MyAnTargetParameter){ System.out.println("参数上的注解值 === "+((MyAnTargetParameter) t1).value()); } } } method.invoke(new AnnotationTest(), "改变默认参数"); // 获取AnnotationTest类上字段field的注解MyAnTargetField MyAnTargetField fieldAn = AnnotationTest.class.getDeclaredField("field").getAnnotation(MyAnTargetField.class); System.out.println("字段上的注解值 === "+fieldAn.value()); } catch (Exception e) { e.printStackTrace(); } } }
5. 参考资料
- Eckel B. Java 编程思想 [M]. 机械工业出版社, 2007.
- Cay S. Horstmann.JAVA核心技术(卷1)[M]. 机械工业出版社, 2008.