代理模式
一、代理模式
代理(Proxy)是一种设计模式,提供了对目标对象另外的访问方式;即通过代理对象访问目标对象.这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能.
编程中的思想是:不要随意去修改别人已经写好的代码或者方法,如果需要改修改,可以通过代理的方式来扩展该方法
举个例子来说明代理的作用:假设我们想邀请一位明星(例如坤坤),那么并不是直接连接明星,而是联系明星的经纪人,来达到同样的目的.明星就是一个目标对象,他只要负责活动中的节目,而其他琐碎的事情就交给他的代理人(经纪人)来解决.这就是代理思想在现实中的一个例子
用图表示如下:
代理模式的核心是代理对象与目标对象,而代理对象是对目标对象的扩展,并会调用目标对象
二、静态代理
静态代理类似于装饰器,静态代理在使用时,需要定义接口或者父类,被代理对象与代理对象一起实现相同的接口或者是继承相同父类.
下面举个案例来解释:
模拟保存动作,定义一个新增动作的接口:UserDao.java,然后目标对象实现这个接口的方法UserDao.java,此时如果使用静态代理方 式,就需要在代理对象UserDaoProxy.java中也实现UserDao接口,调用的时候通过调用代理对象的方法来调用目标对象.需要注意的是,代理对象与目标对象要实现相同的接口,然后通过调用相同的方法来调用目标对象的方法
静态代理优缺点:
- 可以做到在不修改目标对象的功能前提下,对目标功能扩展.
- 缺点:因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类,类太多.同时,一旦接口增加方法,目标对象与代理对象都要维护.
如何解决静态代理中的缺点呢?答案是可以使用动态代理方式
实例代码如下:
1.创建接口
package com.augus.dao; public interface UserDao { //新增数据 void add(); }
2.创建实现类,也就是上面说的目标对象
package com.augus.dao.impl; import com.augus.dao.UserDao; public class UserDaoImpl implements UserDao { @Override public void add() { System.out.println("***********新增了一条数据***********"); } }
3.创建代理对象,代理对象也要实现和目标对象相同的接口
package com.augus.dao.impl; import com.augus.dao.UserDao; public class UserDaoProxy implements UserDao { //接收保存目标对象 private UserDao userDao; public UserDaoProxy(UserDao userDao){ this.userDao = userDao; } @Override public void add() { System.out.println("-----------开始事物-----------"); userDao.add(); System.out.println("-----------提交事务-----------"); } }
4.创建测试类
import com.augus.dao.UserDao; import com.augus.dao.impl.UserDaoImpl; import com.augus.dao.impl.UserDaoProxy; import org.junit.Test; public class Test1 { @Test public void testProxy(){ //目标对象 UserDao userDao = new UserDaoImpl(); //代理对象 UserDaoProxy proxy = new UserDaoProxy(userDao); //执行的是代理对象的方法 proxy.add(); } }
二、动态代理
1.动态代理有以下特点:
- 代理对象,不需要实现接口
- 代理对象的生成,是利用JDK的API,动态的在内存中构建代理对象(需要我们指定创建代理对象/目标对象实现的接口的类型)
- 动态代理也叫做:JDK代理,接口代理
2.JDK中生成代理对象的API
代理类所在包:java.lang.reflect.Proxy
JDK实现代理只需要使用newProxyInstance方法,但是该方法需要接收三个参数,完整的写法是:
static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler h )
注意该方法是在Proxy类中是静态方法,且接收的三个参数依次为:
ClassLoader loader,
:指定当前目标对象使用类加载器,获取加载器的方法是固定的Class<?>[] interfaces,
:目标对象实现的接口的类型,使用泛型方式确认类型InvocationHandler h
:事件处理,执行目标对象的方法时,会触发事件处理器的方法,会把当前执行目标对象的方法作为参数传入
3.案例演示
3.1.定义接口:
package com.augus.dao; public interface UserDao { //新增数据 void add(); //删除 void delete(); void addAndDelete(); }
3.2.实现类:
package com.augus.dao.impl; import com.augus.dao.UserDao; public class UserDaoImpl implements UserDao { @Override public void add() { System.out.println("***********这是add()***********"); } @Override public void delete() { System.out.println("***********这是delete()***********"); } @Override public void addAndDelete() { add(); delete(); } }
3.3.代理工厂类
package com.augus.Factory; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; public class ProxyFatory { //维护目标对象 private Object object; public ProxyFatory(Object object){ this.object = object; } //给目标对象生成代理对象 public Object getProxy(){ //InvocationHandler h,执行处理器对象,专门用于定义增强的规则 final InvocationHandler invocationHandler = new InvocationHandler() { //invoke是当我们让代理对象调用任何方法时,都会触发invoke方法的执行 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { /* Object proxy, 代理对象 Method method,被代理的方法 Object[] args,被代理方法运行时的实参 */ System.out.println("开始事物*************"); //运行反射执行目标对象 Object invoke = method.invoke(object, args); System.out.println("结束事物*************"); return invoke; } }; // object.getClass().getClassLoader() 被代理的对象的类加载器 // object.getClass().getInterfaces() 被代理对象所实现的所有接口 Object o = Proxy.newProxyInstance(object.getClass().getClassLoader(), object.getClass().getInterfaces(), invocationHandler); return o; } }
3.4.测试类
import com.augus.Factory.ProxyFatory; import com.augus.dao.UserDao; import com.augus.dao.impl.UserDaoImpl; import org.junit.Test; public class Test1 { @Test public void testProxy(){ //目标对象 UserDao userDao = new UserDaoImpl(); // 给目标对象,创建代理对象 UserDao proxy = (UserDao)new ProxyFatory(userDao).getProxy(); //调用方法 proxy.add(); proxy.delete(); proxy.addAndDelete(); } }
需要注意的是从上图执行结果可以看出addAndDelete 内部调用add和delete方法没有走拦截到的方法,这是因为内部调用直接走的方法调用,没用代理对象。