Spring之AOP

Spring之AOP

这是我参与11月更文挑战的第21天,活动详情查看:2021最后一次更文挑战

Spring的核心是控制反转(IoC)面向切面(AOP)

前面我们已经了解了IOC,现在来了解什么是AOP!

一、AOP的概念

AOP(Aspect Oriented Programming)意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

为什么说Spring是一个一站式的轻量级开源框架呢?JavaEE开发可分成三层架构,针对JavaEE的三层结构,每一层Spring都提供了不同的解决技术。

• WEB层:SpringMVC

• 业务层:Spring的IoC

• 持久层:Spring的JDBCTemplate(Spring的JDBC模板,ORM模板用于整合其他的持久层框架)

Spring AOP 模块提供拦截器来拦截一个应用程序,例如,当执行一个方法时,你可以在方法执行之前或之后添加额外的功能。

如下图:

image.png

在我们的开发中:dao、service、controller、view层都可以认为是一个切面,**对象与对象之间,方法与方法之间,模块与模块之间都是一个个切面。**我们不改变源代码的情况下,在执行某个切面里的某个方法的时候,在后期开发想添加一些功能,改动源代码代价太大,那么我们就可以通过切入点表达式,指定拦截哪些类的哪些方法, 给指定的类在运行的时候植入切面类代码。这就是面向切面编程。下图也是一个实现的流程。

image.png

注意:AOP不是一种技术,实际上是编程的思想。

二、AOP组成

可以通过预编译方式和运行期动态代理实现在不修改源代码的情况下给程序动态统一添加功能的一种技术。

主要的功能是:日志记录,性能统计,安全控制,事务处理,异常处理等等。

在我们开始使用 AOP 工作之前,让我们熟悉一下 AOP 概念和术语。这些术语并不特定于 Spring,而是与 AOP 有关的。

描述
Aspect(切面) 一个模块具有一组提供横切需求的 APIs。例如,一个日志模块为了记录日志将被 AOP 方面调用。应用程序可以拥有任意数量的方面,这取决于需求。
Join point(切入点) 在你的应用程序中它代表一个点,你可以在插件 AOP 方面。你也能说,它是在实际的应用程序中,其中一个操作将使用 Spring AOP 框架。
Advice(通知) 这是实际行动之前或之后执行的方法。这是在程序执行期间通过 Spring AOP 框架实际被调用的代码。
Pointcut(切入短) 这是一组一个或多个连接点,通知应该被执行。你可以使用表达式或模式指定切入点正如我们将在 AOP 的例子中看到的。
Introduction 引用允许你添加新方法或属性到现有的类中。
Target object(目标) 被一个或者多个方面所通知的对象,这个对象永远是一个被代理对象。也称为被通知对象。
Weaving(织入) Weaving 把方面连接到其它的应用程序类型或者对象上,并创建一个被通知的对象。这些可以在编译时,类加载时和运行时完成。

通知的类型

SpringAOP中,通过Advice可以使用下面提到的五种通知工作:

通知 描述
前置通知 在一个方法执行之前,执行通知。
后置通知 在一个方法执行之后,不考虑其结果,执行通知。
返回后通知 在一个方法执行之后,只有在方法成功完成时,才能执行通知。
抛出异常后通知 在一个方法执行之后,只有在方法退出抛出异常时,才能执行通知。
环绕通知 在建议方法调用之前和之后,执行通知。

三、AOP的实现

  • 通过 Spring API 实现
  • 自定义类来实现Aop
  • 通过注解实现

项目结构:

image.png

需要导入一个AOP织入依赖包!

1
2
3
4
5
6
xml复制代码<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>

1、通过 Spring API 实现

编写业务接口

1
2
3
4
5
6
java复制代码public interface UserService {
public void add();
public void delete();
public void update();
public void search();
}

编写业务接口实现类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
java复制代码public class UserServiceImpl implements UserService{
@Override
public void add() {
System.out.println("xiaoqian增加用户");
}
@Override
public void delete() {
System.out.println("xiaoqian删除用户");
}
@Override
public void update() {
System.out.println("xiaoqian更新用户");
}
@Override
public void search() {
System.out.println("xiaoqian查询用户");
}
}

编写两个日志增强类

前置增强

1
2
3
4
5
6
7
8
java复制代码public class BeforeLog implements MethodBeforeAdvice {

//method : 要执行的目标对象的方法 objects : 被调用的方法的参数 Object : 目标对象
@Override
public void before(Method method, Object[] objects, Object o) throws Throwable {
System.out.println( o.getClass().getName() + "的" + method.getName() + "方法被执行了");
}
}

后置增强

1
2
3
4
5
6
7
8
9
10
11
12
java复制代码public class AfterLog implements AfterReturningAdvice {
//returnValue 返回值
//method被调用的方法
//args 被调用的方法的对象的参数
//target 被调用的目标对象
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("执行了" + target.getClass().getName()
+"的"+method.getName()+"方法,"
+"返回值:"+returnValue);
}
}

编写applicationContext.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
xml复制代码<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">

<!--注册bean-->
<bean id="userService" class="com.mq.service.UserServiceImpl"/>
<bean id="before" class="com.mq.log.AfterLog"/>
<bean id="afterLog" class="com.mq.log.BeforeLog"/>


<!--aop的配置 需要导入aop的约束-->
<aop:config>
<!--切入点 expression:表达式匹配要执行的方法-->
<aop:pointcut id="pointcut" expression="execution(* com.mq.service.UserServiceImpl.*(..))"/>
<!--执行环绕; advice-ref执行方法 . pointcut-ref切入点-->
<aop:advisor advice-ref="before" pointcut-ref="pointcut"/>
<aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
</aop:config>
</beans>

这里用的是 通过类定义切点

拓展:execution表达式的使用:

详细的使用可以参照这篇博客execution表达式
测试类

1
2
3
4
5
6
7
8
9
10
java复制代码public class Text {

@Test
public void test(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService = (UserService) context.getBean("userService");
userService.search();
}

}

结果:

image.png

2、自定义类来实现Aop

自定义一个DiypointCut,写上自己想执行的方法

1
2
3
4
5
6
7
8
9
java复制代码public class DiypointCut {

public void before(){
System.out.println("=========before方法执行前========");
}
public void after(){
System.out.println("=========after方法执行后=========");
}
}

编写applicationContext.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
xml复制代码 <!--第二种方式自定义实现-->
<!--注册bean-->
<bean id="userService" class="com.mq.service.UserServiceImpl"/>
<bean id="diy" class="com.mq.diy.DiypointCut"/>
<!--aop的配置-->
<aop:config>
<!--第二种方式:使用AOP的标签实现-->
<aop:aspect ref="diy">
<aop:pointcut id="diyPonitcut" expression="execution(* com.mq.service.UserServiceImpl.*(..))"/>
<aop:before pointcut-ref="diyPonitcut" method="before"/>
<aop:after pointcut-ref="diyPonitcut" method="after"/>
</aop:aspect>
</aop:config>

测试类

1
2
3
4
5
6
7
8
9
10
java复制代码public class Text {

@Test
public void test(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService = (UserService) context.getBean("userService");
userService.delete();
}

}

结果:

image.png

3、注解实现

编写AnnotationPointcut注解实现的增强类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
java复制代码@Aspect  //标注这个类是一个切面
public class AnnotationPointcut {

@Before("execution(* com.mq.service.UserServiceImpl.*(..))")
public void before(){
System.out.println("---------方法执行前---------");
}
@After("execution(* com.mq.service.UserServiceImpl.*(..))")
public void after(){
System.out.println("---------方法执行后---------");
}

//在环绕增强中,我们可以给定一个参数,代表我们要获取处理切入的点
@Around("execution(* com.mq.service.UserServiceImpl.*(..))")
public void around(ProceedingJoinPoint jp) throws Throwable {
System.out.println("环绕前");
System.out.println("签名:"+jp.getSignature());
//执行目标方法proceed
Object proceed = jp.proceed();
System.out.println("环绕后");
System.out.println(proceed);
}
}

在Spring配置文件中,注册bean,并增加支持注解的配置

1
2
3
4
5
6
7
8
9
xml复制代码    <!--第三种方式:注解实现-->
<bean id="annotationPointcut" class="com.mq.diy.AnnotationPointcut"/>
<bean id="userService" class="com.mq.service.UserServiceImpl"/>
<!-- proxy-target-class属性,默认为false,表示使用jdk动态代理织入增强,
当配为<aop:aspectj-autoproxy poxy-target-class="true"/>时,
表示使用CGLib动态代理技术织入增强。不过即使proxy-target-class设置为false,
如果目标类没有声明接口,则spring将自动使用CGLib动态代理。-->
<!--开启注解支持-->
<aop:aspectj-autoproxy proxy-target-class="true"/>

测试类:

1
2
3
4
5
6
7
8
9
10
java复制代码public class Text {

@Test
public void test(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService = (UserService) context.getBean("userService");
userService.add();
}

}

结果:

image.png

学习一定要多看官方文档,多看源码,了解底层原理是怎么实现的,今日份学习!

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

0%