反射机制与动态代理
一、反射机制
1.反射机制原理
【反射的解释】
反射机制为什么叫【反射】?一般情况下,我们使用某个类时知道这个类的具体作用,所以我们会直接实例化并进行调用方法或属性的操作,例如:迷路时如果看到志愿者穿着的人,我们会找他们问路。而对于一些不知道的类对象,例如:我们自身的衣装整不整齐,我们很难判断。这时就需要用到【镜子】通过【反射】可以得知自身的一些状态。
【反射机制的原理】
Apple apple = new Apple(); //直接初始化
apple.setPrice(4);
上面这样对类对象的初始化,是已经确定了要运行的类
而反射是一开始不知道要初始化的类是什么,所以无法使用new关键字来创建对象
使用JDK提供的反射API进行反射调用:
Class clz = Class.forName("com.zeta.reflect.Apple");
Method method = clz.getMethod("setPrice", int.class);
Constructor constructor = clz.getConstructor();
Object object = constructor.newInstance();
method.invoke(object, 4);
上面两段代码的执行结果一样,但是思路不一样,第一段代码在未运行时(编译时)就已经确定了要运行的类(Apple),而第二段代码则是在运行时通过字符串值才得知要运行的类(com.zeta.reflect.Apple)
总结:反射就是在运行时才知道要操作的类是什么,并且可以在运行时获取类的完整构造,并调用相应的方法
2.反射机制的优缺点
优点
- 增加程序的灵活性:
通过修改配置文件,灵活更改获取类的全限定名称。避免将程序写死,降低耦合度。 - 节省资源:
Object o = new Object();运行时,java虚拟机会将java文件编译成.class文件,并记载到jvm的内存中,类Object会加载进入方法区,这时候会生成class类的实例对象加载到堆中。在大型工程项目中,很多类暂时用不到,没有必要把每一个类都生成对象。反射机制解决了这个问题,使java从本质的静态语言变成半动态语言。 - 动态特性:
通过一个类的Class对象,可以动态的创建实例、调用方法、访问属性等
静态类型语言:变量定义时有类型声明,且在运行时不能修改
动态类型语言:变量定义时无类型声明,在运行时确定,且在运行时可修改
缺点
- 性能大幅下降
反射机制是一种解释操作,用于字段和方法接入时要远慢于直接代码
3.反射机制的作用
其实反射机制最主要的用处就两个:
-
根据类名在运行时创建实例(类名可以从配置文件中读取,不用new)
- 实例:JDBC获得数据库驱动
Class.forName("com.mysql.jdbc.Driver");
java本身只提供接口,具体的实现需要mysql,orcal,sqlserver这些数据库厂商实现的,如果想要使用java连接到这些数据库,必须要使用这些厂商提供的驱动,厂商提供的驱动都是一些类,类是不会执行的,jvm要为他们生成对象。反射机制 Class.forName会加载类到JVM中,这个过程会执行该类的静态代码,从而加载驱动类。
new com.mysql.jdbc.Driver();
如果使用new的方式创建驱动类,会对这个类产生依赖,后续如果要更换数据库驱动,就得重新修改代码,使用反射的方式,只需要在配置文件中修改相应的驱动和url即可。
jdbc.driver=com.mysql.jdbc.Driver
-
通过Method.invoke()动态执行方法
先通过类.getMethod反射机制来获取方法Method,然后Method.invoke执行方法Method set = class.getMethod("setPrice",int.class); set.invoke(appleObj,14);
- 实例:动态代理
当使用动态代理动态调用方法时,即:
JdkProxyExample jdk = new JdkProxyExample();//创建代理类 HelloWorld proxy = (HelloWorld)jdk.getProxy(new HellpWorldImpl());//生成代理对象,参数:目标对象 proxy.sayHelloWorld();//通过代理对象增强目标对象的方法
上述代码中的jdk为InvocationHandler代理类,proxy为生成的代理对象,proxy.sayHelloworld();相当于,代理类调用了invoke方法并传入了方法参数:
jdk.invoke(proxy,sayHelloWorld);
- 实例:动态代理
二、代理模式
通过代理对象访问目标对象,可以在目标对象的基础上增强额外的功能,如:添加权限,访问控制等
代理三要素
- 有共同的行为——接口
- 目标对象——实现行为
- 代理对象——实现行为,并增强目标对象行为
三、静态代理
简介:
正常编译,在运行前已经存在class。需要为每一个类都编写一个对应的代理类,并且让他们实现相同的接口
缺点:
代理的角色是固定的,需要手动为每一个类都编写相应的代理类,工作量太大
举例:
上述uml图中,其中RealSubject为目标对象,Proxy为代理对象,Subject为共同实现的接口【公共行为】。例如:结婚是【Subject】,是新人【目标对象】和婚庆公司【代理对象】共同要实现的接口,婚庆公司会在新人结婚的基础上,会增强一些行为,比如:主持婚礼、提供场地、准备婚宴等。
实现步骤:
Proxy proxy = new Proxy(RealSubject);
proxy.operation();
其中proxy.operation()是增强后的行为,其包括:
Before();//前置增强
Subject.operation();//此处为RealSubject
After();//后置增强
四、动态代理
简介:
相比静态代理,动态代理在创建代理对象时更加灵活,动态代理类的字节码在程序运行时,由java反射机制动态产生,他会根据需要,在程序运行期间,动态的为目标对象创建代理对象。动态代理有两种实现方式:JDK实现和cglib实现,
动态代理的作用:
简化编程操作、提高可扩展性
1)在目标类源代码不改变的情况下,增加功能
2)减少代码的重复
3)专注业务逻辑diamante
3)解耦合,让你的业务功能和日志,事务非业务功能分离
JDK和Cglib动态代理实现原理
JDK动态代理实现原理
原理: JDK动态代理是java.lang.reflect.*包提供的方式,它必须借助一个接口【公共接口】才能产生代理对象,在JDK动态代理中,要实现代理逻辑类必须去实现java.lang.reflevt.InvocationHandler接口,它里面定义了一个invoke方法,并提供接口数组用于下挂代理对象。
实现步骤:
- 定义接口【公共接口】
public interface HelloWorld(){
public void sayHelloWorld();
}
- 实现接口【目标对象】
public class HelloWorldImpl implements HelloWorld{
@Override
public void sayHelloWorld(){
System.out.println("HelloWorld!");
}
}
- 动态代理绑定和代理逻辑实现
public class JdkProxyExample implements InvocationHandler{
//真实对象
private Object target = null;
/**
* 建立代理对象和真实对象的代理关系,并返回代理对象
* newProxyInstance方法有三个参数:
* loader:目标对象的类加载器
* interfaces:动态代理类需要实现的接口数组,
* handler:代理对象
*/
public Object getProxy(Object target){
this.target = target;
return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(),this);
}
/**
* 代理方法逻辑【增强】
*/
@Override
public Object invoke(Object proxy,Method method,Object[] args) throws Throwable{
System.out.println("进入代理逻辑方法");
System.out.println("在调度真实对象之前的服务");
Object obj = method.invoke(target,args);//相当于调用sayHelloWold方法【方法反射】
System.out.println("在调度真实对象之后的服务");
return obj;//方法返回值
}
}
- 测试JDK动态代理
public void testJdkProxy(){
JdkProxyExample jdk = new JdkProxyExample();
//绑定关系,因为挂在接口HelloWorld下,所以声明代理对象HelloWorld proxy
HelloWorld proxy = (HelloWorld)jdk.getProxy(new HelloWorldImpl());
//注意,此时HelloWorld对象已经是一个代理对象,它会进入代理的逻辑方法invoke里
proxy.sayHelloWorld();//实际运行的是jdk.invoke(proxy,sayHelloWorld,args);方法
}
//测试结果
进入代理逻辑方法
在调度真实对象之前的服务
Helo World
在调度真实对象之后的服务
总结:
JDK代理不需要第三方库支持,只需要JDK环境就可以进行代理,使用条件:
1)实现InvocationHandler
2)使用Proxy.newProxyInstance产生代理对象
3)被代理的对象必须实现公共接口
Cglib动态代理实现原理
原理: JDK动态代理必须提供接口才能使用,在一些不能提供接口【公共接口】的环境中,只能采用其他第三方技术,比如CGLIB动态代理。它的优势在于不需要提供接口,只需要一个非抽象类就能实现动态代理
实现步骤:
- 不存在实现任何接口的类
public class ReflectServiceImpl{
public void sayHello(String name){
System.out.println("Hello" + name);
}
}
- CGLIB动态代理
public class CglibProxyExample implements MethodInterceptor{
/**
* 生成CGLIB代理对象
* class类为目标对象类
*/
public Object getProxy(Class cls){
//CGLIB增强类对象
Enhancer enhancer = new Enhancer();
//设置增强类型
enhancer.setSuperclass(cls);//cls为目标对象类
//定义代理逻辑对象为当前对象,要求当前对象实现MethodInterceptor方法
enhancer.setCallback(this);
//生成并返回代理对象
return enhancer.create();
}
/**
* 代理方法逻辑【增强】
*/
@Override
public Object intercept(Object proxy, Method method, Objectp[] args,MethodProxy methodProxy) thorws Throwable{
System.out.println("调用真实对象前");
//CGLIB反射调用真实对象方法
Object result = methodProxy.invokeSuper(proxy,args);
System.out.println("调用真实对象后");
return result;
}
}
- 测试CGLIB动态代理
public void testCGLIBProxy(){
CglibProxyExample cpe = new CglibProxyExample();
ReflextServiceImpl obj = (ReflextServiceImpl)cpe.getProxy(ReflextServiceImpl.class);
obj.sayHello("张三");
}
//测试结果
调用真实对象前
Hello 张三
调用真实对象后
总结
1.首先实现一个MethodInterceptor【方法拦截器】来生成返回代理对象
2.方法调用会被转发到该类的intercept()方法。
动态代理总结
1.JDK动态代理需要共同的接口
2.CGLIB不需要公共接口,只需要一个非抽象类就介意实现动态代理
参考文档
https://www.cnblogs.com/chanshuyi/p/head_first_of_reflection.html
参考文档
https://developer.aliyun.com/article/502557