Class类是Reflection API中核心的类,他位于java.lang.Class
列出一些常用的方法。
- getName() : 获得类的完整名字
- getFields() : 获得类的public类型的属性
- getDeclaredFields() : 获得类的所有属性
- getMethods() : 获得类的public类型的方法
- getDeclaredMethods() : 获得类的所有方法
- getMethod(String name, Class[] parameterTypes) : 获取类的特定方法(name参数指定方法名字,parameterTypes参数指定方法参数类型)
- getConstructors() : 获得类的public类型的构造方法
- getConstructor(Class[] parameterTypes) : 获得类的特定构造方法(parameterTypes参数指定构造方法的参数类型)
- newInstance() : 通过类的不带参数的构造方法创建这个类的一个对象
如果想使用反射,有2个关键的部分
1.获取Class对象
2.获得对象实例
下面来介绍这两个部分:
1.如何获取Class对象
获取某个类或某个对象所对应的Class对象的常用的3种方法
a) 使用Class类的静态方法forName:
Class.forName("java.lang.String");
b) 使用类的.class语法:
String.class;
c) 使用对象的getClass()方法(java.lang.Object类中的方法):
String s = "aa";
Class<?> clazz = s.getClass();
2.如何获得对象实例
大家都知道获得对象实例就是去new一个,其实就是调用对象的构造方法
这里将调用构造方法参数的不同分为两种类型:
a)调用无参数的构造方法:
1.调用Class对象的newInstance()方法:
Class<?> classType = ClassClass.forName("java.lang.String");
Object object = classTpye.newInstance();
2.调用Class对象的Constructor对象的newInstance()方法,传递一个空的Class对象数组作为参数:
Class<?> classType = ClassClass.forName("java.lang.String");
Constructor cons = classType.getConstructor(new Class[]{});
Object object =cons.newInstance(new Object[]{});
b)调用有参数的构造方法:
1.调用Class对象的Constructor对象的newInstance()方法,传递一个可变长的Class对象数组作为参数,本例传递String,int两个参数:
Class<?> classType = ClassClass.forName("java.lang.String");
Constructor cons = classType.getConstructor(new Class[]{String.class, int.class});
Object object = cons.newInstance(new Object[]{"hello",3});
下面来一个小Demo,由于过于简单这里就只贴代码了
这个demo简单的实现了上面介绍的一些关于反射的用法,算是一个复习巩固:
class Person { private long id; private int age; private String name; public Person() { } public Person(String name, int age) { this.name = name; this.age = age; } @Override public String toString() { return "Name="+getName()+" Age="+getAge()+" Id="+getId(); } public long getId() { return id; } public void setId(long id) { this.id = id; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } }很简单的一个自定义类,两个构造方法,和一些get,set方法,还有一个重写的toString
下面是调用反射的main方法中的代码,主要就是用反射的方法调用Person的带参构造方法给age,name赋值,在调用setId给id赋值,最后调用toString方法打印出结果
public static void main(String[] args) throws Exception { //获取Person类的Class对象 Class<?> classType = Class.forName("Person"); //调用Person类的两个参数构造方法生成对象 Constructor constructor = classType.getConstructor(new Class[]{String.class, int.class}); Object object = constructor.newInstance(new Object[]{"Dean",25}); //获取setId方法 Method setId = classType.getMethod("setId", new Class[]{long.class}); //调用setId方法设置Id setId.invoke(object, new Object[]{10}); //调用toString输出结果 Method toString = classType.getMethod("toString", new Class[]{}); String result = (String) toString.invoke(object, new Object[]{}); System.out.println(result); }
最近正在看反射的视频和资料,写这个博文的目的就是对于学习的一个总结吧。
陆续可能会更新一些我总结的更深层的反射知识,希望大家支持
大家都知道正常的调用是不可以访问对象的private修饰的属性和方法的,这也是java的封装性原则。
但是有没有方法可以强制去访问对象的private修饰的属性和方法呢?那就是用反射!(这个可能在面试题中被问到哦)
接下来就来看看是如何实现的:
我们先去jdk里看一下描述属性的类Field,和方法的类Method:
可以看到这两个类有个共通的特点,就是他们都继承自java.lang.reflect.AccessibleObject这个类,我们好好看看这个类的描述
public class AccessibleObject
extends Object
implements AnnotatedElement
大致意思就是:
这个AccessibleObject类是Field, Method and Constructor对象的一个父类,他可以让一个反射对象去禁止Java语言的访问控制检测。控制检测有public, default (package) access, protected, and private。。。blah blah blah。。。
这里我贴出控制访问控制检测的这个方法:(这个类里还有一些相关的方法,有兴趣的大家可以自己去看看)
public void setAccessible(boolean flag) throws SecurityException
accessible
flag for this object to the indicated boolean value. A value of
true
indicates that the reflected object should suppress Java language access checking when it is used. A value of
false
indicates that the reflected object should enforce Java language access checks.
大致意思:
设置标志去指示对象的boolean值,如果是true则禁止java访问控制检查,如果是false则强制反射对象使用java访问控制检查
知道了这个方法就可以做一个小例子测试一下啦。
下面这个例子很简单,就是定义一个dog类,里面有个private的属性dogName,和private的方法say。
main函数里用反射先去修改dogName,然后在调用say方法打印出来:
public class Test2 { public static void main(String[] args) throws Exception { //获得Dog类的Class对象 Class<?> classType = Class.forName("Dog"); //生成对象的实例 Object obj = classType.newInstance(); //取得dogName属性 Field dogName = classType.getDeclaredField("dogName"); //禁止Field的访问控制检查 dogName.setAccessible(true); //将Field的值设为“Xiao Qiang” dogName.set(obj, "Xiao Qiang"); //取得say()方法 Method say = classType.getDeclaredMethod("say", new Class[]{}); //禁止say方法的访问控制检查 say.setAccessible(true); //调用say方法 say.invoke(obj, new Object[]{}); } } class Dog { //私有的属性 private String dogName = "Wang Cai"; //私有的方法 private void say() { System.out.println(dogName + ": Wang Wang"); } }输出结果:Xiao Qiang: Wang Wang
这里需要特别注意一个地方:
如果想用反射修改访问控制检查的话,获取Method和Field对象的时候一定要用getDeclaredField和getDeclaredMethod。不要用getField和getMethod。
虽然这两个方法的参数都是相同的,但不同点在于getMethod和getField只能获得public修饰的属性和方法。而getDeclared可以获取任何类型的属性和方法,因为这个例子要调用私有的属性和方法,所以要用getDeclaredXX。
反射最常见的应用就是代理模式了。
本文先简单介绍一下代理模式,并写一个静态代理的例子。为下一篇重要的动态代理做点铺垫
代理模式的作用是:
为其他对象提供一种代理以控制对这个对象的访问。
另外在某些情况下,一个客户不想或着不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。
说白了,代理模式就是一个"中介",他有这客户们的引用,所以可以用这个"中介"来代替客户来做操作。
代理模式设计的角色:
想使用代理模式,需要定义如下的3个角色:
抽象角色:
声明真实对象和代理对象的共同接口
真实对象:
代理角色所代表的真实对象,是我们最终要引用的对象
代理角色:
1.代理对象内部含有对真实对象的引用,从而可以操作真实的对象。
2.代理对象提供与真实对象相同的接口以便在任何时刻都能代替真实对象。
3.代理对象可以在执行真实对象操作时,附加其他的操作,相当于对真实对象进行封装
可能看完概念后有点迷糊,接下来直接上代码看例子应该会比较容易理解:
1.首先定义抽象角色对象,使用抽象类或者接口来定义一个代理角色和真实对象共有的一个方法。这个方法就是被代理角色委托给"中介"来操作的方法
//这里定义一个抽象类 public abstract class Subject { //空的抽象方法,这个方法需要代理角色和真实角色都去实现它 public abstract void request(); }
2.定义真实角色对象
//真实角色对象,继承自抽象角色,重写定义的方法。 public class RealSubject extends Subject{ //在重写的方法中实现需要的操作,这里只是简单打印一句 @Override public void request() { System.out.println("this is real subject"); } }
3.定义代理角色对象
//代理角色,同样继承抽象角色,重写抽象角色中的方法 public class ProxySubject extends Subject{ //在代理角色内部对真实角色的引用 private RealSubject realSubject; //重写的实现抽象角色中的方法 @Override public void request() { this.preRequest(); //在调用真实角色操作之前所附加的操作 if (realSubject == null) { realSubject = new RealSubject(); } realSubject.request(); //真实角色所完成的事情 this.postRequest(); //在真实角色操作之后所附加的操作 } //自定义的方法,在真实方法执行之前调用的方法 private void preRequest() { System.out.println("pre request"); } //自定义的方法,在真实方法执行之后调用的方法 private void postRequest() { System.out.println("post request"); } }
在实现代理角色对象时,最重要的一点是要有一个真实对象的引用。
通过这个引用在代理对象中去调用真实对象的方法,并且可以自定义一些其他的操作,比如本例子中的preRequest()和postRequest(),他们分别在之前和之后被调用。
这样做可以记录一下运行时间或打log之类的操作,但这里仅仅是打印了一句话。
4.最后看看main方法中如何调用:
public class TestProxy { public static void main(String[] args) { //实例化一个代理角色对象 Subject subject = new ProxySubject(); //用代理对象去调用定义的通用方法 subject.request(); } }
输出为:
pre request
this is real subject
post request
大家看代码后可以发现,这里并没有用到反射的知识。而且对于上面的例子来说,真实角色对象是事先必须已经存在的,并且必须是作为代理对象的内部属性。
如果这样的话,有一个真实角色那就必须在代理角色中有一个他的引用,如果在不知道真实角色的情况下又需要怎么办?这就需要"动态代理"来解决了。
下一篇文章就来介绍"动态代理"。
通过上一篇文章介绍的静态代理Java反射学习总结三(静态代理)中,大家可以发现在静态代理中每一个代理类只能为一个接口服务,这样一来必然会产生过多的代理,而且对于每个实例,如果需要添加不同代理就要去添加相应的代理类。解决这一问题最好的做法是可以通过一个代理类完成全部的代理功能或者说去动态的生成这个代理类,那么此时就必须使用动态代理完成。
动态代理知识点:
Java动态代理类位于java.lang.reflect包下,主要有以下一个接口和一个类:
1.InvocationHandler接口: 该接口中仅有一个方法
public object invoke(Object obj, Method method, Object[] args)
在实际使用时,obj一般是指代理类,method是被代理的方法,args为该方法的参数数组。这个抽象的invoke方法在代理类中动态实现。
2.Proxy类: 该类即为动态代理类,这里只介绍一下newProxyInstance()这个方法
static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h)
这个方法是最主要的方法,它会返回代理类的一个实例,返回后的代理类可以当做被代理类使用
实现动态代理需4步骤:
1.创建一个实现接口InvocationHandler的类,它必须实现invoke方法。
2.通过Proxy的静态方法newProxyInstance创建一个代理
3.创建被代理的类以及接口
4.通过代理调用方法
下面看这个例子具体说明如何通过上面的4个步骤来建立一个动态代理:
步骤1和步骤2合并写在一个类中,命名为DynamicProxy
public class DynamicProxy implements InvocationHandler { // 需要被代理类的引用 private Object object; // 通过构造方法传入引用 public DynamicProxy(Object object) { this.object = object; } // 定义一个工厂类,去生成动态代理 public Object getProxy() { // 通过Proxy类的newProxyInstance方法动态的生成一个动态代理,并返回它 return Proxy.newProxyInstance(object.getClass().getClassLoader(), object .getClass().getInterfaces(), this); } // 重写的invoke方法,这里处理真正的方法调用 @Override public Object invoke(Object obj, Method method, Object[] args) throws Throwable { beforeDoing(); Object invoke = method.invoke(object, args); afterDoing(); return invoke; } public void beforeDoing() { System.out.println("before ............"); } public void afterDoing() { System.out.println("after ............."+"\n"); } }
该类实现了InvocationHandler接口,并且自定义了一个getProxy()方法去调用Proxy类的newProxyInstance()去生成一个动态代理。
步骤3:创建被代理的类以及接口
//真实角色对象,继承自抽象角色,重写定义的方法。 public class RealSubject implements Subject1,Subject2{ //Subject1接口中的方法 @Override public void request() { System.out.println("this is real subject"); } //Subject1接口中的方法 @Override public void ask() { System.out.println("this is real ask"); } //Subject2接口中的方法 @Override public void request2() { System.out.println("this is real subject2"); } }
这个类就是我们需要被代理的类,他继承了两个接口分别是Subject1,Subject2
interface Subject1 { public void request(); public void ask(); }
interface Subject2 { public void request2(); }
4.通过代理调用方法
接下来在main方法中通过动态生成的代理来调用方法
public static void main(String[] args) { //需要被代理的类 RealSubject realSubject = new RealSubject(); //用于创建动态代理的类,将被代理类的引用传递进去 DynamicProxy dynamicProxy = new DynamicProxy(realSubject); //通过getProxy方法动态的获取代理类,转换成需要调用的接口类型后调用方法 Subject1 s1 = (Subject1) dynamicProxy.getProxy(); s1.request(); s1.ask(); //通过getProxy方法动态的获取代理类,转换成需要调用的接口类型后调用方法 Subject2 s2 = (Subject2) dynamicProxy.getProxy(); s2.request2(); }
最后打印:
before ............
this is real subject
after .............
before ............
this is real ask
after .............
before ............
this is real subject2
after .............
简单介绍动态代理内部实现原理:
例子看完了,肯定有如下疑问:
动态代理在哪里应用了反射机制?仅仅通过一个InvocationHandler接口和一个Proxy类的newProxyInstance方法是如何动态的生成代理?
下面就来简单的分析一下InvocationHandler,和Proxy的newProxyInstance方法是如何在运行时动态的生成代理的:
以下代码都是伪代码而且内容大部分参考马士兵动态代理的视频。如果感兴趣,建议找视频去学。
先看newProxyInstance是如何定义的
static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h)
这里需要传入3个参数。先看第二个参数,传入一个接口类型的Class数组。
上面例子中传入的参数是object.getClass().getInterfaces()
object是被代理对象,这个参数就是通过反射拿到被代理对象的所有接口
在上面例子中就是我们定义的Subject1,Subject2接口了
有了接口数组,就可以通过类似下面的代码使用反射拿到接口中的所有方法
for (interface infce : interfaces[]) { Method[] methods = infce.getMethods(); for (Method m : method) { m.getName(); } }
在正常情况下,知道了被代理的接口和接口里面的方法就可以去生成代理类了。
大概就是下面这种的一个简单的实现:一个很固定的套路,只要知道实现接口和方法就仿照写出。
public class ProxySubject implements Subject{ private RealSubject realSubject; @Override public void request() { realSubject.request(); } }
动态代理还会在代理的方法中做一些其他的操作,如添加日志,时间,权限等操作。这时候就要靠InvocationHandler接口中的invoke方法。看看例子中如何实现的。
@Override public Object invoke(Object obj, Method method, Object[] args) throws Throwable { beforeDoing(); Object invoke = method.invoke(object, args); afterDoing(); return invoke; }
这些代码是我们自定义的,需要实现什么操作就写在里面。
这段代码存在于Invocationhandler对象中,这个对象会在调用Proxy的newProxyInstance的方法中传递进去。
这时候可以通过反射知道被调用方法的名字等信息,之后还是通过字符串的形式拼接处类似下面的动态代理类
public class ProxySubject implements Subject{ private RealSubject realSubject; @Override public void request() { Methond md = Subject.getMethod("methodName"); handler.invoke(this, md); } }
这个大概就是根据传递的接口对象和InvocationHandler结合后应该生成的代理类。但现在的问题是如何去动态的生成上面这样的代理类。
答案是使用字符串拼接的方式。
从看上面的代码可以看出,除了接口和调用方法不同其他都相同。而且我们已经通过反射获得了方法和接口名字,这样就可以按着这个“套路”去用字符串拼接成这样的一类。
大概就是下面这种代码:
String source = "package com.gxy.proxy;" + rt + "public class "+ClassName+"implements "+InterfaceName+ rt + "{" + rt + "private "+ ClassName + ClassName.toLowerCase()+" ; " + rt + "@Override" + "public Void "+InterfaceName+ "()" + rt + " {" + "Method md = "+InterfaceName+".getMethod("+ methodName+");" +rt + "hander.invoke(this, md);" + rt + "}" + rt + "}";
用反射生成的出来类名,接口名,方法名去动态的创建这样一个类的字符串。
之后就特定的方法去将这个字符串生成成类。在用反射把这个类取出来。这样就有了这个“动态”生成的代理类了。
就简单介绍到这吧。。。
最后大家可以发现例子中的动态代理里都是通过接口来实现的,如果对于不能实现接口的类就不能用JDK的动态代理了。如果想用就需要使用cglib了,因为cglib是针对类来实现的。
关于动态代理我研究了一个多礼拜,觉得理解起来还是比较困难的,勉勉强强的知道了个大概。
以后有时间我会继续深入的学习动态代理,但暂时还是以反射为主。下一篇准备写一下反射与注解的内容,希望大家多多支持。