本文已参与「新人创作礼」活动,一起开启掘金创作之路。
代理的概念在生活中经常出现,比如火车票代售点和黄牛,他们都充当了代理卖票的角色,只是他们还有其他功能上的区别。
代理分为静态代理和动态代理。动态代理又分为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 - wyhuiii/cglib-proxy: cglib动态代理