【辰兮要努力】:hello你好我是辰兮,很高兴你能来阅读,昵称是希望自己能不断精进,向着优秀程序员前行!
博客来源于项目以及编程中遇到的问题总结,偶尔会有读书分享,我会陆续更新Java前端、后台、数据库、项目案例等相关知识点总结,感谢你的阅读和关注,希望我的博客能帮助到更多的人,分享获取新知,大家一起进步!
吾等采石之人,应怀大教堂之心,愿大家奔赴在各自的热爱里…
一、初识代理模式
关于jdk的动态代理和cglib的动态代理,我会通过两篇文章案例,和大家一起学习
首先我们一起了解一下什么是代理模式
代理(Proxy)是一种设计模式,提供了对目标对象另外的访问方式;即通过代理访问目标对象。这样好处:可以在目标对象实现的基础上,增强额外的功能操作。(扩展目标对象的功能)。
CGLIB相关学习参考:【设计模式】代理模式之CGLIB动态代理(案例详解)
本期重点还是一起学习JDK动态代理
JDK动态代理:利用拦截器(拦截器必须实现InvocationHanlder)加上反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。
JDK动态代理
1、实现InvocationHandler
2、使用Proxy.newProxyInstance产生代理对象
3、被代理的对象必须要实现接口
JDK动态代理和CGLIB动态代理的区别?
1、JDK动态代理只能对实现了接口的类生成代理,而不能针对类。
2、CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法, 并覆盖其中方法实现增强,但是因为采用的是继承,所以该类或方法最好不要声明成final,对于final类或方法,是无法继承的。
Spring如何选择用JDK还是CGLiB?
1、当Bean实现接口时,Spring就会用JDK的动态代理。
2、当Bean没有实现接口时,Spring使用CGlib是实现。
3、可以强制使用CGlib(在配置文件中配置)
在spring配置中加入<aop:aspectj-autoproxy proxy-target-class="true"/>
二、代码案例学习
【模拟代码】JDK动态代理-相关实现代码
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
/**
* JDK动态代理具体实现原理:
*
* 1、通过实现InvocationHandler接口创建自己的调用处理器;
* 2、通过为Proxy类指定ClassLoader对象和一组interface来创建动态代理;
* 3、通过反射机制获取动态代理类的构造函数,其唯一参数类型就是调用处理器接口类型;
* 4、通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数参入;
*/
public class JDKProxy implements InvocationHandler {
private Object targetObject;
/**
* 将真实对象和代理对象建立联系,通过真实对象来返回一个代理对象(猜测该代理对象可能为真实对象的子类)
* @param targetObject 真实对象
* @return 代理对象
*/
public Object newProxy(Object targetObject){
this.targetObject = targetObject;
/**
* 参数1:类加载器,采用target本身的类加载器
* 参数2:生成的动态代理对象挂在那个接口下,采用target实现的接口下,即Woker接口。
* 参数3:定义实现方法逻辑的代理类,this表示当前对象,,它必须实现InvocationHandler的invoke方法。
*/
return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(),targetObject.getClass().getInterfaces(), this);
}
/**
* 代理方法逻辑
* @param proxy 代理对象
* @param method 当前调度方法
* @param args 当前方法参数
* @return 代理结果返回
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
handle();
System.out.println("拦截方法:" + method);
System.out.println("方法参数:" + Arrays.toString(args));
Object obj = method.invoke(targetObject,args);
return obj;
}
private void handle(){
//处理一些业务逻辑
System.out.println("JDKProxy处理业务逻辑");
}
}
创建一个user对象
@Data
public class User {
private String id;
private String name;
private Integer age;
}
创建一个接口
public interface UserManager {
public void addUser(String id, String userName);
}
创建如上接口的实现类
public class UserManagerImpl implements UserManager {
@Override
public void addUser(String id, String password) {
System.out.println("调用了UserManagerImpl.addUser()方法!"+id+"-"+password);
}
}
创建一个测试类
/**
* @program: demo
* @description: ProxyTest 测试类
* @author: 辰兮要努力
* @create: 2021-10-25 21:08
*/
public class ProxyTest {
public static void main(String[] args) {
JDKProxy jdkProxy = new JDKProxy();
UserManager userManagerJDK = (UserManager)jdkProxy.newProxy(new UserManagerImpl());
userManagerJDK.addUser("1", "chenXI");
}
}
UserManager userManagerJDK = (UserManager)jdkProxy.newProxy(new UserManagerImpl());
如上的代码逻辑我可以理解为一个帮助类,帮助我们获取了一个代理对象
userManagerJDK.addUser("1", "chenXI");
我们在通过代理对象调用我们对象本身的方法
执行如上程序,输出结果
JDKProxy处理业务逻辑
拦截方法:public abstract void com.chenxi.demo.dao.UserManager.addUser(java.lang.String,java.lang.String)
方法参数:[1, chenXI]
调用了UserManagerImpl.addUser()方法!1-chenXI
Process finished with exit code 0
ps:我把代码都粘贴下来是希望如果你看不太懂可以一起本地实践
三、断点深入探究
我们一起debug执行一下对应的流程
1、首先是一个帮助类传入我们需要的对象后帮助我们生成一个代理对象
2、代理对象执行原对象的方法(功能)
真正执行方法的代码 Object obj = method.invoke(targetObject,args);
同理我们可以在此方法 Object obj = method.invoke(targetObject,args);
前后新增方法可以增强原方法
JDK动态代理是java.lang.reflect.*包所提供的方式,它所代理的真实对象必须实现一个接口,依据该接口才能生成真实对象的代理
更详细的学习可以学习一下对应生成代理对象的源码newProxyInstance
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
//如果h为空将抛出异常
Objects.requireNonNull(h);
final Class<?>[] intfs = interfaces.clone();//拷贝被代理类实现的一些接口,用于后面权限方面的一些检查
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
//在这里对某些安全权限进行检查,确保我们有权限对预期的被代理类进行代理
checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
}
/*
* 下面这个方法将产生代理类
*/
Class<?> cl = getProxyClass0(loader, intfs);
/*
* 使用指定的调用处理程序获取代理类的构造函数对象
*/
try {
if (sm != null) {
checkNewProxyPermission(Reflection.getCallerClass(), cl);
}
final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
//假如代理类的构造函数是private的,就使用反射来set accessible
if (!Modifier.isPublic(cl.getModifiers())) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
cons.setAccessible(true);
return null;
}
});
}
//根据代理类的构造函数来生成代理类的对象并返回
return cons.newInstance(new Object[]{h});
} catch (IllegalAccessException|InstantiationException e) {
throw new InternalError(e.toString(), e);
} catch (InvocationTargetException e) {
Throwable t = e.getCause();
if (t instanceof RuntimeException) {
throw (RuntimeException) t;
} else {
throw new InternalError(t.toString(), t);
}
} catch (NoSuchMethodException e) {
throw new InternalError(e.toString(), e);
}
}
小结
AOP面向切面编程,可以通过预编译方式和运行期动态代理实现在不修改源代码的情况下给程序动态统一添加功能的一种技术。
思考:有了动态代理,如果我们想在原有方法前后新增逻辑,如权限,日志等,我们就可以通过如上方式实现。
四、拓展面试相关
如何强制使用CGLIB实现AOP?
1、添加CGLIB库
(aspectjrt-xxx.jar、aspectjweaver-xxx.jar、cglib-nodep-xxx.jar)
2、在Spring配置文件中加入
<aop:aspectj-autoproxy proxy-target-class="true"/>
那到底何时使用JDK何时使用CGLIB?
1、如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP。
2、如果目标对象实现了接口,可以强制使用CGLIB实现AOP。
3、如果目标对象没有实现了接口,必须采用CGLIB库,Spring会自动在JDK动态代理和CGLIB之间转换。
非常感谢你阅读到这里,如果这篇文章对你有帮助,希望能留下你的点赞👍 关注❤️ 分享👥 留言💬thanks!!!
愿你们奔赴在自己的热爱里!