Java代理模式

代理模式是一种很重要的设计模式,中介代理模式,其目的就是为其他对象提供一个代理以控制对某个真实对象的访问, 不管在程序世界里,或是在现实生活中(比如房产中介、婚姻所、会计中介、招聘中介、农副产品中介(一块钱的农产品通过各个中介层层包装,卖给消费者时就是10块钱了)等), 我就介绍下编程下的代理中介长什么样。

 

要学习技术,我们需要知道它为什么会出现,以便我们有目标和动力去更好地学习和理解它。

首先,为什么要有代理?

有一个共同的要求:如何在不修改A类代码的情况下调用A类方法时增加和增强某些功能?

在不考虑哪些代理不是的情况下,我们设计了一个简单的实现方案:

创建了一个新的类B,即类B组合类A,在类B中创建了方法B,在方法B中调用了方法A中的方法a,并且可以在调用之前和之后添加一些自定义添加和增强代码。 当需要调用类A的方法a并想要添加一个附加函数时,调用类B的方法B可以达到上述要求;

为了便于理解,附加了以下伪代码:

// 定义类A
public class  ClassA {
    public void methodA(){
       System.out.println("调用方法methodA!");
    }
}

// 定义类B
public class ClassB{
    //
    ClassA  instanceA;
    public ClassB(ClassA A){
        this.instanceA = A;
    }

    @Override
    public void methodB(){
        System.out.println("调用methodA前");
        instanceA.methodA();
        System.out.println("调用methodA后");
    }
}

接下来,让我们调用ClassB的methodB方法来生成以下输出:

调用methodA前
调用方法methodA!
调用methodA后

可以发现方法a执行并向方法a添加其他功能,而无需修改代码类;
不难 其实上面的代码是最简单的代理模式

代理存在的意义:使用代理模式,您可以通过扩展代理类来添加和增强某些功能,而无需修改其他代理对象代码

代理模式分两种情况,分别是静态代理和动态代理。

涉及的设计模式是代理模式本身,代理程序模式通常包括几个元素,如下图所示:

代理模式
代理模式图

 

主题Subject:定义代理类和真实主题的公共外部方法。 代理类代表真实主题也是一种方法。
实际主题Real subject:真正实现业务逻辑的课程;
代理类Proxy class用于代理和封装真实主题
客户端Client:使用代理类和主题界面来做一些工作。

 

1.  静态代理(static proxy)

所谓静态代理是指在运行程序之前已经存在了代理类的字节码文件,并且在程序运行之前就已经确定了代理类和委托类之间的关系。

为了更好地理解,我写了小例子, 伪代码如下:

先定义一个业务接口

package code.proxy;

public interface ProgrammingLanguageServer {
	/** 随机获取一种编程语言 */
	public String getProgrammingLanguage();
	/** 添加一种编程语言 */
	public void addProgrammingLanguage(String programmingLanguage);
}

 然后实现该接口

public class ProgrammingLanguageServerImpl implements ProgrammingLanguageServer {

	List<String> programmingLanguageList = new ArrayList<String>();

	ProgrammingLanguageServerImpl() {
		programmingLanguageList.add("Java");
		programmingLanguageList.add("JavaScript");
		programmingLanguageList.add("Python");
		programmingLanguageList.add("Golang");
	}

	/** 随机获取一种编程语言 */
    @Override
	public String getProgrammingLanguage() {
		System.out.println("No proxy Calling getProgrammingLanguage()");
		return (String)programmingLanguageList.get((int)(Math.random()*programmingLanguageList.size()));
	}
    
    /** 添加一种编程语言 */
    @Override
	public void addProgrammingLanguage(String programmingLanguage) {
		System.out.println("No proxy Calling addProgrammingLanguage()");
		programmingLanguageList.add(programmingLanguage);
	}
}

代理也实现该业务接口

/**
 * @author dongguangming
 */
public class SimpleProxy implements ProgrammingLanguageServer {
	//真实对象
	private ProgrammingLanguageServer target;
	
	public SimpleProxy(ProgrammingLanguageServer target) {
		super();
		this.target = target;
	}

	@Override
	public String getProgrammingLanguage() {
		// TODO Auto-generated method stub
		System.out.println("Proxy Calling getProgrammingLanguage()");
		return target.getProgrammingLanguage();
	}

	@Override
	public void addProgrammingLanguage(String programmingLanguage) {
		// TODO Auto-generated method stub
		System.out.println("Proxy Calling addProgrammingLanguage()  start");
		target.addProgrammingLanguage(programmingLanguage);
		System.out.println("Proxy Calling addProgrammingLanguage()  end");
	}
}

最后测试

 * @author dongguangming
 */
public class SimpleProxyTest {

	/** Here we show the whole thing in operation. */
	public static void main(String[] args) {
		// Proxy is commonly used with some kind of creational method which
		// the client calls to obtain the "real" object but actually gets
		// the proxied object, the proxy must therefore be substituable
		// for the real object
		//ProgrammingLanguageServer programmingLanguageServer = getProgrammingLanguageServer();
		
		//真实对象
		ProgrammingLanguageServer target = new ProgrammingLanguageServerImpl();
        //获取代理对象看个人爱好,有两种方式
		//代理对象
		ProgrammingLanguageServer proxy = new SimpleProxy(target);
        //代理对象,匿名实现
		//ProgrammingLanguageServer proxy = getProgrammingLanguageServer(); 
		
		System.out.println("代理对象proxy object is " + proxy.getClass());
		
		proxy.addProgrammingLanguage("C");
		System.out.println("随机获取一种编程语言: " + proxy.getProgrammingLanguage());
	}

	/*
	 * 备用,匿名实现,不想多增加文件
	 */
	public static ProgrammingLanguageServer getProgrammingLanguageServer() {
		//真实对象
		ProgrammingLanguageServer target = new ProgrammingLanguageServerImpl();
		//代理对象
		ProgrammingLanguageServer proxy = new ProgrammingLanguageServer() {
			public String getProgrammingLanguage() {
				System.out.println("Proxy Calling getQuote()");
				
				return target.getProgrammingLanguage();
			}
			public void addProgrammingLanguage(String programmingLanguage) {
				System.out.println("Proxy Calling addQuote()");
				
				target.addProgrammingLanguage(programmingLanguage);
			}
		};
		
		return proxy;
	}
}

输出结果

 

上面的代码确实实现一个静态代理,实际上,该静态代理可以满足上述要求。 为什么我们需要动态代理? 这是静态代理的两个缺点:

  1. 代理对象的接口仅服务一种类型的对象。 如果要代理的方法很多,则它必然会代理每种方法。 当程序规模太大时,太多的静态代理类将导致代码混乱
  2. 如果将方法添加到接口,则除了需要实现此方法的所有实现类之外,所有代理类也都需要实现此方法,这会增加代码维护的复杂性。

基于以上两个问题,动态代理诞生了!!!

 

2.  动态代理(dynamic proxy),很重要!!!!!!

动态代理是在程序运行以创建代理类时通过反射获取代理类的字节码内容

什么是动态代理?动态,程序中的动态是指在程序运行时根据配置自动生成代理类,并且只有在代理类和代理类运行时才能确定它们之间的关系;

在JDK中,动态代理有两种实现机制:JDK Proxy和CGLib;

让我们以JDK Proxy为例,基于源代码分析来解释动态代理和应用场景。

2.1  JDK代理

JDK代理动态代理,在java.lang.reflect包中,您可能会发现,为什么它在反射包下? 下面的源代码分析将解决此问题;

它的核心api包括两个重要的核心接口和类:一个是InvocationHandler(Interface),另一个是Proxy(Class)。 简单来说,这两个非常简单。 这两个对于我们实现动态代理都是必需的。 这是两个类:
java.lang.reflect.Proxy(类):代理是Java动态代理机制的主要类。 它提供了一组静态方法来为一组接口动态生成代理类及其对象。 有四种静态方法:

  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):此方法用于为指定的类加载器,一组接口和调用处理器生成动态代理类实例。 它包含以下参数:加载程序指定代理类的ClassLoader加载程序,接口指定代理类要实现的所有接口,h指示在调用方法时动态代理对象将与哪个InvocationHandler对象相关联

此方法用于为指定的类加载器,一组接口和调用处理器生成动态代理类实例。

java.lang.reflect.InvocationHandler (Interface): InvocationHandler是newProxyInstance方法的InvocationHandler h参数的输入,该参数负责连接必须由代理类的中间类和委托类实现的接口
它定义了一个invoke方法,该方法用于处理动态代理类对象上的方法调用,并且通常在此方法中实现对代理类的代理访问。

这是动态代理的两种核心方法, 让我们用上面的代码实现一个动态代理。 

首先:创建处理器类以实现InvocationHandler接口并覆盖invoke方法,伪代码如下:

public class MyInvocationHandler implements InvocationHandler {
     private Object delegate;   
	    public MyInvocationHandler(Object delegate){
	       this.delegate = delegate;
	    }

	    // Override invoke method
	    @Override
	    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
	        System.out.println("Additional code execution before called by proxy method~ ");
	        // Real proxy method call
	        Object ret = method.invoke(delegate, args);
	        System.out.println("Additional code execution after being called by proxy method~ ");
	        return ret;
	    } 
}

基本就是这样,处理器就完成了。 当我们调用代理类的方法时,我们将执行上面重写的invoke方法。 接下来,创建ClassA的代理类

其次:创建代理类并调用代理方法

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
/**
 * @author dgm
 * @describe "测试JDK动态代理"
 * @date 2020年12月8日
 */
public class JdkDynamicProxyDemoTest {

	public static void main(String[] args) {
		  // Create proxied object
		ImpA A = new ClassA();

        // Create processor class implementation
        InvocationHandler myHandler = new MyInvocationHandler(A);

        // A key! Generate proxy class, where proxyA is the proxy class of A
        ImpA proxyA = (ImpA)Proxy.newProxyInstance(A.getClass().getClassLoader(), A.getClass().getInterfaces(), myHandler);

        // To call the method a method of the agent of the agent class, you will call the invoke method area of myHandler above to execute. As for why, leave the question first, and we will make it clear below~
        proxyA.methodA();
	}
}

现在已经建立了一个动态代理, 执行代码后,您将找到输出:

jdk动态代理输出
标题

恩太简单了。但是呢再好的特性也有缺陷, 以下是动态代理的优缺点的摘要:

优点:

动态代理类不仅简化了编程工作,而且还改善了软件系统的可伸缩性,因为Java反射机制可以生成任何类型的动态代理类。

动态代理类的字节码在程序运行时由Java反射机制动态生成,因此程序员无需手动编写其源代码。

该界面添加了一个方法。 除了需要实现此方法的所有实现类之外,动态代理类还将直接自动生成相应的代理方法。

缺点:
JDK代理只能代理具有实现接口的类,也就是说,没有接口实现的类不能由JDK代理代理。 为什么? 以下将得到回答

大千世界,有解决方案吗? 当然还有一个动态代理方案:CGLib,它为一个类实现代理。 它主要为指定的类生成一个子类,涵盖其中的方法,并涵盖其中的方法实现增强。 但是,因为采用了继承,所以最好不要将类或方法声明为final。 对于最终的类或方法,它不能被继承,并且JDK代理的基本概念相似。 毕竟,这也是动态代理的实现方案。

 

2.2  cglib代理

有时目标对象只是单个对象,而且没有实现任何接口,此时我们可以使用类的方式来实现目标对象是代理的子类,这种方法称为:Cglib agent代理。

Cglib代理,也称为代理的子类(像final是不行了),它是在内存中构建子类对象以实现目标对象的功能扩展。

JDK动态代理存在一个限制,即使用动态代理对象必须实现一个或多个接口,如果希望代理类不实现该接口,则可以使用Cglib实现。

例子略

 

 

小结:

代理分为静态代理和动态代理。

有两种方法可以实现动态代理:JDK Proxy和CGLib。

动态代理的核心反射机制是在运行时通过反射获得代理类的字节码和处理类的字节码。

动态代理类的生成是通过重新组合字节码来实现的。

应用场景,比如我们经常用的spring  aop(请参考我的以前总结Spring AOP如何产生代理对象)、mybatis(比如只定了接口怎么能执行业务方法,其实已经是代理对象了,也请看我以前写的总结MyBatis中一个SQL语句的执行过程解析)、rpc框架

 

 

当然结合下面的总结学习,效果会很好:

更多文献  https://github.com/dongguangming/java

 

 

十年总结,你能把注解、反射、代理、线程等搞明白了(就是说拿到jvm里的class字节码,通过它可做大量的发挥和创新),就会理解了很多框架,当然也包括扩写或集成第三方,再加上mvc模型、分发、请求响应、tcp、http、代理、网关、缓存、消息搜索、状态机和分布式抽象理论,会挥洒的淋漓尽致!!!!!!

 

 

参考:

  1. Dynamic Proxy Classes  https://docs.oracle.com/javase/8/docs/technotes/guides/reflection/proxy.html

  2. Dynamically implement an Interface https://www.logicbig.com/tutorials/core-java-tutorial/java-dynamic-proxies/runtime-interface-implementation.html

  3. java动态代理原理及解析 https://www.sohu.com/a/246547051_132276

  4. Java常见几种动态代理的对比  https://zhuanlan.zhihu.com/p/87393183

  5. Proxy模式以及java动态代理实现  https://www.iteye.com/blog/mouer-901136

  6. sun.misc: ProxyGenerator.java   http://www.docjar.com/html/api/sun/misc/ProxyGenerator.java.html
  7. Object Adapter based on Dynamic Proxy  https://www.artima.com/weblogs/viewpost.jsp?thread=109017
  8. The power of proxies in Javahttps://blog.frankel.ch/the-power-of-proxies-in-java/
  9.  Static agents, dynamic proxies and proxy cglib  https://www.programmersought.com/article/38261971824/
  10. Spring AOP https://docs.spring.io/spring/docs/2.0.0/reference/aop.html#d0e9015
  11. Spring AOP API https://docs.spring.io/spring/docs/2.0.0/reference/aop-api.html
  12. In-depth source code analysis SpringAOP implementation principle http://www.programmersought.com/article/7735361531/
  13. 金九银十想面BAT?那这些JDK 动态代理的面试点你一定要知道 https://juejin.im/post/6872216516859789320

  14. The difference between static agent and dynamic agent  https://programmer.group/the-difference-between-static-agent-and-dynamic-agent.html

  15. Java Proxy (static agents, JDK dynamic proxies, CGLIB dynamic proxies)

    https://titanwolf.org/Network/Articles/Article?AID=f046a899-ec09-40c0-9167-78a6d6b9f449#gsc.tab=0

  16. An in depth understanding of two ways to implement java dynamic proxies (JDK and Cglib) https://ofstack.com/Java/26273/an-in-depth-understanding-of-two-ways-to-implement-java-dynamic-proxies-(jdk-and-cglib).html

  17. Design pattern 1 Proxy Pattern - Proxy Pattern https://www.fatalerrors.org/a/design-pattern-1-proxy-pattern-proxy-pattern.html

  18. Dynamic Envoy Proxy on Linux Machine  https://medium.com/cstech/dynamic-envoy-proxy-on-linux-machine-25ccf8b159be

  19. Various Proxy Design Pattern implementation variants in Java, ABAP and JavaScript  https://blogs.sap.com/2017/04/17/various-proxy-design-pattern-implementation-variants-in-java-and-abap/

  20. Decorating with dynamic proxies  https://www.ibm.com/developerworks/library/j-jtp08305/index.html

  21. Static Analysis of Java Dynamic Proxies https://yanniss.github.io/issta18-dynamic-proxies-preprint.pdf
  22. sun.misc.ProxyGenerator.java  https://github.com/JetBrains/jdk8u_jdk/blob/master/src/share/classes/sun/misc/ProxyGenerator.java   http://www.docjar.com/html/api/sun/misc/ProxyGenerator.java.html  http://hg.openjdk.java.net/jdk7/jdk7/jdk/file/f097ca2434b1/src/share/classes/sun/misc/ProxyGenerator.java
  23. Java Examples for sun.misc.ProxyGenerator  https://www.javatips.net/api/sun.misc.proxygenerator  https://javasourcequery.com/example/sun.misc.ProxyGenerator.generateProxyClass

  24. jdk Proxy https://www.arabicprogrammer.com/article/64431100995/
  25. Java Dynamic Proxy --> Draft  https://stevenjsmin.tistory.com/21

  26. SOFA RPC源码解析之RPC代理机制 http://www.jeepxie.net/article/1029439.html

  27.  
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值