【Java】Java强大的机制——代理机制

本文深入讲解Java中的代理机制,重点介绍静态代理、JDK动态代理和Cglib动态代理的区别及应用场景。通过实例演示如何使用代理模式扩展目标对象的功能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


  今天来整理新学到的Java中一种强大的机制——代理机制。先简单讲下我在网上看到的代理机制的描述,有一个目标类的对象,他不直接面对用户,而是在它的基础之上有另外一个对象,称为代理对象,用户用的是这个代理对象,真正的操作还是用的目标对象。接下来给例子讲下两种动态代理JDK和Cglib。

  代理(Proxy)是一种设计模式,定义:为其他对象提供一个代理以控制对某个对象的访问,即通过代理对象访问目标对象。这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能。

  这里使用到编程中的一个思想:不要随意去修改别人已经写好的代码或者方法,如果需改修改,可以通过代理的方式来扩展该方法。(开闭原则)

  代理模式的关键点是:代理对象与目标对象。代理对象是对目标对象的扩展,并会调用目标对象。

静态代理

静态代理在使用时,需要定义接口或者父类,被代理对象与代理对象一起实现相同的接口或者是继承相同父类。
关键:在编译期确定代理对象,在程序运行前代理类的.class 文件就已经存在了。

public interface IUserDao {

	void save();
}

public class UserDao implements IUserDao {

	@Override
	public void save() {
		System.out.println("数据已经保存");
	}

}

public class UserDaoProxy implements IUserDao {

	private IUserDao target;
	
	public UserDaoProxy(IUserDao target) {
		this.target = target;
	}
	
	@Override
	public void save() {
		System.out.println("开始事务");
		target.save();
		System.out.println("结束事务");
	}

}

public class Test {

	public static void main(String[] args) {
		UserDao userDao = new UserDao();//目标对象
		UserDaoProxy userDaoProxy = new UserDaoProxy(userDao);//代理对象,把目标传递给代理对象,建立代理关系
		userDaoProxy.save();//执行代理方法
	}

}

静态代理优点:可以做到在不修改目标对象的功能前提下,对目标功能扩展。

静态代理缺点:代理类和目标类实现相同的接口,同时要实现相同的方法。这样就出现了大量的代码重复。如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。

一:JDK代理

直接简单粗暴点上例子,给出我们要代理的目标类SomeClass和接口ISomeClass

public interface ISomeClass {

	void fun1();
	String fun2(int num);
}
public class SomeClass implements ISomeClass {

	public SomeClass() {
	}
	
	@Override
	public void fun1() {
		System.out.println("执行fun1()");
	}

	@Override
	public String fun2(int num) {
		System.out.println("执行fun2()");
		return "num为" + num;
	}

	public void classSpMethod() {
		System.out.println("SomeClass没有接口限制,特有的方法");
	}
	
	public final void finalMethod() {
		System.out.println("被final修饰的方法");
	}
	
}


JDKProxy:JDK代理的获取,利用java.lang.reflect.Proxy类

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class JDKProxy {
	
	public JDKProxy() {
	}
	
	@SuppressWarnings("unchecked")
	public static <T> T getJDKProxy(Object object) {
		Class<?> klass = object.getClass();
		ClassLoader classLoder = klass.getClassLoader();
		Class<?>[] interfaces = klass.getInterfaces();
		return (T) Proxy.newProxyInstance(classLoder, interfaces, new InvocationHandler() {
			
			@Override
			public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
				Object result = null;
				System.out.println("前置拦截");
				try {
					result = method.invoke(object, args);
					System.out.println("后置拦截");
				} catch(Throwable e){
					System.out.println("异常拦截");
					throw e;
				}
				
				return result;
			}
		});
	}
}

JDKProxy测试

public class JDKProxyDemo {

	public static void main(String[] args) {

		SomeClass someClass = new SomeClass();
		ISomeClass jdkProxy = JDKProxy.getJDKProxy(someClass);//必须强制为接口类型,否则报错
		jdkProxy.fun1();
		String result = jdkProxy.fun2(100);
		System.out.println(result);
//		jdkProxy.finalMethod();报错,没定义
		//验证其内部关系
		System.out.println("jdkProxy instanceof SomeClass:" + (jdkProxy instanceof SomeClass));
		System.out.println("jdkProxy instanceof ISomeClass:" + (jdkProxy instanceof ISomeClass));
		System.out.println("someClass instanceof ISomeClass:" + (someClass instanceof ISomeClass));
	}

}


/*输出结果
前置拦截
执行fun1()
后置拦截
前置拦截
执行fun2()
后置拦截
num为100
jdkProxy instanceof SomeClass:false
jdkProxy instanceof ISomeClass:true
someClass instanceof ISomeClass:true
*/

从上述结果可以知道我们成功的获取了相关类的JDK代理。

从验证内部关系我们可以得到如下结论:

在这里插入图片描述
JDK产生的代理对象和目标对象好像一个兄弟关系,共同的父亲是那个接口。

JDK代理要求

  被代理的类必须是接口的实现类,往后这个代理对象必须强转为接口类型,因为产生的代理对象是接口类实现类类型,代理对象只能调用接口方法(帽子现在为接口)。

二:Cglib代理

需要cglib-nodep-x.x.x.jar的jar包。

CglibProxy:Cglib代理的获取

import java.lang.reflect.Method;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

public class CglibProxy {

	public CglibProxy() {
	}
	
	@SuppressWarnings("unchecked")
	public static <T> T getCglibProxy(Object object) {
		Class<?> klass = object.getClass();
		Enhancer enhancer = new Enhancer();
		enhancer.setSuperclass(klass);	//从这里就可以看出Cglib创建代理的端倪
		enhancer.setCallback(new MethodInterceptor() {	//上面全当巫师的咒语吧
			
			@Override
			public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
				Object result = null;
				System.out.println("前置拦截");
				try {
					result = method.invoke(object, args);
					System.out.println("后置拦截");
				} catch(Throwable e) {
					System.out.println("异常拦截");
					throw e;
				}
				
				return result;
			}
		});
		return (T) enhancer.create();
	}
}

还是上面SomeClass的相关测试

public class CglibProxyDemo {

	public static void main(String[] args) {
		SomeClass someClass = new SomeClass();
		SomeClass cglibProxy = CglibProxy.getCglibProxy(someClass);
		cglibProxy.fun1();
		String result = cglibProxy.fun2(100);
		System.out.println(result);
		cglibProxy.classSpMethod();
		cglibProxy.finalMethod();
		System.out.println("cglibProxy instanceof SomeClass:" + (cglibProxy instanceof SomeClass));
	}
}

/*输出结果
前置拦截
执行fun1()
后置拦截
前置拦截
执行fun2()
后置拦截
num为100
前置拦截
SomeClass没有接口限制,特有的方法
后置拦截
被final修饰的方法
cglibProxy instanceof SomeClass:true

*/

Cglib代理分析

从结果分析我们可以得知:

Cglib产生的代理对象和目标对象是个父子关系,父为目标对象的类子类对象可以强转为父类型,所有目标类里面的方法(对外提供的)都可以调用,被代理的类不需要实现接口了

但是也正是因为代理对象是被代理的类的派生类的对象。因为派生(继承的关系),父类final方法不能覆盖,不能进行代理了,但是还是可以调用!final修饰方法可以执行,但是不能被代理增加相应东西了若代理类本身是final类,则根本就不可能被代理。

简述原理:实际上是动态生成了被代理类的子类字节码,因为字节码都是按照JVM编译后形成的class文件的规范编写的,所以它可以被JVM正常加载并且运行。

三:代理作用

  学完代理,第一疑惑就是这有啥用?目标类的对象都已经new出来了,直接调用其方法不就行了,还要进行这复杂的操作,又是写接口又是导包的。

  确实,现在我们没有需求只是简单接触代理当然感觉没用。就像刚学反射机制的时候我也这么想,后来用反射机制的时候那真是香的一X。

  存在即合理。这种机制既然Java前辈为我们辛辛苦苦(代理和字节码有关)创造出来了,那么它后面一定会大放异彩的。

先简单说下目前我能想到的作用!

  1. 可以在参数那里偷梁换柱!
			@Override
			public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
				Object result = null;
				System.out.println("前置拦截");
				try {
					args = new Object[] {Integer.valueOf(98)};	//可以在这里改参数
					result = method.invoke(object, args);
					System.out.println("后置拦截");
				} catch(Throwable e) {
					System.out.println("异常拦截");
					throw e;
				}
				
				return result;
			}
public class Demo {

	public static void main(String[] args) {
		SomeClass someClass = new SomeClass();
		SomeClass cglibProxy = CglibProxy.getCglibProxy(someClass);
		String result = cglibProxy.fun2(100);
		System.out.println(result);
	}
}

/*输出结果
前置拦截
执行fun2()
后置拦截
num为98
*/

可以看到我明明传进去的是100,但是可以在代理里改相关的参数。用户传递参数过来,我们做事需要用这些参数做一些封装和调整

  1. 相关的拦截器
  • 前置拦截:例如判断用户是否有权限进行这个方法的调用,没有权限就可以阻止该方法的执行。

  • 后置拦截:记录哪个用户哪个时间做了什么的详细过程。具体可以写到外存做成日志。

  • 异常拦截:方法执行出现异常,执行需要做的事。

四:代理使用场景

  1. RMI短连接,就是用的JDKProxy,因为接口成为了客户端和服务器之间的必须遵守的规范!接口是规范,是约束!

  2. AOP。非侵入式的功能扩充。不改变原来代码的任何内容,可以对功能进行增加(体现在前后置拦截)。满足开闭原则:对修改关闭对扩展开放!

这些都是后面博文所要涉及到的问题,等我写到了再把代理拿出来一起讲,理解就更深了。

五:后记(Q&A)

对于学习代理机制我有以下思考的问题。

  • 问题一:既然Cglib代理能调用所有可以被继承的方法,那也包括接口所给的方法,这样看来Cglib代理比JDK代理好多了,只用Cglib“以偏概全”不行吗?

    答:Cglib确实在使用的时候看起来能力更多点,但是能力越大责任越多,安全因素也就更多。Cglib代理这种能力大的代理在“工程控制”角度而言是有害的,会导致滥用的问题。而JDK因为使用接口正好补上了这个缺陷。接口是简单的,简单就是安全!再次强调接口是规范,接口是约束!规定了该做什么不该做什么,接口是面向未来的,为未来的工程考虑的!

  • 问题二:在来解释下博文开始提到的:用户用的是这个代理对象,真正的操作还是用的目标对象。该如何理解?

    答:从上面的所有例子我们就可以看到。调用方法的时候都是method.invoke(object, args),这个object就是我们传参传过来的需要代理的目标类的对象。所以再次强调,当我们使用代理对象执行方法的时候,其内部调用方法的对象其实还是原对象,并不是代理对象执行该方法! 因为你必须是原对象调用方法才会对原对象产生相应的改变啊,代理对象可以看成就是AOP(面向切面编程),切点就是执行的方法,可以切前扩展(前置拦截),切后扩展(后置拦截)!

静态代理和动态代理的区别。

静态代理的代码需要在编译前自己写好(例:UserDaoProxy),也正因为此造成了改一处改多出的情况,导致代码维护起来不容易。动态代理,在运行期默默帮你写了(例JDK的$Proxy0.class),不用明确定义代理类,动态生成代理对象给你。

JDK代理具体分析

要想弄明白JDK动态代理,将它动态生成的匿名类$Proxy0.class文件反编译成源码分析一下就可以得到。与字节码有关的知识。JDK代理为什么必须强调接口。因为只有给定接口,JDK的Proxy经过一系列操作(asm操作)动态生成的匿名类$Proxy0.class文件才能把相关方法填进去。(实验得知。任何Java生成的class文件二进制开头cafe baby)

反射

所谓反射,不需要看到源码,通过分析通过classloader加载到内存的二进制字节码,可以知道这个类的一些属性和方法。

动态

Java是动态语言,动态意义就是在运行过程中可以对类的相关方法属性进行操作,添加或删除。

cglib问题

cg(codeGenerate)代码生成。从这单词可以得知依然与字节码有关。cglib不用接口,因为分析可得知它生成的是被代理类的子类,就是对被代理类的所有方法进行拦截。

cglib底层还是使用的asm。

AOP初识

例子:业务逻辑代码都写好了。 生产 ,库存,订单。 突然有需求,要求在所有的业务逻辑代码的所有操作上都加上权限检查,没有权限不能进行方法的操作。 怎么做?第一种方法是找到所有方法,对所有方法加操作。这种太麻烦。

第二种解决方法生成动态代理,每一个业务逻辑类生成动态代理,再写相关权限检查代码,代理做相关方法时将检查代码切入。

aop 写好可以切的类和代码。通过配置文件,要对哪个类和哪个方法往里切,顺序问题都在配置文件写好。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值