文章目录
一、理解AOP
AOP(Aspect Oriented Programming),面向切面思想,是Spring的三大核心思想之一(两外两个:IOC-控制反转、DI-依赖注入)。
(一)什么是AOP
用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
下面有一些图片辅助理解:
(二)AOP中的相关概念
-
Aspect(切面)
: Aspect 声明类似于 Java 中的类声明,在 Aspect 中会包含着一些 Pointcut 以及相应的 Advice。 -
Joint point(连接点)
:表示在程序中明确定义的点,典型的包括方法调用,对类成员的访问以及异常处理程序块的执行等等,它自身还可以嵌套其它 joint point。 -
Pointcut(切点)
:表示一组 joint point,这些 joint point 或是通过逻辑关系组合起来,或是通过通配、正则表达式等方式集中起来,它定义了相应的 Advice 将要发生的地方。 -
Advice(增强)
:Advice 定义了在 Pointcut 里面定义的程序点具体要做的操作,它通过 before、after 和 around 来区别是在每个 joint point 之前、之后还是代替执行的代码。 -
Target(目标对象)
:织入 Advice 的目标对象。 -
Weaving(织入)
:将 Aspect 和其他对象连接起来, 并创建 Adviced object 的过程。
二、使用Spring实现AOP
导入依赖
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
使用Aspectj实现AOP有俩种方式:基于XML的声明式和基于注解的声明式
(一)基于XML的声明式
基于XML的声明式是指通过XML文件来定义切面、切入点以及通知,所有切面、切入点和通知都必须定义在<aop:config>
元素中。
Spring配置文件中的<bean>
元素下可以包含多个<aop:config>
元素,一个<aop:config>
元素中有可以包含属性和子元素,子元素包括<aop:pointcut>
、<aop:advisor>
、<aop:aspect>
,配置的时候也必须按照此顺序来。
常用配置代码如下:
<?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
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--定义切面Bean-->
<bean id="myAspect" class="com.weirdo.aspectj"/>
<aop:config>
<!--1.配置切面-->
<aop:aspect id="aspect" ref="myAspect">
<!--2.配置切入点-->
<aop:pointcut id="myPointCut" expression="execution(* com.weirdo.aspectj.* (..))"/>
<!--3.配置通知-->
<!--前置通知-->
<aop:before method="myBefore" pointcut-ref="myPointCut"/>
<!--后置通知-->
<aop:after-returning method="myAfterReturning" pointcut-ref="myPointCut" returning="returnVal"/>
<!--环绕通知-->
<aop:around method="myAround" pointcut-ref="myPointCut"/>
<!--异常通知-->
<aop:after-throwing method="myAfterThrowing" pointcut-ref="myPointCut" throwing="e"/>
<!--最终通知-->
<aop:after method="myAfter" pointcut-ref="myPointCut"/>
</aop:aspect>
</aop:config>
</beans>
UserDao.java
public interface UserDao {
public void addUser();
public void deleteUser();
}
UserDaoImpl.java
public class UserDaoImpl implements UserDao{
@Override
public void addUser() {
System.out.println("添加新用户");
}
@Override
public void deleteUser() {
System.out.println("删除用户");
}
}
MyAspectj.java
/**
* 切面类,此面写通知
*/
public class MyAspectj {
//前置通知
public void myBefore(JoinPoint joinPoint){
System.out.println("前置通知:模拟执行权限检查");
System.out.println("目标类"+joinPoint.getTarget());
System.out.println("被植入的强处理的目标方法为"+joinPoint.getSignature().getName());
}
//后置通知
public void myAfterReturning(JoinPoint joinPoint){
System.out.println("后置通知:模拟记录日志");
System.out.println("被植入强处理的目标方法为"+joinPoint.getSignature().getName());
}
/**
* 环绕通知
* proceedingJoinPoint 是 JoinPoint的子接口,表示可执行目标方法
* 1.必须是Object类型的返回值
* 2.必须接受一个参数,类型为ProceedingJoinPoint
* 3.必须是throws Throwable
*/
public Object myAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{
//开始
System.out.println("环绕开始:执行目标方法之前,模拟开启事务");
//执行当前目标方法
Object obj = proceedingJoinPoint.proceed();
//结束
System.out.println("环绕结束:执行目标方法之后,模拟关闭事务");
return obj;
}
//异常通知
public void myAfterThrowing(JoinPoint joinPoint,Throwable e){
System.out.println("异常通知:出现错误"+e.getMessage());
}
//最终通知
public void myAfter(){
System.out.println("最终通知:模拟方法结束后释放资源");
}
}
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: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/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--目标类-->
<bean id="userDao" class="com.weirdo.dao.UserDaoImpl"/>
<!--切面-->
<bean id="myAspectj" class="com.weirdo.dao.MyAspectj"/>
<aop:config>
<!--1.配置切面-->
<aop:aspect id="aspect" ref="myAspectj">
<!--2.配置切入点-->
<aop:pointcut id="myPointCut" expression="execution(* com.weirdo.dao.MyAspectj.*.*(..))"/>
<!--3.配置通知-->
<!--前置通知-->
<aop:before method="myBefore" pointcut-ref="myPointCut"/>
<!--后置通知-->
<aop:after-returning method="myAfterReturning" pointcut-ref="myPointCut" returning="joinPoint"/>
<!--环绕通知-->
<aop:around method="myAround" pointcut-ref="myPointCut"/>
<!--异常通知-->
<aop:after-throwing method="myAfterThrowing" pointcut-ref="myPointCut" throwing="e"/>
<!--最终通知-->
<aop:after method="myAfter" pointcut-ref="myPointCut" />
</aop:aspect>
</aop:config>
</beans>
TestXMLAspectj.java
public class TestXMLAspectj {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserDao userDao = (UserDao) context.getBean("userDao");
userDao.deleteUser();
}
}
(二)基于注解的声明式
- 在UserDaoImpl类上添加
@Repository("userDao")
MyAspect.java
/**
* 切面类,此面写通知
*/
@Aspect
@Component
public class MyAspectj {
//定义切入点表达式
@Pointcut("execution(* com.weirdo.dao.MyAspectj.*.*(..))")
//使用一个返回值为void 方法体为空的方法来命名切入点
public void myPointCut(){}
//前置通知
@Before("myPointCut()")
public void myBefore(JoinPoint joinPoint){
System.out.println("前置通知:模拟执行权限检查");
System.out.println("目标类"+joinPoint.getTarget());
System.out.println("被植入的强处理的目标方法为"+joinPoint.getSignature().getName());
}
//后置通知
@AfterReturning(value = "myPointCut()")
public void myAfterReturning(JoinPoint joinPoint){
System.out.println("后置通知:模拟记录日志");
System.out.println("被植入强处理的目标方法为"+joinPoint.getSignature().getName());
}
/**
* 环绕通知
* proceedingJoinPoint 是 JoinPoint的子接口,表示可执行目标方法
* 1.必须是Object类型的返回值
* 2.必须接受一个参数,类型为ProceedingJoinPoint
* 3.必须是throws Throwable
*/
@Around("myPointCut()")
public Object myAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{
//开始
System.out.println("环绕开始:执行目标方法之前,模拟开启事务");
//执行当前目标方法
Object obj = proceedingJoinPoint.proceed();
//结束
System.out.println("环绕结束:执行目标方法之后,模拟关闭事务");
return obj;
}
//异常通知
@AfterThrowing(value = "myPointCut()",throwing = "e")
public void myAfterThrowing(JoinPoint joinPoint,Throwable e){
System.out.println("异常通知:出现错误"+e.getMessage());
}
//最终通知
@After("myPointCut()")
public void myAfter(){
System.out.println("最终通知:模拟方法结束后释放资源");
}
}
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:aop="http://www.springframework.org/schema/aop"
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/aop
http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!--指定需要扫描的包,使用注解生效-->
<context:component-scan base-package="com.weirdo"/>
<!--启动基于注解的声明式支持-->
<aop:aspectj-autoproxy />
</beans>