欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

JAVA代理那些事儿

程序员文章站 2022-04-30 23:44:56
...

JAVA代理那些事儿

1.先看一个房屋租赁例子

JAVA代理那些事儿
问题:此时若有人来整房东,派很多人来找房东假租房,这会导致房东一天到晚都忙且没收获。带来这个问题就是:重复,且责任不分离,其实房东最关系的就是签合同和收房租。
JAVA代理那些事儿

静态代理

1.代理模式

客户端直接使用的都是代理对象,不知道真实对象是谁,此时代理对象可以在客户端和真实对象之间起到中介的作用。
1.1、代理对象完全包含真实对象,客户端使用的都是代理对象的方法,和真实对象没有直接关系;
1.2、代理模式的职责:把不是真实对象该做的事情从真实对象上撇开——职责分离。

2.定义流程

在程序运行前就已经存在代理类的字节码文件,代理对象和真实对象的关系在运行前就确定了。
JAVA代理那些事儿

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("回滚事务");
	}
}

配置文件
JAVA代理那些事儿
测试用例:

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("你好世界!");
	}

}

控制台输出结果:
JAVA代理那些事儿

4.静态代理优缺点

优点
业务类只需要关注业务逻辑本身,保证了业务类的重用性。
把真实对象隐藏起来了,保护真实对象。

缺点:
代理对象的某个接口只服务于某一种类型的对象,也就是为每个真实类创建一个代理类,比如项目还有其他 service 呢。
若需要代理的方法很多,则要为每一种方法都进行代理处理。
若接口增加一个方法,除了所有实现类需要实现这个方法外,代理类也需要实现此方法。

动态代理

1.问题

就是静态代理的缺点:需要为每个真实类创建一个代理类,随着程序规模变大导致代理类急剧膨胀。可以通过动态代理解决。

2.字节码加载

JAVA代理那些事儿
如何动态的创建一份字节码?
由于 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给注释了.
JAVA代理那些事儿
测试用例:

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!");
	}

}

控制台输出结果:
JAVA代理那些事儿
JAVA代理那些事儿

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、调用流程
JAVA代理那些事儿
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、调用流程

JAVA代理那些事儿

动态代理总结

1、动态代理图示

JAVA代理那些事儿

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 接口的实现类。