首先我们要在Maven工程中的pom.xml中导入两个坐标,第一个是Spring的坐标,第二个就是解析切入点表达式的org.aspectj
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.itheima</groupId>
<artifactId>day03_eesy_SpringAop</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<!--
这个坐标是为了解析切入点表达式的
比如:* com.ysw.service.impl.*.*(..)
-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.7</version>
</dependency>
</dependencies>
</project>
其次,我们先来定义我们Service层的接口类IAccountService
package com.ysw.service;
/**
* 账户的业务层接口
*/
public interface IAccountService {
/**
* 模拟保存账户
*/
void saveAccount();
/**
* 模拟更新账户
*/
void updateAccount(Integer i);
/**
* 删除账户
* @return
*/
Integer deleteAccount();
}
其次我们再来定义我们的service实现类,这里我们的方法是分三种的,一个是无返回值无形参,一个是无返回值有形参,最后一个是有返回值无形参。这里主要是想分别对切入点表达式进行测试的操作。
package com.ysw.service.impl;
import com.ysw.service.IAccountService;
/**
* 账户的业务层实现类
*/
public class AccountServiceImpl implements IAccountService {
public void saveAccount() {
System.out.println("执行了保存");
}
public void updateAccount(Integer i) {
System.out.println("执行了更新" + i);
}
public Integer deleteAccount() {
System.out.println("执行了删除");
return 0;
}
}
写好了接口和实现类之后,我们就来写我们的Logger切面类。
package com.ysw.utils;
/**
* 用于记录日志的工具类,它里面提供了公共的代码
*/
public class Logger {
/**
* 用于打印日志,计划让其在切入点方法执行前执行(切入点方法就是业务层方法)
*/
public void printLog(){
System.out.println("Logger类中的printLog开始记录日志了......");
}
}
最后,我们要在Xml文件中配置我们的Aop,关于Spring中基于Xml的Aop配置步骤:
1、首先把通知的bean也交给我们的Spring来进行管理(这里的通知,我们可以回想我们在动态代理中所执行方法前前后后要执行的具体方法)
2、使用标签<aop:config></aop:config>来表明aop开始配置。
3、使用标签<aop:aspect></aop:aspect>来表示我们开始配置切面(切面就是我们所有通知所在的那个类,这里就是Logger类)。
4、在标签<aop:aspect>标签内部使用相对应的标签来配置通知的类型:<aop:before>、<aop:after-returning>、<aop:after-throwing>、<aop:after>分别是四种通知类型。分别代表:前置通知、后置通知、异常通知、最终通知。里面有一个method属性,是用于指定切面中的通知所用的。
5、pointcut属性:用于指定切入点表达式,切入点表达式是用于指定对业务层中的哪个方法进行增强所用的。
切入点表达式写法:
关键字:execution(表达式)
表达式:访问修饰符(可以省略) 返回值 包名.包名.类名.方法名(参数列表)
标准的表达式写法:public void com.ysw.service.impl.AccountServiceImpl.saveAccount()
全通配写法:execution( * *..*.*.*(..) )
注意:!!!在实际开发中,一般都是切到业务层实现类下的所有方法
* com.ysw.service.Impl.*.*(..)
详情配置的代码:
<?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="accountService" class="com.ysw.service.impl.AccountServiceImpl"></bean>
<!--配置logger通知类,也就是切面-->
<bean id="logger" class="com.ysw.utils.Logger"></bean>
<aop:config>
<!--日志通知,配置切面-->
<aop:aspect id="logAdvice" ref="logger">
<!--
前置通知执行的方法,就是在切点前方执行
并且method指定了执行的方法是printLog方法
-->
<!--配置通知类型且建立通知方法和切入点方法的关联-->
<aop:before method="printLog" pointcut="execution(* com.ysw.service.impl.*.*(..))"></aop:before>
</aop:aspect>
</aop:config>
</beans>
Logger切面的环绕通知:
/**
* 环绕通知
* 问题:
* 当我们配置了环绕通知之后,切入点方法没有执行,而通知方法执行了
* 分析:
* 通过对比动态代理中的环绕通知代码,发现动态代理中的环绕通知,
* 有明确的业务层切入点方法调用,而我们的代码中没有
*
* 解决:
* Spring框架为我们提供了一个接口,ProceedingJoinPoint
* 该接口有一个方法proceed(),此方法相当于明确调用切入点方法
* 该接口可以作为环绕通知的方法参数,在程序执行时Spring框架会为我们提供该接口的实现类供我们使用
*
* Spring中的环绕通知:
* 它是Spring框架为我们提供的一种可以在代码中通过手动控制的方式控制增强方法何时执行的方式
*
*/
public Object aroundPrintLog(ProceedingJoinPoint pjp){
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 throwable) {
//把通知写在异常处,就是异常通知
System.out.println("Logger类中的aroundPrintLog开始记录日志了......");
throw new RuntimeException(throwable);
} finally {
//把异常写在finally就是最终通知
System.out.println("Logger类中的aroundPrintLog开始记录日志了......");
}
}
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="accountService" class="com.ysw.service.impl.AccountServiceImpl"></bean>
<!--配置logger通知类-->
<bean id="logger" class="com.ysw.utils.Logger"></bean>
<aop:config>
<!--日志通知,配置切面-->
<aop:aspect id="logAdvice" ref="logger">
<!--
前置通知执行的方法,就是在切点前方执行
并且method指定了执行的方法是printLog方法
-->
<!--配置前置通知,在切入点方法执行之前-->
<!--<aop:before method="beforePrintLog" pointcut="execution(* com.ysw.service.impl.*.*(..))"></aop:before>-->
<!--因为下面已经定义了pointcut表达式,使用pointcut-ref引用即可-->
<!--<aop:before method="beforePrintLog" pointcut-ref="pt1"></aop:before>-->
<!--配置后置通知,在切入点方法正常执行之后-->
<!--<aop:after-returning method="afterReturningPrintLog" pointcut="execution(* com.ysw.service.impl.*.*(..))"></aop:after-returning>-->
<!--<aop:after-returning method="afterReturningPrintLog" pointcut-ref="pt1"></aop:after-returning>-->
<!--配置异常通知,在切入点方法异常执行的时候-->
<!--<aop:after-throwing method="afterThrowingPrintLog" pointcut="execution(* com.ysw.service.impl.*.*(..))"></aop:after-throwing>-->
<!--<aop:after-throwing method="afterThrowingPrintLog" pointcut-ref="pt1"></aop:after-throwing>-->
<!--配置最终通知,在切入点方法最终执行完之后(无论是否正常都会执行这个)-->
<!--<aop:after method="afterPrintLog" pointcut="execution(* com.ysw.service.impl.*.*(..))"></aop:after>-->
<!--<aop:after method="afterPrintLog" pointcut-ref="pt1"></aop:after>-->
<!--
配置切入点表达式
id属性用于指定表达式的唯一标志
expression是用于指定表达式内容的
此配置标签写在aop:aspect标签的内部,只能当前切面使用
它也可以写在aop:aspect标签外部,此时就变成了所有切面可用(但是一定要在aop:aspect之前,因为有约束要求)
-->
<aop:pointcut id="pt1" expression="execution(* com.ysw.service.impl.*.*(..))"></aop:pointcut>
<!--配置环绕通知 详细的注释请看Logger类中-->
<aop:around method="aroundPrintLog" pointcut-ref="pt1"></aop:around>
</aop:aspect>
</aop:config>
</beans>