java动态代理详解-java动态代理
目录
概念
代理:为了控制A对象,创建一个新的B对象,由B对象代替执行A对象的所有操作,称为代理。 代理系统的建立涉及三个参与角色:真实对象(A)、代理对象(B)和客户端。
代理对象(B)起中介作用,连接真实对象(A)和客户端。 如果进一步扩展,代理对象可以实现更复杂的逻辑,比如对真实对象的访问控制。
案子
需求:员工业务层接口调用save需要admin权限,调用list不需要权限,未经权限调用会抛出异常提示。
静态代理
/**
* 代理接口
*/
public interface IEmployeeService {
void save();
void list();
}
/**
* 真实对象
*/
public class EmployeeServiceImpl implements IEmployeeService {
@Override
public void save() {
System.out.println("EmployeeServiceImpl-正常的save....");
}
@Override
public void list() {
System.out.println("EmployeeServiceImpl-正常的list....");
}
}
/**
* 模拟当前登录用户对象
*/
public class SessionHolder {
private static String currentUser;
public static String getCurrentUser(){
return currentUser;
}
public static void setCurrentUser(String currentUser){
SessionHolder.currentUser = currentUser;
}
}
/**
* 代理对象
*/
public class EmployeeProxy implements IEmployeeService {
//真实对象
private EmployeeServiceImpl employeeService;
public EmployeeProxy(EmployeeServiceImpl employeeService){
this.employeeService = employeeService;
}
@Override
public void save() {
//权限判断
if("admin".equals(SessionHolder.getCurrentUser())){
employeeService.save();
}else{
throw new RuntimeException("当前非admin用户,不能执行save操作");
}
}
@Override
public void list() {
employeeService.list();
}
}
public class App {
public static void main(String[] args) {
System.out.println("----------------真实对象--------------------");
EmployeeServiceImpl employeeService = new EmployeeServiceImpl();
employeeService.list();
employeeService.save();
System.out.println("----------------代理对象--------------------");
SessionHolder.setCurrentUser("dafei"); //设置权限(当前登录用户)
EmployeeProxy employeeProxy = new EmployeeProxy(employeeService);
employeeProxy.list();
employeeProxy.save();
}
}
----------------真实对象--------------------
EmployeeServiceImpl-正常的list....
EmployeeServiceImpl-正常的save....
----------------代理对象--------------------
EmployeeServiceImpl-正常的list....
Exception in thread "main" java.lang.RuntimeException: 当前非admin用户,不能执行save操作
at com.langfeiyes.pattern.proxy.demo.EmployeeProxy.save(EmployeeProxy.java:20)
at com.langfeiyes.pattern.proxy.demo.App.main(App.java:16)
使用真实对象EmployeeServiceImpl直接调用时,list和save都可以直接访问,但不满足admin权限限制要求。 如果使用代理对象EmployeeProxy,就可以实现需求。
代理逻辑是通过直接新建一类代理对象来完成的,称为静态代理模式。
JDK动态代理模式
Java中常用的动态代理模式有JDK动态代理和cglib动态代理。 这里重点介绍JDK动态代理
还是原来的需求,之前的IEmployeeService EmployeeServiceImpl SessionHolder没有变化,增加了一个新的JDK代理控制器-EmployeeInvocationHandler
/**
* jdk动态代理控制类,由它牵头代理类获取,代理方法的执行
*/
public class EmployeeInvocationHandler implements InvocationHandler {
//真实对象-EmployeeServiceImpl
private Object target;
public EmployeeInvocationHandler(Object target){
this.target = target;
}
//获取jvm在内存中生成代理对象
public Object getProxy(){
return Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
this);
}
//代理对象控制执行方法
//参数1:代理对象
//参数2:真实对象的方法(使用方式得到方法对象)
//参数3:真实对象方法参数列表
//此处是代理对象对外暴露的可编辑的方法处理场所,代理对象每调用一个次方法,就会执行一次invoke
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String name = method.getName();
if("save".equals(name) && !"admin".equals(SessionHolder.getCurrentUser())){
throw new RuntimeException("当前非admin用户,不能执行save操作");
}
return method.invoke(target, args);
}
}
测试 App 类略有变化:
public class App {
public static void main(String[] args) {
System.out.println("----------------真实对象--------------------");
EmployeeServiceImpl employeeService = new EmployeeServiceImpl();
employeeService.list();
employeeService.save();
System.out.println("----------------代理对象--------------------");
SessionHolder.setCurrentUser("dafei");
EmployeeInvocationHandler handler =
new EmployeeInvocationHandler(employeeService);
IEmployeeService proxy = (IEmployeeService) handler.getProxy();
proxy.list();
proxy.save();
}
}
上面的代码也能满足要求,和静态代理的区别是创建的代理对象少。 这时候就有一个疑惑点,没有创建代理对象,为什么代理类调用可以实现呢? ?
原理分析
先总结一下JDK动态代理的底层实现原理:使用接口实现方式在运行时在内存中动态构建一个类,然后编译执行。 这个类是一次性的,JVM停止,代理类消失。
参与角色 要了解JDK动态代理的原理,首先要了解JDK动态代理涉及的类
InvocationHandler:真实对象方法调用处理器,内置invoke方法,其作用:自定义真实对象的代理逻辑
EmployeeInvocationHandler:Employee服务真实对象方法调用处理程序java动态代理详解,该类有3个用途:1>设置真实对象
//真实对象-EmployeeServiceImpl
private Object target;
public EmployeeInvocationHandler(Object target){
this.target = target;
}
2> 自定义代理方法实现逻辑
增加真实对象保存方法的权限验证逻辑
//代理对象控制执行方法
//参数1:代理对象
//参数2:真实对象的方法(使用方式得到方法对象)
//参数3:真实对象方法参数列表
//此处是代理对象对外暴露的可编辑的方法处理场所,代理对象每调用一个次方法,就会执行一次invoke
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String name = method.getName();
if("save".equals(name) && !"admin".equals(SessionHolder.getCurrentUser())){
throw new RuntimeException("当前非admin用户,不能执行save操作");
}
return method.invoke(target, args);
}
3> 返回代理对象
该方法执行后返回一个名为:$ProxyX(其中X为序列号,一般默认为0)的代理类,由JDK动态构造。
//获取jvm在内存中生成代理对象
public Object getProxy(){
return Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
this);
}
Proxy:动态代理控件类,是JDK动态生成的$ProxyX类的父类。 其功能如下:
1> 通过调用ProxyBuilder类构建器方法构建代理对象类
private static Constructor> getProxyConstructor(Class> caller,
ClassLoader loader,
Class>... interfaces){
return proxyCache.sub(intf).computeIfAbsent(
loader,
(ld, clv) -> new ProxyBuilder(ld, clv.key()).build()
);
}
2> 通过newProxyInstance方法返回$ProxyX类的实例
public static Object newProxyInstance(ClassLoader loader,
Class>[] interfaces,
InvocationHandler h) {
//...
}
$Proxy0:App类运行时,JDK动态构造的代理类,继承自Proxy类
public class App {
public static void main(String[] args) {
//System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
System.setProperty("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true");
System.out.println("----------------真实对象--------------------");
EmployeeServiceImpl employeeService = new EmployeeServiceImpl();
employeeService.list();
employeeService.save();
System.out.println("----------------代理对象--------------------");
SessionHolder.setCurrentUser("dafei");
EmployeeInvocationHandler handler =
new EmployeeInvocationHandler(employeeService);
IEmployeeService proxy = (IEmployeeService) handler.getProxy();
proxy.list();
proxy.save();
}
}
默认情况下,JVM 不保存动态创建的代理类字节码对象。 可以在main方法中配置代理参数,保持字节码
//JDK8之前
System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
//JDK8之后
System.setProperty("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true");
执行后会在项目根目录下生成代理类字节码对象。
为了方便解释,去掉一些不需要的方法后
$Proxy0 类
public class $Proxy0 extends Proxy implements IEmployeeService {
private static Method m4;
private static Method m3;
static {
try {
m4 = Class.forName("com.langfeiyes.proxy.demo.IEmployeeService")
.getMethod("save");
m3 = Class.forName("com.langfeiyes.proxy.demo.IEmployeeService")
.getMethod("list");
} catch (Exception e) {
e.printStackTrace();
}
}
public $Proxy0(InvocationHandler var1) throws Throwable {
super(var1);
}
public final void save() throws Throwable {
super.h.invoke(this, m4, (Object[])null);
}
public final void list() throws Throwable{
super.h.invoke(this, m3, (Object[])null);
}
}
从源码来看java动态代理详解,$Proxy0的特点:
真相大白
下图中所有参与动态代理的类:
下图是上图的操作时序图,跟着走就行了
至此,JDK动态代理就ok了。