现在说的动态代理,无非是两种,一种是cglib和jdk原生的动态代理
CGLIB(Code Generator Library)是一个强大的、高性能的代码生成库。其被广泛应用于AOP框架(Spring、dynaop)中,用以提供方法拦截操作。Hibernate作为一个比较受欢迎的ORM框架,同样使用CGLIB来代理单端(多对一和一对一)关联(延迟提取集合使用的另一种机制)。
CGLIB代理主要通过对字节码的操作,为对象引入间接级别,以控制对象的访问。我们知道Java中有一个动态代理也是做这个事情的,那我们为什么不直接使用Java动态代理,而要使用CGLIB呢?答案是CGLIB相比于JDK动态代理更加强大,JDK动态代理虽然简单易用,但是其有一个致命缺陷是,只能对接口进行代理。如果要代理的类为一个普通类、没有接口,那么Java动态代理就没法使用了,在spring中接口默认走的是jdk动态代理,其余都是走的cglib动态代理。
动态代理到底是什么呢,为什么又叫动态代理呢。我们先看看设计模式中的代理模式。
代理模式分两种,静态代理和动态代理
先看一下静态代理怎么用,这里直接从网上拷贝一些内容
定义接口UserManager
public interface UserManager{
public void addUser(String userId, String userName)
public void delUser(String userId)
}
接口实现类UserManagerImpl
public class UserManagerImpl implements UserManager {
@Override
public void addUser(String userId, String userName) {
System.out.println("UserManagerImpl.addUser");
}
@Override
public void delUser(String userId) {
System.out.println("UserManagerImpl.delUser");
}
}
接口实现类的代理类UserManagerImplProxy
public class UserManagerImplProxy implements UserManager {
// 目标对象
private UserManager userManager;
// 通过构造方法传入目标对象
public UserManagerImplProxy(UserManager userManager){
this.userManager=userManager;
}
@Override
public void addUser(String userId, String userName) {
try{
//添加打印日志的功能
//开始添加用户
System.out.println("start-->addUser()");
userManager.addUser(userId, userName);
//添加用户成功
System.out.println("success-->addUser()");
}catch(Exception e){
//添加用户失败
System.out.println("error-->addUser()");
}
}
@Override
public void delUser(String userId) {
userManager.delUser(userId);
}
}
测试类
public class Client {
public static void main(String[] args){
//UserManager userManager=new UserManagerImpl();
UserManager userManager=new UserManagerImplProxy(new UserManagerImpl());
userManager.addUser("1111", "张三");
}
}
静态代理的优势和劣势
静态代理就是一个代理类UserManagerImplProxy 和被代理对象UserManagerImpl 实现同一个接口,代理类持有被代理对象的引用,在代理类中调用被代理对象的相关方法,并且在调用前后执行其他逻辑,比如记录日志。
根据如上的介绍,你会发现每个代理类只能为一个接口服务,这样程序开发中必然会产生许多的代理类,又或者说UserManagerImpl这个类想要添加修改用户的功能,UserManagerImplProxy这个代理类也需要修改,这里不符合设计模式原则,对扩展开放,对修改关闭。
下面我们看个需求
现在需求如下,我想要管理用户,现有功能是可以删除和添加用户基本信息,未来的规划想要扩展其他功能比如说修改用户基本信息。并且,需要单独管理用户的家庭情况。最后,我还需要在每次操作的时候记录操作内容和操作时间。
如果用静态代理模式,我除了需要新建代理类UserManagerImpl和UserFamilyManagerImpl,还需要新建两个实现类的代理类,在两个代理类中操做的时候记录操作内容和操作时间。并且在未来添加修改用户基本信息的时候,还需要对UserManagerImpl和UserManagerImplProxy修改,这样看起来是不是对于代码和程序的维护成本较高。
有人会问了,我直接新建UserManagerImpl的时候加上修改用户的功能不久行了,并且不用静态代理,我也能在UserManagerImpl中记录操作内容和操作时间呀。以上,我只是举个例子,来方便说明一定的问题,但是实际生产上,未来的规划是不可预期的(修改用户),并且在设计模式中也提到职责单一原则,也就是专人干专事。用户管理类就管理用户,其他需求(记录日志)需要其他类实现。
综上可以看到,静态代理的劣势相对还是比较明显的,这里引入动态代理,我们就会想办法可以通过一个代理类完成全部的代理功能,
在上面的示例中,一个代理只能代理一种类型(用户管理和用户家庭管理需要两个代理类),而且是在编译器就已经确定被代理的对象。而动态代理是在运行时,通过反射机制实现动态代理,并且能够代理各种类型的对象。
JDK动态代理实现用户管理和用户家庭管理
先看看怎么用
新建用户管理接口和用户家庭管理接口,这里我们简单模拟一下就好了。
public interface UserManager{
public void addUser(String userId, String userName)
public void delUser(String userId)
}
public interface UserFamilyManager{
public int getFamilyNumber();
}
新建用户管理接口实现和用户家庭管理接口实现,简单模拟一下。
public class UserMangerImpl implements UserManager {
public void addUser(final String userId, final String userName) {
System.out.println("新增用户");
}
public void delUser(final String userId) {
System.out.println("删除用户");
}
public class UserFamilyManagerImpl implements UserFamilyManager {
public int getFamilyNumber() {
System.out.println("获得家庭人数");
return 5;
}
}
jdk动态代理,实现了InvocationHandler就行了,新建代理类,实现对UserManager和UserFamilyManager代理
public class UserJdkProxy implements InvocationHandler {
private Object target;
public UserJdkProxy(final Object um) {
target = um;
}
public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
//模拟记录日志
System.out.println("调用了" + method.getName() + "调用时间是" + new Date());
Object obj = method.invoke(target, args);
//模拟记录日志
System.out.println("操作完成时间是" + new Date());
return obj;
}
}
测试类
@Test
public void cap1() {
UserManager userManager = (UserManager) Proxy.newProxyInstance(UserManager.class.getClassLoader(),
new Class[] { UserManager.class }, new UserJdkProxy(new UserMangerImpl()));
UserFamilyManager userFamilyManaUser = (UserFamilyManager) Proxy.newProxyInstance(
UserFamilyManager.class.getClassLoader(), new Class[] { UserFamilyManager.class },
new UserJdkProxy(new UserFamilyManagerImpl()));
int i = userFamilyManaUser.getFamilyNumber();
userManager.addUser("k", "xiong");
}
测试结果
调用了getFamilyNumber调用时间是Thu Aug 22 16:03:40 CST 2019
获得家庭人数
操作完成时间是Thu Aug 22 16:03:40 CST 2019
调用了addUser调用时间是Thu Aug 22 16:03:40 CST 2019
新增用户
操作完成时间是Thu Aug 22 16:03:40 CST 2019
其中代理的目的就是业务逻辑和业务逻辑不相关进行解耦。在进行动态代理后,如果对UserManager想要增加修改用户的功能,直接修改UserManager类就行,不用修改代理类。
Cglib动态代理实现用户管理和用户家庭管理
maven项目中需要在pom.xml添加jar包依赖
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.2.2</version>
</dependency>
新建代理类
public class UserCglibProxy implements MethodInterceptor {
private Object target;
public UserCglibProxy(final Object target) {
this.target = target;
}
public Object intercept(final Object obj, final Method method, final Object[] args, final MethodProxy proxy)
throws Throwable {
// 模拟记录日志
System.out.println("调用了" + method.getName() + "调用时间是" + new Date());
//下面method.invoke和roxy.invokeSuper都可以达到调用原有类比如UserManagerImpl的方法
// Object obj1 = method.invoke(target, args);
Object obj1 = proxy.invokeSuper(obj, args);
// 模拟记录日志
System.out.println("操作完成时间是" + new Date());
return obj1;
}
}
新建测试类
@Test
public void cap2() {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(UserFamilyManagerImpl.class);
enhancer.setCallback(new UserCglibProxy(new UserFamilyManagerImpl()));
UserFamilyManager userFamilyManaUser = (UserFamilyManager) enhancer.create();
int i = userFamilyManaUser.getFamilyNumber();
Enhancer enhancer2 = new Enhancer();
enhancer2.setSuperclass(UserManagerImpl.class);
enhancer2.setCallback(new UserCglibProxy(new UserManagerImpl()));
UserManager userManager = (UserManager) enhancer2.create();
userManager.addUser("k", "xiong");
}
测试结果如下:
调用了getFamilyNumber调用时间是Thu Aug 22 16:30:44 CST 2019
获得家庭人数
操作完成时间是Thu Aug 22 16:30:44 CST 2019
调用了addUser调用时间是Thu Aug 22 16:30:44 CST 2019
新增用户
操作完成时间是Thu Aug 22 16:30:44 CST 2019
动态代理扩展
jdk动态代理和cglib动态代理的区别:
1.jdk通过Proxy.newProxyInstance后面的参数,来设置被代理的类和代理类,而cglib通过enhancer.setSuperclass和setCallBack来设置被代理的类和代理类
2.jdk只能代理接口,而cglib可以代理任何objcet,这也是spring为什么在aop实现的时候,为什么用了两种代理,其中接口默认走jdk动态代理。