AOP的定义
AOP(Aspect Orient Programming),直译过来就是面向切片编程,AOP是一种编程思想,是面向对象编程(OOP)的一种补充。
Java是一个面向对象的编程语言,但它有一个弊端就是当需要为多个不具有继承关系的对象引入一个公共的行为时,例如日志记录、权限校验、事务管理、统计等功能,只能在每一个对象里都引入公共的行为,这样做不便于维护,而且有大量重复的代码,AOP的出现弥补了OOP的这点不足。
面向切面编程,实现在不修改源代码的情况下给程序动态统一添加额外功能的一种技术。AOP可以拦截指定的方法并对其方法增强,而且无需侵入到业务的代码中,时业务与非业务处理逻辑分离,比如Spring的事务,通过对事务的注解配置,Spring会自动在业务方法中开启、提交事务,并且在业务处理失败时,执行相对应的回滚策略。
AOP 可以帮助程序员在不改变原有代码的情况下,将横切关注点的功能模块化,提高代码的可维护性和可重用性。
AOP的作用
**AOP(Aspect-Oriented Programming)**具有以下几个主要作用:
- 分离关注点:AOP 可以将横切关注点(cross-cutting concerns)从应用程序的核心业务逻辑中分离出来,使得代码更具模块化和可维护性。例如,日志记录、事务管理、安全性控制等功能可以通过 AOP 管理,而不需要与核心业务逻辑混在一起。
- 避免重复代码:通过 AOP,相同的横切关注点可以在多个地方重复使用,避免了重复编写相同的代码,提高了代码的重用性。
- 提高代码可读性:AOP 使得核心业务逻辑更加清晰,去除了大量非核心的代码,使得代码更易于阅读和理解。
- 简化开发流程:AOP 可以简化开发流程,例如在 Spring 框架中,通过 AOP 可以轻松地实现诸如事务管理、安全性控制、性能监控等功能,而无需手动编写大量重复代码。
- 提高系统的灵活性和可扩展性:通过 AOP,横切关注点的变化可以更容易地进行管理和调整,系统更容易适应新的需求和变化。
总之AOP 的作用在于帮助开发者更好地管理和维护横切关注点,使得代码更具可维护性、可重用性,并且提高系统的灵活性和可扩展性。
AOP的相关术语
- Target(目标对象):代理的目标对象
- Proxy(代理):一个类被AOP织入增强后,就产生一个结果代理类
- Joinpoint(连接点):所谓连接点是指那些被拦截到的点。在spring,,这些点指的是方法,因为spring只支持方法类型的连接点
- Pointcut(切入点):所谓切入点是指我们要对哪些Joinpoint进行拦截的定义
- Advice(通知/增强):所谓通知是指拦截Joinpoint之后所要做的事情就是通知
- Aspect(切面):是切入点和通知(引介)的结合
- Weaving (织入):是指把增强应用到目标对象来创建新的代理对象的过程。spring采用动态代理织入,而AspectJ采用编译期织入和类装载期织入
快速入门
1.使用配置文件的方式
(1)搭建maven项目,引入AOP相关依赖
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.8</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.3.8</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.6</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
(2)创建目标类和接口
在项目工程里面创建service包并在包里面创建接口
package com.cqie.service;
public interface PhoneService {
public void select();
}
(3)在service包里面创建impl包用于实现接口,在impl包里面创建目标类
package com.cqie.service.impl;
import com.cqie.service.PhoneService;
public class PhoneServiceImpl implements PhoneService {
@Override
public void select() {
System.out.println("使用华为手机");
}
}
(4)创建切面类
在项目里面创建aop的包,然后在包下面创建切面类 LogManager,先实现环绕通知
package com.cqie.aop;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import java.time.LocalDateTime;
public class LogManager {
public Object printLog(ProceedingJoinPoint pjp) throws Throwable {
Signature signature = pjp.getSignature();
System.out.println(LocalDateTime.now()+"==============>"+signature+"===============>开始执行!");
Object proceed = pjp.proceed();
System.out.println(LocalDateTime.now()+"==============>"+signature+"===============>结束执行!");
return proceed;
}
}
(5)在resource里面配置application.xml文件,然后将目标类和切面类放入ioc容器,交给Spring管理
<bean id="logManager" class="com.cqie.aop.LogManager"></bean>
<bean id="phoneService" class="com.cqie.service.impl.PhoneServiceImpl"></bean>
(6)在application.xml文件中配置织入关系
织入方式一:
<aop:config>
<aop:aspect ref="logManager">
<aop:around method="printLog" pointcut="execution(* com.cqie.service..*.*(..))"></aop:around>
</aop:aspect>
</aop:config>
织入方式二:
<aop:config>
<aop:aspect ref="logManager">
<aop:pointcut id="logPointcut" expression="execution(* com.cqie.service..*.*(..))"/>
<aop:around method="printLog" pointcut-ref="logPointcut"></aop:around>
</aop:aspect>
</aop:config>
execution切点表达式的写法
execution([修饰符]返回值类型包名.类名.方法名(参数))
- 访问修饰符可以省略
- 返回值类型、包名、类名、方法名可以使用星号*代表任意
- 包名与类名之间一个点.代表当前包下的类,两个点…表示当前包及其子包下的类
- 参数列表可以使用两个点…表示任意个数,任意类型的参数列表
例子:
execution(public void com.iflytek.aop.Target.method())
execution(void com.iflytek.aop.Target.* ( ..))
execution(* com.iflytek.aop.*.*( ..))
execution(* com.iflytek.aop..*.* (..))
execution(* *..*.*(..))
(7)使用junit进行测试
package com.cqie.service.impl;
import com.cqie.service.PhoneService;
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.SpringJUnit4ClassRunner;
import static org.junit.Assert.*;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class PhoneServiceImplTest {
@Autowired
private PhoneService phoneService;
@Test
public void select() {
phoneService.select();
}
}
输出的结果在Select方法输出语句之前和之后有输出我们切面类里面打印结果就成功了
通知的类型
名称 | 标签 | 注解 | 说明 |
---|---|---|---|
前置通知 | <aop:before> | @Before | 用于配置前置通知。指定增强的方法在切入点方法之前执行 |
后置通知 | <aop:AfterReturning> | @AfterReturning | 用于配置后置通知。指定增强的方法在切入点方法之后执行 |
环绕通知 | <aop:Around> | @Around | 用于配置环绕通知。指定增强的方法在切入点方法之前和之后都执行 |
异常抛出通知 | <aop:AfterThrowing> | @AfterThrowing | 用于配置异常抛出通知。指定增强的方法在出现异常时执行 |
最终通知 | <aop:After> | @After | 用于配置最终通知。无论增强方式执行是否有异常都会执行 |
我们可以通过更换配置文件里面的标签去更改通知的类型
2.纯注解的方式
使用纯注解的方式使用AOP就不需要application.xml文件,在项目里面创建config文件用于存放配置类
(1)在config文件夹里面创建SpringConfig类,并在类里面加上注解
package com.cqie.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration
@ComponentScan("com.cqie")
@EnableAspectJAutoProxy
public class SpringConfig {
}
@EnableAspectJAutoProxy用于开启Aspectj自动代理功能。它的作用是告诉 Spring 容器要使用 AspectJ 代理来管理被 @Aspect 注解的切面(Aspect),并且将这些切面应用到被 Spring 管理的 Bean 上。
(2)创建切面类
这次使用纯注解方式,就需要在类和方法上加上Aspectj的一些注解(测试前置通知)
package com.cqie.aspect;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class LogManager {
@Pointcut(value = "execution(* com.cqie.service..*.*(..))")
public void pointCut(){}
@Before("LogManager.pointCut()")
public void before(){
System.out.println("前置通知========>");
}
}
(3)创建service包和对应的目标接口和目标类
它们的创建方式跟非注解的创建方式一致,只是创建目标类的时候需要在类上加入@Service注解
package com.cqie.service.impl;
import com.cqie.service.PhoneService;
import org.springframework.stereotype.Service;
@Service
public class PhoneServiceImpl implements PhoneService {
@Override
public void select() {
System.out.println("使用华为手机");
}
}
(4)测试
编写测试类
import com.cqie.config.SpringConfig;
import com.cqie.service.PhoneService;
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.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {SpringConfig.class})
public class test {
@Autowired
PhoneService phoneService;
@Test
public void test(){
phoneService.select();
}
}
输出结果如下:
测试结果跟图片一致测试成功。
常见错误
运行的时候报 Error creating bean with name 'springConfig’错误
这个错误是因为我们在前置通知里面加入了属于环绕通知的参数导致
@Before("LogManager.pointCut()")
public void before(ProceedingJoinPoint pjp){
System.out.println("前置通知========>");
}
把这个前置通知里面的参数删掉就可以解决这个问题
见错误
运行的时候报 Error creating bean with name 'springConfig’错误
[外链图片转存中…(img-oMfOBeyK-1699937308662)]
这个错误是因为我们在前置通知里面加入了属于环绕通知的参数导致
@Before("LogManager.pointCut()")
public void before(ProceedingJoinPoint pjp){
System.out.println("前置通知========>");
}
把这个前置通知里面的参数删掉就可以解决这个问题