什么是代理模式
代理模式是在 不改变源代码(零侵入,完全解耦) 的情况下,实现 对目标代码功能的扩展和增强。
代理模式有两种,一种是静态代理,另外一种是动态代理。
静态代理是在运行前代理类就已经被编译成了class文件,这种定义其实是相对于动态代理而言的。反之,动态代理就是在运行时依据被代理类为模板动态的生成其代理类class文件,并被类加载器加载,通过反射生成代理类的实例。
动态代理一般又有两种实现方式,一种是JDK动态代理,另一种是Cglib实现的动态代理。无论用哪种方式实现,代理模式要解决的问题依然是解耦和功能增强。
本篇主要通过静态代理和动态代理对比,以及分析JDK源码来探讨下JDK动态代理那些事儿。
静态代理
public interface UserService {
void insertUser(String username);
void delUser(int id);
}
public class UserServiceImpl implements UserService {
@Override
public void insertUser(String username) {
System.out.println("insertUser被代理执行了!!!【"+ username +"】");
}
@Override
public void delUser(int id) {
System.out.println("delUser被代理执行了!!!【"+ id +"】");
}
}
UserService的静态代理类
public class StaticProxyImpl implements UserService {
private UserService target;
public StaticProxyImpl(UserService target){
this.target = target;
}
@Override
public void insertUser(String username) {
//做其他的事情,比如记录日志,增强了方法的功能
System.out.println("insertUser方法记录了日志!");
target.insertUser(username);
}
@Override
public void delUser(int id) {
target.delUser(id);
}
}
通过代理类执行UserService的insertUser方法
public class StaticProxyApp {
public static void main(String[] args) {
UserService userService = new UserServiceImpl();
UserService proxy = new StaticProxyImpl(userService);
proxy.insertUser("9527");
}
}
执行的结果
这段代码对UserServiceImpl类没有任何侵入,成功实现了解耦,也增强了记录日志的功能。
可暴露的问题很明显,如果还有一个LoginService也要增强记录日志的功能,StaticProxyImpl这个类是无能为力的,那就又要单独为LoginService写一个代理实现类,并实现LoginService接口。这样会造成代理类过多冗余,代码重复率高。
也许有人想到在StaticProxyImpl这个类用泛型来解决代理类过多问题,在代理类方法调用的处理上也是很难实现的。也许有大神能解决这个问题,那恭喜你,你又为代理模式的发展贡献了力量!
下面我们来看看JDK是如何解决上面这个问题的。
JDK动态代理
JDK动态代理主要用Proxy这个类和InvocationHandler这个接口来实现。
直接上代码。上面的UserService和UserServiceImpl不变,我们用JDK实现动态代理,用DynamicProxyImpl替换StaticProxyImpl。
public class DynamicProxyImpl<T> implements InvocationHandler {
private T target;
public DynamicProxyImpl(T target){
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//做其他的事情,比如记录日志,增强了方法的功能
System.out.println(method.getName() + "方法记录了日志!");
method.invoke(target, args);
return null;
}
}
这里的DynamicProxyImpl是实现了InvocationHandler接口,它与StaticProxyImpl不同的是,StaticProxyImpl是UserServiceImpl的真正代理类,它代理UserServiceImpl执行了insertUser方法。而DynamicProxyImpl其实不是UserServiceImpl的代理类,它只是协助处理器(这个名字起得不太好),因为JDK会在运行时为UserServiceImpl生成名为$Proxy前缀的代理类,这才是UserServiceImpl真正的代理类,它会代理UserServiceImpl执行insertUser方法。看JDK源码解释:
A method invocation on a proxy instance through one of its proxy
interfaces will be dispatched to the {@link InvocationHandler#invoke
invoke} method of the instance’s invocation handler, passing the proxy
instance, a {@code java.lang.reflect.Method} object identifying
the method that was invoked, and an array of type {@code Object}
containing the arguments.
代理实例方法的调用是通过它实现的一个代理接口转发到它相关联的InvocationHandler的invoke方法,代理实例会通过反射包下的Method类来被执行,还可以带Object类型的一个数组作为参数。
我们再来看看执行结果
public class JdkProxyApp {
public static void main( String[] args ) {
//配置这个参数是为了让JDK保存在运行时生成的代理类文件,后面跟源码会演示
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
UserService target = new UserServiceImpl();
InvocationHandler handler = new DynamicProxyImpl<UserService>(target);
UserService userService = (UserService) Proxy.newProxyInstance(UserService.class.getClassLoader(), new Class[]{UserService.class}, handler);
System.out.println(userService.getClass().getCanonicalName());
userService.insertUser("9527");
}
}
再贴一张编译了但是不运行的目录结构图
运行后的目录结构图
上面编译但不运行的那张图只生成了target目录(编译的class
文件的目录),而运行后的那张图还生成了$ Proxy0.class这个代理类,说明JDK动态代理在编译期间是不生成的,而是在运行期间动态生成。
System.out.println(userService.getClass().getCanonicalName());这行代码的控制台结果为com.sun.proxy.$Proxy0,也说明UserServiceImpl被代理了。
JDK动态代理就比静态代理好多了,它解决了静态代理类过多冗余的问题,也不需要写重复的代码,只需要重新构造一个InvocationHandler的实现类DynamicProxyImpl< LoginService >,并交给Proxy类生成LoginService的代理类就行了。
下面我们再来结合JDK的源码来分析动态代理的具体实现机制。