Java中的反射和代理
特别声明:本文主要是记录了学习反射和代理的学习过程,代理部分的大段文本均属转载。您可以阅读原文。除此之外,所有代码及反射部分均属个人所写。
反射
最近刚读了一遍《Java编程思想》第四版中第14章——类型信息,获益匪浅,摘取了一部分内容。具体如下:
反射:运行时的类信息
和C++体系中的RTTI相似,反射是在运行时获取某个类的信息。Class类与java.lang.reflect类库一起对反射的概念进行了支持,该类包括Field、Method以及Constructor类(每个类都实现了Member接口)。这些类型的对象都是由JVM在运行时创建的,用以表示未知类里对应的成员。这样你就可以使用Constructor创建新的对象,用get()和set()方法读取和修改有关Field对象关联的字段,用invoke方法调用有关Method对象关联的方法。另外,还可以调用getFields()、getMethods()和getConstructors()等很便利的方法以返回表示字段、方法和构造器的对象的数组。理解RTTI和反射的真正区别:
对RTTI来说,编译器在编译时打开和检查.class文件。而对于反射机制来说,.class文件在编译时是不可获取的,所以是在运行时打开和检查.class文件。
示例如下:
//详解见《Java编程思想》第四版
package me.ethan.reflect;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.regex.Pattern;
public class ShowMethods {
private static String usage = "usage:\n" +
"ShowMethods qualified.class.name\n" +
"To show all methods in class or:\n" +
"ShowMethods qualified.class.name word\n" +
"To search for methods involving 'word'";
private static Pattern p = Pattern.compile("\\w+\\.");
public static void main(String[] args) {
if (args.length < 1) {
System.out.println(usage);
System.exit(0);
}
int lines = 0;
try {
//返回一个Class对象
Class<?> c = Class.forName(args[0]);
Method[] methods = c.getMethods(); //返回一个Method数组
Constructor[] constructors = c.getConstructors(); //返回一个Constructor数组
if (args.length == 1) {
for (Method method : methods) {
System.out.println(p.matcher(method.toString()).replaceAll(""));
}
for (Constructor constructor : constructors) {
System.out.println(p.matcher(constructor.toString()).replaceAll(""));
}
lines = methods.length + constructors.length;
} else {
for (Method method : methods) {
if (method.toString().indexOf(args[1]) != -1) {
System.out.println(p.matcher(method.toString()).replaceAll(""));
lines++;
}
}
for (Constructor constructor : constructors) {
if (constructor.toString().indexOf(args[1]) != -1) {
System.out.println(p.matcher(constructor.toString()).replaceAll(""));
lines++;
}
}
}
} catch (ClassNotFoundException e) {
System.out.println("No such Class"+e);
}
}
}
输出结果:
public static void main(String[])
public final void wait() throws InterruptedException
public final void wait(long,int) throws InterruptedException
public final native void wait(long) throws InterruptedException
public boolean equals(Object)
public String toString()
public native int hashCode()
public final native Class getClass()
public final native void notify()
public final native void notifyAll()
public ShowMethods() //这个是默认的无参构造器
执行上面的main()方法需要手动设置参数,参数为:包名+类名(即类的全限定名)
代理
举个例子来说明代理的作用:假设我们想邀请一位明星,那么并不是直接连接明星,而是联系明星的经纪人,来达到同样的目的,明星就是一个目标对象,他只要负责活动中的节目,而其他琐碎的事情就交给他的代理人(经纪人)来解决这就是代理思想在现实中的一个例子。
代理模式的关键点是:代理对象与目标对象.代理对象是对目标对象的扩展,并会调用目标对象。
静态代理
静态代理在使用时,需要定义接口或者父类,被代理对象与代理对象一起实现相同的接口或者是继承相同父类。
示例如下:
//公共接口(被代理类和代理类都要实现该接口)
package me.ethan.reflect;
public interface Image {
void display();
}
//被代理类
package me.ethan.reflect;
public class RealImage implements Image {
private String fileName;
public RealImage(String fileName) {
this.fileName = fileName;
loadFromDisk(fileName);
}
@Override
public void display() {
System.out.println("Displaying "+fileName);
}
public void loadFromDisk(String fileName) {
System.out.println("Loading "+fileName);
}
}
//代理类
package me.ethan.reflect;
public class ProxyImage implements Image {
private RealImage realImage;
private String fileName;
public ProxyImage(String fileName) {
this.fileName = fileName;
}
@Override
public void display() {
if (realImage == null) {
realImage = new RealImage(fileName);
}
realImage.display();
}
}
//测试类
package me.ethan.reflect;
public class Test {
public static void main(String[] args) {
Image image = new ProxyImage("test.jpg");
//调用了被代理对象的loadFromDisk()方法,从磁盘加载
image.display();
System.out.println("");
//未从磁盘加载
image.display();
}
}
输出如下:
Loading test.jpg
Displaying test.jpg
Displaying test.jpg
静态代理的优缺点:
优点:可以做到在不修改目标对象的功能前提下,对目标功能扩展。
缺点:因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类,类太多,同时,一旦接口增加方法,目标对象与代理对象都要维护。
为了应对静态代理所带来的不便,动态代理应运而生。
JDK动态代理
JDK动态代理有几个特点:
- 代理对象,不需要实现接口
- 代理对象的生成,是利用JDK的API,动态的在内存中构建代理对象(需要我们指定创建代理对象/目标对象实现的接口的类型)
- JDK动态代理也叫做:JDK代理,接口代理
JDK中生成代理对象的API
JDK实现代理只需要使用newProxyInstance()方法,但是该方法需要接收三个参数,完整的写法是:
static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler h )
/*
分别解释一下上面的三个参数
ClassLoader loader:很明显,需要一个类加载器。写法固定:
被代理对象.getClass().getClassLoader();
Class<?>[] interfaces:被代理对象实现的接口的类型,使用泛型方式确认类型。多种写法:
被代理对象.getClass().getInterfaces();
或者
Class[Interface1.class,Interface2.class,Interface3.class……]
InvocationHandler h:一个实现了InvocationHandler接口的类或者匿名内部类均可
*/
示例如下:
//动态代理类
package me.ethan.reflect;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class ProxyFactory {
//维护一个Object对象(意味着可以转为任意的对象)
private Object image;
public ProxyFactory(Object image) {
this.image = image;
}
//JDK动态代理
public Object getProxyInstance() {
return Proxy.newProxyInstance(
image.getClass().getClassLoader(),
image.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("开始事务……");
//执行被代理对象方法
Object obj = method.invoke(image, args);
System.out.println("提交事务……");
return obj;
}
});
}
}
//测试类
package me.ethan.reflect;
public class Test {
public static void main(String[] args) {
//被代理对象
Image image = new RealImage("test.jpg");
System.out.println(image.getClass().getSimpleName()); //类型为RealImage,而不是Image
System.out.println("");
//创建代理对象
Image proxyImage = (Image) new ProxyFactory(image).getProxyInstance();
System.out.println(proxyImage.getClass().getCanonicalName()); //内存中的动态代理对象 com.sun.proxy.$Proxy0
proxyImage.display();
}
}
输出如下:
Loading test.jpg
RealImage
com.sun.proxy.$Proxy0
开始事务……
Displaying test.jpg
提交事务……
缺点:代理对象不需要实现接口,但是目标对象一定要实现接口,否则不能用动态代理
除上述JDK代理之外,还有不是基于接口的Cglib动态代理。
Cglib动态代理
静态代理和动态代理模式都是要求目标对象是实现一个接口的目标对象,但是有时候目标对象只是一个单独的对象,并没有实现任何的接口,这个时候就可以使用以目标对象子类的方式实现代理,这种方法就叫做:Cglib动态代理。
Cglib代理,也叫作子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能的扩展。
- JDK的动态代理有一个限制,就是使用动态代理的对象必须实现一个或多个接口,如果想代理没有实现接口的类,就可以使用Cglib实现
- Cglib是一个强大的高性能的代码生成包,它可以在运行期扩展java类与实现java接口,它广泛的被许多AOP的框架使用,例如Spring AOP和synaop,为他们提供方法的interception(拦截)
- Cglib包的底层是通过使用一个小而快的字节码处理框架ASM来转换字节码并生成新的类,不鼓励直接使用ASM,因为它要求你必须对JVM内部结构包括class文件的格式和指令集都很熟悉
Cglib子类代理实现方法:
- 需要引入cglib的jar文件,但是Spring的核心包中已经包括了Cglib功能,所以直接引入
spring-core:4.3.13.RELEASE
即可 - 引入功能包后,就可以在内存中动态构建子类
- 代理的类不能为final,否则报错
- 目标对象的方法如果为final/static,那么就不会被拦截,即不会执行目标对象额外的业务方法。
示例如下:
导入spring-core:4.3.13.RELEASE
:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>4.3.13.RELEASE</version>
</dependency>
主要代码:
//创建Cglib动态代理类
package me.ethan.reflect;
import org.springframework.cglib.proxy.Callback;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class CglibProxyFactory implements MethodInterceptor {
//维护被代理对象
public Object image;
public CglibProxyFactory(Object image) {
this.image = image;
}
//为被代理对象创建一个代理对象
public Object getProxyInstance() {
//工具类
Enhancer enhancer = new Enhancer();
//设置父类
enhancer.setSuperclass(image.getClass());
//设置回调函数
enhancer.setCallback((Callback) this);
//创建子类(代理对象)
return enhancer.create();
}
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("开始事务……");
//执行目标方法
Object obj = method.invoke(image, objects);
System.out.println("提交事务……");
return obj;
}
}
//测试类
package me.ethan.reflect;
public class Test {
public static void main(String[] args) {
//创建目标对象(假设没有实现任何接口)
RealImage realImage = new RealImage("test.jpg");
//创建代理对象
RealImage proxyImage = (RealImage) new CglibProxyFactory(realImage).getProxyInstance();
//执行代理对象的方法
proxyImage.display();
}
}
输出如下:
Loading test.jpg
开始事务……
Displaying test.jpg
提交事务……
在练习使用Cglib对目标对象进行动态代理时,出现了一个小插曲:首次运行时,抛出异常:
Exception in thread "main" java.lang.IllegalArgumentException: Superclass has no null constructors but no arguments were given
原因:使用Cglib动态代理时,要求目标类必须要有一个无参构造器。
添加无参构造器后运行正常。