「这是我参与2022首次更文挑战的第24天,活动详情查看:2022首次更文挑战」。
首先讲代理模式
定义:给目标对象提供一个代理对象,并由代理对象控制对目标对象的引用
代理模式中,是需要代理对象和目标对象实现同一个接口
目的:不改变目标对象方法的情况下对方法进行增强
对于代理模式,其大概结构如下:
动态代理
而这种方式为什么叫动态代理,是因为在运行期间动态生成的。
动态代理有两种,分别是jdk 动态代理,cglib 动态代理。
jdk 动态代理要基于接口实现,cglib 要通过子类来实现
jdk 动态代理
实现动态代理的方法如下:
Proxy.newProxyInstance()
其中有三个参数:
第一个是类加载器,用来动态加载动态生成的类的字节码。
第二个是一个class 数组,是接口中的方法。接口中可以实现多个方法,所以把接口中所有方法的实现一遍。jdk 的动态代理既然想调用用户端method1,那么通过技术手段,通过反射get 到接口,生成一个类,实现了所有接口中所有方法的类,是动态生成的Proxy。 然后如何加载呢,需要使用类加载器。是使用被代理类的加载器。
第三个参数是一个Handler,即用户想调用A 的method1,那么不直接给出A,为了把一段公共代码脱离出来,所以不能给出A。这个时候生成一个代理类。Handler 指导如何生成一个代理类。(可以理解为代理类的模板)。第三个参数其实就是“代理逻辑实现类”,这个类的编写要去实现java.lang.reflect.InvocationHandler 接口。
为啥叫动态代理,因为是要动态生成的。
上代码
一个接口。
public interface UserDao {
public Integer addUser();
public void editUser();
}
接口的实现类,也就是要执行“动态代理”的目标类。
public class UserDaoImpl implements UserDao {
@Override
public Integer addUser() {
System.out.println("调用add 方法");
return 1;
}
@Override
public void editUser() {
System.out.printf("调用edit 方法");
}
}
模拟一个切面Aspect,里面是对目标类的增强的两个操作,一个是检查权限,一个是日志记录。
public class MyAspect {
public void check_permission() {
System.out.println("检查权限");
}
public void log() {
System.out.println("日志记录...");
}
}
动态代理相关的Handler 实现,即如何把增强的逻辑写入到目标类中。(这个就是动态代理逻辑类)
// 此类 决定我们如何动态去生成或者编织我们的代理类 实现接口的方法 如何被我们修改的
public class MyInvocationHandler implements InvocationHandler {
// 我们要编织的对象(这里是要通过写的那个Aspect 来做一部分"模仿切面操作")
// 或者可以理解为我们要进行动态代理操作的类(就是不改变这个类的原有属性和方法,去为这个类添加一些额外的功能)
private Object object;
// 构造方法
// 实现我们要编织的对象(在这个例子里就是要用那个Aspect 来进行一个"伪切面")
public MyInvocationHandler(Object object) {
this.object = object;
}
/**
* @param proxy 代理类的实例对象。动态生成的实例类的对象
* @param method 用户请求的方法
* @param args 方法的参数
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 对于动态代理类的方法,如何修改。就在这个invoke 方法里进行修改。
MyAspect myAspect = new MyAspect();
myAspect.check_permission();
Object result = method.invoke(object, args);
myAspect.log();
// 对于这个result 的理解。
// 举例来说,比如说调用要进行动态代理的对象"UserDaoImpl" 里的addUser() 方法,是有个返回值的,
// 对应上面method.invoke 方法,返回结果就是result。
return result;
}
}
测试:
public class JdkTest {
public static void main(String[] args) {
// 1. 原始代理对象(目标对象)
UserDao userDao = new UserDaoImpl();
// 2. 调用如何织入我们的代理类 实现哪些接口是如何被我们修改的(就是为目标类中的方法进行增强的逻辑)
MyInvocationHandler myInvocationHandler = new MyInvocationHandler(userDao);
// 3. 通过jdk proxy 的代理方法 动态生成了原始类实例实现接口的动态代理类 返回的是动态代理类的实例
UserDao userDaoProxy = (UserDao) Proxy.newProxyInstance(userDao.getClass().getClassLoader()
, userDao.getClass().getInterfaces()
, new MyInvocationHandler(userDao));
// 调用一下这个方法进行验证
Integer result = userDaoProxy.addUser();
System.out.println(result);
userDaoProxy.editUser();
// 注意这里,输出是UserDaoImpl
System.out.println(userDaoProxy);
// 输出"true",发现userDaoProxy 就是Proxy 的子类。是基于刚才那个模板生成的。
// 因为java 中,每个类只能有一个父类,所以是Proxy 的类,就不能是其他类的子类了。
// 所以是对接口进行"动态编织"
System.out.println(userDaoProxy instanceof Proxy);
}
}
输出结果:
检查权限
调用add 方法
日志记录...
1
检查权限
调用edit 方法日志记录...
CGLib动态代理
上面代理方式,目标对象UserServiceimpl 实现了一个接口。
如果只是一个普通的类,没有实现任何接口,该如何进行代理呢?这就引出了CGLib动态代理。
CGLib动态代理也叫子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能的扩展。
要用cglib代理,需要导入相应的包,好在spring已经集成了它,引入spring即可。
上代码
代理类
public class CGLibProxy implements MethodInterceptor {
private Object target;
// 或者定义函数签名为:
// public Object getProxy(Object target) {}
public Object bind(Object target) {
this.target = target;
// CGLib enhancer 增强类对象
Enhancer enhancer = new Enhancer();
// 设置增强类型
enhancer.setSuperclass(target.getClass());
// 设置回调函数
//(定义代理逻辑对象为当前对象,要求当前对象实现MethodInterceptor 方法)
enhancer.setCallback(this);
// 创建并返回子类对象
return enhancer.create();
}
/**
* @param obj
* @param method
* @param args
* @param proxy
* @return
* @throws Throwable
*/
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
Object object = proxy.invoke(obj, args);
System.out.println("打印日志");
return object;
// Object result = proxy.invokeSuper(proxy, args);
// System.err.println("调用真实对象后");
// result result;
}
}
测试类:
public class CGLibProxyTest {
public static void main(String [] args) {
CGLibProxy cgLibProxy = new CGLibProxy();
UserService userService = (UserService) cgLibProxy.bind(new UserServiceImpl());
userService.addUser();
userService.updateUser();
}
}
至此,java 动态代理相关的一些知识及使用方法已讲解差不多了,后续还会继续更新其他内容。