文章内容
介绍了代理模式的基本概念、静态代理的代码实现。jdk代理的使用及其实现、cglib代理的使用及其实现。
写在前面
沸羊羊爱上了美羊羊,于是沸羊羊想送花给美羊羊,但是沸羊羊又是个害羞且审美不太行的汉子,所以求喜羊羊帮忙,于是喜羊羊帮沸羊羊买花,找到美羊羊,再送花,最后还要帮沸羊羊美言几句。最后皆大欢喜,喜羊羊和美羊羊在一起了(bushi)。
在上面的故事中,沸羊羊和喜羊羊同时实现了送花的接口,沸羊羊就是被代理的对象,喜羊羊就是代理对象,代理对象接管了被代理对象实现了对被代理对象访问,同时又实现了功能的增强,这就是代理模式。
定义
被代理的对象只要完成简单的工程,而代理对象对该功能能进行扩展。
类图
Subject:被代理和代理同时实现的接口。
RealSubject:访问实体,需要被代理的对象。
Proxy:代理对象,组合了RealSubject,在RealSubject的request方法上进行增强。
代理有静态代理和动态代理两种,下面将详细介绍。
静态代理
首先我们来完成静态代理,就拿之前沸羊羊的例子来实现。
SendFlower对应-Subject接口
FeiSheep-RealSubject
XiSheep-Proxy
在喜羊羊中组合了沸羊羊中,在沸羊羊的的送花前后,增强了功能
/**
* @version 1.0
* @author: Pig One
* @date: 2022/7/28 16:11
* @decription: 静态代理
*/
public class StaticProxy {
public static void main(String[] args) {
XiSheep xiSheep = new XiSheep(new FeiSheep());
xiSheep.sendflower();
}
}
class XiSheep implements SendFlower{
private FeiSheep feiSheep;
public XiSheep(FeiSheep feiSheep) {
this.feiSheep = feiSheep;
}
@Override
public void sendflower() {
System.out.println("买花");
System.out.println("找到美羊羊");
feiSheep.sendflower();
System.out.println("帮忙美言几句");
}
}
class FeiSheep implements SendFlower{
@Override
public void sendflower() {
System.out.println("给美羊羊送花");
}
}
interface SendFlower{
void sendflower();
}
结果如下
静态代理比较简单,但是试想,如果现在沸羊羊愈加情深,想要送零食,想化妆品等等该怎么办?如果这样的话,需要加很多代码,是不是特别麻烦?于是痴情的沸羊羊有了新的想法,他找了一个机器人,直接给他下命令就可以了!而这个机器人已经在羊村有售,这就是jdk代理和cglib代理!
动态代理
动态代理的著名实现有jdk代理和cglib代理,下面将详细介绍。
jdk代理
jdk代理是基于接口的代理实现,所有的代理类和被代理类继承同一个接口,他们是“兄弟关系”。
jdk代理的使用
首先看看jdk代理是如何使用的,还是拿沸羊羊为例。
/**
* @version 1.0
* @author: Pig One
* @date: 2022/7/28 16:44
* @decription: 使用jdk代理完成
*
*/
public class JdkUse {
interface Send {
void sendFlower();
void sendChocolate();
}
static class FeiSheepJdk implements Send{
@Override
public void sendFlower() {
System.out.println("送花");
}
@Override
public void sendChocolate() {
System.out.println("送巧克力");
}
}
public static void main(String[] args) {
FeiSheepJdk feiSheep = new FeiSheepJdk();
//jdk代理需要类加载器
ClassLoader classLoader = JdkUse.class.getClassLoader();
//第一个参数是类加载器,第二个是要需要增加的类,第三个是回调接口
Send proxy = (Send)Proxy.newProxyInstance(classLoader,
new Class[]{Send.class}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("买花");
System.out.println("找到美羊羊");
//这个第一个参数是要代理的对象,执行对应的方法
Object invoke = method.invoke(feiSheep, args);
System.out.println("帮忙美言几句");
return invoke;
}
});
proxy.sendFlower();
proxy.sendChocolate();
}
}
并不需要再去创建一个喜羊羊帮忙
回到之前的问题,如果沸羊羊有了新的需求,只需要在接口中添加新的方法并实现,代理类分别调用方法即可。
interface SendFlower {
void sendflower();
void sendChocolate();
}
但是并不能如意,因为前后增强是写好的。
可以这么解决:
完成了需求。
为什么说这个需求,因为下面会提!!
提一嘴,由于反射的效率很差,所以jdk进行了优化,在使用达到16次之后不再反射调用方法,而是直接用代理类。
手动实现jdk代理
- 第一步:静态代理
首先看看简单的实现,和要增强的类继承同一个接口,要使用的时候直接new 一个代理类就行了,但是这样的话功能都是写死的,并不能符合我们动态代理的要求
/**
* @version 1.0
* @author: Pig One
* @date: 2022/7/28 18:04
* @decription: 代理类
*/
public class $Proxy implements MyJdkUser.Send {
@Override
public void sendFlower() {
System.out.println("买花");
System.out.println("找到美羊羊");
new MyJdkUser.MyJdkFeiSheep().sendFlower();
System.out.println("帮忙美言几句");
}
@Override
public void sendChocolate() {
System.out.println("买巧克力");
System.out.println("找到美羊羊");
new MyJdkUser.MyJdkFeiSheep().sendChocolate();
System.out.println("帮忙美言几句");
}
}
- 第二步:使用回调接口,回忆一下,我们在使用jdk代理的时候,是不是创建了一个InvocationHandler方法?
好的我们现在就来用这个接口把主动权交付给用户
创建回调接口
interface InvocationHandler{
void invoke();
}
让代理类实现:
public class $Proxy implements MyJdkUser.Send {
private MyJdkUser.InvocationHandler handler;
public $Proxy(MyJdkUser.InvocationHandler handler) {
this.handler=handler;
}
@Override
public void sendFlower() {
handler.invoke();
}
@Override
public void sendChocolate() {
handler.invoke();
}
}
这样用户就能通过回调接口主动实现功能的增强
$Proxy proxy = new $Proxy(new InvocationHandler() {
@Override
public void invoke() {
System.out.println("买花");
System.out.println("找到美羊羊");
new MyJdkFeiSheep().sendChocolate();
new MyJdkFeiSheep().sendFlower();
System.out.println("帮忙美言几句");
}
});
proxy.sendChocolate();
proxy.sendFlower();
这样确实将主动权交付给了用户,但是怎么说呢,交付了,但是没有完全交付!因为回到之前那个_需求,_我不能
根据指定的方法完成指定的增强!
- 方法和参数也需要传递!
沸羊羊不可能只送同样的东西,还可能指定数量或者指定东西!这时候就需要传入参数了。
就不得不使用反射了
先改写接口,那么接下来用户可以直接通过方法和参数列表进行方法的反射调用
interface InvocationHandler{
void invoke(Method method ,Object[] args);
}
而在代理类中,通过反射获得方法然后传递给用户。
下面是有有参和无参两种,当然,为了避免重复使用,可以将获取方法的代码抽取出来
@Override
public void sendChocolate() {
Class<?>[] interfaces = $Proxy.class.getInterfaces();
try {
Method sendChocolate = interfaces[0].getMethod("sendChocolate");
handler.invoke(sendChocolate,new Object[0]);
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
@Override
public void sendSomething(String msg) {
Class<?>[] interfaces = $Proxy.class.getInterfaces();
try {
Method sendSomething = interfaces[0].getMethod("sendSomething", String.class);
// System.out.println(parameter.toString());
handler.invoke(sendSomething,new Object[]{msg});
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
抽取出来更加简洁,同时也避免了多次反射获取方法。
public class $Proxy implements MyJdkUser.Send {
private MyJdkUser.InvocationHandler handler;
static Method sendFlower ;
static Method sendChocolate ;
static Method sendSomething ;
static {
Class<?>[] interfaces = $Proxy.class.getInterfaces();
try {
sendFlower = interfaces[0].getMethod("sendFlower");
sendChocolate = interfaces[0].getMethod("sendChocolate");
sendSomething = interfaces[0].getMethod("sendSomething", String.class);
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
public $Proxy(MyJdkUser.InvocationHandler handler) {
this.handler = handler;
}
@Override
public void sendFlower() {
//只继承了一个接口,
handler.invoke(sendFlower, new Object[0]);
}
@Override
public void sendChocolate() {
handler.invoke(sendChocolate, new Object[0]);
}
@Override
public void sendSomething(String msg) {
// System.out.println(parameter.toString());
handler.invoke(sendSomething, new Object[]{msg});
}
}
那么我们沸羊羊还是只需要命令机器人去做就行了,不过这里用的是反射,而且对于有参的也可以实现。
$Proxy proxy = new $Proxy(new InvocationHandler() {
@Override
public void invoke(Method method, Object[] args) {
try {
String name = method.getName();
if (name.equals("sendFlower")) {
System.out.println("买花");
}else if(name.equals("sendChocolate")){
System.out.println("买巧克力");
}
System.out.println("找到美羊羊");
//方法的反射调用
method.invoke(myJdkFeiSheep,args);
System.out.println("帮忙美言几句");
} catch (IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
}
});
proxy.sendChocolate();
proxy.sendFlower();
proxy.sendSomething("hhh");
于是沸羊羊特别开心,再也不用担心美羊羊喜欢喜羊羊了!
- 代理对象
然而新的问题又出现了!有的时候沸羊羊希望机器人自己做一点,那就也需要把代理对象也传入
在接口中加入即可
interface InvocationHandler{
void invoke(Object proxy,Method method ,Object[] args);
}
在代理的实现中,传入this即表示当前对象。
- 返回值
苦人心天不负!有一天美羊羊终于想给沸羊羊一点回应了!
但是机器人没有返回值的功能,所以需要加入返回值
将之前的接口方法设置为有返回值
interface InvocationHandler{
Object invoke(Object proxy,Method method ,Object[] args);
}
这里可能有一点绕,用户重写了回调接口invoke方法,在方法中获得要增强方法的返回值(反射调用),然后代理类中通过回调接口的返回值返回给用户。
沸羊羊终于得偿所愿!
cglib代理
cglib代理是用子类实现的,不强求一定要使用接口,同时也可以不是用反射。
cglib代理的使用
我们继续拿沸羊羊开刀,
cglib代理可以避免使用反射,直接完成方法的调用,在注释里写了三种方法的实现。
/**
* @version 1.0
* @author: Pig One
* @date: 2022/7/28 17:28
* @decription: cglib代理的使用
*/
public class CglibUse {
interface Send {
void sendFlower();
void sendChocolate();
}
static class FeiSheepCglib implements Send {
@Override
public void sendFlower() {
System.out.println("送花");
}
@Override
public void sendChocolate() {
System.out.println("送巧克力");
}
}
public static void main(String[] args) {
// FeiSheepCglib feiSheepCglib = new FeiSheepCglib();
FeiSheepCglib fei = (FeiSheepCglib)Enhancer.create(FeiSheepCglib.class, new MethodInterceptor() {
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
String name = method.getName();
if (name.equals("sendFlower")) {
System.out.println("买花");
} else if (name.equals("sendChocolate")) {
System.out.println("买巧克力");
}
System.out.println("找到美羊羊");
//需要使用原目标且需要反射
// method.invoke(feiSheepCglib,args);
//需要原目标不需要反射
// methodProxy.invoke(feiSheepCglib,args);
//不需要原目标,不需要反射
Object invoke = methodProxy.invokeSuper(proxy, args);
System.out.println("帮忙美言几句");
return invoke;
}
});
fei.sendFlower();
fei.sendChocolate();
}
}
手动实现cglib代理
- 简单实现
cglib是基于子类实现的,简单实现和jdk代理差不多。需要注意的是,方法和类一定不能是final的,否的无法使用!
这里和jdk实现差不多,不赘述了,直接上代码。
/**
* @version 1.0
* @author: Pig One
* @date: 2022/7/28 22:14
* @decription: 用cglib实现代理
*/
public class MyCglibUser {
interface MethodInterceptor{
Object intercept(Object proxy ,Method method,Object[] args,Method methodSuper);
}
public static void main(String[] args) {
FeiSheep feiSheep = new FeiSheep();
Proxy proxy = new Proxy(new MethodInterceptor() {
@Override
public Object intercept(Object proxy, Method method, Object[] args, Method methodSuper) {
try {
System.out.println("找到美羊羊");
Object invoke = method.invoke(feiSheep, args);
System.out.println("美言几句");
return invoke;
} catch (IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
return null;
}
});
proxy.sendFlower();
proxy.sendFlower(1);
proxy.sendFlower(10L);
}
}
//要被代理的类
class FeiSheep {
public void sendFlower(){
System.out.println("送花");
}
public void sendFlower(int i){
System.out.println("送"+i+"朵花");
}
public String sendFlower(long j){
System.out.println("送"+j+"朵花");
return "美羊羊想和沸羊羊约会";
};
}
/**
* @version 1.0
* @author: Pig One
* @date: 2022/7/28 22:14
* @decription: cglib的代理类
*/
public class Proxy extends FeiSheep{
private MyCglibUser.MethodInterceptor methodInterceptor;
public Proxy(MyCglibUser.MethodInterceptor methodInterceptor) {
this.methodInterceptor = methodInterceptor;
}
//
static Method sendFlower0;
static Method sendFloweri;
static Method sendFlowerj;
static {
try {
sendFlower0 = FeiSheep.class.getMethod("sendFlower");
sendFloweri = FeiSheep.class.getMethod("sendFlower",int.class);
sendFlowerj = FeiSheep.class.getMethod("sendFlower",long.class);
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
@Override
public void sendFlower() {
methodInterceptor.intercept(this,sendFlower0,new Object[0],null);
}
@Override
public void sendFlower(int i) {
methodInterceptor.intercept(this,sendFloweri,new Object[]{i},null);
}
@Override
public String sendFlower(long j) {
Object intercept = methodInterceptor.intercept(this, sendFlowerj, new Object[]{j}, null);
return (String) intercept;
}
}
- 不是用反射
上面的写法虽然简单易懂,但是还是跟jdk的区别不大,使用反射,而cglib可以不是用反射直接实现。
主要使用下面这个静态方法,第三个参数的见:https://www.lmlphp.com/user/16709/article/item/526082/
使用MethodProxy方法
//里面的参数是,第一个是指要增强的类,第二个是代理类,第三个是参数和返回值,第四个是要增强的方法,第五个是带原始功能的方法(即改进之后的方法)
sendFlower0Proxy =MethodProxy.create(FeiSheep.class,Proxy.class,"()V","sendFlower","sendFlowerSuper");
代理类:
/**
* @version 1.0
* @author: Pig One
* @date: 2022/7/28 22:14
* @decription: cglib的代理类
*/
public class Proxy extends FeiSheep{
private MyCglibUser.MethodInterceptor methodInterceptor;
public Proxy(MyCglibUser.MethodInterceptor methodInterceptor) {
this.methodInterceptor = methodInterceptor;
}
//
static Method sendFlower0;
static Method sendFlowerI;
static Method sendFlowerJ;
static MethodProxy sendFlower0Proxy;
static MethodProxy sendFlowerIProxy;
static MethodProxy sendFlowerJProxy;
static {
try {
sendFlower0 = FeiSheep.class.getMethod("sendFlower");
sendFlowerI = FeiSheep.class.getMethod("sendFlower",int.class);
sendFlowerJ = FeiSheep.class.getMethod("sendFlower",long.class);
//里面的参数是,第一个是指要增强的类,第二个是代理类,第三个是参数和返回值,第四个是要增强的方法,第五个是带原始功能的方法(即改进之后的方法)
sendFlower0Proxy =MethodProxy.create(FeiSheep.class,Proxy.class,"()V","sendFlower","sendFlowerSuper");
sendFlowerIProxy =MethodProxy.create(FeiSheep.class,Proxy.class,"(I)V","sendFlower","sendFlowerSuper");
sendFlowerJProxy =MethodProxy.create(FeiSheep.class,Proxy.class,"(J)Ljava/lang/String;","sendFlower","sendFlowerSuper");
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
public void sendFlowerSuper(){ super.sendFlower();}
public void sendFlowerSuper(int i){ super.sendFlower(i);}
public String sendFlowerSuper(long j){ return super.sendFlower(j);}
@Override
public void sendFlower() {
try {
methodInterceptor.intercept(this,sendFlower0,new Object[0],sendFlower0Proxy);
} catch (Throwable e) {
throw new UndeclaredThrowableException(e);
}
}
@Override
public void sendFlower(int i) {
try {
methodInterceptor.intercept(this,sendFlowerI,new Object[]{i},sendFlowerIProxy);
} catch (Throwable e) {
throw new UndeclaredThrowableException(e);
}
}
@Override
public String sendFlower(long j) {
Object intercept = null;
try {
intercept = methodInterceptor.intercept(this, sendFlowerJ, new Object[]{j},sendFlowerJProxy);
} catch (Throwable e) {
throw new UndeclaredThrowableException(e);
}
return (String) intercept;
}
}
public static void main(String[] args) {
FeiSheep feiSheep = new FeiSheep();
Proxy proxy = new Proxy(new MethodInterceptor() {
@Override
public Object intercept(Object p, Method method, Object[] args, MethodProxy methodSuper) throws Throwable {
System.out.println("找到美羊羊");
//反射
// Object invoke = method.invoke(feiSheep, args);
//需要目标的反射
// Object invoke = methodSuper.invoke(feiSheep, args);
//不需要目标的反射
Object invoke = methodSuper.invokeSuper(p, args);
System.out.println("美言几句");
return invoke;
}
});
proxy.sendFlower();
proxy.sendFlower(1);
proxy.sendFlower(10L);
}
- MethodProxy
MethodProxy底层是用一个fastclass实现的,fastclass是一个代理类,根据方法生成对应的签名,然后方法过来的时候进行判断是哪个方法,选择以后直接使用。前面出了点小问题找了很久,有机会再补这里吧。
优点及应用场景
高扩展性
Spring的AOP就是使用的代理模式。