代理模式定义
为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用(摘自百度百科)
程序来源于生活,生活中也有代理模式的例子。比如电影《我不是药神》中,程勇拿到了印度"格列宁"在中国的代理权,中国的病人要买印度"格列宁"只能通过程勇来买。那程勇就是代理对象,印度药厂就是真实对象,程勇在这个过程中就起到了中介的作用。
代理模式涉及的角色
抽象角色:一般是接口或抽象类,定义角色的功能
真实角色:抽象角色的具体实现
代理角色:抽象角色的具体实现,并且包含真实角色的引用,通过调用真实对象同名的方法来实现代理的作用,还可以加入附加的操作,增强真实角色的功能
代理模式的好处
1.职责清晰,真实的对象可以专注于业务的具体实现,一些附加的操作就由代理对象来完成,结构清晰,分工明确
2.代理对象可以在客户端跟目标对象之间起到中介的作用,从而起到保护目标对象引用的作用
静态代理
静态代理是由程序员创建或工具生成代理类的源码,再编译代理类
实现如下:
public interface PersonService {
public void addPerson();
}
public class PersonServiceImpl implements PersonService {
public void addPerson() {
System.out.println("add person----");
}
}
public class ProxyPersonServiceImpl implements PersonService {
private PersonService personService;
public ProxyPersonServiceImpl(PersonService personService){
this.personService = personService;
}
public void addPerson() {
System.out.println("新增之前添加额外操作 -------");
personService.addPerson();
System.out.println("新增之后添加额外操作 --------");
}
}
静态代理的缺点:
代理对象只能服务于单一的对象,如果需要代理的对象比较多,会有很多个代理对象;比如,我要在很多个对象中加入日志记录,如果使用静态代理就要创建很多代理对象,造成代码冗余,不利于维护。此时,就需要动态代理了
动态代理
动态代理是在实现阶段不用关心代理类,而在运行阶段才指定代理的对象
一般动态代理有两种实现方式:JDK动态代理、CGLIB动态代理
JDK动态代理
实现方式:利用Java的反射机制,在java.lang.reflect 包下提供了Proxy类和InvocationHandler 接口来实现动态代理
代码实现
下面以增加日志的需求来演示一下JDK动态代理的用法
//抽象对象
public interface CustService {
public void editCust();
}
//真实对象
public class CustServiceImpl implements CustService {
public void editCust() {
System.out.println("edit cust ----");
}
}
//日志对象
public class LogService {
public void addLog(){
System.out.println("add log ----");
}
}
//动态代理对象
public class JDKProxyCustService implements InvocationHandler {
private Object target;
private LogService logService = new LogService();
public JDKProxyCustService(Object target){
this.target = target;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
System.out.println("methodName:" + methodName );
logService.addLog();
Object o = method.invoke(target,args);
logService.addLog();
return o;
}
}
//测试
public class TestJDKProxy {
public static void main(String[] args){
CustService custService = new CustServiceImpl();
Class c = custService.getClass();
ClassLoader classLoader = c.getClassLoader();//目标对象的类加载器
Class[] interfaces = c.getInterfaces();//目标对象实现的所有接口
InvocationHandler h = new JDKProxyCustService(custService);//获取一个InvocationHandler,并将custService对象传入
/**
* 参数说明:
* 1.classLoader表示目标对象的类加载器
* 2.interfaces表示目标对象实现的所有接口
* 3.InvocationHandler接口的实现
*/
CustService proxy = (CustService) Proxy.newProxyInstance(classLoader,interfaces,h);
proxy.editCust();
}
}
运行结果:
methodName:editCust
add log ----
edit cust ----
add log ----
每个动态代理类(JDKProxyCustService)都必须实现InvocationHandler接口,当我们通过代理对象调用方法时,调用会被转到InvocationHandler接口的invoke方法。
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
proxy:被代理的真实对象
method:调用真实对象的某个方法
args:调用真实对象某个方法所传的参数
实际上,真正有用的代码是:
Object o = method.invoke(target,args);
利用Java的反射机制,动态的去调用目标对象的方法。
再来看看Proxy类,我们在测试类中真正有用的代码是:
CustService proxy = (CustService) Proxy.newProxyInstance(classLoader,interfaces,h);
Proxy类的作用就是创建一个动态代理对象的类,newProxyInstance方法的作用是创建一个动态代理的对象
参数说明:
classLoader:目标对象的类加载器
interfaces:目标对象所实现的所有接口
h:InvocationHandler接口的实现
还有这句:
InvocationHandler h = new JDKProxyCustService(custService);
传入需要代理的真实对象,实现调用的就是真实对象的方法
CGLIB动态代理
JDK动态代理要求被代理的类必须实现接口,并且代理对象与真实对象必须实现同一接口,CGLIB则没有这样的限制,简单来说CGLIB动态代理的原理是,生成的代理类继续自被代理类,在代理类中增加额外的逻辑实现功能增强,但由于代理类要继承自被代理类,final类是无法由CGLIB动态代理的
//被代理类
public class HelloWorldService {
public void hello(){
System.out.println("Hello World!");
}
}
//代理类
public class HelloWorldInterceptor implements MethodInterceptor {
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy)
throws Throwable {
System.out.println(method.getName() + "调用之前");
Object object = methodProxy.invokeSuper(o,objects);
System.out.println(method.getName() + "调用之后");
return object;
}
}
//测试类
public class HelloWorldClient {
public static void main(String[] args){
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(HelloWorldService.class);//继承被代理类
enhancer.setCallback(new HelloWorldInterceptor());//回调
HelloWorldService helloWorldService = (HelloWorldService) enhancer.create();//创建代理类对象
helloWorldService.hello();
}
}
运行结果:
hello调用之前
Hello World!
hello调用之后
从逻辑上来看,CGLIB实现动态代理更加简单
要实现CGLIB动态代理,首先要实现MethodInterceptor 接口,然后重写intercept方法
代理类对象是由Enhancer类创建的,Enhancer是CGLIB的字节码增强器,可以很方便的对类进行拓展
JDK动态代理与CGLIB动态代理的对比
1.JDK动态代理,代理类与真实类实现同一接口,通过代理类实现InvocationHandler并重写invoke方法来进行动态代理的,在invoke方法中实现额外的处理。缺点:只能够代理实现了接口的类
2.CGLIB动态代理,生成的代理类继承被代理类,通过实现MethodInterceptor接口并重写intercept方法来实现额外的处理。
缺点:因为要继承,final类以及final方法无法使用CGLIB动态代理。