1. 初识AOP
1.1 什么是AOP
AOP 为 Aspect Oriented Programming的缩写,意思为面向切面编程
AOP 是 OOP(面向对象编程) 的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内
容,利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高
程序的可重用性,同时提高了开发的效率。
好处
- 在程序运行期间,在不修改源码的情况下对方法进行功能增强
- 逻辑清晰,开发核心业务的时候,不必关注增强业务的代码
- 减少重复代码,提高开发效率,便于后期维护
1.2 AOP底层实现
AOP 的底层是通过 Spring提供的的动态代理技术实现的。在运行期间,Spring通过动态代理技术动态的生成代理对象,代理对象方法执行时进行增强功能的介入,在去调用目标对象的方法,从而完成功能的增强。
1.3 AOP相关术语
- Target(目标对象):代理的目标对象 (AccountServiceImpl)
- Proxy (代理):一个类被AOP织入增强后,就产生一个结果代理类
- Joinpoint(连接点):所谓连接点是指那些可以被拦截到的点。在spring中,这些点指的是方法,因为spring只支持方法类型的连接点
- Pointcut(切入点):所谓切入点是指我们要对哪些 Joinpoint 进行拦截的定义 (AccountServiceImpl.transfer())
- Advice(通知/ 增强):所谓通知是指拦截到 Joinpoint 之后所要做的事情就是通知 分类:前置通知、后置通知、异常通知、最终通知、环绕通知
(MyAdvice)
- Aspect(切面):是切入点和通知(引介)的结合
- Weaving(织入):是指把增强应用到目标对象来创建新的代理对象的过程,spring采用动态代理织入。
1.4 AOP开发明确事项
1.4.1 开发阶段(我们做的)
- 编写核心业务代码切入点 (目标类的目标方法)
- 把公用代码抽取出来,制作成通知(增强功能方法) 通知
- 在配置文件中,声明切入点与通知间的关系,即切面
1.4.2 运行阶段(Spring框架完成的)
Spring 框架监控切入点方法的执行。一旦监控到切入点方法被运行,使用代理机制,动态创建目标对
象的代理对象,根据通知类别,在代理对象的对应位置,将通知对应的功能织入,完成完整的代码逻辑
运行。
1.4.3 底层代理实现
在 Spring 中,框架会根据目标类是否实现了接口来决定采用哪种动态代理的方式。
- 当bean实现接口时,会用JDK代理模式
- 当bean没有实现接口,用cglib实现( 可以强制使用cglib(在spring配置中加入<aop:aspectjautoproxy proxyt-target-class=”true”/>)
2.基于XML的AOP开发
2.1 项目搭建
2.1.1 pom.xml
<dependencies>
<!--导入spring的context坐标,context依赖aop-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
<!-- aspectj的织入(切点表达式需要用到该jar包) -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.13</version>
</dependency>
<!--spring整合junit-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
复制代码
2.1.2 业务接口
public interface AccountService {
public void transfer();
}
复制代码
2.1.3 业务实现
@Service
public class AccountServiceImpl implements AccountService {
@Override
public void transfer() {
System.out.println("转账业务执行了。。。");
}
}
复制代码
2.1.4 通知类
@Component
public class MyAdvice {
public void before(){
System.out.println("前置通知...");
}
}
复制代码
2.1.5 applicationContext.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:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation=" http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--开启组件扫描-->
<context:component-scan base-package="com.lagou"/>
<aop:config>
<!--Aspect(切面) 切点+通知-->
<aop:aspect ref="myAdvice">
<!--配置目标类的transfer方法执行时,使用通知类的before方法进行前置增强 pointcut(切入点)-->
<aop:before method="before" pointcut="execution(public void com.lagou.service.impl.AccountServiceImpl.transfer())"/>
</aop:aspect>
</aop:config>
</beans>
复制代码
2.1.6 测试类
@RunWith(SpringRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class TestAccount {
@Autowired
private AccountService accountService;
@Test
public void testTransfer(){
accountService.transfer();
}
}
/////
前置通知...
转账业务执行了。。。
复制代码
2.2 XML配置AOP详解
2.2.1 切点表达式
(1)语法
execution([修饰符] 返回值类型 包名.类名.方法名(参数))
复制代码
- 访问修饰符可以省略
- 返回值类型、包名、类名、方法名可以使用星号 * 代替,代表任意
- 包名与类名之间一个点 . 代表当前包下的类,两个点 .. 表示当前包及其子包下的类
- 参数列表可以使用两个点 .. 表示任意个数,任意类型的参数列表
(2)示例
execution(public void com.lagou.service.impl.AccountServiceImpl.transfer())
execution(void com.lagou.service.impl.AccountServiceImpl.*(..))
execution(* com.lagou.service.impl.*.*(..))
execution(* com.lagou.service..*.*(..))
复制代码
(3)切点表达式抽取
当多个增强的切点表达式相同时,可以将切点表达式进行抽取,在增强中使用 pointcut-ref 属性代替
pointcut 属性来引用抽取后的切点表达式。
<aop:config>
<!--抽取的切点表达式-->
<aop:pointcut id="myPointCut" expression="execution(public void com.lagou.service.impl.AccountServiceImpl.transfer())"/>
<!--Aspect(切面) 切点+通知-->
<aop:aspect ref="myAdvice">
<!--配置目标类的transfer方法执行时,使用通知类的before方法进行前置增强 pointcut(切入点)-->
<aop:before method="before" pointcut-ref="myPointCut"/>
</aop:aspect>
</aop:config>
复制代码
2.2.2 通知类型
语法:
<aop:通知类型 method=“通知类中方法名” pointcut=“切点表达式"></aop:通知类型>
复制代码
名称 | 标签 | 说明 |
---|---|---|
前置通知 | aop:before | 用于配置前置通知。指定增强的方法在切入点方法之前执行 |
后置通知 | aop:afterReturning | 用于配置后置通知。指定增强的方法在切入点方法之后执行 |
异常通知 | aop:afterThrowing | 用于配置异常通知。指定增强的方法出现异常后执行 |
最终通知 | aop:after | 用于配置最终通知。无论切入点方法执行时是否有异常,都会执行 |
环绕通知 | aop:around | 用于配置环绕通知。开发者可以手动控制增强代码在什么时候执行 |
注意:通常情况下,环绕通知都是独立使用的
3.基于XML的AOP开发
3.1 项目搭建
3.1.1 在通知类中使用注解配置织入关系,升级为切面类
/**
* @author 振帅
* Created on 2021/06/23 15:03
*/
@Component
@Aspect
public class MyAdvice {
@Before("execution(* com.lagou..*.*(..))")
public void before(){
System.out.println("前置通知...");
}
}
复制代码
3.1.2 开启AOP的自动代理
<!--aop的自动代理-->
<aop:aspectj-autoproxy />
复制代码
3.2 注解配置AOP详解
3.2.1 切点表达式的抽取
/**
* @author 振帅
* Created on 2021/06/23 15:03
*/
@Component
@Aspect
public class MyAdvice {
@Pointcut("execution(* com.lagou..*.*(..))")
public void myPoint() {
}
@Before("MyAdvice.myPoint()")
public void before(){
System.out.println("前置通知...");
}
}
复制代码
3.2.2 通知类型
名称 | 标签 | 说明 |
---|---|---|
前置通知 | @Before | 用于配置前置通知。指定增强的方法在切入点方法之前执行 |
后置通知 | @AfterReturning | 用于配置后置通知。指定增强的方法在切入点方法之后执行 |
异常通知 | @AfterThrowing | 用于配置异常通知。指定增强的方法出现异常后执行 |
最终通知 | @After | 用于配置最终通知。无论切入点方法执行时是否有异常,都会执行 |
环绕通知 | @Around | 用于配置环绕通知。开发者可以手动控制增强代码在什么时候执行 |
当前四个通知组合在一起时,执行顺序如下:
@Before -> @After -> @AfterReturning(如果有异常:@AfterThrowing)
3.2.3 纯注解配置
(1)spring配置类
package com.lagou.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
/**
* @Author 振帅
* @Date 2021/06/23 20:37
*/
@Configuration //指定当前类是一个Spring 配置类,当创建容器时会从该类上加载注解
@ComponentScan("com.lagou") //替代 <context:component-scan base-package="com.lagou"/>
@EnableAspectJAutoProxy //替代 <aop:aspectj-autoproxy />
public class SpringConfig {
}
复制代码
(2)测试类
package com.lagou;
import com.lagou.config.SpringConfig;
import com.lagou.service.AccountService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
/**
* @author 振帅
* Created on 2021/06/22 17:26
*/
@RunWith(SpringRunner.class)
@ContextConfiguration( classes = SpringConfig.class)
public class TestAccount {
@Autowired
private AccountService accountService;
@Test
public void testTransfer(){
accountService.transfer();
}
}
复制代码
4.AOP优化转账案例
依然使用前面的转账案例,将两个代理工厂对象直接删除!改为spring的aop思想来实现
4.1 xml配置实现
<!--AOP配置-->
<aop:config>
<!--切点表达式-->
<aop:pointcut id="myPointcut" expression="execution(* com.lagou.service..*.*(..))"/>
<!-- 切面配置 -->
<aop:aspect ref="transactionManager">
<aop:before method="beginTransaction" pointcut-ref="myPointcut"/>
<aop:after-returning method="commit" pointcut-ref="myPointcut"/>
<aop:after-throwing method="rollback" pointcut-ref="myPointcut"/>
<aop:after method="release" pointcut-ref="myPointcut"/>
</aop:aspect>
</aop:config>
复制代码
4.2 注解配置实现
/**
* @Author 振帅
* @Date 2021/06/23 20:51
*/
@Component
@Aspect
public class TransactionManager {
@Autowired
ConnectionUtils connectionUtils;
@Around("execution(* com.lagou.serivce..*.*(..))")
public Object around(ProceedingJoinPoint pjp) {
Object object = null;
try {
// 开启事务
connectionUtils.getThreadConnection().setAutoCommit(false);
// 业务逻辑
pjp.proceed();
// 提交事务
connectionUtils.getThreadConnection().commit();
} catch (Throwable throwable) {
throwable.printStackTrace();
// 回滚事务
try {
connectionUtils.getThreadConnection().rollback();
} catch (SQLException e) {
e.printStackTrace();
}
} finally {
try {
connectionUtils.getThreadConnection().setAutoCommit(true);
connectionUtils.getThreadConnection().close();
connectionUtils.removeThreadConnection();
} catch (SQLException e) {
e.printStackTrace();
}
}
return object;
}
}
复制代码
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END