JAVA代理那些事儿
JAVA代理那些事儿
1.先看一个房屋租赁例子
问题:此时若有人来整房东,派很多人来找房东假租房,这会导致房东一天到晚都忙且没收获。带来这个问题就是:重复,且责任不分离,其实房东最关系的就是签合同和收房租。
静态代理
1.代理模式
客户端直接使用的都是代理对象,不知道真实对象是谁,此时代理对象可以在客户端和真实对象之间起到中介的作用。
1.1、代理对象完全包含真实对象,客户端使用的都是代理对象的方法,和真实对象没有直接关系;
1.2、代理模式的职责:把不是真实对象该做的事情从真实对象上撇开——职责分离。
2.定义流程
在程序运行前就已经存在代理类的字节码文件,代理对象和真实对象的关系在运行前就确定了。
3.代码实现
代理接口
package cn.dale.spring.static_proxy.service;
public interface IEmployeeService {
void save(String name);
void update(String name);
}
真实类或委托类
package cn.dale.spring.static_proxy.service.impl;
import cn.dale.spring.static_proxy.service.IEmployeeService;
public class EmployeeServiceImpl implements IEmployeeService {
public void save(String name) {
System.out.println("保存" + name);
}
public void update(String name) {
System.out.println("更新" + name);
}
}
代理类
package cn.dale.spring.static_proxy.service.impl;
import org.springframework.beans.factory.annotation.Autowired;
import cn.dale.spring.static_proxy.service.IEmployeeService;
import cn.dale.spring.static_proxy.tx.MyTx;
public class EmployeeServiceProxy implements IEmployeeService {
@Autowired
private IEmployeeService service;//观察能否调用到另一个实现类EmployeeServiceImpl的save()方法
@Autowired
private MyTx tx;
public void setService(IEmployeeService service) {
this.service = service;
}
// public void setTx(MyTx tx) {
// this.tx = tx;
// }
public void save(String name) {
tx.begin();
try {
service.save(name);
tx.commit();
} catch (Exception e) {
tx.rollback();
} finally {
System.out.println("释放资源");
}
}
public void update(String name) {
tx.begin();
try {
service.update(name);
tx.commit();
} catch (Exception e) {
tx.rollback();
} finally {
System.out.println("释放资源");
}
}
}
事务处理类
package cn.dale.spring.static_proxy.tx;
import org.springframework.stereotype.Component;
@Component(value="tx")
public class MyTx {
public void begin(){
System.out.println("开启事务");
}
public void commit(){
System.out.println("提交事务");
}
public void rollback(){
System.out.println("回滚事务");
}
}
配置文件
测试用例:
package cn.dale.spring.static_proxy;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import cn.dale.spring.static_proxy.service.IEmployeeService;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class static_proxyTest {
@Autowired
private IEmployeeService proxy;
@Test
public void testStatic_Proxy() {
proxy.save("HelloWorld!");
proxy.update("你好世界!");
}
}
控制台输出结果:
4.静态代理优缺点
优点:
业务类只需要关注业务逻辑本身,保证了业务类的重用性。
把真实对象隐藏起来了,保护真实对象。
缺点:
代理对象的某个接口只服务于某一种类型的对象,也就是为每个真实类创建一个代理类,比如项目还有其他 service 呢。
若需要代理的方法很多,则要为每一种方法都进行代理处理。
若接口增加一个方法,除了所有实现类需要实现这个方法外,代理类也需要实现此方法。
动态代理
1.问题
就是静态代理的缺点:需要为每个真实类创建一个代理类,随着程序规模变大导致代理类急剧膨胀。可以通过动态代理解决。
2.字节码加载
如何动态的创建一份字节码?
由于 JVM 通过字节码的二进制信息加载类的,如果我们在运行期系统中,遵循 Java 编译系统组织 .class 文件的格式和结构,生成相应的二进制数据,然后再把这个二进制数据加载转换成对应的类。如此,就完成了在代码中动态创建一个类的能力了。
3.动态代理动态生成字节码
动态代理类是在程序运行期间由 JVM 通过反射等机制动态的生成的,所以不存在代理类的字节码文件,动态生成字节码对象,代理对象和真实对象的关系是在程序运行时期才确定的。
4.实现动态代理方式
4.1、针对有接口:使用 JDK 动态代理;
4.2、针对无接口:使用 CGLIB 或 Javassist 组件。
接下来详细讲解JDK动态代理
1、前提
委托类(真实类),必须实现接口。
2、JDK 动态代理 API
2.1、java.lang.reflect.Proxy 类
Java 动态代理机制生成的所有动态代理类的父类,它提供了一组静态方法来为一组接口动态地生成代理类及其对象。
主要方法:public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler hanlder)
方法职责:为指定类加载器、一组接口及调用处理器生成动态代理类实例
参数:
loader :类加载器,一般传递真实对象的类加载器
interfaces:代理类需要实现的接口
handler:代理执行处理器,说人话就是生成代理对象要帮你做什么
返回:创建的代理对象
2.2、java.lang.reflect.InvocationHandler 接口
主要方法:public Object invoke(Object proxy, Method method, Object[] args)
方法职责:负责集中处理动态代理类上的所有方法调用,让使用者自定义做什么事情,对原来方法增强。
参数:
proxy :生成的代理对象
method:当前调用的真实方法对象
args :当前调用方法的实参
返回:真实方法的返回结果
3、操作步骤
3.1、定义封装事务操作的一个模拟类。
3.2、实现 InvocationHandler 接口,实现 invoke 方法,实现增强操作。
3.3、在 Spring 配置文件中配置 InvocationHandler 实现类、事务操作模拟类、真实对象,让其帮我们创建对象组装依赖。
3.4、在单元测试类中注入 InvocationHandler 的 bean,在测试方法中手动使用 Proxy 创建代理对象,调用代理对象的方法
4、代码实现
事务操作类
package cn.dale.spring.jdk_proxy.tx;
import org.springframework.stereotype.Component;
@Component
public class MyTx {
public void begin(){
System.out.println("开启事务");
}
public void commit(){
System.out.println("提交事务");
}
public void rollback(){
System.out.println("回滚事务");
}
}
真实类或委托类,就是房东
package cn.dale.spring.jdk_proxy.service.impl;
import org.springframework.stereotype.Service;
import cn.dale.spring.jdk_proxy.service.IEmployeeService;
@Service(value="service")
public class EmployeeServiceImpl implements IEmployeeService {
public void save(String name) {
System.out.println(1/0);
System.out.println("保存" + name);
}
public void update(String name) {
System.out.println("更新" + name);
}
}
代理执行处理器类
package cn.dale.spring.jdk_proxy.handler;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import cn.dale.spring.jdk_proxy.tx.MyTx;
@Component
public class TxInvocationHandler implements InvocationHandler{
@Autowired
private Object service;//真实对象的引用,类型是Object
public Object getService() {
return service;
}
@Autowired
private MyTx tx;//事务处理对象的引用
//负责集中处理动态代理类上的所有方法调用,让使用者自定义做什么事情,对原来方法做增强
//proxy代理对象,method调用的方法,args方法调用参数
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object ret = null;
tx.begin();
try {
//调用真实对象的方法
ret = method.invoke(service, args);
tx.commit();
} catch (Throwable e) {
tx.rollback();
}
return ret;
}
}
配置文件,由于事务处理器对象和事务处理器代理类都用注解配置了,故配置文件的相关bean给注释了.
测试用例:
package cn.dale.spring.jdk_proxy;
import java.lang.reflect.Proxy;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import cn.dale.spring.jdk_proxy.handler.TxInvocationHandler;
import cn.dale.spring.jdk_proxy.service.IEmployeeService;
/**
* @author dale
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext-jdk_proxy.xml")
public class jdk_proxyTest {
//注入代理执行处理器对象
@Autowired
private TxInvocationHandler handler;
@Test
public void testSave() {
//根据提供的条件动态生成代理类及创建其对象
IEmployeeService service = (IEmployeeService)Proxy.newProxyInstance
(handler.getService().getClass().getClassLoader(),
handler.getService().getClass().getInterfaces(),
handler);
//调用代理类的方法
service.save("HelloWorld!");
}
@Test
public void testUpdate() {
IEmployeeService service = (IEmployeeService)Proxy.newProxyInstance
(handler.getService().getClass().getClassLoader(),
handler.getService().getClass().getInterfaces(),
handler);
service.update("HelloWorld!");
}
}
控制台输出结果:
5、JDK动态代理原理
1、生成动态代理的字节码
执行main方法生成字节码
import java.io.FileOutputStream;
import sun.misc.ProxyGenerator;
@SuppressWarnings("restriction")
public class DynamicProxyClassGenerator {
public static void main(String[] args) throws Exception {
generateClassFile(EmployeeServiceImpl.class, "EmployeeServiceProxy2");
}
public static void generateClassFile(Class<?> targetClass, String proxyName) throws Exception {
// 根据类信息和提供的代理类名称,生成字节码
byte[] classFile = ProxyGenerator.generateProxyClass(proxyName, targetClass.getInterfaces());
String path = targetClass.getResource(".").getPath();
System.out.println(path);
FileOutputStream out = null;
// 保留到硬盘中
out = new FileOutputStream(path + proxyName + ".class");
out.write(classFile);
out.close();
}
}
2、 通过反编译工具查看字节码文件
观察:save 方法,发现底层其实依然在执行 InvocationHandler 中的 invoke 方法。
public final void save(String paramString)
throws
{
try
{
//h是增强处理器
this.h.invoke(this, m4, new Object[] { paramString });
return;
}
catch (RuntimeException localRuntimeException)
{
throw localRuntimeException;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
3、调用流程
4、优缺点
优点:对比静态代理,发现不需手动地提供那么多代理类。
缺点:
1. 真实对象必需实现接口(JDK 动态代理特有)。
2. 动态代理的最小单位是类(类中某些方法都会被处理),如果只想拦截一部分方法,可以在 invoke 方法中对要执行的方法名进行判 断。
3. 对多个真实对象进行代理的话,若使用 Spring 的话配置太多了。
4. 要手动创建代理对象,用起来麻烦。
接下来详细讲解CGLIB动态代理和原理
1、JDK 动态代理的问题
JDK 动态代理要求真实类必须实现接口。而 CGLIB 与 JDK 动态代理不同是,真实类不用实现接口,生成代理类的代码不一样且代理类会继承真实类。
2、CGLIB 动态代理 API
org.springframework.cglib.proxy.Enhancer,类似 JDK 中 Proxy,用来生成代理类创建代理对象的。
org.springframework.cglib.proxy.InvocationHandler,类似 JDK 中 InvocationHandler,让使用者自定义做什么事情,对原来方法增强。
3、操作步骤
3.1、修改 TransactionHandler 实现 org.springframework.cglib.proxy.InvocationHandler 接口,其他不变。
3.2、修改单元测试类中的测试方法,改用 Enhancer 来生成代理类创建代理对象的。
4、代码实现
代理执行处理器类
package cn.dale.spring.cglib_proxy.handler;
import java.lang.reflect.Method;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import cn.dale.spring.cglib_proxy.tx.MyTx;
@Component(value="txx")
public class TxInvocationHandler implements org.springframework.cglib.proxy.InvocationHandler{
@Autowired
private Object service;
public Object getService() {
return service;
}
@Autowired
private MyTx tx;
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object ret = null;
tx.begin();
try {
ret = method.invoke(service, args);
tx.commit();
} catch (Exception e) {
tx.rollback();
}
return ret;
}
}
其他的组件和JDK动态代理一样!
测试用例:
package cn.dale.spring.cglib_proxy;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import cn.dale.spring.cglib_proxy.handler.TxInvocationHandler;
import cn.dale.spring.cglib_proxy.service.impl.EmployeeServiceImpl;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext-cglib_proxy.xml")
public class Cglib_ProxyTest {
//注入代理执行处理器对象
@Autowired
private TxInvocationHandler handler;
@Test
public void testCglib_Proxy() {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(handler.getService().getClass().getClass());//设置代理类的父类对象为真实对象
enhancer.setCallback(handler);//设置真实对象方法增强
EmployeeServiceImpl proxy = (EmployeeServiceImpl)enhancer.create();//根据提供的条件生成动态代理类及其对象
proxy.save("HelloWorld!");//调用代理对象方法
proxy.update("HelloWorld!");
}
}
5、调用流程
动态代理总结
1、动态代理图示
2、JDK动态代理总结
2.1、Java 动态代理是使用 java.lang.reflect 包中的 Proxy 类与 InvocationHandler 接口这两个来完成的。
2.2、要使用 JDK 动态代理,真实类必须实现接口。**
2.3、JDK 动态代理将会拦截所有 pubic 的方法(因为只能调用接口中定义的方法),这样即使在接口中增加了新的方法,不用修改代码也会被拦截。
2.4、动态代理的最小单位是类(类中某些方法都会被处理),如果只想拦截一部分方法,可以在 invoke 方法中对要执行的方法名进行判断。
3、CGLIB 动态代理总结
3.1、CGLIB 可以生成真实类的子类,并重写父类非 final 修饰符的方法。
3.2、要求类不能是 final 的,要拦截的方法要是非 final、非 static、非 private 的。
3.3、动态代理的最小单位是类(类中某些方法都会被处理),如果只想拦截一部分方法,可以在 invoke 方法中对要执行的方法名进行判断。
4、关于性能
JDK 动态代理是基于实现接口的,CGLIB 和 Javassit 是基于继承委托类的。
从性能上考虑:Javassit > CGLIB > JDK。
MyBatis 延迟加载对象,采用的是 Javassit 的方式。
对接口创建代理优于对类创建代理,因为会产生更加松耦合的系统,也更符合面向接口编程规范。
若委托类实现了接口,优先选用 JDK 动态代理。
若委托类没有实现任何接口,使用 Javassit 和 CGLIB 动态代理。
动态代理问题:对多个 service 对象增强配置太多,还有要手动创建代理对象,在使用时不是面向接口,还要编写 InvocationHandler 接口的实现类。