转:https://blog.csdn.net/cmavb/article/details/90720386
代理模式是Java常见的设计模式之一。所谓代理模式是指通过代理对象来访问被代理对象 ,可以在被代理对象的基础之上,增强额外的功能,而不需要修改别人已经写好的代码。
为什么要采用这种间接的形式来调用对象呢?一般是因为客户端不想直接访问实际的对象,或者访问实际的对象存在困难,因此通过一个代理对象来完成间接的访问。
在现实生活中,这种情形非常的常见,比如请一个律师代理来打官司、车票代售点代售车票等。
代理模式的实现
代理模式可以有两种实现的方式,一种是静态代理类,另一种是各大框架都喜欢的动态代理。下面我们主要讲解一下这两种代理模式
1.静态代理
静态代理在使用时,需要定义接口或者父类,目标对象与代理对象一起实现相同的接口或者是继承相同父类。
/** * 接口 */ public interface IUserDao { void save(); }
/** * 接口实现 * 被代理对象(目标对象) */ public class UserDao implements IUserDao { public void save() { System.out.println("----已经保存数据!----"); } }
/** * 代理对象,静态代理 */ public class UserDaoProxy implements IUserDao{ //接收保存目标对象 private IUserDao target; public UserDaoProxy(IUserDao target){ this.target=target; } public void save() { System.out.println("开始事务..."); target.save(); //代理对象的方法中包含了被代理对象的方法,执行被代理对象的方法 System.out.println("提交事务..."); } }
/** * 测试类 */ public class App { public static void main(String[] args) { //目标对象 UserDao target = new UserDao(); //代理对象,把目标对象传给代理对象,建立代理关系 UserDaoProxy proxy = new UserDaoProxy(target); proxy.save();//执行的是代理对象的方法 } }
总结:代理对象特意包含了目标对象的方法,正是这种特征使得看起来像是“扩展”了目标对象的方法。这里的包含是关键。
静态代理的缺点:
1、需增加的代理类太多
2、被实现的接口如果增加了方法,被代理对象与代理对象都需要修改
2.动态代理
在Spring项目中用的注解,例如依赖注入的@Bean、@Autowired,事务注解@Transactional等都有用到,换言之就是Srping的AOP(切面编程)。可以非常灵活地在某个类,某个方法,某个代码点上切入我们想要的内容,就是动态代理其中的内容。
2.1 基于接口的动态代理(JDK官方)
动态的在内存中构建代理对象(需要我们指定创建代理对象/目标对象实现的接口的类型)。代理对象不需要实现接口,需要指定接口类型。但是目标对象一定要实现接口,否则不能用动态代理。
代理类所在包:java.lang.reflect.Proxy
JDK实现代理只需要使用newProxyInstance方法,但是该方法需要接收三个参数,完整的写法是:
static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler h )
三个参数的意义是:
ClassLoader loader:指定当前目标对象使用的类加载器,也就是代理对象和被代理对象使用相同的类加载器
Class<?>[] interfaces:目标对象实现的接口的类型。也就是代理对象和被代理对象具有相同的行为。实现相同的接口。
InvocationHandler h:事件处理,执行目标对象的方法时,会触发事件处理器的方法,会把当前执行目标对象的方法作为参数传入。
public interface IActor { /** * 基本演出 * @param time 表演时长 */ public void basicAct(float time); /** * 危险演出 * @param time 表演时长 */ public void dangerousAct(float time); }
/** * 目标对象 */ public class Actor implements IActor { /** * 基本演出 * * @param time 表演时长 */ @Override public void basicAct(float time) { System.out.println("基本表演,时长为:" + time); } /** * 危险演出 * * @param time 表演时长 */ @Override public void dangerousAct(float time) { System.out.println("危险表演,时长为:" + time); } }
/** *测试类,在本类中实现了代理对象 */ public class ClientTest { public static void main(String[] args){ // 生成目标对象,此处和静态代理的目的不一样,所以不是生成IActor的实例 final Actor actor = new Actor(); // 内部类调用外部成员变量,必须为final // 获取代理对象 IActor proxy = (IActor)Proxy.newProxyInstance(actor.getClass().getClassLoader(), actor.getClass().getInterfaces(), new InvocationHandler() { /** * 执行目标对象的任何方法,都会经过该方法。 * 此方法有拦截的功能。 * @param proxy 代理对象的引用。不一定每次都用得到 * @param method 当前执行的方法对象 * @param args 执行方法所需的参数 * @return 前执行方法的返回值 * @throws Throwable */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { String name = method.getName(); // 目标对象执行了接口声明的任意方法都会调用本invoke方法,此处获得是调用了哪个方法名return method.invoke(actor, time);//执行目标对象的方法
} });
// 开始测试
proxy.basicAct(100);
}
}
上面的测试类中,明明代理对象已经是IActor类型的,为什么还要使用第二个getInterfaces()参数呢?如果看下面这个例子就能明白了:
/** * 创建动态代理对象 * 动态代理不需要实现接口,但是需要指定接口类型 */ public class ProxyFactory{ //维护一个目标对象 private Object target; public ProxyFactory(Object target){ this.target=target; } //给目标对象生成代理对象 public Object getProxyInstance(){ return Proxy.newProxyInstance( target.getClass().getClassLoader(), target.getClass().getInterfaces(), // 此处不知道目标对象的接口 new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("开始事务2"); //执行目标对象方法 Object returnValue = method.invoke(target, args); System.out.println("提交事务2"); return returnValue; } } ); } }
2.2 基于子类的动态代理(第三方CGLib)
有时候目标对象只是一个单独的对象,并没有实现任何的接口,这个时候就可以使用以目标对象子类的方式类实现代理,这种方法就叫做:Cglib代理
Cglib子类代理实现步骤:
1.需要引入cglib的jar文件,但是Spring的核心包中已经包括了Cglib功能,所以直接引入spring-core-3.2.5.jar即可。
2.引入功能包后,就可以在内存中动态构建子类
3.目标类不能为final,否则报错。(因为final修饰的类不能被继承)
4.目标对象的方法如果为final/static,那么就不会被拦截,即不会执行目标对象额外的业务方法。
//还是之前的目标对象,但是没有实现接口 public class Actor { /** * 基本演出 * * @param time 表演时长 */ @Override public void basicAct(float time) { System.out.println("基本表演,时长为:" + time); } /** * 危险演出 * * @param time 表演时长 */ @Override public void dangerousAct(float time) { System.out.println("危险表演,时长为:" + time); } }
//测试类,包含了生成的代理对象 public class Client { /** * 基于子类的动态代理 * 要求: * 目标类不能用final修饰 * 用到的类: * Enhancer * 用到的方法: * create(Class, Callback):创建参数Class的子类对象 * 方法的参数: * Class:被代理对象的字节码 * Callback:如何代理 * * @param args */ public static void main(String[] args) { final Actor actor = new Actor(); Actor cglibActor = (Actor) Enhancer.create(actor.getClass(), new MethodInterceptor() { /** * 执行目标对象的任何方法,都会经过该方法。在此方法内部就可以对目标对象的任何 方法进行增强。 在调用目标方法时,CGLib会回调MethodInterceptor接口方法拦截,来实现你自 己的代理逻辑,类似于JDK中的InvocationHandler接口 * * 参数: * 前三个和基于接口的动态代理是一样的。 * proxy为:由CGLib动态生成的代理类实例 * MethodProxy:当前执行方法的代理对象,也就是生成的代理类对方法的代理引用。 * 更通俗的说,MethodProxy是cglib生成用来代替Method对象的一个对象,使用 * MethodProxy比调用JDK自身的Method直接执行方法效率会有提升。 * 返回值: * 当前执行方法的返回值 */ @Override public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { String name = method.getName(); Float time = (Float) args[0]; Object rtValue = null; if ("basicAct".equals(name)) { //基本演出 if (time > 2000) { rtValue = method.invoke(actor, time / 2); } } return rtValue; } }); cglibActor.basicAct(10000); } }
Spring AOP 怎么应用的动态代理
在Spring的AOP编程中:
如果加入容器的目标对象有实现接口,用JDK代理。
如果目标对象没有实现接口,用Cglib代理。