⭐⭐⭐ Spring Boot 项目实战 ⭐⭐⭐ Spring Cloud 项目实战
《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 blog.csdn.net/hongtaolong/article/details/88688634 「菜鸟的奋斗ing」欢迎转载,保留摘要,谢谢!


🙂🙂🙂关注**微信公众号:【芋道源码】**有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言将得到认真回复。甚至不知道如何读源码也可以请教噢
  4. 新的源码解析文章实时收到通知。每周更新一篇左右
  5. 认真的源码交流微信群。

前言

动态代理应用非常的广泛,在各种开源的框架中都能看到他们的身影,比如spring中的aop使用动态代理增强,mybatis中使用动态代理生成mapper,动态代理主要有JDK和CGLIB两种方式,今天来学习下这两种方式的实现,以及它们的优缺点

动态代理:是使用反射和字节码的技术,在运行期创建指定接口或类的子类,以及其实例对象的技术,通过这个技术可以无侵入的为代码进行增强

一、JDK实现的动态代理

1、解析

jdk实现的动态代理由两个重要的成员组成,分别是ProxyInvocationHandler

  • **Proxy:**是所有动态代理的父类,它提供了一个静态方法来创建动态代理的class对象和实例
  • **InvocationHandler:**每个动态代理实例都有一个关联的InvocationHandler,在代理实例上调用方法是,方法调用将被转发到InvocationHandler的invoke方法

2、简单看下jdk的动态代理的原理图

3、代码实现

现在模拟一个用户注册的功能,动态代理对用户的注册功能进行增强,会判断用户名和密码的长度,如果用户名<=1和密码<6则会抛出异常

User.java

package com.taolong;

public class User {

private String name;

private Integer age;

private String password;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}


public Integer getAge() {
return age;
}

public void setAge(Integer age) {
this.age = age;
}

public String getPassword() {
return password;
}

public void setPassword(String password) {
this.password = password;
}

@Override
public String toString() {
return "User [name=" + name + ", age=" + age + ", password=" + password + "]";
}

}

UserService.java

package com.taolong.jdk;

import com.taolong.User;

public interface UserService {

void addUser(User user);
}

UserServiceImpl.java

package com.taolong.jdk;

import com.taolong.User;

public class UserServiceImpl implements UserService {

@Override
public void addUser(User user) {
System.out.println("jdk...正在注册用户,用户信息为:"+user);
}

}

UserServiceInterceptor.java

package com.taolong.jdk;

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

import com.taolong.User;

public class UserServiceInterceptor implements InvocationHandler {

private Object realObj;

public UserServiceInterceptor(Object realObject) {
super();
this.realObj = realObject;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (args!=null && args.length > 0 && args[0] instanceof User) {
User user = (User)args[0];
//进行增强判断
if (user.getName().length() <= 1) {
throw new RuntimeException("用户名长度必须大于1");
}
if (user.getPassword().length() <= 6) {
throw new RuntimeException("密码长度必须大于6");
}
}
Object result = method.invoke(realObj, args);
System.out.println("用户注册成功...");
return result;
}

public Object getRealObj() {
return realObj;
}

public void setRealObj(Object realObj) {
this.realObj = realObj;
}

}

ClientTest.java

package com.taolong.jdk;

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

import com.taolong.User;

public class ClientTest {

public static void main(String[] args) {
User user = new User();
user.setName("hongtaolong");
user.setPassword("hong");
user.setAge(23);
//被代理类
UserService delegate = new UserServiceImpl();
InvocationHandler userServiceInterceptor = new UserServiceInterceptor(delegate);
//动态代理类
UserService userServiceProxy = (UserService)Proxy.newProxyInstance(delegate.getClass().getClassLoader(),
delegate.getClass().getInterfaces(), userServiceInterceptor);
System.out.println("动态代理类:"+userServiceProxy.getClass());
userServiceProxy.addUser(user);
}
}

运行结果:当密码的长度小于6时

这里就起到了动态增强的作用,mybatis的使用中我们知道不需要创建dao中的mapper接口的子类,也能调用到相应的方法,其实就是生成的实现了mapper接口的动态的代理类,我们可以去看看它的这个方法

接下来我们看下cglib的使用

二、CGLIB动态代理

1、解析

CGLIB(Code Generation Library)是一个基于ASM的字节码生成库,它允许我们在运行时对字节码进行修改和动态生成。CGLIB通过继承的方式实现代理(最后这部分我们深思一下,它可能有哪些局限,final方法是不能够被重写,所以它不能增强被final修饰的方法,这个等下我们来验证)

CGLIB的实现也有两个重要的成员组成,EnhancerMethodInterceptor,其实这两个的使用和jdk实现的动态代理的ProxyInvocationHandler非常相似

  • **Enhancer:**来指定要代理的目标对象,实际处理代理逻辑的对象,最终通过调用create()方法得到代理对象、对这个对象所有的非final方法的调用都会转发给MethodInterceptor
  • **MethodInterceptor:**动态代理对象的方法调用都会转发到intercept方法进行增强

2、图解

3、代码的实现

还是上面的场景,注册用户进行拦截增强,部分代码如下

UserServiceCglibInterceptor.java

package com.taolong.cglib;

import java.lang.reflect.Method;

import com.taolong.User;

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

public class UserServiceCglibInterceptor implements MethodInterceptor {

private Object realObject;

public UserServiceCglibInterceptor(Object realObject) {
super();
this.realObject = realObject;
}

@Override
public Object intercept(Object object, Method method, Object[] args, MethodProxy proxy) throws Throwable {
if (args!=null && args.length > 0 && args[0] instanceof User) {
User user = (User)args[0];
//进行增强判断
if (user.getName().length() <= 1) {
throw new RuntimeException("用户名长度必须大于1");
}
if (user.getPassword().length() <= 6) {
throw new RuntimeException("密码长度必须大于6");
}
}
Object result = method.invoke(realObject, args);
System.out.println("用户注册成功...");
return result;
}

}

ClientTest.java

package com.taolong.cglib;

import com.taolong.User;

import net.sf.cglib.proxy.Enhancer;

public class ClientTest {

public static void main(String[] args) {
User user = new User();
user.setName("hongtaolong");
user.setPassword("hong");
user.setAge(23);
//被代理的对象
UserServiceImplCglib delegate = new UserServiceImplCglib();
UserServiceCglibInterceptor serviceInterceptor = new UserServiceCglibInterceptor(delegate);
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(delegate.getClass());
enhancer.setCallback(serviceInterceptor);
//动态代理类
UserServiceImplCglib cglibProxy = (UserServiceImplCglib)enhancer.create();
System.out.println("动态代理类父类:"+cglibProxy.getClass().getSuperclass());
cglibProxy.addUser(user);
}
}

运行结果:

这里顺便打印了动态代理类的父类,接下来我们将它的父类UserServiceImplCglib的addUser方法用final修饰,看看是否会被增强

UserServiceImplCglib.java

package com.taolong.cglib;

import com.taolong.User;

public class UserServiceImplCglib {

final void addUser(User user) {
System.out.println("cglib...正在注册用户,用户信息为:"+user);
}
}

运行结果:

总结一下

1、JDK原声动态代理时java原声支持的、不需要任何外部依赖、但是它只能基于接口进行代理(因为它已经继承了proxy了,java不支持多继承)

2、CGLIB通过继承的方式进行代理、无论目标对象没有没实现接口都可以代理,但是无法处理final的情况(final修饰的方法不能被覆写)

文章目录
  1. 1. 前言
  2. 2. 一、JDK实现的动态代理
    1. 2.0.1. 1、解析
    2. 2.0.2. 2、简单看下jdk的动态代理的原理图
    3. 2.0.3. 3、代码实现
  • 3. 二、CGLIB动态代理
    1. 3.0.1. 1、解析
    2. 3.0.2. 2、图解
    3. 3.0.3. 3、代码的实现
  • 4. 总结一下