代理模式
定义
代理模式就是给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。通俗讲代理模式就是我们生活中常见的中介。
使用场景
远程(Remote)代理:为一个位于不同的地址空间的对象提供一个本地的代理对象,这个不同的地址空间可以是在同一台主机中,也可是在另一台主机中。
虚拟(Virtual)代理:如果需要创建一个资源消耗较大的对象,先创建一个消耗相对较小的对象来表示,真实对象只在需要时才会被真正创建。
Copy-on-Write代理:它是虚拟代理的一种,把复制(克隆)操作延迟到只有在客户端真正需要时才执行。一般来说,对象的深克隆是一个开销较大的操作,Copy-on-Write代理可以让这个操作延迟,只有对象被用到的时候才被克隆。
保护(Protect or Access)代理:控制对一个对象的访问,可以给不同的用户提供不同级别的使用权限。
缓冲(Cache)代理:为某一个目标操作的结果提供临时的存储空间,以便多个客户端可以共享这些结果。
防火墙(Firewall)代理:保护目标不让恶意用户接近。
同步化(Synchronization)代理:使几个用户能够同时使用一个对象而没有冲突。
智能引用(Smart Reference)代理:当一个对象被引用时,提供一些额外的操作,如将此对象被调用的次数记录下来等。
优点
在目标对象实现的基础上,可以增强额外的功能操作,即扩展目标对象的功能。
代理模式主要起增强方法和权限拦截的作用。
缺点
1.增加了代理对象可能导致请求的处理速度变慢。
2.实现代理模式需要额外工作,有些代理模式的实现非常复杂。
组成
代理模式通常分成静态代理和动态代理。动态代理又分为JDK代理和Cglib代理
tips
1、和适配器模式的区别:适配器模式主要改变所考虑对象的接口,而代理模式不能改变所代理类的接口。
2、和装饰器模式的区别:装饰器模式为了增强功能,而代理模式是为了加以控制。
静态代理需要自己手动编写代理类和目标方法。
动态代理就不需要自己手动实现代理类和目标方法,但动态代理的目标类要必须实现接口!
Cglib代理的目标类可以实现接口也可以不实现,因为可以使用继承子类的方式代理。
关键代码
实现代理类与被代理类的组合
类图
普通代理模式
静态代理
动态代理
Cglib代理
具体实现
静态代理
静态代理在使用时,需要定义接口或者父类,目标对象与代理对象一起实现相同的接口或者是继承相同的父类。
静态代理的优缺点:
优点:在不修改目标对象功能的前提下,能通过代理对象对目标功能扩展。
确定:需要很多代理类;一旦接口增加,目标对象和代理对象都要维护。
1.定义一个接口ITeacherDao
2.目标对象TeacherDao实现接口ITeacherDAO
3.使用静态代理方式,需要代理对象TeacherDaoProxy也实现ITeacherDao
4.调用时,通过调用代理对象的方法来调用目标对象
5.代理对象和目标对象要实现相同的接口,并通过调用相同的方法来调用目标对象的方法
package Proxy.staticProxy;
public interface ITeacherDao {
void teach();
}
package Proxy.staticProxy;
/**
* @title: TeacherDao
* @Author yzhengy
* @Date: 2020/7/27 16:23
* @Question: 目标对象
*/
public class TeacherDao implements ITeacherDao {
@Override
public void teach() {
System.out.println("老师正在授课......");
}
}
package Proxy.staticProxy;
/**
* @title: TeacherDaoProxy
* @Author yzhengy
* @Date: 2020/7/27 16:24
* @Question: 代理对象
*/
public class TeacherDaoProxy implements ITeacherDao {
private ITeacherDao target;//目标对象,通过接口来聚合
//构造器
public TeacherDaoProxy(ITeacherDao target) {
this.target = target;
}
@Override
public void teach() {
System.out.println("开始代理......操作过程进行中");
target.teach();
System.out.println("提交操作");
}
}
package Proxy.staticProxy;
/**
* @title: Client
* @Author yzhengy
* @Date: 2020/7/27 16:27
* @Question: 测试类
*/
public class Client {
public static void main(String[] args) {
//创建目标对象
TeacherDao teacherDao = new TeacherDao();
//创建代理对象,同时将目标对象传递给代理对象
TeacherDaoProxy teacherDaoProxy = new TeacherDaoProxy(teacherDao);
//通过代理对象,调用目标对象的方法(执行的是代理对象的方法,代理对象再去调用目标对象的方法)
teacherDaoProxy.teach();
}
}
动态代理
1.代理对象不需要实现接口,但目标对象需要实现接口。
2.代理对象的生成是利用JDK的API,动态地在内存中构建代理对象。
3.代理类所在的包:java.lang.reflect.Proxy
4.JDK实现代理只需要使用newProxyInstance方法。static Object newProxyInstance(Classloader loader,Class<?>[]interfaces,InvocationHandler h)
代理类是由Proxy这个类通过newProxyInstance方法动态生成的,生成对象后使用“实例调用方法”的方式进行方法调用,那么代理类的被代理类的关系只有在执行这行代码的时候才会生成,因此成为动态代理。
package Proxy.dynamicProxy;
public interface ITeacherDao {
void teacher();
void sayHello(String name);
}
package Proxy.dynamicProxy;
/**
* @title: TeacherDao
* @Author yzhengy
* @Date: 2020/7/27 17:04
* @Question: 目标对象
*/
public class TeacherDao implements ITeacherDao {
@Override
public void teacher() {
System.out.println("老师授课中......");
}
@Override
public void sayHello(String name) {
System.out.println("hello" + name);
}
}
package Proxy.dynamicProxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* @title: ProxyFactory
* @Author yzhengy
* @Date: 2020/7/27 17:05
* @Question: 代理对象工厂
*/
public class ProxyFactory {
private Object target;//维护一个目标对象
public ProxyFactory(Object target){//构造器,对target进行初始化
this.target = target;
}
//给目标对象生成一个相应的代理对象
public Object getProxyInstance(){
/*说明
* public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
1. ClassLoader loader : 指定当前目标对象使用的类加载器, 获取加载器的方法固定
2. Class<?>[] interfaces: 目标对象实现的接口类型,使用泛型方法确认类型
3. InvocationHandler h : 事情处理,执行目标对象的方法时,会触发事情处理器方法, 会把当前执行的目标对象方法作为参数传入
*/
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("JDK动态代理开始......");
Object returnVal = method.invoke(target,args);//利用反射机制调用目标对象的方法
System.out.println("JDK动态代理提交......");
return returnVal;
}
});
}
}
package Proxy.dynamicProxy;
/**
* @title: Client
* @Author yzhengy
* @Date: 2020/7/27 17:13
* @Question: 测试类
*/
public class Client {
public static void main(String[] args) {
//创建目标对象
ITeacherDao target = new TeacherDao();
//给了目标对象,创建代理对象,需要转成ITeacherDao
ITeacherDao proxyInstance = (ITeacherDao)new ProxyFactory(target).getProxyInstance();
//proxyInstance=class com.sun.proxy.$Proxy0 内存中动态生成了代理对象
System.out.println("proxyInstance=" + proxyInstance.getClass());
//通过代理对象,调用目标对象的方法
proxyInstance.teacher();
proxyInstance.sayHello("tina");
}
}
Cglib代理
CGLib采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。但因为采用的是继承,所以不能对final修饰的类进行代理。
1.引用Cglib的jar包
2.在内存中动态地创建子类,注意代理的类不能为final,否则会出现java.lang.IllegalArgumentException错误。
3.目标对象的方法如果为final/static,那么就不会被拦截,即不会执行目标对象额外的业务方法。
package Proxy.cglibProxy;
/**
* @title: TeacherDao
* @Author yzhengy
* @Date: 2020/7/27 18:10
* @Question: 目标对象
*/
public class TeacherDao {
public String teach(){
System.out.println("老师授课中 , 我是cglib代理,不需要实现接口");
return "hello";
}
}
package Proxy.cglibProxy;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/**
* @title: ProxyFactory
* @Author yzhengy
* @Date: 2020/7/27 17:38
* @Question: 代理对象
*/
public class ProxyFactory implements MethodInterceptor {
//维护一个目标对象
private Object target;
public ProxyFactory(Object target) {
this.target = target;
}
//返回一个代理对象,是target目标对象的代理对象
public Object getProxyInstance(){
//1.创建一个工具类
Enhancer enhancer = new Enhancer();
//2.设置父类
enhancer.setSuperclass(target.getClass());
//3.设置回调函数
enhancer.setCallback(this);
//4.创建子类对象,即代理对象
return enhancer.create();
}
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("cglib代理模式......开始");
Object returnVal = method.invoke(target,args);
System.out.println("cglib代理模式......结束");
return returnVal;
}
}
package Proxy.cglibProxy;
/**
* @title: Client
* @Author yzhengy
* @Date: 2020/7/27 18:21
* @Question: 测试类
*/
public class Client {
public static void main(String[] args) {
//.创建目标对象
TeacherDao target = new TeacherDao();
//.获取代理对象,并将目标对象传递给代理对象
TeacherDao proxyInstance = (TeacherDao)new ProxyFactory(target).getProxyInstance();
//.执行代理对象的方法,触发intercept方法,实现对目标对象方法的调用
String res = proxyInstance.teach();
System.out.println("res=" + res);
}
}
使用代理模式的源码
Spring AOP 编程的实现原理就是动态代理。使用的是JDK代理和cglib代理,比如Spring的事务使用的是AOP技术,当目标类没有实现接口时候,会使用cglib代理,实现了接口默认使用JDK代理。
public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {
@Override
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
Class<?> targetClass = config.getTargetClass();
if (targetClass == null) {
throw new AopConfigException("TargetSource cannot determine target class: " +
"Either an interface or a target is required for proxy creation.");
}
// 判断目标类是否是接口或者目标类是否Proxy类型,若是则使用JDK动态代理
if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
return new JdkDynamicAopProxy(config);
}
// 配置了使用CGLIB进行动态代理或者目标类没有接口,那么使用CGLIB的方式创建代理对象
return new ObjenesisCglibAopProxy(config);
}
else {
// 上面的三个方法没有一个为true,那使用JDK的提供的代理方式生成代理对象
return new JdkDynamicAopProxy(config);
}
}
//其他方法略……
}