03-Spring、AOP
一、AOP的相关概念
1.1、AOP概述
AOP: Aspect Oriented Programming 即:面向切面编程。
AOP (面向切面编程)
在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
就是把程序重复的代码抽取出来,在需要执行的时候,使用动态代理的技术,在不修改源码的基础上,对已有方法进行增强。
1.1.2、AOP的作用及优势
在程序运行期间,不修改源码对已有方法进行增强。
减少重复代码
提高开发效率
维护方便
1.2、AOP的具体应用
1.2.1、问题
账户的业务实现类
/** * 账户的持久层实现类 */ public class AccountDaoImpl implements IAccountDao { private QueryRunner runner; public void setRunner(QueryRunner runner) { this.runner = runner; } @Override public List<Account> findAllAccount() { try{ return runner.query("select * from account",new BeanListHandler<Account>(Account.class)); }catch (Exception e) { throw new RuntimeException(e); } } @Override public Account findAccountById(Integer accountId) { try{ return runner.query("select * from account where id = ? ",new BeanHandler<Account>(Account.class),accountId); }catch (Exception e) { throw new RuntimeException(e); } } @Override public void saveAccount(Account account) { try{ runner.update("insert into account(name,money)values(?,?)",account.getName(),account.getMoney()); }catch (Exception e) { throw new RuntimeException(e); } } @Override public void updateAccount(Account account) { try{ runner.update("update account set name=?,money=? where id=?",account.getName(),account.getMoney(),account.getId()); }catch (Exception e) { throw new RuntimeException(e); } } @Override public void deleteAccount(Integer accountId) { try{ runner.update("delete from account where id=?",accountId); }catch (Exception e) { throw new RuntimeException(e); } } @Override public Account findAccountByName(String accountName) { try{ List<Account> accounts = runner.query("SELECT * FROM account WHERE name=?", new BeanListHandler<Account>(Account.class), accountName); if(accounts==null||accounts.size()==0){ return null; } if(accounts.size()>1){ throw new RuntimeException("结果集不唯一,数据有问题。"); } return accounts.get(0); }catch (Exception e) { throw new RuntimeException(e); } } }
问题就是:
事务被自动控制了。换言之,使用了 connection 对象的 setAutoCommit(true) 。
此方式控制事务,如果我们每次都执行一条 sql 语句,没有问题,但是如果业务方法一次要执行多条 sql 语句,这种方式就无法实现功能了。
1.2.2、问题的解决
业务层(而不是持久层)来控制事务的提交和回滚。
/** * 账户的业务层实现类 */ public class AccountServiceImpl_OLD implements IAccountService{ private IAccountDao accountDao; private TransactionManager txManager; public void setTxManager(TransactionManager txManager) { this.txManager = txManager; } public void setAccountDao(IAccountDao accountDao) { this.accountDao = accountDao; } @Override public List<Account> findAllAccount() { try { //1.开启事务 txManager.beginTransaction(); //2.执行操作 List<Account> accounts = accountDao.findAllAccount(); //3.提交事务 txManager.commit(); return accounts; }catch (Exception e){ //4.回滚操作 txManager.rollback(); return null; }finally { //5.释放连接 txManager.release(); } } @Override public Account findAccountById(Integer accountId) { try { //1.开启事务 txManager.beginTransaction(); //2.执行操作 Account account = accountDao.findAccountById(accountId); //3.提交事务 txManager.commit(); return account; }catch (Exception e){ //4.回滚操作 txManager.rollback(); throw new RuntimeException(e); }finally { //5.释放连接 txManager.release(); } } @Override public void saveAccount(Account account) { try { //1.开启事务 txManager.beginTransaction(); //2.执行操作 accountDao.saveAccount(account); //3.提交事务 txManager.commit(); }catch (Exception e){ //4.回滚操作 txManager.rollback(); }finally { //5.释放连接 txManager.release(); } } @Override public void updateAccount(Account account) { try { //1.开启事务 txManager.beginTransaction(); //2.执行操作 accountDao.updateAccount(account); //3.提交事务 txManager.commit(); }catch (Exception e){ //4.回滚操作 txManager.rollback(); }finally { //5.释放连接 txManager.release(); } } @Override public void deleteAccount(Integer acccountId) { try { //1.开启事务 txManager.beginTransaction(); //2.执行操作 accountDao.deleteAccount(acccountId); //3.提交事务 txManager.commit(); }catch (Exception e){ //4.回滚操作 txManager.rollback(); }finally { //5.释放连接 txManager.release(); } } @Override public void transfer(String sourceName, String targetName, Float money) { try { System.out.println(txManager); //1.开始事务 txManager.beginTransaction(); //2.执行操作 Account sourceAccount = accountDao.findAccountByName(sourceName); Account targetAccount = accountDao.findAccountByName(targetName); sourceAccount.setMoney(sourceAccount.getMoney()-money); targetAccount.setMoney(targetAccount.getMoney()+money); accountDao.updateAccount(sourceAccount); int i= 1/0; accountDao.updateAccount(targetAccount); System.out.println("提交了。。。。"); txManager.commit(); } catch (Exception e) { System.out.println("rollback---------"); txManager.rollback(); e.printStackTrace(); }finally { //释放链接 txManager.release(); } } }
事务控制类:
/** * 和事务相关的工具类。包含了:开启事务,提交事务,关闭事务,回滚事务 */ public class TransactionManager { private ConnectionUtils connectionUtils; public void setConnectionUtils(ConnectionUtils connectionUtils) { this.connectionUtils = connectionUtils; } /** * 开启事务 */ public void beginTransaction(){ try { connectionUtils.getThreadConnection().setAutoCommit(false); }catch (Exception e){ e.printStackTrace(); } } /** * 提交事务 */ public void commit(){ try { connectionUtils.getThreadConnection().commit(); } catch (SQLException e) { e.printStackTrace(); } } /** * 回滚事务 */ public void rollback(){ try { connectionUtils.getThreadConnection().rollback(); }catch (Exception e){ e.printStackTrace(); } } /** * 释放链接 */ public void release(){ try { connectionUtils.getThreadConnection().close(); connectionUtils.removeConnection(); } catch (SQLException e) { e.printStackTrace(); } } }
连接工具类:它用于从数据源中获取一个连接,并且实现和线程的绑定
/** * 连接工具类,它用于从数据源中获取一个连接,并且实现和线程的绑定 */ public class ConnectionUtils { private ThreadLocal<Connection> tl = new ThreadLocal<Connection>(); private DataSource dataSource; public void setDataSource(DataSource dataSource) { this.dataSource = dataSource; } /**获取当前线程上的链接 * @return */ public Connection getThreadConnection(){ try { Connection conn = tl.get(); if(conn==null){ conn = dataSource.getConnection(); tl.set(conn); } //返回当前线程上的线程 return conn; } catch (Exception e) { throw new RuntimeException(); } } //将连接和线程解绑 public void removeConnection(){ tl.remove(); } }
持久层实现:AccountDaoimpl
public class AccountDaoImpl implements IAccountDao { private QueryRunner runner; private ConnectionUtils connectionUtils; public void setRunner(QueryRunner runner) { this.runner = runner; } @Override public List<Account> findAllAccount() { try{ return runner.query(connectionUtils.getThreadConnection(),"select * from account",new BeanListHandler<Account>(Account.class)); }catch (Exception e) { throw new RuntimeException(e); } } }
1.2.3、新的问题
业务层方法变得臃肿了,里面充斥着很多重复代码。并且业务层方法和事务控制方法耦合了。
如果此时提交,回滚,释放资源中任何一个方法名变更,都需要修改业务层的代码,况且这还只是一个业务层实现类。
1.2.4、动态代理
1.2.4.1、动态代理的特点
字节码随用随创建,随用随加载。
它与静态代理的区别也在于此。因为静态代理是字节码一上来就创建好,并完成加载。
装饰者模式就是静态代理的一种体现
作用:
不修改源码的基础上对方法增强
1.2.4.2、动态代理常用的两种方式
基于接口的动态代理
JDK 官方的 Proxy 类。
被代理类最少实现一个接口。 如果没有则不能使用。
基于子类的动态代理
第三方的 CGLib,如果报 asmxxxx 异常,需要导入 asm.jar。
被代理类不能用 final 修饰的类(最终类)。
1.2.4.3、使用JDK官方的Proxy类创建代理对象
/** * 对生产厂家要求的接口 */ public interface IProducer { /** * 销售 * @param money */ public void saleProduct(float money); /** * 售后 * @param money */ public void afterService(float money); }
/** * 一个生产者 */ public class Producer implements IProducer{ /** * 销售 * @param money */ public void saleProduct(float money){ System.out.println("销售产品,并拿到钱:"+money); } /** * 售后 * @param money */ public void afterService(float money){ System.out.println("提供售后服务,并拿到钱:"+money); } }
/** * 模拟一个消费者 */ public class Client { public static void main(String[] args) { final Producer producer = new Producer(); IProducer proxyProducer = (IProducer) Proxy.newProxyInstance(producer.getClass().getClassLoader(), producer.getClass().getInterfaces(), new InvocationHandler() { /** * 作用:执行被代理对象的任何接口方法都会经过该方法 * 方法参数的含义 * @param proxy 代理对象的引用 * @param method 当前执行的方法 * @param args 当前执行方法所需的参数 * @return 和被代理对象方法有相同的返回值 * @throws Throwable */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //提供增强的代码 Object returnValue = null; //1.获取方法执行的参数 Float money = (Float)args[0]; //2.判断当前方法是不是销售 if("saleProduct".equals(method.getName())) { returnValue = method.invoke(producer, money*0.8f); } return returnValue; } }); proxyProducer.saleProduct(10000f); } }
newProxyInstance方法的参数:
ClassLoader:类加载器
它是用于加载代理对象字节码的。和被代理对象使用相同的类加载器。固定写法。
Class[]:字节码数组
它是用于让代理对象和被代理对象有相同方法。固定写法。
InvocationHandler:用于提供增强的代码
写如何代理。我们一般都是写一个该接口的实现类,通常情况下都是匿名内部类,但不是必须的。
此接口的实现类都是谁用谁写。
--->匿名内部类访问外部成员变量,外部成员变量要求是final的。
/** * 作用:执行被代理对象的任何接口方法都会经过该方法 * 方法参数的含义 * @param proxy 代理对象的引用 * @param method 当前执行的方法 * @param args 当前执行方法所需的参数 * @return 和被代理对象方法有相同的返回值 * @throws Throwable */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
1.2.4.4、使用 CGLib 的 Enhance 类创建代理对象
/** * 一个生产者 */ public class Producer{ /** * 销售 * @param money */ public void saleProduct(float money){ System.out.println("销售产品,并拿到钱:"+money); } /** * 售后 * @param money */ public void afterService(float money){ System.out.println("提供售后服务,并拿到钱:"+money); } }
/** * 模拟一个消费者 */ public class Client { public static void main(String[] args) { final Producer producer = new Producer(); Producer cglibProducer = (Producer) Enhancer.create(producer.getClass(), new MethodInterceptor() { public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { //提供增强的代码 Object returnValue = null; //1.获取方法执行的参数 Float money = (Float) args[0]; //2.判断当前方法是不是销售 if ("saleProduct".equals(method.getName())) { returnValue = method.invoke(producer, money * 0.8f); } return returnValue; } }); cglibProducer.saleProduct(12000f); } }
create方法的参数:
Class:字节码
它是用于指定被代理对象的字节码。
Callback:用于提供增强的代码
它是让我们写如何代理。我们一般都是些一个该接口的实现类,通常情况下都是匿名内部类,但不是必须的。
此接口的实现类都是谁用谁写。
我们一般写的都是该接口的子接口实现类:MethodInterceptor (方法拦截器)
1.2.5、解决问题
创建客户业务层对象工厂 (当然也可以创建其他业务层对象,只不过我们此处不做那么繁琐)
/** * 用于创建Service的代理对象的工厂 */ public class BeanFactory { private IAccountService accountService; public void setAccountService(IAccountService accountService) { this.accountService = accountService; } private TransactionManager txManager; public void setTxManager(TransactionManager txManager) { this.txManager = txManager; } //获取Service的代理对象 public IAccountService getAccountService(){ return (IAccountService) Proxy.newProxyInstance(accountService.getClass().getClassLoader(), accountService.getClass().getInterfaces(), new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object rtValue = null; try { //1.开启事务 txManager.beginTransaction(); //2.执行操作 rtValue = method.invoke(accountService, args); //3.提交事务 txManager.commit(); return rtValue; }catch (Exception e){ //4.回滚操作 txManager.rollback(); throw new RuntimeException(e); }finally { //5.释放连接 txManager.release(); } } }); } }
此时,业务层控制事务的重复代码可以删除。
/** * 账户的业务层实现类 */ public class AccountServiceImpl implements IAccountService{ private IAccountDao accountDao; /* private TransactionManager txManager; public void setTxManager(TransactionManager txManager) { this.txManager = txManager; }*/ public void setAccountDao(IAccountDao accountDao) { this.accountDao = accountDao; } @Override public List<Account> findAllAccount() { return accountDao.findAllAccount(); } @Override public Account findAccountById(Integer accountId) { return accountDao.findAccountById(accountId); } @Override public void saveAccount(Account account) { accountDao.saveAccount(account); } @Override public void updateAccount(Account account) { accountDao.updateAccount(account); } @Override public void deleteAccount(Integer acccountId) { accountDao.deleteAccount(acccountId); } @Override public void transfer(String sourceName, String targetName, Float money) { Account sourceAccount = accountDao.findAccountByName(sourceName); Account targetAccount = accountDao.findAccountByName(targetName); sourceAccount.setMoney(sourceAccount.getMoney()-money); targetAccount.setMoney(targetAccount.getMoney()+money); accountDao.updateAccount(sourceAccount); // int i= 1/0; accountDao.updateAccount(targetAccount); } }
添加bean,及注入的配置
<!--配置BeanFactory-->
<bean id="beanFactory" class="com.itheima.factory.BeanFactory">
<property name="accountService" ref="accountService"></property>
<property name="txManager" ref="txManager"></property>
</bean>
<!--配置代理AccountService-->
<bean id="proxyAccountService" factory-bean="beanFactory" factory-method="getAccountService"></bean>
二、AOP
2.1、细节
使用aop及配置的方式,实现上述的功能。
2.1.2、AOP相关术语
Joinpoint(连接点)
所谓连接点是指那些被拦截到的点。在 spring 中,这些点指的是方法,因为 spring 只支持方法类型的连接点。
--->如业务层中的所有的方法,都是连接点。 连接业务和增强方法中的那个点。
Pointcut(切入点)
所谓切入点是指我们要对哪些 Joinpoint 进行拦截的定义。
--->即那些被增强的方法,叫做切入点。
--->所有的切入点都是连接点,反之不成立。
--->test()方法,是连接点,在代理中未被增强,所以不是一个切入点。
Advice(通知 / 增强)
所谓通知是指拦截到 Joinpoint 之后所要做的事情就是通知。 ---> 即具体的增强方法。
通知的类型:前置通知,后置通知,异常通知,最终通知,环绕通知。 
Introduction(引介)
引介是一种特殊的通知。在不修改类代码的前提下,Introduction 可以在运行期为类动态地添加一些方法或 Field。
Target(目标对象)
代理的目标对象。 --->如BeanFactory中的accountService对象
Weaving(织入)
是指把增强应用到目标对象来创建新的代理对象的过程。
spring 采用动态代理织入,而 AspectJ 采用编译期织入和类装载期织入。
Proxy(代理)
一个类被 AOP 织入增强后,就产生一个结果代理类。
Aspect(切面) --->切入点+通知
是切入点和通知(引介)的结合。
2.1.3、要明确的事
a、开发阶段
编写核心业务代码(开发主线):大部分程序员来做,要求熟悉业务需求。
把公用代码抽取出来,制作成通知。(开发阶段最后再做):AOP 编程人员来做。
在配置文件中,声明切入点与通知间的关系,即切面。:AOP 编程人员来做。
b、运行阶段(Spring框架完成的)
Spring 框架监控切入点方法的执行。一旦监控到切入点方法被运行,使用代理机制,动态创建目标对象的代理对象。
根据通知类别,在代理对象的对应位置,将通知对应的功能织入,完成完整的代码逻辑运行。
2.1.4、关于代理的选择
spring 中,框架会根据目标类是否实现了接口来决定采用哪种动态代理的方式。
2.2、基于XML的AOP配置
2.2.2.1、把通知(增强)类用bean标签配置起来
<!--配置logger类--> <bean id="logger" class="com.itheima.utils.Logger"></bean>
2.2.2.2、使用 aop:config 声明 aop 配置
aop:config
用于声明开始 aop 的配置
<aop:config> <!-- 配置的代码都写在此处 --> </aop:config>
2.2.2.3、使用aop:aspect配置切面
aop:aspect
用于配置切面。 --->切面=切入点+通知
属性:
id:给切面提供一个唯一标识。 ref:引用配置好的通知类 bean 的 id。 <aop:aspect id="txAdvice" ref="txManager"> <!--配置通知的类型要写在此处--> </aop:aspect>
2.2.2.4、使用aop:pointcut配置切入点表达式
aop:pointcut
用于配置切入点表达式。就是指定对哪些类的哪些方法进行增强。
--->此标签写在aop:aspect中。只能在当前切面中使用。
还可以写在aop:aspect外面,此时变成所有切面可以使用。由于约束上的要求,需要写在要使用的aop:aspect前面。
属性:
expression:用于定义切入点表达式。 id:用于给切入点表达式提供一个唯一标识 <aop:pointcut expression="execution(public void com.itheima.service.impl.AccountServiceImpl.transfer( java.lang.String, java.lang.String, java.lang.Float) )" id="pt1"/>
2.2.2.5、使用aop:xxx配置对应的通知类型
aop:before
用于配置前置通知。指定增强的方法在切入点方法之前执行 。
属性:
method:用于指定通知类中的增强方法名称 ponitcut-ref:用于指定切入点的表达式的引用 pointcut:用于指定切入点表达式 执行时间点: 切入点方法执行之前执行 <aop:before method="beginTransaction" pointcut-ref="pt1"/>
aop:after-returning
用于配置后置通知
属性:
method:指定通知中方法的名称。 pointct:定义切入点表达式 pointcut-ref:指定切入点表达式的引用 执行时间点: 切入点方法正常执行之后。它和异常通知只能有一个执行 <aop:after-returning method="commit" pointcut-ref="pt1"/>
aop:after-throwing
用于配置异常通知
属性:
method:指定通知中方法的名称。 pointct:定义切入点表达式 pointcut-ref:指定切入点表达式的引用 执行时间点: 切入点方法执行产生异常后执行。它和后置通知只能执行一个 <aop:after-throwing method="rollback" pointcut-ref="pt1"/>
aop:after
用于配置最终通知。
属性:
method:指定通知中方法的名称。 pointct:定义切入点表达式 pointcut-ref:指定切入点表达式的引用 执行时间点: 无论切入点方法执行时是否有异常,它都会在其后面执行。 <aop:after method="release" pointcut-ref="pt1"/>
配置通知类型:
<?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> <!--配置logger类--> <bean id="logger" class="com.itheima.utils.Logger"></bean> <!--配置AOP--> <aop:config> <!-- 配置切入点表达式 id属性用于指定表达式的唯一标识。expression属性用于指定表达式内容 此标签写在aop:aspect标签内部只能当前切面使用。 它还可以写在aop:aspect外面,此时就变成了所有切面可用 --> <aop:pointcut id="pt1" expression="execution(* com.itheima.service.impl.*.*(..))"></aop:pointcut> <!--配置切面--> <aop:aspect id="logAdice" ref="logger"> <!--配置前置通知--> <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> </aop:aspect> </aop:config> </beans>

2.2.3、切入点表达式
execution:匹配方法的执行
execution([修饰符] 返回值类型 包名.类名.方法名(参数))
写法说明:
全匹配方式:
public void com.itheima.service.impl.AccountServiceImpl.saveAccount(com.itheima.domain.Account)
访问修饰符可以省略
void com.itheima.service.impl.AccountServiceImpl.saveAccount(com.itheima.domain.Account)
返回值可以使用*号,表示任意返回值
* com.itheima.service.impl.AccountServiceImpl.saveAccount(com.itheima.domain.Account)
包名可以使用*号,表示任意包,但是有几级包,需要写几个*
* *.*.*.*.AccountServiceImpl.saveAccount(com.itheima.domain.Account)
使用..来表示当前包,及其子包 --->任意多级包
* com..AccountServiceImpl.saveAccount(com.itheima.domain.Account)
类名可以使用*号,表示任意类
* com..*.saveAccount(com.itheima.domain.Account)
方法名可以使用*号,表示任意方法
* com..*.*( com.itheima.domain.Account)
参数列表可以使用*,表示参数可以是任意数据类型,但是必须有参数
* com..*.*(*)
参数列表可以使用..表示有无参数均可,有参数可以是任意类型
* com..*.*(..)
全通配方式
* *..*.*(..)
附注:
通常情况下,都是对业务层的方法进行增强,所以切入点表达式都是切到业务层实现类。
execution(* com.payn.service.impl.*.*(..))
其中:pom.xml文件中的以下坐标的jar包,用于解析切入点表达式。
<dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.8.7</version> </dependency> </dependencies>
2.2.4、环绕通知
配置方式:
<aop:config> <aop:pointcut expression="execution(* com.itheima.service.impl.*.*(..))" id="pt1"/> <aop:aspect id="txAdvice" ref="txManager"> <!-- 配置环绕通知 --> <aop:around method="transactionAround" pointcut-ref="pt1"/> </aop:aspect> </aop:config>
aop:around
用于配置环绕通知
属性:
method:指定通知中方法的名称。
pointct:定义切入点表达式
pointcut-ref:指定切入点表达式的引用
说明:
它是 spring 框架为我们提供的一种可以在代码中手动控制增强代码什么时候执行的方式。
注意:
通常情况下,环绕通知都是独立使用的。
如果是如下写法:
public void aroundPrintLog(ProceedingJoinPoint pjp){ System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。前置"); }
切入点方法没有执行,而通知方法执行了。对比如下动态代理中的环绕通知代码,发现动态代理的环绕通知有明确的切入点方法调用,而我们的代码中没有。
解决:
Spring框架为我们提供了一个接口:ProceedingJoinPoint。该接口有一个方法proceed(),此方法就相当于明确调用切入点方法。
该接口可以作为环绕通知的方法参数,在程序执行时,spring框架会为我们提供该接口的实现类供我们使用。
spring中的环绕通知:
它是spring框架为我们提供的一种可以在代码中手动控制增强方法何时执行的方式。
/** * 用于记录日志的工具类,它里面提供了公共的代码 */ public class Logger { /** * 前置通知 */ public void beforePrintLog(){ System.out.println("前置通知Logger类中的beforePrintLog方法开始记录日志了。。。"); } /** * 后置通知 */ public void afterReturningPrintLog(){ System.out.println("后置通知Logger类中的afterReturningPrintLog方法开始记录日志了。。。"); } /** * 异常通知 */ public void afterThrowingPrintLog(){ System.out.println("异常通知Logger类中的afterThrowingPrintLog方法开始记录日志了。。。"); } /** * 最终通知 */ public void afterPrintLog(){ System.out.println("最终通知Logger类中的afterPrintLog方法开始记录日志了。。。"); } /** * 环绕通知 * 问题: * 当我们配置了环绕通知之后,切入点方法没有执行,而通知方法执行了。 * 分析: * 通过对比动态代理中的环绕通知代码,发现动态代理的环绕通知有明确的切入点方法调用,而我们的代码中没有。 * 解决: * Spring框架为我们提供了一个接口:ProceedingJoinPoint。该接口有一个方法proceed(),此方法就相当于明确调用切入点方法。 * 该接口可以作为环绕通知的方法参数,在程序执行时,spring框架会为我们提供该接口的实现类供我们使用。 * * spring中的环绕通知: * 它是spring框架为我们提供的一种可以在代码中手动控制增强方法何时执行的方式。 */ public Object aroundPringLog(ProceedingJoinPoint pjp){ Object rtValue = null; try{ Object[] args = pjp.getArgs();//得到方法执行所需的参数 System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。前置"); rtValue = pjp.proceed(args);//明确调用业务层方法(切入点方法) System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。后置"); return rtValue; }catch (Throwable t){ System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。异常"); throw new RuntimeException(t); }finally { System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。最终"); } } }
2.3、基于注解的AOP配置
2.3.1、环境搭建
2.3.1.1、准备必要的代码和jar包
2.3.1.2、在配置文件中导入context名称空间
2.3.1.3、把资源使用注解配置
2.3.1.4、在配置文件中指定spring要扫描的包
<!-- 配置spring创建容器时要扫描的包-->
<context:component-scan base-package="com.payn"></context:component-scan>
2.3.2、配置步骤
2.3.2.1、第一步:把通知类也使用注解配置
2.3.2.2、第二步:在通知类上使用 @Aspect 注解声明为切面
@Component("logger")
@Aspect //作用:把当前类声明为切面类
public class Logger {
2.3.2.3:第三步:在增强的方法上使用注解配置通知
@Before
把当前方法看成是前置通知。
属性:
value:用于指定切入点表达式,还可以指定切入点表达式的引用。
@AfterReturning
把当前方法看成是后置通知。
属性:
value:用于指定切入点表达式,还可以指定切入点表达式的引用。
@AfterThrowing
把当前方法看成是异常通知。
@After
把当前方法看成是最终通知
@Component("logger")
@Aspect //表名当前类是一个切面类
public class Logger {
@Pointcut("execution(* com.itheima.service.impl.*.*(..))")
public void pt1(){}
/**
* 前置通知
*/
@Before("pt1()")
public void beforePrintLog(){
System.out.println("前置通知Logger类中的beforePrintLog方法开始记录日志了。。。");
}
/**
* 后置通知
*/
@AfterReturning("pt1()")
public void afterReturningPrintLog(){
System.out.println("后置通知Logger类中的afterReturningPrintLog方法开始记录日志了。。。");
}
/**
* 异常通知
*/
@AfterThrowing("pt1()")
public void afterThrowingPrintLog(){
System.out.println("异常通知Logger类中的afterThrowingPrintLog方法开始记录日志了。。。");
}
/**
* 最终通知
*/
@After("pt1()")
public void afterPrintLog(){
System.out.println("最终通知Logger类中的afterPrintLog方法开始记录日志了。。。");
}
/**
* 环绕通知
*
*/
@Around("pt1()")
public Object aroundPrintLog(ProceedingJoinPoint pjp){
Object rtValue = null;
try {
Object[] args = pjp.getArgs(); //得到方法执行所需的参数
System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。前置");
rtValue = pjp.proceed(args); //明确调用业务层方法(切入点方法)
System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。后置");
return rtValue;
} catch (Throwable throwable) {
System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。异常");
throw new RuntimeException(throwable);
}finally {
System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。最终");
}
}
}
--->使用注解的方式进行配置通知,会存在顺序问题。即:最终通知先于后置通知执行。此种方式不推荐使用。
--->所以使用注解的方式,还是使用环绕通知。
混合两个使用是执行顺序:
2.3.2.4、第四步:在spring配置文件中开启spring对注解AOP的支持
<!-- 配置spring开启注解AOP的支持 --> <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
2.3.3、环绕通知注解配置
@Around
把当前方法看成是环绕通知。
属性:
value:用于指定切入点表达式,还可以指定切入点表达式的引用
2.3.4、切入点表达式注解
@Pointcut
指定切入点表达式 。
属性:
value:指定表达式的内容 @Pointcut("execution(* com.itheima.service.impl.*.*(..))") public void pt1(){ }
此时:
<?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>
@Service("accountService")
public class AccountServiceImpl implements IAccountService {
@Override
public void saveAccount() {
System.out.println("执行了保存");
}
@Override
public void updateAccount(int i) {
System.out.println("执行了更新"+i);
}
@Override
public int deleteAccount() {
System.out.println("执行了删除");
return 0;
}
}
@Component("logger")
@Aspect
public class Logger {
@Pointcut("execution(* com.itheima.service.impl.*.*(..))")
public void pt1(){}
/**
* 前置通知
*/
@Before("pt1()")
public void beforePrintLog(){
System.out.println("前置通知Logger类中的beforePrintLog方法开始记录日志了。。。");
}
/**
* 后置通知
*/
@AfterReturning("pt1()")
public void afterReturningPrintLog(){
System.out.println("后置通知Logger类中的afterReturningPrintLog方法开始记录日志了。。。");
}
/**
* 异常通知
*/
@AfterThrowing("pt1()")
public void afterThrowingPrintLog(){
System.out.println("异常通知Logger类中的afterThrowingPrintLog方法开始记录日志了。。。");
}
/**
* 最终通知
*/
@After("pt1()")
public void afterPrintLog(){
System.out.println("最终通知Logger类中的afterPrintLog方法开始记录日志了。。。");
}
/**
* 环绕通知
*
*/
@Around("pt1()")
public Object aroundPrintLog(ProceedingJoinPoint pjp){
Object rtValue = null;
try {
Object[] args = pjp.getArgs(); //得到方法执行所需的参数
System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。前置");
rtValue = pjp.proceed(args); //明确调用业务层方法(切入点方法)
System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。后置");
return rtValue;
} catch (Throwable throwable) {
System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。异常");
throw new RuntimeException(throwable);
}finally {
System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。最终");
}
}
}
2.3.5、不使用XML配置的方式
@Configuration @ComponentScan(basePackages="com.payn") @EnableAspectJAutoProxy public class SpringConfiguration { }

浙公网安备 33010602011771号