【Java基础】代理(Proxy)

代理模式

1.什么是代理模式?

代理模式的定义:给某一个对象提供一个代理,并由代理对象控制对原对象的引用。

  1. 代理是一23种设计模式之一,其目的就是: 为其他对象提供一个代理以控制对某个对象的访问 。
  2. 代理类负责为委托类 预处理消息,过滤消息并转发消息,以及进行消息被委托类执行后的后续处理。
  3. 代理模式有几个: 虚拟代理计数代理远程代理动态代理。主要分为2类: 静态代理和动态代理

2.代理模式的组成

在这里插入图片描述

  • 真实角色 (RealSubject)
    实现抽象角色,定义真实角色所要实现的业务逻辑,提供代理角色调用

  • 抽象角色 (Subject)
    通过接口或抽象类 声明真实角色实现的业务方法。

  • 代理角色 (ProxySubject)
    实现抽象角色,是真实角色的代理,通过真实角色的业务逻辑方法来实现抽象方法,并可以附加自己的操作。

代理模式包含如下角色:

  • ISubject:抽象角色,是一个接口。该接口是真实对象和它的代理对象共用的接口。
  • RealSubject:真实角色,是实现抽象角色接口的类。
  • Proxy:代理角色,内部含有对真实对象RealSubject的引用,从而可以操作真实对象。代理对象与真实对象实现了相同的接口,在任何时刻都能代替真实对象。同时,代理对象可以在执行真实对象操作时,附加其他的操作,相当于对真实对象进行封装。

注意:实现动态代理的关键技术是反射

静态代理

静态代理比较简单,是由程序员编写的代理类,并在程序运行前就编译好的,而不是由程序动态产生代理类,这就是所谓的静态

1.聚合式静态代理

抽象角色

public interface Subject {
	public String talk(String message);
}

真实角色–汪老师

public class RealSubject implements Subject {
	@Override
	public String talk(String message) {
		if("一起吃饭吧".equals(message)) {
			return "你请客。。。";
		}
		if("一起跑步吧".equals(message)) {
			return "我请客。。。";
		}
		return "好的好的";
	}

}

代理角色–用于给汪老师聊天的代理

public class ProxyTalkSubject implements Subject{
  	//真实对象的引用
    private Subject subject;

	//真实对象的引用
	public ProxyTalkSubject(Subject subject) {
		this.subject=subject;
	}

	@Override
	public String talk(String message) {
		if("在吗?".equals(message)) {
			return "在!";
		}
		if("你好?".equals(message)) {
			return "你好!";
		}
		// 如果代理类处理不了,就交给真实类处理
		return subject.talk(message);
	}
}

代理对象可以在执行真实对象操作时,附加其他的操作,相当于对真实对象进行封装。

  • 如:一些汪老师不需要的回答,代理可以自动帮他回答了,汪老师只需要关注自己需要的聊天即可

测试方法

public void static main(String [] args) {
         //真实角色
		Subject wang=new RealSubject();

          //代理类依赖于真实类
		Subject proxy=new ProxyTalkSubject(wang);

		System.out.println(proxy.talk("在吗?"));
		System.out.println( proxy.talk("你好?"));
		System.out.println( proxy.talk("一起吃饭吧"));
		System.out.println(proxy.talk("一起跑步吧"));
		System.out.println(proxy.talk("一起LoL吧"));
		System.out.println(proxy.talk("一起王者吧"));
}		

执行结果
在这里插入图片描述

代理之间也可以相互传递
在上一个例子的基础上增加一个统计汪老师聊天次数的代理

统计聊天代理

public class ProxyCountSubject implements Subject {
   private Integer count=0;
	
	private Subject subject;

	public ProxyCountSubject(Subject subject) {
		this.subject=subject;
	}
	
	@Override
	public String talk(String message) {
		System.out.println("聊天:"+(++count));
		return subject.talk(message);
	}
    
}

测试代码

public void static main(String [] args) {
		//真实角色
		Subject wang=new RealSubject();
		
		//获取聊天代理
		Subject proxy1=new ProxyTalkSubject(wang);
		
		//记录聊天次数
		Subject proxy2=new ProxyCountSubject(proxy1);
	
		System.out.println(proxy2.talk("在吗?"));
		System.out.println( proxy2.talk("你好?"));
		System.out.println( proxy2.talk("一起吃饭吧"));
		System.out.println(proxy2.talk("一起跑步吧"));
		System.out.println(proxy2.talk("一起LoL吧"));
		System.out.println(proxy2.talk("一起王者吧"));
}

执行结果:
在这里插入图片描述

Java中如何使用JDK动态代理

需求:记录每次数据库操作的时间

年级抽象类

public interface GradeService {
	void query();
	void add();
}

年级真实类:(此处操作数据库使用伪代码)

public class GradeServiceImpl implements GradeService{
	@Override
	public void add() {
		try {
			Thread.sleep(2000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("年级增加...");
	}
	
	@Override
	public void query() {
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("年级查询..");
	}
}

年级代理类

public class GradeServiceProxy implements GradeService {

	private GradeService gradeService;

	public GradeServiceProxy(GradeService gradeService) {
		this.gradeService=gradeService;
	}

	@Override
	public void query() {
		Long startTime=System.currentTimeMillis();
		
		gradeService.query();

		Long endTime=System.currentTimeMillis();
		System.out.println("共耗时(s):"+(endTime-startTime)/1000);

	}

	
	@Override
	public void add() {
		Long startTime=System.currentTimeMillis();

		gradeService.add();

		Long endTime=System.currentTimeMillis();
		System.out.println("共耗时(s):"+(endTime-startTime)/1000);
	}
}

缺点:

  1. 代理类需要对重写之后的所有方法前后都加入相同的代码
  2. 如果有很多类都需要记录数据库操作时间,那么就需要对每一个真实类,编写一个对象的代理类
    动态代理

2.继承式静态代理

代理类继承真实类,通过在代理类中调用父类的的方法,实现给方法前后添加操作

真实类

public class Admin {
    @Override
    public void doSomething() {
        System.out.println("Log:admin新增了一个用户");
    }
}

代理类

public class AdminProxy extends Admin {
    @Override
    public void doSomething() {
        System.out.println("Log:admin操作开始");
        super.doSomething();
        System.out.println("Log:admin操作开始");
    }
}

测试代码

        AdminProxy proxy = new AdminProxy();
        proxy.doSomething();

聚合式与继承式静态代理的区别

  1. 聚合实现方式中代理类聚合了被代理类,且代理类及被代理类都实现了同一个接口,可实现灵活多变。
  2. 继承式的实现方式则不够灵活
    比如:在管理员操作的同时需要进行权限的处理,操作日志的记录,操作后数据的变化三个功能。三个功能的排列组合有6种,也就是说使用继承要编写6个继承了Admin的代理类,而使用聚合,仅需要针对权限的处理、操作日志记录和数据变化三个功能编写代理类,在业务逻辑中根据具体需求改变代码顺序即可。

动态代理

1. 什么是动态代理?

  1. 一般来说,对代理模式而言,一个真实类与一个代理类一一对应,这也是静态代理模式的特点
    但是,也存在这样的情况,有n各真实类,但是代理类中的“前处理、后处理”都是一样的,仅调用真实类不同。也就是说,多个真实类类对应一个代理类,共享“前处理,后处理”功能,动态调用所需真实类,大大减小了程序规模,这就是动态代理模式的特点。

JDK动态代理
为了保持行为的一致性,代理角色和真实角色通常会实现相同的接口(抽象角色),所以在调用者看来两者没有丝毫的区别。通过代理类这中间一层,能有效控制对真实对象的直接访问,也可以很好地隐藏和保护真实对象,同时也为实施不同控制策略预留了空间,从而在设计上获得了更大的灵活性。

Java 动态代理机制以巧妙的方式近乎完美地实践了代理模式的设计理念。

2. JDK动态代理

需求: 记录所有dao层 增 删 改 查的运行时间

要求记录所有dao层 增 删 改 查的运行时间
1如果使用静态代理的方式,需要为每一个service设置一个代理

抽象角色

public interface GradeService {
	void update(String name);
	void delete(String name);
	void query(String name);
	void add(String name);
}

真实角色

public class GradeServiveImpl implements GradetService {

	@Override
	public void add(String name) {
		try {
			Thread.sleep(666);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("添加年级:"+name);
	}

	@Override
	public void query(String name) {
		try {
			Thread.sleep(2222);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("查询年级:"+name);
	}

	@Override
	public void delete(String name) {
		try {
			Thread.sleep(1100);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("删除年级:"+name);
	}

	@Override
	public void update(String name) {
		try {
			Thread.sleep(1500);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("修改年级:"+name);
	}
}

代理角色实现InvocationHandler接口,重写invoke()方法

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class ServiceProxy implements InvocationHandler {
	private Object object;

	// 1.写一个构造器接收真实对象
	public ServiceProxy(Object object) {
		this.object=object;
	}

	//2.重写invoke方法
	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		Long startTime=System.currentTimeMillis();
		
		//3.反射调用真实对象的方法  method.invoke(真实对象, 方法实参)
		Object obj= method.invoke(object, args);
		
		Long endTime=System.currentTimeMillis();

		System.out.println("执行"+method.getName()+"耗时"+(endTime-startTime)+"毫秒\n");
		
		return obj;
	}
}

动态创建代理对象,调用代理方法

public class GradeCtrl {
	
		private GradeService gradeService =(GradeService)Proxy.newProxyInstance(
						GradeCtrl.class.getClassLoader(),
                        new Class<?>[] {GradeService.class},
                        new ServiceProxy(new GradeServiceImpl())
                        );
	
	@RequestMapping("/grade/add.do")
	public void add(HttpServletRequest req, HttpServletResponse resp) throws IOException {
		gradeService.add();
		resp.getWriter().println("增加年级成功...");
	}
	
	@RequestMapping("/grade/query.do")
	public void query(HttpServletRequest req, HttpServletResponse resp) throws IOException {
		gradeService.query();
		resp.getWriter().println("查询年级成功...");
	}
	
}

好处: 只用编写一个代理类就可以供所有的类来使用,记录时间,减少了很多的重复代码

实现步骤

  1. 创建代理处理类实现java.lang.reflect.InvocationHandler 接口
    这是调用处理器接口,它自定义了一个invoke 方法,用于集中处理在动态代理类对象上的方法调用,通常在该方法中实现对委托类的代理访问。
class MyInvocationHandler implements InvocationHandler{

// 真实对象
private Object realSubject;

public MyInvocationHandler(Object realSubject) {
   this.realSubject = realSubject;
}

/**
*  三个参数
*  proxy  -> 代理对象
*  method -> 调用的方法描述
*  args   -> 调用的实际参数
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
	return method.invoke(realSubject, args);
}
  1. 动态创建代理对象
    通过调用Proxy.newProxyInstance方法来动态创建代理对象
// 三个参数
// 1. 加载接口的类加载器 (一般获取系统默认的,也可以自定义)
       (当前类.class.getClassLoder())

// 2. 被代理的抽象类(接口),可以为多个以逗号分隔
         (new Class<?> [] {xxxx.class,aaaa.class})

// 3. 调用处理器, 实际交由处理的实现类(真正执行代码的地方)
              new StudentServiceProxy(真实类)

//返回了一个动态代理的实例对象,类型为接口类型 如: StudentService studentService` 
  StudentService studentService =(StudentService ) Proxy.newProxyInstance(
                        当前类.class.getClassLoader(),// 传入ClassLoader
                        new Class<?>[] {被代理的抽象类(接口)可以为多个以逗号分隔}, // 传入要实现的接口
                        new 处理类(真实类)// 传入处理调用方法的InvocationHandler
                        );

总结

JDK动态代理需要的类与接口

  • java.lang.reflect.Proxy
    用于创建代理对象
  • java.lang.reflect.InvocationHandler
    用于实现代理业务逻辑

实现步骤

  1. 通过实现 InvocationHandler 接口创建自己的调用处理器;
  2. 在调用处理器调用invoke方法统一处理所有代理调用,传入调用方法对象,实际参数 ,在方法的前后增加额外操作
  3. 通过为 Proxy 类指定ClassLoader 对象,一组 interface,调用处理器实例来创建动态代理类对象来调用代理方法;
  4. 生成的代理对象调用接口方法

优缺点

  • 优点: 可以做到在不修改目标对象的功能前提下,对目标功能扩展

  • 美中不足
    Proxy 已经设计得非常优美,但是还是有一点点小小的遗憾之处,那就是它始终无法摆脱仅支持 interface 代理的桎梏,因为它的设计注定了这个遗憾。回想一下那些动态生成的代理类的继承关系图,它们已经注定有一个共同的父类叫 Proxy。Java 的继承机制注定了这些动态代理类们无法实现对 class 的动态代理,原因是多继承在 Java 中本质上就行不通。

因此: 动态代理也叫接口代理

3.CGLIB代理

1.CGLIB是什么?

  1. CGLIB是一个功能强大,高性能的代码生成包
  2. 它为没有实现接口的 "类" 提供代理,为JDK的动态代理提供了很好的补充。
  3. 通常可以使用Java的动态代理创建代理,但当要代理的类没有实现接口或者为了更好的性能,CGLIB是一个好的选择。

2.Java中如何使用CGLIB代理

代理角色

import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;

public class CglibProxy implements MethodInterceptor {
    private Enhancer enhancer = new Enhancer();

        public Object getProxy(Class<?> clazz) {
            // 设置需要创建子类的类
            enhancer.setSuperclass(clazz);
            enhancer.setCallback(this);
            // 通过字节码技术动态创建子类实例
            return enhancer.create();
        }

        // 实现MethodInterceptor接口方法
        @Override
        public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
            System.out.println("前置代理");
            // 通过代理类调用父类中的方法
            Object result = proxy.invokeSuper(obj, args);
            System.out.println("后置代理");
            return result;
        }
}

真实角色

public class SayHello {
     public void say() {
       System.out.println("hello everyone");
     }
}

测试方法


public static void main(String[] args) {
        CglibProxy proxy = new CglibProxy();
        SayHello proxyImp = (SayHello) proxy.getProxy(SayHello.class);
        proxyImp.say();
}

3.CGLIB原理

原理

  1. 动态生成一个要代理类的子类,子类重写要代理的类中所有不是final的方法。
  2. 在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。它比使用java反射的JDK动态代理要快。

CGLIB底层:

  1. 使用字节码处理框架ASM,来转换字节码并生成新的类。
  2. 不鼓励直接使用ASM,因为它要求你必须对JVM内部结构包括class文件的格式和指令集都很熟悉。,

缺点

对于final方法,无法进行代理。

  1. CGLib创建的动态代理对象性能比JDK创建的动态代理对象的性能高不少
  2. 但是CGLib在创建 代理对象 时所花费的时间却比JDK多得多,所以对于单例的对象,
    因为无需频繁创建对象,用CGLib合适,反之,使用JDK方式要更为合适一些。同时,
  3. 由于CGLib由于是采用动态创建子类的方法,对于final方法,无法进行代理

https://www.liaoxuefeng.com/wiki/1252599548343744/1264804593397984

发布了87 篇原创文章 · 获赞 132 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/qq877728715/article/details/104574852