一、概述
1、AOP
- 全称是 Aspect Oriented Programming 即:面向切面编程
- AOP 就是把我们程序重复的代码抽取出来,在需要执行的时候,使用动态代理的技术,在不修改源码的基础上,对我们的已有方法进行增强。
- AOP:面向切面编程,aop就是在某一个类或方法执行前后打个标记,声明在执行到这里之前要先执行什么,执行完这里之后要接着执行什么,插入新的执行方法。在Spring中,它是以JVM的动态代理技术为基础,然后设计一系列AOP横切实现,比如前置通知、返回通知、异常通知等,同时Pointcut接口来匹配切入点,可以使用现有切入点来设计横切面,也可以扩展相关方法根据需求进行切入。
2、作用:
- 在程序运行期间,不修改源码对已有方法进行增强。
3、优势:
- 减少重复代码
- 提高开发效率
- 维护方便
4、AOP 的实现方式
- 使用动态代理技术
关于动态代理请点击
二、AOP(面向切面编程)
1、AOP 相关术语
- Joinpoint(连接点) : 是指那些被拦截到的点。在 spring 中,这些点指的是方法,因为 spring 只支持方法类型的连接点。
- Pointcut(切入点) : 是指我们要对哪些 Joinpoint 进行拦截的定义。
- Advice(通知/增强) : 所谓通知是指拦截到 Joinpoint 之后所要做的事情就是通知。
通知的类型 :前置通知,后置通知,异常通知,最终通知,环绕通知。 - Introduction(引介) : 是一种特殊的通知在不修改类代码的前提下, Introduction 可以在运行期为类动态地添加一些方法或 Field。
- Target(目标对象) : 代理的目标对象。
- Weaving(织入) : 是指把增强应用到目标对象来创建新的代理对象的过程。
spring 采用动态代理织入,而 AspectJ 采用编译期织入和类装载期织入。 - Proxy(代理): 一个类被 AOP 织入增强后,就产生一个结果代理类。
- Aspect(切面) : 是切入点和通知(引介)的结合。
2、spring 中的 AOP 要明确的事
a、开发阶段(程序员做的)
- 编写核心业务代码(开发主线):大部分程序员来做,要求熟悉业务需求。
- 把公用代码抽取出来,制作成通知。(开发阶段最后再做):AOP 编程人员来做。
- 在配置文件中,声明切入点与通知间的关系,即切面。:AOP 编程人员来做。
b、运行阶段(Spring 框架完成的)
- Spring 框架监控切入点方法的执行。一旦监控到切入点方法被运行,使用代理机制,动态创建目标对象的代理对象,根据通知类别,在代理对象的对应位置,将通知对应的功能织入,完成完整的代码逻辑运行。
三、XML 配置 AOP 步骤
第一步:配置要搜索的包
<context:component-scan base-package="cn.lemon"></context:component-scan>
第二步:使用 aop:config 声明 aop 配置
- 作用:用于声明开始 aop 的配置
<!--aop 配置
# proxy-target-class="true":使用CGLib代理,默认是 接口代理(JDK代理)
-->
<aop:config proxy-target-class="true">
<!--配置的代码都写在此处-->
</aop:config>
第三步:使用 aop:aspect 配置切面
- 作用:用于配置切面。
- 属性:id:给切面提供一个唯一标识。ref:引用配置好的通知类 bean 的 id。
<aop:aspect ref="loggerAdvice">
<!--配置通知的类型要写在此处-->
</aop:aspect>
第四步:使用 aop:pointcut 配置切入点表达式
- 作用:用于配置切入点表达式。就是指定对哪些类的哪些方法进行增强。
- 属性:expression:用于定义切入点表达式。id:用于给切入点表达式提供一个唯一标识
<!--切入点表达式:定义那些方法织入增强-->
<aop:pointcut id="serviceMethod" expression="execution(* cn.lemon.service.impl.*.*(..))"></aop:pointcut><!--切入点-->
切入点表达式说明
表达式语法:execution([修饰符] 返回值类型 包名.类名.方法名(参数))
全匹配方式:
public void cn.lemon.service.impl.AccountServiceImpl.addAccount(cn.lemon.domain.Account)
访问修饰符可以省略
void cn.lemon.service.impl.AccountServiceImpl.addAccount(cn.lemon.domain.Account)
返回值可以使用*
号,表示任意返回值
* cn.lemon.service.impl.AccountServiceImpl.addAccount(cn.lemon.domain.Account)
包名可以使用*
号,表示任意包,但是有几级包,需要写几个*
* *.*.*.*.AccountServiceImpl.addAccount(cn.lemon.domain.Account)
使用..
来表示当前包,及其子包
* cn..AccountServiceImpl.addAccount(cn.lemon.domain.Account)
类名可以使用*
号,表示任意类
* cn..*.addAccount(cn.lemon.domain.Account)
方法名可以使用*
号,表示任意方法:
* cn..*.*( cn.lemon.domain.Account)
参数列表可以使用*
,表示参数可以是任意数据类型,但是必须有参数:
* com..*.*(*)
参数列表可以使用..
表示有无参数均可,有参数可以是任意类型:
* com..*.*(..)
全通配方式:
* *..*.*(..)
注意:
通常情况下,我们都是对业务层的方法进行增强,所以切入点表达式都是切到业务层实现类。
execution(* com.lxs.service.impl.*.*(..))
第五步:使用 aop:xxx 配置对应的通知类型
<aop:before method="before" pointcut-ref="serviceMethod"></aop:before><!--织入前置增强-->
<aop:after-returning method="afterReturning" pointcut-ref="serviceMethod" returning="result"></aop:after-returning><!--后置增强-->
<aop:around method="around" pointcut-ref="serviceMethod"></aop:around><!--环绕增强-->
<aop:after-throwing method="exception" pointcut-ref="serviceMethod" <aop:after method="after" pointcut-ref="serviceMethod"></aop:after><!--最终增强-->
四、Spring 使用 XML 配置 AOP
1、新建持久层(dao),接口以及实现类
package cn.lemon.dao;
public interface IAccountDao {
void in(Double money);
void out(Double money);
}
package cn.lemon.dao.impl;
import cn.lemon.dao.IAccountDao;
import org.springframework.stereotype.Repository;
@Repository
public class AccountDaoImpl implements IAccountDao {
@Override
public void in(Double money) {
System.out.println("成功转入" + money + "元钱");
}
@Override
public void out(Double money) {
System.out.println("成功转出" + money + "元钱");
}
}
2、新建业务层(service),接口以及实现类
package cn.lemon.service;
public interface IAccountService {
void transfer(Double money);
}
package cn.lemon.service.impl;
import cn.lemon.dao.IAccountDao;
import cn.lemon.service.IAccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class AccountServcieImpl implements IAccountService {
@Autowired
private IAccountDao iAccountDao;
@Override
public void transfer(Double money) {
iAccountDao.in(money);
iAccountDao.out(money);
}
}
3、增强类
package cn.lemon.advice;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.springframework.stereotype.Component;
@Component
public class LoggerAdvice {
/**
*
* @param joinPoint 得到业务方法的信息
* getSignature() 表示签名
*/
public void before(JoinPoint joinPoint){
System.out.println("开启事务:前置增强,方法名为:" + joinPoint.getSignature().getName());
}
public void afterReturning(JoinPoint joinPoint,Object result){
System.out.println("提交事务:后置增强,方法名为:" + joinPoint.getSignature().getName() + "返回值为:" + result);
}
public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("环绕前置增强.......");
//执行业务方法
Object result = proceedingJoinPoint.proceed();
System.out.println("环绕后置增强。。。。。。");
return result;
}
public void exception(JoinPoint joinPoint,Exception e){
System.out.println("异常增强,异常信息为:" + e.getMessage());
}
public void after(JoinPoint joinPoint){
System.out.println("最终增强,方法名为;" + joinPoint.getSignature().getName());
}
}
4、AOP 的 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:context="http://www.springframework.org/schema/context"
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/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<context:component-scan base-package="cn.lemon"></context:component-scan>
<!--aop 配置
# proxy-target-class="true":使用CGLib代理,默认是 接口代理(JDK代理)
-->
<aop:config proxy-target-class="true">
<!--切入点表达式:定义那些方法织入增强-->
<aop:pointcut id="serviceMethod" expression="execution(* cn.lemon.service.impl.*.*(..))"></aop:pointcut><!--切入点-->
<aop:aspect ref="loggerAdvice">
<aop:before method="before" pointcut-ref="serviceMethod"></aop:before><!--织入前置增强-->
<aop:after-returning method="afterReturning" pointcut-ref="serviceMethod" returning="result"></aop:after-returning><!--后置增强-->
<aop:around method="around" pointcut-ref="serviceMethod"></aop:around><!--环绕增强-->
<aop:after-throwing method="exception" pointcut-ref="serviceMethod" throwing="e"></aop:after-throwing><!--异常增强-->
<aop:after method="after" pointcut-ref="serviceMethod"></aop:after><!--最终增强-->
</aop:aspect>
</aop:config>
</beans>
5、测试类
package cn.lemon.service.impl;
import cn.lemon.service.IAccountService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:/applicationContext.xml")
public class AccountServcieImplTest {
@Autowired
private IAccountService iAccountService;
@Test
public void transfer() {
iAccountService.transfer(1000d);
}
}