从代理机制到Spring AOP,这篇给你安排得明明白白的

将明星的表演抽象成一个ShowService接口,包括了唱歌、跳舞的功能

public interface ShowService {

// 歌唱表演

void sing(String songName);

// 舞蹈表演

void dance();

}

明星类实现了ShowService接口:

package com.fufu.aop;

public class Star implements ShowService{

private String name;

@Override

public void sing(String songName) {

System.out.println(this.name + " sing a song: " + songName);

}

@Override

public void dance() {

System.out.println(this.name + “dance”);

}

public Star(String name) {

this.name = name;

}

public Star() {

}

public static void main(String[] args) {

Star star = new Star(“Eminem”);

star.sing(“Mockingbird”);

}

}

用AspectJ语法实现一个代理AgentAspectJ:

package com.fufu.aop;

public aspect AgentAspectJ {

/**

  • 定义切点

*/

pointcut sleepPointCut():call(* Star.sing(…));

/**

  • 定义切点

*/

pointcut eatPointCut():call(* Star.eat(…));

/**

  • 定义前置通知

  • before(参数):连接点函数{

  • 函数体
    
  • }

*/

before():sleepPointCut(){

getMoney();

}

/**

  • 定义后置通知

  • after(参数):连接点函数{

  • 函数体
    
  • }

*/

after():sleepPointCut(){

writeReceipt();

}

private void getMoney() {

System.out.println(“get money”);

}

private void writeReceipt() {

System.out.println(“write receipt”);

}

}

创建一个Star并运行方法:

public static void main(String[] args) {

Star star = new Star(“Eminem”);

star.sing(“Mockingbird”);

}

输出:

get money

Eminem sing a song: Mockingbird

write receipt

可以看到Star的sing()方法前后输出了我们在AgentAspectJ中定义的前置通知和后置通知,所以是AspectJ在编译期间,根据AgentAspectJ代码中定义的代码,生成了增强的Star类,而我们实际调用时,就会实现代理类的功能。

具体的AspectJ语法我们不深究,只需要知道pointcut是定义代理要代理的切入点,这里是定义了两个pointcut,分别是Star类的sing()方法和dance()方法。而before()和after()分别可以定义具体在切入点前后需要的额外操作。

总结一下,AspctJ就是用特定的编译器和语法,对类实现编译期增强,实现静态代理技术,下面我们看JDK静态代理。

3.2 JDK静态代理

===========

通常情况下, JDK静态代理更多的是一种设计模式,JDK静态代理的代理类和委托类会实现同一接口或是派生自相同的父类,代理模式的基本类图入下:

从代理机制到Spring AOP,这篇给你安排得明明白白的

我们接着通过把上面的明星和经纪人的例子写成代码来实现一个JDK静态代理模式。

经纪人类也实现了ShowService接口,持有了一个明星对象来提供真正的表演,并在各项表演的前后加入了经纪人需要处理的事情,如收钱、开发票等:

package com.fufu.aop;

/**

  • 经纪人

*/

public class Agent implements ShowService{

private Star star;

public Agent(Star star) {

this.star = star;

}

private void getMoney() {

System.out.println(“get money”);

}

private void writeReceipt() {

System.out.println(“write receipt”);

}

@Override

public void sing(String songName) {

// 唱歌开始前收钱

getMoney();

// 明星开始唱歌

star.sing(songName);

// 唱歌结束后开发票

writeReceipt();

}

@Override

public void dance() {

// 跳舞开始前收钱

getMoney();

// 明星开始跳舞

star.dance();

// 跳舞结束后开发票

writeReceipt();

}

}

通过经纪人来请明星表演:

public static void main(String[] args) {

Agent agent = new Agent(new Star(“Eminem”));

agent.sing(“Mockingbird”);

}

输出:

get money

Eminem sing a song: Mockingbird

write receipt

以上就是一个典型的静态代理的实例,很简单但是也能说明问题,我们来看看静态代理的优缺点:

优点: 业务类可以只关注自身逻辑,可以重用,通过代理类来增加通用的逻辑处理。

缺点:

  1. 代理对象的一个接口只服务于一种类型的对象,如果要代理的类很多,势必要为每一个类都进行代理,静态代理在程序规模稍大时就无法胜任了。

  2. 如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度

另外,如果要按照上述的方法使用代理模式,那么真实角色(委托类)必须是事先已经存在的,并将其作为代理对象的内部属性。但是实际使用时,一个真实角色必须对应一个代理角色,如果大量使用会导致类的急剧膨胀;此外,如果事先并不知道真实角色(委托类),该如何使用代理呢?这些问题可以通过Java的动态代理类来解决。

4.动态代理

======

动态代理类的源码是在程序运行期间由JVM根据反射等机制动态的生成,所以不存在代理类的字节码文件。代理类和委托类的关系是在程序运行时确定。

4.1 动态代理思路

==========

想弄明白动态代理类实现的思路是什么,我们还需用从静态代理的存在的问题入手,因为毕竟动态代理是为了解决静态代理存在问题而出现的,回过头来看静态代理的问题:

  1. 类膨胀:每个代理类都是一个需要程序员编写的具体类,不现实。

  2. 方法级代理:代理类和实现类都实现相同接口,导致代理类每个方法都需要进行代理,你有几个方法我就要有几个,编码复杂,无法维护。

动态代理如何解决:

  1. 第一个问题很容易回答,类似使用Javasisst的例子,在代码中动态的创建代理类的字节码,然后获取到代理类对象。

  2. 第二问题就要引出InvocationHandler了,为了构造出具有通用性和简单性的代理类,可以将所有的触发真实角色动作交给一个触发的管理器,让这个管理器统一地管理触发。这种管理器就是Invoca

【一线大厂Java面试题解析+核心总结学习笔记+最新架构讲解视频+实战项目源码讲义】

浏览器打开:qq.cn.hn/FTf 免费领取

tionHandler。静态代理中,代理类无非是在前后加入特定逻辑后,调用对应的实现类的方法,sleep()对应sleep(),run()对应run(),而在Java中,方法Method也是一个对象,所以,动态代理类可以将对自己的所有调用作为Method对象都交给InvocationHandler处理,InvocationHandler根据是什么Method调用具体实现类的不同方法,InvocationHandler负责增加代理逻辑和调用具体的实现类的方法。

也就是说,动态代理类还是和实现类实现相同的接口,但是动态代理类是根据实现类实现的接口动态生成,不需要使用者关心,另外动态代理类的所有方法调用,统一交给InvocationHandler,不用处理实现类每个接口的每个方法。

在这种模式之中:代理Proxy和RealSubject应该实现相同的功能,这一点相当重要。(我这里说的功能,可以理解为某个类的public方法)

在面向对象的编程之中,如果我们想要约定Proxy和RealSubject可以实现相同的功能,有两种方式:

a.一个比较直观的方式,就是定义一个功能接口,然后让Proxy 和RealSubject来实现这个接口。

b.还有比较隐晦的方式,就是通过继承。因为如果Proxy继承自RealSubject,这样Proxy则拥有了RealSubject的功能,Proxy还可以通过重写RealSubject中的方法,来实现多态。

其中JDK中提供的创建动态代理的机制,是以a这种思路设计的,而cglib则是以b思路设计的。

4.1 JDK动态代理(通过接口)

=================

先来看一个具体的例子,还是以上边明星和经纪人的模型为例,这样方便对比理解:

将明星的表演抽象成一个ShowService接口,包括了唱歌、跳舞的功能:

package com.fufu.aop;

public interface ShowService {

// 歌唱表演

void sing(String songName);

// 舞蹈表演

void dance();

}

明星类实现了ShowService接口:

package com.fufu.aop;

/**

  • 明星类

*/

public class Star implements ShowService{

private String name;

@Override

public void sing(String songName) {

System.out.println(this.name + " sing a song: " + songName);

}

@Override

public void dance() {

System.out.println(this.name + “dance”);

}

public Star(String name) {

this.name = name;

}

public Star() {

}

}

实现一个代理类的请求处理器,处理对具体类的所有方法的调用:

package com.fufu.aop;

import java.lang.reflect.InvocationHandler;

import java.lang.reflect.Method;

public class InvocationHandlerImpl implements InvocationHandler {

ShowService target;

public InvocationHandlerImpl(ShowService target) {

this.target = target;

}

@Override

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

// 表演开始前收钱

getMoney();

// 明星开始唱歌

Object invoke = method.invoke(target, args);

// 表演结束后开发票

writeReceipt();

return invoke;

}

private void getMoney() {

System.out.println(“get money”);

}

private void writeReceipt() {

System.out.println(“write receipt”);

}

}

通过JDK动态代理机制实现一个动态代理:

package com.fufu.aop;

import java.lang.reflect.InvocationHandler;

import java.lang.reflect.Proxy;

public class JDKProxyDemo {

public static void main(String[] args) {

// 1.创建被代理的具体类

Star star = new Star(“Eminem”);

// 2.获取对应的ClassLoader

ClassLoader classLoader = star.getClass().getClassLoader();

// 3.获取被代理对象实现的所有接口

Class[] interfaces = star.getClass().getInterfaces();

// 4.设置请求处理器,处理所有方法调用

InvocationHandler invocationHandler = new InvocationHandlerImpl(star);

/**

  • 5.根据上面提供的信息,创建代理对象 在这个过程中,

  • a.JDK会通过根据传入的参数信息动态地在内存中创建和.class文件等同的字节码

  • b.然后根据相应的字节码转换成对应的class,

  • c.然后调用newInstance()创建实例

*/

Object o = Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);

ShowService showService = (ShowService)o;

showService.sing(“Mockingbird”);

}

}

我们从代理的创建入手,看看JDK的动态代理都做了什么:

Object o = Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);

  1. Proxy.newProxyInstance()获取Star类的所有接口列表(第二个参数:interfaces)

  2. 确定要生成的代理类的类名,默认为:com.sun.proxy.$ProxyXXXX

  3. 根据需要实现的接口信息,在代码中动态创建该Proxy类的字节码;

  4. 将对应的字节码转换为对应的class对象;

  5. 创建InvocationHandler实例handler,用来处理Proxy所有方法调用

  6. Proxy的class对象以创建的handler对象为参数(第三个参数:invocationHandler),实例化一个Proxy对象

而对于InvocationHandler,我们需要实现下列的invoke方法:

public Object invoke(Object proxy, Method method, Object[] args)

在调用代理对象中的每一个方法时,在代码内部,都是直接调用了InvocationHandler的invoke方法,而invoke方法根据代理类传递给自己的method参数来区分是什么方法。

推荐:Java进阶视频资源

可以看出,Proxy.newProxyInstance()方法生成的对象也是实现了ShowService接口的,所以可以在代码中将其强制转换为ShowService来使用,和静态代理到达了同样的效果。我们可以用下面代码把生成的代理类的字节码保存到磁盘里,然后反编译看看JDK生成的动态代理类的结构。

package com.fufu.aop;

import sun.misc.ProxyGenerator;

import java.io.FileOutputStream;

import java.io.IOException;

public class ProxyUtils {

public static void main(String[] args) {

Star star = new Star(“Eminem”);

generateClassFile(star.getClass(), “StarProxy”);

}

public static void generateClassFile(Class clazz, String proxyName) {

//根据类信息和提供的代理类名称,生成字节码

byte[] classFile = ProxyGenerator.generateProxyClass(proxyName, clazz.getInterfaces());

String paths = clazz.getResource(".").getPath();

System.out.println(paths);

FileOutputStream out = null;

try {

//保留到硬盘中

out = new FileOutputStream(paths + proxyName + “.class”);

out.write(classFile);

out.flush();

} catch (Exception e) {

e.printStackTrace();

} finally {

try {

out.close();

} catch (IOException e) {

e.printStackTrace();

}

}

}

}

反编译StarPoxy.class文件后得到:

//

// Source code recreated from a .class file by IntelliJ IDEA

// (powered by Fernflower decompiler)

//

import com.fufu.aop.ShowService;

import java.lang.reflect.InvocationHandler;

import java.lang.reflect.Method;

import java.lang.reflect.Proxy;

import java.lang.reflect.UndeclaredThrowableException;

// 动态代理类StarPoxy实现了ShowService接口

public final class StarProxy extends Proxy implements ShowService {

// 加载接口中定义的所有方法

private static Method m1;

private static Method m3;

private static Method m4;

private static Method m2;

private static Method m0;

//构造函数接入InvocationHandler,也就是持有了InvocationHandler对象h

public StarProxy(InvocationHandler var1) throws {

super(var1);

}

public final boolean equals(Object var1) throws {

try {

return ((Boolean)super.h.invoke(this, m1, new Object[]{var1})).booleanValue();

} catch (RuntimeException | Error var3) {

throw var3;

} catch (Throwable var4) {

throw new UndeclaredThrowableException(var4);

}

}

// 自动生成的sing()方法,实际调用InvocationHandler对象h的invoke方法,传入m3参数对象代表sing()方法

public final void sing(String var1) throws {

try {

super.h.invoke(this, m3, new Object[]{var1});

} catch (RuntimeException | Error var3) {

throw var3;

} catch (Throwable var4) {

throw new UndeclaredThrowableException(var4);

}

}

//同理生成dance()方法

public final void dance() throws {

try {

super.h.invoke(this, m4, (Object[])null);

} catch (RuntimeException | Error var2) {

throw var2;

} catch (Throwable var3) {

throw new UndeclaredThrowableException(var3);

}

}

public final String toString() throws {

try {

return (String)super.h.invoke(this, m2, (Object[])null);

} catch (RuntimeException | Error var2) {

throw var2;

} catch (Throwable var3) {

throw new UndeclaredThrowableException(var3);

}

}

public final int hashCode() throws {

try {

return ((Integer)super.h.invoke(this, m0, (Object[])null)).intValue();

} catch (RuntimeException | Error var2) {

throw var2;

} catch (Throwable var3) {

throw new UndeclaredThrowableException(var3);

}

}

// 加载接口中定义的所有方法

static {

try {

m1 = Class.forName(“java.lang.Object”).getMethod(“equals”, new Class[]{Class.forName(“java.lang.Object”)});

m3 = Class.forName(“com.fufu.aop.ShowService”).getMethod(“sing”, new Class[]{Class.forName(“java.lang.String”)});

m4 = Class.forName(“com.fufu.aop.ShowService”).getMethod(“dance”, new Class[0]);

m2 = Class.forName(“java.lang.Object”).getMethod(“toString”, new Class[0]);

m0 = Class.forName(“java.lang.Object”).getMethod(“hashCode”, new Class[0]);

} catch (NoSuchMethodException var2) {

throw new NoSuchMethodError(var2.getMessage());

} catch (ClassNotFoundException var3) {

throw new NoClassDefFoundError(var3.getMessage());

}

}

}

通过上面反编译后的代码可以看出,JDK生成的动态代理类实现和具体类相同的接口,并持有InvocationHandler对象(InvocationHandler对象又持有具体类),调用动态代理类中方法,会触发传入InvocationHandler的invoke()方法,通过method参数,来区分调用的是什么具体的方法,具体如下图所示:

从代理机制到Spring AOP,这篇给你安排得明明白白的

4.2 CGLIB动态代理(通过继承)

===================

JDK中提供的生成动态代理类的机制有个鲜明的特点是:

某个类必须有实现的接口,而生成的代理类也只能代理某个类接口定义的方法,比如:如果上面例子的Star实现了继承自ShowService接口的方法外,另外实现了方法play(),则在产生的动态代理类中不会有这个方法了!更极端的情况是:如果某个类没有实现接口,那么这个类就不能用JDK产生动态代理了!

幸好我们有cglib,“CGLIB(Code Generation Library),是一个强大的,高性能,高质量的Code生成类库,它可以在运行期扩展Java类与实现Java接口。”

cglib 创建某个类A的动态代理类的模式是:

  1. 查找A上的所有非final 的public类型的方法定义;

  2. 将这些方法的定义转换成字节码;

  3. 将组成的字节码转换成相应的代理的class对象;

  4. 实现 MethodInterceptor接口,用来处理对代理类上所有方法的请求(这个接口和JDK动态代理InvocationHandler的功能和角色是一样的)

有了上边JDK动态代理的例子,cglib的理解起来就简单了,还是先以实例说明,ShowService接口和Star类都复用之前的不变:

实现 MethodInterceptor接口:

package com.fufu.aop;

import net.sf.cglib.proxy.MethodInterceptor;

import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class MethodInterceptorImpl implements MethodInterceptor {

@Override

public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {

// 表演开始前收钱

getMoney();

// 明星开始唱歌

Object invoke = methodProxy.invokeSuper(o, objects);

// 表演结束后开发票

上一篇:【路径规划】基于matlab HybridA_Star算法机器人路径规划【含Matlab源码 1390期】


下一篇:关于 B - Star or Not 的一些领悟