AOP如何实现及实现原理

本文详细介绍了AOP(面向切面编程)的基本概念,包括切面、切点和通知类型,并通过一个学校管理系统的需求场景展示了AOP如何减少代码重复性和耦合性。在代码实现部分,给出了一个使用注解驱动的AOP示例,通过自定义注解和切面类实现了记录操作的功能。接着,文章探讨了AOP的动态代理原理,对比了静态代理和动态代理,并通过Java代码展示了静态和动态代理的工作方式,强调动态代理在代理类方法处理上的便利性。
摘要由CSDN通过智能技术生成

接下来介绍一下提到AOP就必须要了解的知识点:

  • 切面:拦截器类,其中会定义切点以及通知

  • 切点:具体拦截的某个业务点。

  • 通知:切面当中的方法,声明通知方法在目标业务层的执行位置,通知类型如下:

  1. 前置通知:@Before 在目标业务方法执行之前执行

  2. 后置通知:@After 在目标业务方法执行之后执行

  3. 返回通知:@AfterReturning 在目标业务方法返回结果之后执行

  4. 异常通知:@AfterThrowing 在目标业务方法抛出异常之后

  5. 环绕通知:@Around 功能强大,可代替以上四种通知,还可以控制目标业务方法是否执行以及何时执行

2. 代码中实现举例

===========

上面已经大概的介绍了AOP中需要了解的基本知识,也知道了AOP的好处,那怎么在代码中实现呢?给大家举个例子:我们现在有个学校管理系统,已经实现了对老师和学生的增删改,又新来个需求,说是对老师和学生的每次增删改做一个记录,到时候校长可以查看记录的列表。那么问题来了,怎么样处理是最好的解决办法呢?这里我罗列了三种解决办法,我们来看下他的优缺点。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

-最简单的就是第一种方法,我们直接在每次的增删改的函数当中直接实现这个记录的方法,这样代码的重复度太高,耦合性太强,不建议使用。

-其次就是我们最长使用的,将记录这个方法抽离出来,其他的增删改调用这个记录函数即可,显然代码重复度降低,但是这样的调用还是没有降低耦合性。

-这个时候我们想一下AOP的定义,再想想我们的场景,其实我们就是要在不改变原来增删改的方法,给这个系统增加记录的方法,而且作用的也是一个层面的方法。这个时候我们就可以采用AOP来实现了。

我们来看下代码的具体实现:

1. 首先我定义了一个自定义注解作为切点

@Target(AnnotationTarget.FUNCTION) //注解作用的范围,这里声明为函数

@Order(Ordered.HIGHEST_PRECEDENCE) //声明注解的优先级为最高,假设有多个注解,先执行这个

annotation class Hanler(val handler: HandlerType) //自定义注解类,HandlerType是一个枚举类型,里面定义的就是学生和老师的增删改操作,在这里就不展示具体内容了

2. 接下来就是要定义切面类了

@Aspect //该注解声明这个类为一个切面类

@Component

class HandlerAspect{

@Autowired

private lateinit var handlerService: HandlerService

@AfterReturning(“@annotation(handler)”) //当有函数注释了注解,将会在函数正常返回后在执行我们定义的方法

fun hanler(hanler: Hanler) {

handlerService.add(handler.operate.value) //这里是真正执行记录的方法

}

}

3. 最后就是我们本来的业务方法了

/**

  • 删除学生方法

*/

@Handler(operate= Handler.STUDENT_DELETE) //当执行到删除学生方法时,切面类就会起作用了,当学生正常删除后就会执行记录方法,我们就可以看到记录方法生成的数据

fun delete(id:String) {

studentService.delete(id)

}

3. AOP实现原理

===========

我们现在了解了代码中如何实现,那么AOP实现的原理是什么呢?之前看了一个博客说到,提到AOP大家都知道他的实现原理是动态代理,显然我之前就是不知道的,哈哈,但是相信阅读文章的你们一定是知道的。

讲到动态代理就不得不说代理模式了, 代理模式的定义:给某一个对象提供一个代理,并由代理对象控制对原对象的引用。代理模式包含如下角色:subject:抽象主题角色,是一个接口。该接口是对象和它的代理共用的接口; RealSubject:真实主题角色,是实现抽象主题接口的类; Proxy:代理角色,内部含有对真实对象RealSubject的引用,从而可以操作真实对象。代理对象提供与真实对象相同的接口,以便代替真实对象。同时,代理对象可以在执行真实对象操作时,附加其他的操作,相当于对真实对象进行封装。如下图所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

那么代理又分为静态代理和动态代理,这里写两个小的demo,动态代理采用的就是JDK代理。举个例子就是现在一个班上的学生需要交作业,现在由班长代理交作业,那么班长就是代理,学生就是被代理的对象。

3.1 静态代理


首先,我们创建一个Person接口。这个接口就是学生(被代理类),和班长(代理类)的公共接口,他们都有交作业的行为。这样,学生交作业就可以让班长来代理执行。

/**

  • Created by Mapei on 2018/11/7

  • 创建person接口

*/

public interface Person {

//交作业

void giveTask();

}

Student类实现Person接口,Student可以具体实施交作业这个行为。

/**

  • Created by Mapei on 2018/11/7

*/

public class Student implements Person {

private String name;

public Student(String name) {

this.name = name;

}

public void giveTask() {

System.out.println(name + “交语文作业”);

}

}

StudentsProxy类,这个类也实现了Person接口,但是还另外持有一个学生类对象,那么他可以代理学生类对象执行交作业的行为。

/**

  • Created by Mapei on 2018/11/7

  • 学生代理类,也实现了Person接口,保存一个学生实体,这样就可以代理学生产生行为

*/

public class StudentsProxy implements Person{

//被代理的学生

Student stu;

public StudentsProxy(Person stu) {

// 只代理学生对象

if(stu.getClass() == Student.class) {

this.stu = (Student)stu;

}

}

//代理交作业,调用被代理学生的交作业的行为

public void giveTask() {

stu.giveTask();

}

}

下面测试一下,看代理模式如何使用:

/**

  • Created by Mapei on 2018/11/7

*/

public class StaticProxyTest {

public static void main(String[] args) {

//被代理的学生林浅,他的作业上交有代理对象monitor完成

Person linqian = new Student(“林浅”);

//生成代理对象,并将林浅传给代理对象

Person monitor = new StudentsProxy(linqian);

//班长代理交作业

monitor.giveTask();

}

}

运行结果:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这里并没有直接通过林浅(被代理对象)来执行交作业的行为,而是通过班长(代理对象)来代理执行了。这就是代理模式。代理模式就是在访问实际对象时引入一定程度的间接性,这里的间接性就是指不直接调用实际对象的方法,那么我们在代理过程中就可以加上一些其他用途。比如班长在帮林浅交作业的时候想告诉老师最近林浅的进步很大,就可以轻松的通过代理模式办到。在代理类的交作业之前加入方法即可。这个优点就可以运用在spring中的AOP,我们能在一个切点之前执行一些操作,在一个切点之后执行一些操作,这个切点就是一个个方法。这些方法所在类肯定就是被代理了,在代理过程中切入了一些其他操作。

3.2 动态代理


动态代理和静态代理的区别是,静态代理的的代理类是我们自己定义好的,在程序运行之前就已经变异完成,但是动态代理的代理类是在程序运行时创建的。相比于静态代理,动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类中的方法。比如我们想在每个代理方法之前都加一个处理方法,我们上面的例子中只有一个代理方法,如果还有很多的代理方法,就太麻烦了,我们来看下动态代理是怎么去实现的。

首先还是定义一个Person接口:

/**

  • Created by Mapei on 2018/11/7

  • 创建person接口

*/

public interface Person {

//交作业

void giveTask();

}

接下来是创建需要被代理的实际类,也就是学生类:

/**

  • Created by Mapei on 2018/11/7

*/

public class Student implements Person {

private String name;

public Student(String name) {

this.name = name;

}

public void giveTask() {

System.out.println(name + “交语文作业”);

}

}

创建StuInvocationHandler类,实现InvocationHandler接口,这个类中持有一个被代理对象的实例target。InvocationHandler中有一个invoke方法,所有执行代理对象的方法都会被替换成执行invoke方法。

/**

  • Created by Mapei on 2018/11/7

*/

public class StuInvocationHandler implements InvocationHandler {

//invocationHandler持有的被代理对象

T target;

public StuInvocationHandler(T target) {

this.target = target;

}

/**

  • proxy:代表动态代理对象

  • method:代表正在执行的方法

  • args:代表调用目标方法时传入的实参

*/

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

System.out.println(“代理执行” +method.getName() + “方法”);

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

return result;

}

}

那么接下来我们就可以具体的创建代理对象了。

/**

  • Created by Mapei on 2018/11/7

  • 代理类

*/

public class ProxyTest {

public static void main(String[] args) {

//创建一个实例对象,这个对象是被代理的对象

Person linqian = new Student(“林浅”);

//创建一个与代理对象相关联的InvocationHandler

InvocationHandler stuHandler = new StuInvocationHandler(linqian);

//创建一个代理对象stuProxy来代理linqian,代理对象的每个执行方法都会替换执行Invocation中的invoke方法

Person stuProxy = (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), new Class<?>[]{Person.class}, stuHandler);

//代理执行交作业的方法

stuProxy.giveTask();

}

}

我们执行代理测试类,首先我们创建了一个需要被代理的学生林浅,将林浅传入stuHandler中,我们在创建代理对象stuProxy时,将stuHandler作为参数,那么所有执行代理对象的方法都会被替换成执行invoke方法,也就是说,最后执行的是StuInvocationHandler中的invoke方法。所以在看到下面的运行结果也就理所当然了。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

那么到这里问题就来了,为什么代理对象执行的方法都会通过InvocationHandler中的invoke方法来执行,带着这个问题,我们需要看一下动态代理的源码,对他进行简单的分析。

上面我们使用Proxy类的newProxyInstance方法创建了一个动态代理对象,看一下他的源码:

public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)

throws IllegalArgumentException

{

Objects.requireNonNull(h);

final Class<?>[] intfs = interfaces.clone();

final SecurityManager sm = System.getSecurityManager();

if (sm != null) {

checkProxyAccess(Reflection.getCallerClass(), loader, intfs);

}

/*

  • Look up or generate the designated proxy class.

*/

Class<?> cl = getProxyClass0(loader, intfs);

/*

  • Invoke its constructor with the designated invocation handler.

*/

try {

if (sm != null) {

checkNewProxyPermission(Reflection.getCallerClass(), cl);

}

final Constructor<?> cons = cl.getConstructor(constructorParams);

final InvocationHandler ih = h;

if (!Modifier.isPublic(cl.getModifiers())) {

AccessController.doPrivileged(new PrivilegedAction() {

public Void run() {

cons.setAccessible(true);

return null;

}

});

}

return cons.newInstance(new Object[]{h});

} catch (IllegalAccessException|InstantiationException e) {

throw new InternalError(e.toString(), e);

} catch (InvocationTargetException e) {

Throwable t = e.getCause();

if (t instanceof RuntimeException) {

throw (RuntimeException) t;

} else {

throw new InternalError(t.toString(), t);

}

} catch (NoSuchMethodException e) {

throw new InternalError(e.toString(), e);

}

}

然后,我们需要重点关注Class<?> cl = getProxyClass0(loader, intfs)这句代码,这里产生了代理类,这个类就是动态代理的关键,由于是动态生成的类文件,我们将这个类文件打印到文件中。

byte[] classFile = ProxyGenerator.generateProxyClass(“$Proxy0”, Student.class.getInterfaces());

String path = “/Users/mapei/Desktop/okay/65707.class”;

try{

FileOutputStream fos = new FileOutputStream(path);

fos.write(classFile);

fos.flush();

System.out.println(“代理类class文件写入成功”);

}catch (Exception e) {

System.out.println(“写文件错误”);

}

对这个class文件进行反编译,我们看看jdk为我们生成了什么样的内容:

import java.lang.reflect.InvocationHandler;

import java.lang.reflect.Method;

import java.lang.reflect.Proxy;

import java.lang.reflect.UndeclaredThrowableException;

import proxy.Person;

public final class $Proxy0 extends Proxy implements Person

{

private static Method m1;

private static Method m2;

private static Method m3;

private static Method m0;

/**

*注意这里是生成代理类的构造方法,方法参数为InvocationHandler类型,看到这,是不是就有点明白

*为何代理对象调用方法都是执行InvocationHandler中的invoke方法,而InvocationHandler又持有一个

*被代理对象的实例,就可以去调用真正的对象实例。

*/

public $Proxy0(InvocationHandler paramInvocationHandler)

throws

{

super(paramInvocationHandler);

}

//这个静态块本来是在最后的,我把它拿到前面来,方便描述

static

{

try

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(资料价值较高,非无偿)

img

总结

在这里,由于面试中MySQL问的比较多,因此也就在此以MySQL为例为大家总结分享。但是你要学习的往往不止这一点,还有一些主流框架的使用,Spring源码的学习,Mybatis源码的学习等等都是需要掌握的,我也把这些知识点都整理起来了

面试真题

Spring源码笔记

《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》点击传送门即可获取!
V-1711578816564)]

[外链图片转存中…(img-3ZDfaWjs-1711578816565)]

[外链图片转存中…(img-3TZbhK6Z-1711578816565)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(资料价值较高,非无偿)

img

总结

在这里,由于面试中MySQL问的比较多,因此也就在此以MySQL为例为大家总结分享。但是你要学习的往往不止这一点,还有一些主流框架的使用,Spring源码的学习,Mybatis源码的学习等等都是需要掌握的,我也把这些知识点都整理起来了

[外链图片转存中…(img-4JuOl32m-1711578816565)]

[外链图片转存中…(img-xewRWiLQ-1711578816565)]

《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》点击传送门即可获取!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值