java设计模式-------代理模式

71 阅读8分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。​​​​​​​​​​​​​​​ ​

代理的概念在生活中经常出现,比如火车票代售点和黄牛,他们都充当了代理卖票的角色,只是他们还有其他功能上的区别。

代理分为静态代理和动态代理。动态代理又分为JDK动态代理和cglib代理。

所谓的代理模式是指客户端并不直接调用实际对象,而是通过调用代理对象,来间接调用实际对象的方法。

为什么要采用这种间接的形式来调用对象?

一般是因为客户端不想访问实际的对象,或者访问实际的对象存在困难,因此通过一个代理对象来完成间接访问。

静态代理:代理和被代理对象在代理之前是确定的。他们都实现相同的接口或者继承相同的抽象类。静态代理中,一个被代理类对应一个代理类,代理类在编译期间就已经确定。

静态代理需要有一个接口并定义方法,然后代理类与被代理类都去实现该接口中的方法。代理类中定义一个需要传入被代理类对象的构造方法,在代理类中使用被代理类对象调用想要执行的方法,并可以对该方法的执行前后添加逻辑。在测试时,创建代理对象,并传入被代理对象,调用代理对象的方法。

为什么要提出动态代理?

因为在静态代理中,必须要创建代理类才可以实现代理技术,而如果在业务很多的情况下,比如说,现在的售票代理可以是黄牛,也可以是火车票代售点,那就需要有两个代理类,如果还有更多的代理渠道,那就需要创建更多的代理类,这样会显得很臃肿,所以就提出了动态代理。

为什么称之为动态代理?

动态代理的底层是反射实现的,是在程序运行期间动态的创建接口的实现对象。在静态代理中,需要创建代理类去实现接口,而使用动态代理,就不需要编写代理类去实现接口,而是在底层通过反射技术动态创建一个接口的实现类对象。

下面就以代理售票的场景演示三种代理方式的特点。

1.静态代理:

定义代理类与被代理类实现统一接口,代理类中用被代理类对象调用接口重写的方法。

缺点:代码耦合度高,复用性差。

1.1定义售票接口:

package com.wyh.staticproxy;

/**
* <p>Title: Subject</p>  
* <p>Description: 静态代理接口</p>  
* @author wyh
* @date Jul 6, 2019
*/
public interface Subject {

	//定义方法
	void sell();
}

1.2定义被代理类:

package com.wyh.staticproxy;

/**
* <p>Title: Station</p>  
* <p>Description: 静态代理中的被代理类
* 特点:实现接口中的方法
* </p>  
* @author wyh
* @date Jul 6, 2019
*/
public class Station implements Subject{

	//重写接口中的方法
	@Override
	public void sell() {
		System.out.println("售票");
	}

	//根据需求自定义本类中的其他方法
	public void change() {
		System.out.println("改签");
	}
	
}

1.3定义代理类:

package com.wyh.staticproxy;

/**
* <p>Title: Scalper</p>  
* <p>Description: 静态代理中的代理类
* 特点:
* 1.与被代理类实现同一个接口,重写接口中的方法,在重写的方法中调用被代理类中的该方法,可以根据业务需求在调用被代理类前后执行其他逻辑。
* 2.定义一个构造方法,在该构造方法中需要传入一个参数,这个参数就是被代理对象。
* </p>  
* @author wyh
* @date Jul 6, 2019
*/
public class Scalper implements Subject{

	private Station station;
	public Scalper(Station station) {
		this.station = station;
	}
	
	@Override
	public void sell() {
		System.out.println("在调用被代理类对象的方法之前可以添加其他逻辑");
		station.sell();
		System.out.println("在调用被代理类对象的方法之后可以添加其他逻辑");
	}

}

1.4测试类:

package com.wyh.staticproxy;

/**
* <p>Title: StaticProxyTest</p>  
* <p>Description: 静态代理测试类</p>  
* @author wyh
* @date Jul 6, 2019
*/
public class StaticProxyTest {
	public static void main(String[] args) {
		//由于代理对象在创建时构造函数需要传入一个被代理对象,所以在创建代理类对象之前需要创建一个被代理类对象
		Station station = new Station();
		Scalper scalper = new Scalper(station);
		scalper.sell();
	}

}

运行测试:

2.JDK动态代理:代理类实现InvocationHandler接口,在其中实现invoke()对真正要执行的方法进行增强,通过Proxy.newProxyInstance动态产生代理对象。

2.1接口:

package com.wyh.pattern.jdk;

/**
* <p>Title: Subject</p>  
* <p>Description: JDK动态代理接口</p>  
* @author wyh
* @date Jul 6, 2019
*/
public interface Subject {

	void sell();
}

2.2被代理类:

package com.wyh.pattern.jdk;

/**
* <p>Title: Station</p>  
* <p>Description: 被代理类</p>  
* @author wyh
* @date Jul 6, 2019
 */
public class Station implements Subject {

	@Override
	public void sell() {

		System.out.println("买票");
	}
	
	public void change() {
		System.out.println("改签");
	}

}

2.3实现动态代理接口:

package com.wyh.pattern.jdk;

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

/**
* <p>Title: Scalper</p>  
* <p>Description: 代理类,不用实现Subject接口,只需要实现InvocationHandler接口即可</p>  
* @author wyh
* @date Jul 6, 2019
 */
public class Scalper implements InvocationHandler {

	
	private Object target;
	
	//这里的构造方法中传入的 参数是被代理类对象
	public Scalper(Object target) {
		this.target = target;
	}

	/**
	 * <p>Title: invoke</p>  
	 * <p>Description: 实现InvocationHandler接口中的方法</p>  
	 * @param proxy:被代理类对象
	 * @param method:被代理类对象调用的方法
	 * @param args:被代理类对象调用的方法所需要的参数数组
	 * @return:返回的是被调用的方法的返回值
	 * @throws Throwable
	 */
	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		//在调用被代理类对象之前,可以加上自己的逻辑(此处使用打印输出代替业务)
		System.out.println("调用方法前添加逻辑");
		/**
		 * method.invoke(target, args);底层帮我们调用被代理对象的方法
		 * 参数一是构造方法中传入的对象,参数二是被代理对象的方法中需要的参数
		 * 这里我们要调用的sell()方法中是不需要参数的,所以就把第二个参数去掉了
		 */
		Object result = method.invoke(target);
		//在调用被代理对象之后,可以加上自己的逻辑
		System.out.println("调用方法后添加自己的逻辑");
		return result;
	}

}

2.4测试类:

package com.wyh.pattern.jdk;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;

//JDK动态代理测试类
public class ProxyTest {

	public static void main(String[] args) {
		 Subject subject = new Station();
		 InvocationHandler proxy = new Scalper(subject);
		 ClassLoader classLoader = subject.getClass().getClassLoader();
		 Class<?>[] interfaces = subject.getClass().getInterfaces();
		 /**
		  * Proxy:是动态代理类,返回代理类的一个实例,返回后的代理类可以当做被代理类使用(可使用被代理类在接口中声明过的方法)
		  * 用Proxy.newProxyInstance()动态创建代理类
		  * 参数一:类加载器
		  * 参数二:实现的接口
		  * 参数三:事件处理器
		  * 返回的就是动态产生的代理对象
		  */
		 Subject subjectProxy = (Subject)Proxy.newProxyInstance(classLoader, interfaces, proxy);
		 //代用代理对象的sell()方法时,其实是去调用Scalper的invoke()
		 subjectProxy.sell();
	}
}

运行测试:

3.cglib代理:通过动态代理产生一个实现被代理类的对象,调用invoke实现对方法的增强。

3.1创建maven项目,引入cglib  jar:

<dependencies>
  	<dependency>
  		<groupId>cglib</groupId>
  		<artifactId>cglib</artifactId>
  		<version>3.2.5</version>
  	</dependency>
</dependencies>

3.2被代理类:

package cglibproxy;

//被代理类
public class Station {

	public void sell() {
		System.out.println("卖票");
	}
}

3.3生成代理类:

package cglibproxy;

import java.lang.reflect.Method;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

//生成代理对象的类,实现MethodInterceptor接口
public class MyCglibProxy implements MethodInterceptor {
	
	//维护一个被代理类对象
	private Object target;
	
	//构造方法中传入被代理类对象,以便在getProxy时创建代理对象
	public MyCglibProxy(Object target) {
		this.target = target;
	}
	
	//生成代理对象
	public Object getProxy() {
		//创建代理对象的属性类
		Enhancer enhancer = new Enhancer();
		//设置父类(也就是要为哪个类创建代理类对象),参数是从被代理类对象中获取所属类
		enhancer.setSuperclass(target.getClass());
		//设置回调函数,即设置代理对象为当前对象
		enhancer.setCallback(this);
		//创建子类对象即代理对象
		return enhancer.create();
	}
	
	/**
	 * 拦截所有被代理类方法的调用
	 * obj:被代理类对象
	 * method:目标方法的反射对象
	 * args:目标方法的参数数组
	 * proxy:代理类对象
	 */
	public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
		//调用被代理类对象方法前添加逻辑
		System.out.println("调用被代理对象方法前添加的逻辑");
		//调用真正的被代理类对象的方法,返回的是被代理类对象中方法的返回值,此处方法返回类型是void,所以返回的对象是null
		Object returnValue = method.invoke(target, args);
		//调用方法后添加的逻辑
		System.out.println("调用被代理类对象方法后添加的逻辑");
		return returnValue;
	}

}

3.4测试类:

package cglibproxy;

public class CglibTest {
	public static void main(String[] args) {
		Station station = new Station();
		//创建代理对象
		Station proxy = (Station) new MyCglibProxy(station).getProxy();
		//调用代理对象的方法(实际上执行的是代理类中的intercept方法)
		proxy.sell();
	}

}

测试运行:

JDK动态代理与CGLIB代理的区别:

JDK动态代理:

1.只能代理实现了接口的类。(通过Proxy.newProxyInstance来创建代理对象,显然是要实现接口)

2.没有实现接口的类不能实现JDK动态代理。

CGLIB代理:

1.针对类来实现代理。(主要体现在这里:enhancer.setSuperclass(target.getClass());,显然是要实现一个类)

2.对指定目标类产生一个子类,通过方法拦截技术拦截所有父类方法的调用。

为什么要使用cglib代理?

JDK代理中必须提供接口才可以创建代理对象。在一些不提供接口的环境中,只能采用第三方技术(cglib代理)。他的优势在于不需要提供接口,只要一个非抽象类就可以实现动态代理。

代码地址:github.com/wyhuiii/Age…

GitHub - wyhuiii/cglib-proxy: cglib动态代理