Spring的AOP面向切面编程
1、什么是AOP
AOP(Aspect Oriented Programming)面向切面编程,主张将程序中的相同业务逻辑进行横向隔离,并将重复的业务逻辑抽取到一个独立的模块中,实现提升程序可复用性和开发效率的目的。
用通俗一点话来说就是:把一个具体的操作看作一根竹,然后面向切面就是把竹子的中间切开,然后对这个切面进行操作,就是面向切面编程。假如说如果没有这个切面,那么对这个竹子不会有任何影响,有了这个切面,只会让这个竹子锦上添花。总结来就是,好的AOP应该是:如果去掉这个AOP不会对这个原来的程序造成任何影响,有了AOP会让这个程序更好。目的就是为了降低耦合。
2、Spring AOP的开发方法
Spring AOP的开发方法有两种:
-
基于XML配置文件开发Spring AOP
配置切面,然后在XML配置切点和切面
-
基于注解开发Spring AOP(常用于SpringBoot)
配置切面,并且在切面里直接加上注解配置切点和切面以及通知
3、基于XML开发SpringAOP
-
创建Maven项目,导入依赖:
<dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework/spring-context --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.6.RELEASE</version> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework/spring-context --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.2.6.RELEASE</version> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework/spring-context --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>5.2.6.RELEASE</version> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework/spring-context --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>5.2.6.RELEASE</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.2</version> </dependency> </dependencies>
-
Service业务(里面的方法就是需要被切的操作)
UserService.java接口
public interface UserService { //查询用户 void selectUser(); //新增用户 void insertUser(); //删除用户 void deleteUser(); //修改用户 void updateUser(); }
UserServiceImpl.java
public class UserServiceImpl implements UserService{ @Override public void selectUser() { System.out.println("执行了selectUser方法"); } @Override public void insertUser() { System.out.println("执行了insertUser方法"); } @Override public void deleteUser() { System.out.println("执行了deleteUser方法"); } @Override public void updateUser() { System.out.println("执行了updateUser方法"); } }
UserServiceImpl是UserService的实现类,这里面实际应该是具体的操作,这里为了方便演示就打印就好了。
-
Aspect切面(就是对切面进行的操作)
Aspect.java
public class Aspect { //before前置通知 public void before(){ System.out.println("before前置通知"); } //after后置通知 public void after(){ System.out.println("after后置通知"); } //around环绕通知 public Object around(ProceedingJoinPoint point)throws Throwable{ System.out.println("around环绕通知之前——————"); Object object = point.proceed(); System.out.println("around环绕通知之后——————"); return object; } //after-returning返回通知 public void afterReturning(){ System.out.println("after-returning返回通知"); } //after-throwing异常通知 public void afterThrowing(){ System.out.println("after-throwing异常通知"); } }
-
在XML文件里进行相关配置
spring-dao.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 https://www.springframework.org/schema/aop/spring-aop.xsd"> <!--注册bean--> <bean name="userService" class="cn.wqk.service.UserServiceImpl"/> <bean name="aspect" class="cn.wqk.aop.Aspect"/> <!--配置aop--> <aop:config> <!--切点pointcut--> <aop:pointcut id="pointcut" expression="execution(* cn.wqk.service.UserServiceImpl.*(..))"/> <!--切面aspect--> <aop:aspect id="aspect" ref="aspect"> <!--前置通知--> <aop:before method="before" pointcut-ref="pointcut"/> <!--后置通知--> <aop:after method="after" pointcut-ref="pointcut"/> <!--环绕通知--> <aop:around method="around" pointcut-ref="pointcut"/> <!--返回通知--> <aop:after-returning method="afterReturning" pointcut-ref="pointcut"/> <!--异常通知--> <aop:after-throwing method="afterThrowing" pointcut-ref="pointcut"/> </aop:aspect> </aop:config> </beans>
把userService和aspect都丢进Spring容器里,然后再配置aop:
<aop:config>
:配置aop相关
<aop:pointcut>
配置切点,其中expression里是需要切入的方法
<aop:aspect>
配置切面,其中ref取的的bean里的name为aspect的切面
<aop:before>
配置切面里的前置通知,pointcut-ref里是取的是bean里的name为pointcut的切点等。
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"
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">
<import resource="classpath:spring-aop.xml"/>
</beans>
把spring-aop导入到applicationContext容器里,让spring-aop专注于aop,利用程序后期更好的扩展。
-
测试
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration({"classpath:applicationContext.xml"}) public class TestAOP { @Autowired UserService userService; @Test public void test(){ //调用UserService里的selectUser()方法 userService.selectUser(); } }
测试结果:
我们可以得出结论:
通知 执行顺序 前置通知 在切点方法执行之前执行的 后置通知 在切点方法执行之后执行的(出现异常就不会调用) 环绕通知 在进入方法的时候执行和方法返回结果后执行 返回通知 在方法执行成功,并且返回返回值的时候执行 异常通知 在程序发生异常的时候执行,并且后面的通知也不执行
4、基于注解开发SpringAOP
UserService和UserServiceImpl依旧是上面的,我们只需要重新配置Aspect类
Aspect.java:
@Aspect
public class AspectAdvice {
//定义切点
@Pointcut("execution(* cn.wqk.service.UserServiceImpl.*(..))")
public void pointcut(){}
//before前置通知
@Before("pointcut()")
public void before(){
System.out.println("before前置通知");
}
//after后置通知
@After("pointcut()")
public void after(){
System.out.println("after后置通知");
}
//around环绕通知
@Around("pointcut()")
public Object around(ProceedingJoinPoint point)throws Throwable{
System.out.println("around环绕通知之前——————");
Object object = point.proceed();
System.out.println("around环绕通知之后——————");
return object;
}
//after-returning返回通知
@AfterReturning("pointcut()")
public void afterReturning(){
System.out.println("after-returning返回通知");
}
//after-throwing异常通知
@AfterThrowing("pointcut()")
public void afterThrowing(){
System.out.println("after-throwing异常通知");
}
}
记得在这个类的头上加上@Aspect
切面注解,表面这是一个切面类,每个通知类的值是定义的切点的方法名
注解名 | 描述 |
---|---|
@Aspect | 定义切面 |
@Pointcut | 定义切点 |
@Before | before前置通知 |
@After | after后置通知 |
@Around | around环绕通知 |
@AfterReturning | after-returning返回通知 |
@AfterThrowing | after-throwing异常通知 |
将spring-aop.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
https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--注册bean-->
<bean name="userService" class="cn.wqk.service.UserServiceImpl"/>
<bean name="aspectAdvice" class="cn.wqk.aop.AspectAdvice"/>
<!--开启@Aspect自动代理-->
<aop:aspectj-autoproxy/>
</beans>
注意不要忘了把它import到applicationContext里
最后用相同的方法进行测试就好了。