Aop和Rpc中都大量使用了动态代理,长期以来,一直对动态代理和静态代理的区别,动态代理到底在设计上有什么好处有点困惑,今天终于算明白了。
先说类别:
按照代理的创建时期,代理类可以分为两种:
静态:由程序员创建代理类或特定工具自动生成源代码再对其编译。在程序运行前代理类的.class文件就已经存在了。
动态:在程序运行时运用反射机制动态创建而成。
再说动态代理究竟好在哪里?
缺点:
1)代理类和委托类实现了相同的接口,代理类通过委托类实现了相同的方法。这样就出现了大量的代码重复。如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。
2)代理对象只服务于一种类型的对象,如果要服务多类型的对象。势必要为每一种对象都进行代理,静态代理在程序规模稍大时就无法胜任了。如上的代码是只为UserManager类的访问提供了代理,但是如果还要为其他类如Department类提供代理的话,就需要我们再次添加代理Department的代理类。
举例说明:代理可以对实现类进行统一的管理,如在调用具体实现类之前,需要打印日志等信息,这样我们只需要添加一个代理类,在代理类中添加打印日志的功能,然后调用实现类,这样就避免了修改具体实现类。满足我们所说的开闭原则。但是如果想让每个实现类都添加打印日志的功能的话,就需要添加多个代理类,以及代理类中各个方法都需要添加打印日志功能(如上的代理方法中删除,修改,以及查询都需要添加上打印日志的功能)
即静态代理类只能为特定的接口(Service)服务。如想要为多个接口服务则需要建立很多个代理类。
小黑板的上画重点,静态代理最终的问题是,要对一类接口进行代理时,就需要手工写出一坨静态代理类的代码,并且需要把接口中的所有方法都实现一遍,类会超级多,代码会超级冗
** 而动态代理客服了这一个缺点,我们可以把对被代理接口所做的包装放在一个实现了java.lang.reflect.InvocationHandler接口的类里面,在吗这个类里面我们只需要做好想要包装的动作就好了,不用去关心被代理类实现了哪些方法,不用去关心被代理类是什么样的接口,在运行时jvm可以动态的给我们生成动态代理类,我们所需要的做的,只是把这个代理类和委托类的对象关联起来即可。**
调用下面的方法会生成一个代理类,返回一个代理类对象,interfaces里面指明了要被代理哪些接口类:
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException
然后用返回的代理类对象就可以去做被代理接口的各种动作了。
那么有人会问了,什么时候把被代理的对象的和代理对象关联起来呢?
** 答案就是代理类回去实现InvocationHandler接口,同时,代理类通常会在构造函数里面去保存被代理的对象。**
** 生成代理类对象后,调用代理类对象的方法,执行过程应该是这样的:**
** 代理类对象的method-->invocationHandler实现类的invoke方法-->invoke方法里面先去调用包装的处理过程,然后调用被代理对象的方法。**
下面是使用jdk proxy的一个例子:
/** * 动态代理类对应的调用处理程序类 */ public class SubjectInvocationHandler implements InvocationHandler {
//代理类持有一个委托类的对象引用 private Object delegate;
public SubjectInvocationHandler(Object delegate) {
this.delegate = delegate;
}
@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
long stime = System.currentTimeMillis();
//利用反射机制将请求分派给委托类处理。Method的invoke返回Object对象作为方法执行结果。
//因为示例程序没有返回值,所以这里忽略了返回值处理 method.invoke(delegate, args); long ftime = System.currentTimeMillis();
System.out.println("执行任务耗时"+(ftime - stime)+"毫秒");
return null; }
}
/** * 生成动态代理对象的工厂. */
public class DynProxyFactory {
//客户类调用此工厂方法获得代理对象。 //对客户类来说,其并不知道返回的是代理类对象还是委托类对象。
public static Subject getInstance(){
Subject delegate = new RealSubject();
InvocationHandler handler = new SubjectInvocationHandler(delegate);
Subject proxy = null;
proxy = (Subject)Proxy.newProxyInstance( delegate.getClass().getClassLoader(), delegate.getClass().getInterfaces(), handler);
return proxy; } }
public class Client {
public static void main(String[] args) {
Subject proxy = DynProxyFactory.getInstance();
proxy.dealTask("DBQueryTask");
}
}
好了,上面是动态代理的好处,和jdk proxy的使用,java其实有jdk proxy和cglib两种动态代理方式,各有优缺点,可以看下面的介绍,不多说了!
简单的说说两个代理的不同,CGLIB代理可以视为JDK代理的补充,JDK的代理是基于接口来实现的,也就是说使用JDK代理的类必须包含至少一个接口,调用代理的方法时,会不断地找寻接口中与调用方法匹配的值,然后通过反射找到此接口的方法,调用InvocationHandler的invoke方法拦截代理。
再说CGLIB,与JDK不同,CGLIB不要求代理类必有什么接口的,它通过制造一个类,通过继承的方式实现代理类,类似于钩子回调,也就是说CGLIB要求代理的类不能是final调用的方法也不能是final的。通过查看各开源框架,也能够发现使用CGLIB代理,反射,泛型等乱七八糟的方法比较多。
cglib有两种可选方式,继承和引用。第一种是基于继承实现的动态代理,所以可以直接通过super调用target方法,但是这种方式在spring中是不支持的,因为这样的话,这个target对象就不能被spring所管理,所以cglib还是才用类似jdk的方式,通过持有target对象来达到拦截方法的效果。通过CGLIB代理可以很大程度的增强代理方法。如spring中AOP相关的拦截方法,和hibernate中,毕竟hibernate中有大量的缓存机制和ORM工具方法。当然我并没有说JDK代理要比CGLIB好。在框架中二中代理都被大量的使用,并且在spring中如spring AOP中默认是使用JDK代理实现来实现接口,你可以强制使用CGLIB。在使用struts2框架时,你可能碰到过一种莫名奇妙的场景,就是使用ActionAware接口时,调用代理方法报错找不到有关的方法,这很可能是因为Spring关注到你的action实现了某个接口,于是为你使用JDK代理,进而使你调用的方法不被找到。
那么接下来请参照下面两段代码,进一步理解
首先是基础工作,一个接口:
public interface UserService {
Boolean addUser(User user);
}
实现类
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;// 插入用户 用户id用uuid
@Override
public Boolean addUser(User user) {
user.setRole_id(11001);
user.setActivecode(UUID.randomUUID().toString().replace("-", ""));
user.setUser_id(UUID.randomUUID().toString().replace("-", ""));
user.setRegister(new Date());
return true;//此处请无视// TODO
}
}
这里是代理的实现
package com.dabai.test
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class MyInvocationHandler implements InvocationHandler {
private Object target;
MyInvocationHandler() {
super();
}
MyInvocationHandler(Object target) {
super();
this.target = target;
}
@Override
public Object invoke(Object o, Method method, Object[] args) throws Throwable {
if("getName".equals(method.getName())){
System.out.println("start--" + method.getName());
Object result = method.invoke(target, args);
System.out.println("end--" + method.getName());
return result;
}else{
Object result = method.invoke(target, args);
return result;
}
}
}
这里调用
package com.dabai.test.jdk;
package com.dabai.mytwo.service;
package com.dabai.mytwo.service.impl;
import com.dabai.mytwo.entity.User;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
public class Proxy_Jdk {
public static void main(String[] args) {
UserService userService = new UserServiceImpl();
InvocationHandler invocationHandler = new MyInvocationHandler(userService);
UserService userServiceProxy = (UserService)Proxy.newProxyInstance(userService.getClass().getClassLoader(),
userService.getClass().getInterfaces(), invocationHandler);
userServiceProxy.addUser(new User());
}
}
接下来是CGLIB代理
package com.dabai.test.cglib;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class CglibProxy implements MethodInterceptor {
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("start cg----" + methodProxy.getSuperName());
System.out.println(method.getName());
Object obj = methodProxy.invokeSuper(o, args);
System.out.println("end----" + methodProxy.getSuperName() );
return obj;
}
}
package com.dabai.test.cglib;
package com.dabai.mytwo.service;
package com.dabai.mytwo.service.impl
import net.sf.cglib.proxy.Enhancer;
public class Proxy_Cglib {
public static void main(String[] args) {
CglibProxy cglibProxy = new CglibProxy();
//在此处增强代理方法
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(UserServiceImpl.class);
enhancer.setCallback(cglibProxy);
UserService userService = (UserService)enhancer.create();
userService.addUser(new User());
}
}