AOP环境配置与案例
导入依赖
<dependencies>
<!-- spring -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.3.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.7</version>
</dependency>
</dependencies>
创建业务层接口与其实现类
package com.itheima.service;
/**
* 账户业务层接口
*/
public interface IAccountService {
/**
* 模拟保存账户
*/
void saveAccount();
/**
* 模拟更新账户
* @param i
*/
void updateAccount(int i);
/**
* 模拟删除账户
* @return
*/
int deleteAccount();
}
package com.itheima.service.impl;
import com.itheima.service.IAccountService;
/**
* 账户的业务层实现类
*/
public class AccountService implements IAccountService {
public void saveAccount() {
System.out.println("执行了保存");
}
public void updateAccount(int i) {
System.out.println("执行了更新");
}
public int deleteAccount() {
System.out.println("执行了删除");
return 0;
}
}
目标:需要对IAccountService里面的方法进增强
通知类:
package com.itheima.utils;
/**
* 用于记录日志的工具类,它里面提供了公共代码
*/
public class Logger {
/**
* 用于打印日志:计划让其在切入点方法执行之前执行(切入点方法就是业务层方法)
*/
public void printLog(){
System.out.println("Logger类中的printLog记录日志");
}
}
创建bean.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">
<!-- 配置spring的IOC把service对象配置进来 -->
<bean id="accountSerivce" class="com.itheima.service.impl.AccountService"></bean>
<!--
spring中基于xml的AOP配置步骤
1、把通知bean交给spring来管理
2、使用aop:config标签表面开始aop的配置
3、使用aop:aspect标签表明配置切面
id属性:给切面提供唯一标识
ref属性:指定通知类bean的id
4、在aop:aspect的内部使用对应的标签来配置通知的类型
我们现在的示例是让printLog方法在切入点方法执行之前执行
aop:before:配置前置通知
method:用于指定Logger类中哪个方法是前置通知
pointcut:用于指定切入点表达式,该表达式含义指的是对业务层中哪些方法增强
切入点表达式写法:
关键字:execution(表达式)
表达式写法:
访问修饰符 返回值 包.包....包.类名.方法名(参数列表)
标准的表达式写法:
public void com.itheima.service.impl.AccountService.saveAccount()
访问修饰符可以省略
void com.itheima.service.impl.AccountService.saveAccount()
返回值可以使用通配符,表示任意返回值
* com.itheima.service.impl.AccountService.saveAccount()
包名可以使用通配符,表示任意包。但是有几级包就要写几个*.
* *.*.*.*.AccountService.saveAccount()
包名可以使用 .. 当前包及其子包
* *..AccountService.saveAccount()
类名和方法名都可以使用*来实现通配
* *..*.saveAccount()
参数列表
可以直接写数据类型
基本类型直接写名称 int * *..*.*(int)
引用类型写包名.类名方式 java.lang.String * *..*.*(java.lang.String)
可以使用通配符表示任意类型,但是必须有参数
* *..*.*(*)
可以适用 .. 表示有无参数即可
全通配写法:
* *..*.*(..)
通常写法:
切到业务层实现类下的所有方法
* com.itheima.service.impl.*.*(..)
-->
<!-- 配置Logger类 -->
<bean id="logger" class="com.itheima.utils.Logger"></bean>
<!-- 配置AOP -->
<aop:config>
<!-- 配置切面 -->
<aop:aspect id="logAdvice" ref="logger">
<!-- 配置通知类型且建立通知方法和切入点方法的关联 -->
<aop:before method="printLog" pointcut="execution(* com.itheima.service.impl.*.*(..))"></aop:before>
</aop:aspect>
</aop:config>
</beans>
能在aop:aspect标签中使用不同的标签设置不同的通知
<!-- 配置AOP -->
<aop:config>
<!-- 配置切面 -->
<aop:aspect id="logAdvice" ref="logger">
<!-- 配置前置通知 -->
<aop:before method="beforePrintLog" pointcut="execution(* com.itheima.service.impl.*.*(..))"></aop:before>
<!-- 配置后置通知,它和异常通知两者只能执行一个 -->
<aop:after-returning method="afterReturningPrintLog" pointcut="execution(* com.itheima.service.impl.*.*(..))"></aop:after-returning>
<!-- 配置异常通知,它和后置通知两者只能执行一个 -->
<aop:after-throwing method="afterThrowingPrintLog" pointcut="execution(* com.itheima.service.impl.*.*(..))"></aop:after-throwing>
<!-- 配置最终通知 -->
<aop:after method="afterPrint" pointcut="execution(* com.itheima.service.impl.*.*(..))"></aop:after>
</aop:aspect>
</aop:config>
aop:pointcut标签
<!-- 配置AOP -->
<aop:config>
<!-- 配置切面 -->
<aop:aspect id="logAdvice" ref="logger">
<!--
配置切入点表达式
id属性:用于表达式唯一标志
expression:指定表达式内容
此标签写在aop:aspect标签体内只能在当前切面标签体内使用
它还可以写在aop:aspect外边,这样所有切面都能使用(注意是否有约束的要求)
-->
<aop:pointcut id="pt1" expression="execution(* com.itheima.service.impl.*.*(..))"></aop:pointcut>
<!-- 配置前置通知 -->
<aop:before method="beforePrintLog" pointcut-ref="pt1"></aop:before>
<!-- 配置后置通知,它和异常通知两者只能执行一个 -->
<aop:after-returning method="afterReturningPrintLog" pointcut-ref="pt1"></aop:after-returning>
<!-- 配置异常通知,它和后置通知两者只能执行一个 -->
<aop:after-throwing method="afterThrowingPrintLog" pointcut-ref="pt1"></aop:after-throwing>
<!-- 配置最终通知 -->
<aop:after method="afterPrint" pointcut-ref="pt1"></aop:after>
</aop:aspect>
</aop:config>
环绕通知
<?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">
<!-- 配置spring的IOC把service对象配置进来 -->
<bean id="accountSerivce" class="com.itheima.service.impl.AccountService"></bean>
<!-- 配置Logger类 -->
<bean id="logger" class="com.itheima.utils.Logger"></bean>
<!-- 配置AOP -->
<aop:config>
<!-- 配置切面 -->
<aop:aspect id="logAdvice" ref="logger">
<aop:pointcut id="pt1" expression="execution(* com.itheima.service.impl.*.*(..))"></aop:pointcut>
<!--配置环绕通 详细配置在Logger类中-->
<aop:around method="aroundPrintLog" pointcut-ref="pt1"></aop:around>
</aop:aspect>
</aop:config>
</beans>
Logger类中方法:
/**
* 环绕通知
* 问题:
* 配置了环绕通知之后,切入点方法没有执行,而通知方法执行了
* 分析:
* 动态代理中的环绕通知有明确的切入点方法调用,而我们的方法中没有
* 解决:
* Spring为我们提供了一个接口:ProceedingJoinPoint。该接口有一个方法proceed(),此方法相当于明确调用切入点方法
* 该接口可以作为环绕通知方法参数,在程序执行时,spring框架会为我们提供该接口的实现类供我们使用
*
* 它是spring框架为我们提供的一种可以在代码中手动控制增强代码何时执行的方式
* @throws Throwable
*/
public Object aroundPrintLog(ProceedingJoinPoint pjp) throws Throwable {
Object rtValue = null;
try{
Object[] args = pjp.getArgs(); //得到方法执行所需的参数
System.out.println("前置通知Logger类中的aroundPrintLog记录日志");
rtValue = pjp.proceed(args); //明确调用业务层方法(切入点方法)
System.out.println("后置通知Logger类中的aroundPrintLog记录日志");
return rtValue;
}catch (Throwable t){
System.out.println("异常通知Logger类中的aroundPrintLog记录日志");
throw new RuntimeException(t);`在这里插入代码片`
}finally {
System.out.println("最终通知Logger类中的aroundPrintLog记录日志");
}
}
注解AOP
- spring约束
<?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
http://www.springframework.org/schema/context/spring-context.xsd">
</beans>
- spring配置文件
<?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
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 配置spring创建容器时要扫描的包 -->
<context:component-scan base-package="com.itheima"></context:component-scan>
<!-- 配置spring开启注解AOP支持 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
package com.itheima.utils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
/**
* 用于记录日志的工具类,它里面提供了公共代码
*/
@Component("logger")
@Aspect //表示当前类是切面类
public class Logger {
// 指定切入点表达式
@Pointcut("execution(* com.itheima.service.impl.*.*(..))")
private void pt1(){
}
/**
* 前置通知
*/
@Before("pt1()")
public void beforePrintLog(){
System.out.println("前置通知Logger类中的afterReturningPrintLog记录日志");
}
/**
* 后置通知
*/
@AfterReturning("pt1()")
public void afterReturningPrintLog(){
System.out.println("后置通知Logger类中的afterReturningPrintLog记录日志");
}
/**
* 异常通知
*/
@AfterThrowing("pt1()")
public void afterThrowingPrintLog(){
System.out.println("异常通知Logger类中的afterThrowingPrintLog记录日志");
}
/**
* 最终通知
*/
@After("pt1()")
public void afterPrint(){
System.out.println("最终通知Logger类中的afterPrint记录日志");
}
/**
* 环绕通知
* 问题:
* 配置了环绕通知之后,切入点方法没有执行,而通知方法执行了
* 分析:
*
* 动态代理中的环绕通知有明确的切入点方法调用,而我们的方法中没有
* 解决:
* Spring为我们提供了一个接口:ProceedingJoinPoint。该接口有一个方法proceed(),此方法相当于明确调用切入点方法
* 该接口可以作为环绕通知方法参数,在程序执行时,spring框架会为我们提供该接口的实现类供我们使用
*
* 它是spring框架为我们提供的一种可以在代码中手动控制增强代码何时执行的方式
* @throws Throwable
*/
//@Around("pt1()")
public Object aroundPrintLog(ProceedingJoinPoint pjp) throws Throwable {
Object rtValue = null;
try{
Object[] args = pjp.getArgs(); //得到方法执行所需的参数
System.out.println("前置通知Logger类中的aroundPrintLog记录日志");
rtValue = pjp.proceed(args); //明确调用业务层方法(切入点方法)
System.out.println("后置通知Logger类中的aroundPrintLog记录日志");
return rtValue;
}catch (Throwable t){
System.out.println("异常通知Logger类中的aroundPrintLog记录日志");
throw new RuntimeException(t);
}finally {
System.out.println("最终通知Logger类中的aroundPrintLog记录日志");
}
}
}
使用注解配置推荐使用环绕通知,不会有通知顺序问题