代理模式
意图:为其他对象提供一种代理以控制对这个对象的访问。
主要解决:在直接访问对象时带来的问题,比如说:要访问的对象在远程的机器上。在面向对象系统中,有些对象由于某些原因(比如对象创建开销很大,或者某些操作需要安全控制,或者需要进程外的访问),直接访问会给使用者或者系统结构带来很多麻烦,我们可以在访问此对象时加上一个对此对象的访问层。
何时使用:想在访问一个类时做一些控制。
如何解决:增加中间层。
关键代码:实现与被代理类组合。
优点: 1、职责清晰。 2、高扩展性。 3、智能化。
职责清晰:
真实的角色就是实现实际的业务逻辑,无须关心其他非本职责的事 务,通过后期的代理完成一-件事务,附带的好处就是编程更加简洁清晰。
高扩展性:
具体主题角色是随时都会发生变化的,只要它实现了接口,甭管它如何变化,都逃不脱接口的控制,我们的代理类完全可以在不做任何修改的情况下 使用。
缺点: 1、由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢。 2、实现代理模式需要额外的工作,有些代理模式的实现非常复杂。
1. 静态代理
由程序员创建或工具生成代理类的源码,再编译代理类。所谓静态,也就是在程序运行前就已经存在代理类的字节码文件,代理类和委托类的关系在运行前就确定了。
要求: 真实角色,代理角色;
- 真实角色和代理角色要实现同一个接口,
- 代理角色要持有真实角色的引用(一般在代理角色有参构造器中引用真实对象)
公共接口(业务接口)
//公共接口
public interface House {
//方法:出售
void sales();
}
被代理类(真实角色,业务模块实现类)
//真实角色:屋主,要卖房
public class Owner implements House {
@Override
public void sales() {
System.out.println("出租300平方的豪宅");
}
}
代理类(对业务进行扩展)
//代理类中介
public class MyProxy implements House {
private House house;
public MyProxy(House house) {//引入真实角色的依赖,进而实现代理业务
this.house = house;
}
@Override
public void sales() {
house.sales();//代理真实角色的方法
System.out.println("买房来找梁销售呀");//业务扩展
}
}
开始代理
public class BootStrap {
public static void main(String[] args) {
//静态代理
Owner owner = new Owner();
MyProxy proxy = new MyProxy(owner);
proxy.sales();
}
}
- 静态代理的优点
业务类只需要关注业务逻辑本身,保证了业务类的重用性. - 静态代理的缺点
- 代理对象的一个接口只服务于一一种类型的对象,如果要代理的方法很多,势必要为每一种方法都进行代理,静态代理在程序规模稍大时就无法胜任了。
- 如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。
- 应用场景
在Java中线程的设计就使用了静态代理设计模式,其中自定义线程类实现Runable接口,Thread类也实现了Runalbe接口,在创建子线程的时候,传入了自定义线程类的引用,再通过调用start()方法,调用自定义线程对象的run()方法。实现了线程的并发执行。
//真实角色
public class RunnableDemo implements Runnable {
@override
public void run() {
//TODO
}
}
public class test{
public static void main(String[] args) {
//Runnable实现类对象,真实角色
RunnableDemo thread = new RunnableDemo();
//线程类代理角色,该类也实现了Runnable接口,代理角色
//传入了真实角色的引用
//代理角色通过调用自身的start()方法来实现真实角色的run()方法。
new Thread(thread ,"线程一").start();
}
}
2. 动态代理
动态代理类的源码是在程序运行期间由JVM根据反射等机制动态地生成,所以不存在代理类的字节码文件。代理类和委托类的关系是在程序运行时确定的。
2.1 Java API 原生代理
①java.lang.reflect.Proxy
: 这是Java动态代理机制生成的所有动态代理类的父类,它提供了一组静态方法来为一组接口动态地生成代理类及其对象。
Proxy类中静态方法的格式如下所示:
//方法1:该方法用于获取指定代理对象所关联的调用处理器
static InvocationHandler getInvocationHandler(object proxy)
//方法2:该方法用于获取关联于指定类装载器和一组接口的动态代理类的类对象
static Class getProxyClass(ClassLoader loader, Class[] interfaces)
//方法3:该方法用于判断指定类对象是否是一一个动态代理类
static boolean isProxyClass(Class cl)
//方法4:该方法用于为指定类装载器、一组接口及调用处理器生成动态代理类实例
static object newProxyInstance(ClassLoader loader, Class[] interfaces,InvocationHandler h)
②java.lang.reflect.InvocationHandler
: 这是调用处理器接口,它自定义了一个invoke
方法,用于集中处理在动态代理类对象上的方法调用,通常在该方法中实现对委托类的代理访问。每次生成动态代理类对象时都要指定一个对应的调用处理器对象。
//该方法负责集中处理动态代理类上的所有方法调用。
//第一个参数是代理类实例,第二个参数是被调用的方法对象
//第三个是调用参数。调用处理器根据这三个参数进行预处理或分派到委托类实例上反射执行
Object invoke(object proxy, Method method, object[] args)
③java.lang.ClassLoader
: 是类装载器类,负责将类的字节码装载到Java 虚拟机(JVM)中并为其定义类对象,然后该类才能被使用。Proxy静态方法生成动态代理类同样需要通过类装载器来进行装载才能使用,它与普通类的唯一区别就是其字节码是由JVM在运行时动态生成的,而不是预存在于任何一.class
文件中。每次生成动态代理类对象时都需要指定一个类装载器对象。
2.1.1 实现JDK动态代理的步骤
- 实现
InvocationHandler
接口,创建自己的调用处理器。 - 给Proxy类提供
ClassLoader
和代理接口类型数组,创建动态代理类。 - 以调用处理器类型为参数,利用反射机制得到动态代理类的构造函数。
- 以调用处理器对象为参数,利用动态代理类的构造函数创建动态代理类对象。
首先实现
InvocationHandler
接口,该接口定义了一个invoke
(Object proxy, Method method, Object[] args)方法,其中,proxy
是最终生成的代理实例,一 般不会用到;method
是被代理目标实例的某个具体方法,通过它可以发起目标实例方法的反射调用;args
是被代理实例某个方法的入参,在方法反射调用时使用。
其次,在构造函数里通过target
传入希望被代理的目标对象,在InvocationHandler接口方法invoke(Object proxy, Method method, Object[] args)里,将目标实例传递给method.invoke()
方法,并调用目标实例的方法。
业务模块(公共接口)
public interface UserService {
void add();
void delete();
void update();
void query();
}
业务实现类(被代理类)
public class UserServiceImpl implements UserService {
@Override
public void add() {
System.out.println("添加用户");
}
@Override
public void delete() {
System.out.println("删除用户");
}
@Override
public void update() {
System.out.println("更新用户");
}
@Override
public void query() {
System.out.println("查询用户");
}
}
代理类(实际调用真实角色的方法) 实现InvocationHandler
接口
public class MyInvocationHandler implements InvocationHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(MyInvocationHandler.class);
private Object target;
public MyInvocationHandler(Object target) {
this.target = target;
}
//封装newProxyInstance方法获取代理类对象
public Object getInstance(Object target) {
this.target = target;
Class<?> clazz = target.getClass();//获取代理对象的class类
//通过反射机制生成代理接口的代理对象
return Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//调用代理类的方法,实际上代理类通过调用真实对象的方法实现代理过程
Object result = method.invoke(target, args);
LOGGER.debug("调用了"+method.getName()+"方法");//实现调用每个方法的日志记录
return result;
}
}
执行动态代理
//动态代理
//1、声明要代理的对象
UserService userService = new UserServiceImpl();
//2、实现`InvocationHandler`接口,创建自己的调用处理器。
MyInvocationHandler handler = new MyInvocationHandler(userService);
//第一种方式:由类加载器动态生成代理类去执行代理功能
UserService proxy = (UserService) Proxy.newProxyInstance(userService.getClass().getClassLoader(),
userService.getClass().getInterfaces(), handler);
//第二种方式:调用封装好的newProxyInstance方法,直接调用生成代理对象
UserService proxy = (UserService) handler.getInstance(userService);
proxy.add();
proxy.delete();
proxy.update();
proxy.query();
- 动态代理的优点
动态代理与静态代理相比较,最大的好处是,接口中声明的所有方法都被转移到调用处理器-一个集中的方法中处理(InvocationHandler.invoke)。这样,在接口方法数量比较多的时候,我们可以进行灵活处理,而不需要像静态代理那样每一一个 方法进行中转。
2.2 CGLib 动态代理
前言
使用JDK创建代理有一个限制,即它只能为接口创建代理实例,这一点可 以从Proxy的接口方法newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h)中看得很清楚:第二个入参interfaces就是需要代理实例实现的接口列表。虽然面向接口编程的思想被很多大师级人物(包括Rod Johnson) 所推崇,但在实际开发中,许多开发者也对此深感困惑:难道对一个简单业务表的操作也需要老老实实地创建5个类(领域对象类、DAO接口、DAO实现类、Service 接口和Service实现类)吗?难道不能直接通过实现类构建程序吗?对于这个问题,很难给出一个孰优孰劣的准确判断,但仍有
很多不使用接口的项目也取得了非常好的效果。
对于没有通过接口定义业务方法的类,如何动态创建代理实例呢? JDK动态代理技术显然已经黔驴技穷,CGLib 作为一一个替代者,填补了这项空缺。
CGLib采用底层的字节码技术,可以为一个类创建子类,在子类中采用方法拦截的技术拦截所有父类方法的调用并顺势织入横切逻辑。
也就是说:JDK
动态代理是实现了被代理对象的接口,CGLib
是继承了被代理对象
然后我们用 CGLIB
来实现,如果不是spring(spring已经集成了 CGLIB
)环境需要先引入 jar 包:
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
2.2.1 实现cglib动态代理的步骤
- CGLIB 主要是实现
MethodInterceptor
并重写intercept
方法。
public class MethodInterceptorDemo implements MethodInterceptor {
private static final Logger LOGGER = LoggerFactory.getLogger(MethodInterceptorDemo.class);
//获取代理类对象的方法
public Object getProxy(Class clazz) {
Enhancer enhancer = new Enhancer();//cglib jar包下的用于生成子类的对象
enhancer.setSuperclass(clazz);//设置父类,用来生成子类
enhancer.setCallback(this);
return enhancer.create();//返回继承父类的子类实例对象
}
//拦截父类所有方法的调用
@Override
public Object intercept(Object obj, Method method, Object[] args,
MethodProxy methodProxy) throws Throwable {
//通过代理类调用父类中的方法
Object result = methodProxy.invokeSuper(obj, args);
//切面织入日志记录(对原先业务的增强)
LOGGER.debug(obj.getClass().getName() + "." + method.getName());
return result;
}
}
在上面的代码中,用户可以通过getProxy
(Class clazz)方法为一个类创建动态代理对象,该代理对象通过扩展clazz 实现代理。在这个代理对象中,织入方法调用日志。intercept
(Object obj, Method method, Object[] args,MethodProxy proxy)是CGLib定义的Interceptor 接口方法,它拦截所有目标类方法的调用。其中,obj
表示目标类的实例:method
为目标类方法的反射对象; args
为方法的动态入参; methodProxy
为代理类实例。
public class Test {
public static void main(String[] args) {
CglibProxy cglibProxy = new CglibProxy();
//通过动态生成子类的方式创建代理类
UserServiceImpl proxy = (UserServiceImpl) cglibProxy.getProxy(UserServiceImpl.class);
//通过代理类调用父类中的方法
// 代理类(子类)自身可进行业务的增强-->譬如增加权限验证、日志记录等
proxy.add();
proxy.delete();
proxy.update();
proxy.query();
}
}
观察以上输出,发现代理类的名字变成com.yauyukbiu.spring.InvocationHandler.UserServiceImpl$$EnhancerByCGLIB$$b6ffdbf
,这个特殊的类就是CGLib
为UserServiceImpl 动态创建的子类。
值得一提的是, 由于CGLib采用动态创建子类的方式生成代理对象,所以不能对目标类中的final
或private
方法进行代理。
3. 总结
JDK和CGLIB动态代理对比
JDK
动态代理是实现了被代理对象的接口,CGLib
是继承了被代理对象。- JDK 和 CGLib 都是在运行期生成字节码,JDK 是直接写 Class 字节码,CGLib 使用 ASM框架写 Class 字节码,Cglib 代理实现更复杂,生成代理类比 JDK 效率低。
- JDK 调用代理方法,是通过反射机制调用,CGLib 是通过 FastClass 机制直接调用方法,CGLib 执行效率更高。
- 对于
singleton
的代理对象或者具有实例池的代理,因为无须频繁地创建代理对象,所以比较适合采用CGLib动态代理技术;反之则适合采用JDK动态代理技术。