代理模式
代理类(A) 代理了 被代理类(B)的一部分方法
在我们的代码中需要 使用 B.a() 或者B.b() 地方 我们可以使用A.a(),A.b()来代替。
现实中的例子就是:
你想租房子,你找来了房屋中介代理老宋,在你通过房屋中介租房子的过程中,都是房屋中介在操办出租房子的相关事项,也就是在出租房子这件事上,房屋中介老宋的行为等于房东行为 ,但是这里房屋代理人并不具有房东老王的其他行为,比如下班之后,回老王家,抱老王老婆孩子。
但是对于你来说,最后的结果就是成功的租了房东老王的房子。
使用代理类这么做的好处是 代理类可以在 被代理的B.a()或者B.b()方法执行之前或者之后加一些小动作,就像房屋中介在出租老王的房子之后打算偷偷得NTR他。
话说回来,这里的小动作可以有
比如监控B.a()方法执行次数、比如打印B.a()执行的日志、比如控制B.a()的访问、
比如开启事务。没错,聪明如你肯定会发现使用代理模式就是 面向切面(AOP) 能够实现的关键之一。
我们在之后也会继续深入讨论AOP的相关内容
动态代理模式 动态代理并不需要我们编写 被代理对象的 具体代理类, 而是在运行期动态生成。
java中动态代理可以分为 jdk动态代理(java.lang.relect.Proxy) 和 cglib(Code Generation Library)。
两者的区别就是CGlib创建实例的时间比前者多,但是运行性能比前者快。具体的区别以后再说吧(≧▽≦)
下面来一个网上随处可见的jdk动态代理的例子。
public interface Service {
//目标方法
public abstract void add();
}
public class UserServiceImpl implements Service {
public void add() {
System.out.println("This is add service");
}
}
2、利用java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口定义代理类的实现。
class MyInvocatioHandler implements InvocationHandler {
private Object target;
public MyInvocatioHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("-----before-----");
Object result = method.invoke(target, args);
System.out.println("-----end-----");
return result;
}
}
3、使用动态代理
public class ProxyTest {
public static void main(String[] args) {
Service service = new UserServiceImpl();
MyInvocatioHandler handler = new MyInvocatioHandler(service);
ClassLoader loader = Thread.currentThread().getContextClassLoader();
// 生成代理对象
Service serviceProxy = (Service) Proxy.newProxyInstance(loader, service.getClass().getInterfaces(), handler);
serviceProxy.add();
}
}
执行结果:
-----before-----
This is add service
-----end-----
下面重点讲一下使用jdk的注意事项:
1、被代理对象UserServiceImpl必须把自己需要被代理的方法add()抽象成一个接口 并实现他,这里是 Service.add()方法。
2、 Proxy.newProxyInstance(loader, service.getClass().getInterfaces(), handler);会在运行期动态返回一个 Proxy 对象,就是UserServiceImpl的代理对象。
3、interfaces 就是 代理类需要代理的方法。并且Proxy对象的实例的类型 Service。 所以 你只能把Proxy强转成Service接口的类型,这就限定了代理类只能代理UserServiceImpl.add()方法。
4、InvocatioHandler 接口是实现动态代理的重要组件 顾名思义 Invocation 调用 Handler 处理者,也就是 调用代理类的方法的处理者。
看一下这个接口的 invoke(Object proxy, Method method, Object[] args) 方法。
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("-----before-----");
Object result = method.invoke(target, args);
System.out.println("-----end-----");
return result;
}
在执行 method.invoke(target,args)前后,就是代理对象的target.add()方法前后,我们输出了两行字符串,于是就实现了动态代理。
这个时候,肯定有同学问了 为什么时候的Invocation的invoke方法呢?
问的好,看代理的源码呗,由于动态代理类的具体实现我们是看不到的
于是我们把 Proxy.newProxyInstance(loader, service.getClass().getInterfaces(), handler) 获得的对象 通过ObjectStream输出,
然后在反编译一下,于是就的得到了这个java动态生成的java代码:
public final class $proxy1 extends Proxy implements Service {
public $proxy1(InvocationHandler invocationhandler) {
super(invocationhandler);
}
public final void add() {
try {
super.h.invoke(this, m3, null);
return;
}
catch(Error _ex) { }
catch(Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
private static Method m3;
static {
try {
m3 = Class.forName("zzzzzz.Service").getMethod("add", new Class[0]);
}
catch(NoSuchMethodException nosuchmethodexception) {
throw new NoSuchMethodError(nosuchmethodexception.getMessage());
}
catch(ClassNotFoundException classnotfoundexception) {
throw new NoClassDefFoundError(classnotfoundexception.getMessage());
}
}
}
我们看到了动态生成的proxy1.add()方法,在调用它的时候,调用了父类Proxy.invocationhandler属性的invoke方法。
这里值得注意的是
通过静态代码块,我们看到了 m3 在代理类被加载的就获得了Service.add()方法,清晰明了,最后invoke方法会把Service.add()方法传给invocationhandler的invoke处理。
进一步说明了动态生成的代理类 只能生成
Proxy.newProxyInstance(loader, service.getClass().getInterfaces(), handler)
中第二个参数所指定的接口中的方法
jdk动态代理使用的局限性
通过反射类Proxy和InvocationHandler回调接口实现的jdk动态代理,要求委托类必须实现一个接口,但事实上并不是所有类都有接口,对于没有实现接口的类,便无法使用该方式实现动态代理。
参考: