基于Xml的AOP使用
账户的业务层接口
package com.itheima.service;
/**
* 账户的业务层接口
* 实现无返回值无参,无返回值有参,有返回值无参,有返回值有参
*/
public interface IAccountService {
/**
* 模拟保存账户
*/
void saveAccount();
/**
* 模拟更新账户
*/
void updateAccount(int i);
/**
* 删除账户
*/
int deleteAccount();
}
账户业务层实现类
package com.itheima.service.impl;
import com.itheima.service.IAccountService;
/**
* 账户业务层实现类
*/
public class AccountServiceImpl implements IAccountService {
public void saveAccount() {
System.out.println("执行保存");
// int i=1/0;
}
public void updateAccount(int i) {
System.out.println("执行更新"+i);
}
public int deleteAccount() {
System.out.println("执行了删除");
return 0;
}
}
用于记录日志的工具类,它提供了公共的方法
package com.itheima.utils;
import org.aspectj.lang.ProceedingJoinPoint;
/**
* 用于记录日志的工具类,它提供了公共的方法
*/
public class Logger {
/**
* 前置通知用于打印日志,计划让它在切入点方法执行之前执行
*/
public void beforePrintLog(){
System.out.println("前置通知Logger类中的printLog开始记录日志了。");
}
/**
* 后置通知
*/
public void afterReturningPrintLog(){
System.out.println("后置通知Logger类中的printLog开始记录日志了。");
}
/**
* 异常通知
*/
public void afterThrowingPrintLog(){
System.out.println("异常通知Logger类中的printLog开始记录日志了。");
}
/**
* 最终通知
*/
public void afterPrintLog(){
System.out.println("最终通知Logger类中的printLog开始记录日志了。");
}
/**
* 环绕通知
* 问题:
* 当我们配置了环绕通知以后,切入点方法没有执行,而通知方法执行了
* 分析:
* 通过对比动态代理中的环绕通知代码,发现动态代理的环绕通知发现切入点方法有明确的调用,而这个代码中没有。
* 解决:
* 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 t){
System.out.println("Logger类中的AroundPrintLog方法开始记录日志了。异常");
throw new RuntimeException(t);
}finally {
System.out.println("Logger类中的AroundPrintLog方法开始记录日志了。最终");
}
}
}
在resources文件夹下创建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.itheima.service.impl.AccountServiceImpl"></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()
类名和方法名都可以使用*来实现通配
* *..*.*()
参数列表:
可以直接写数据类型
基本类型直接写名称 int
引用类型直接写包名.类名的方式 java.lang.String
可以使用通配符表示任意类型,但必须有参数
可以使用..表示 有无参数 均可,有参数可以是任意类型
全通配写法:
* *..*.*(..)
实际开发中,切入点表达式的通常写法
切到业务层实现类下的所有方法
* com.itheima.service.impl.*.*(..)
-->
<!--配置Logger类-->
<bean id="logger" class="com.itheima.utils.Logger"></bean>
<!--配置AOP-->
<aop:config>
<!--配置切入点表达式,id属性用于指定表达式的唯一标识,expression属性用于指定表达式的内容
此标签可以写在<aop:aspect> 标签的内部,但只能在这个切面内使用
此标签也可以写在<aop:aspect>标签的外部,此时,所有切面都可以使用。
但根据约束,必须写在<aop:aspect>标签之前
-->
<aop:pointcut id="pt1" expression="execution(* com.itheima.service.impl.*.*(..))"/>
<!--配置切面-->
<aop:aspect id="logAdvice" ref="logger">
<!-- <!–配置通知的类型,并且建立通知的方法与切入点方法的关联–>-->
<!--<!– <aop:before method="beforePrintLog" pointcut="execution(* com.itheima.service.impl.*.*(..))"></aop:before>–>-->
<!-- <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="afterPrintLog" pointcut-ref="pt1"></aop:after>-->
<!--环绕通知,详细注释在Logger类中-->
<aop:around method="aroundPrintLog" pointcut-ref="pt1"></aop:around>
</aop:aspect>
</aop:config>
</beans>
测试代码:
package com.itheima.test;
import com.itheima.service.IAccountService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* 测试AOP的配置
*/
public class AopTest {
public static void main(String[] args) {
//1.获取容器
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//2.获取对象
IAccountService as = (IAccountService) ac.getBean("accountService");
//3.执行方法
as.saveAccount();
}
}
运行结果
Logger类中的AroundPrintLog方法开始记录日志了。前置
执行保存
Logger类中的AroundPrintLog方法开始记录日志了。后置
Logger类中的AroundPrintLog方法开始记录日志了。最终
Process finished with exit code 0