AOP
在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
- 主要功能
日志记录,性能统计,安全控制,事务处理,异常处理等等
- 主要意图
将日志记录,性能统计,安全控制,事务处理,异常处理 等代码从业务逻辑代码中划分出来,通过对这些行为的分离,我们希望可以将它们独立到非指导业务逻辑的方法中,进而改变这些行为的时候不影响业务逻辑的代码。
- 优点
减少重复代码,提高开发效率,维护方便
- 实现方式
主要通过动态代理实现
spring中的AOP
spring的aop就是通过配置的方式实现
Joinpoint(连接点):
所谓连接点是指那些被拦截到的点。在spring中,这些点指的是方法,因为spring只支持方法类型的连接点。
Pointout(切入点):
所谓切入点是指我们要对哪些 Joinpoint进行拦截的定义。(被增强的连接点)
Advice( 通知/ 增强):
所谓通知是指拦截到 Joinpoint 之后所要做的事情就是通知。
通知的类型:前置通知,后置通知,异常通知,最终通知,环绕通知。
@Override 整个invoke方法执行的是环绕通知
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
a.find (); 前置通知
method.invoke(Service, args); 环绕通知中有明确的切入点方法调用
a.save(); 后置通知
} catch (Exception e) {
a.back(); 异常通知
throw new RuntimeException(e);
} finally {
a.close; 最后通知
}
}
Introduction( 引介):
引介是一种特殊的通知在不修改类代码的前提下, Introduction 可以在运行期为类动态地添加一些方
法或 Field。
Target( 目标对象):
代理的目标对象。
Weaving( 织入):
是指把增强应用到目标对象来创建新的代理对象的过程。
spring 采用动态代理织入,而 AspectJ 采用编译期织入和类装载期织入。
Proxy (代理):
一个类被 AOP 织入增强后,就产生一个结果代理类。
Aspect( 切面):
是切入点和通知(引介)的结合。
基于xml的AOP
需要用到
<!-- 解析切入点表达式-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.9</version>
</dependency>
<?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">
<!-- 配置springIOC-->
<bean id="accountService" class="com.cc.service.impl.AccountServiceImpl"></bean>
<!-- spring基于xml配置的aop配置步骤
1.把通知bean交给spring管理-->
<!-- 配置logger类-->
<bean id="logger" class="com.cc.util.Logger"></bean>
<!-- 2.使用aop:config标签表明开始aop配置
3.使用aop:aspect配置切面
id:切面唯一标志
ref:指定通知类bean id
<aop:config>
<aop:aspect id="logAdvice" ref="logger"></aop:aspect>
</aop:config>
aop:aspect内部使用对象标签配置通知类型
methon 用于指定类中哪个方法
pointcut 指定切入点表达式 指对那些方法进行增强
切入点表达式写法:关键字execution(表达式)
表达式:访问修饰符 返回值 包名.类名.方法名(参数列表)
execution(public void com.cc.service.AccountService.saveAccount())
访问修饰符 可以省略
返回值 可以使用通配符,表示任意返回值 *
包名 可以使用通配符,表示任意包,有几级包,就写几个* *.*.*.*(四级包) 可以使用*..表示当前包及子包
类名方法名都可使用*
参数列表 可以直接写数据类型 可以使用*表示任意类型,但必须有参数 可以使用..表示任意条件都可以
基本类型直接写名称 int
引用类型写包名.类名 java.lang.string
全通配写法 * *..*.*(..)
通常写法 * com.cc.service.impl.*.*(..)
-->
<aop:config>
<!-- 配置切入点表达式 id 唯一标识 expression 表达式内容 写在aop:aspect里面只能当前切面使用 可以写在外面,只能定义在aop:aspect标签之前-->
<aop:pointcut id="pt" expression="execution(* *..*.*(..))"/>
<aop:aspect id="logAdvice" ref="logger">
<!-- 前置通知 方法之前执行-->
<aop:before method="beforeLog" pointcut-ref="pt" ></aop:before>
<!-- 后置通知 方法正常执行之后执行-->
<aop:after-returning method="afterLog" pointcut-ref="pt"></aop:after-returning>
<!-- 异常通知 方法异常之后执行-->
<aop:after-throwing method="throwLog" pointcut-ref="pt"></aop:after-throwing>
<!-- 最终通知 最后执行-->
<aop:after method="finalLog" pointcut-ref="pt"></aop:after>
<!-- 环绕通知-->
<!-- <aop:around method="aroundLog" pointcut-ref="pt"></aop:around>-->
</aop:aspect>
</aop:config>
</beans>
基于注解的AOP
public class Logger {
//配置切入点表达式
//execution(* *..*.*(..))
//execution(* com.cc.service.impl.*.*(..))
@Pointcut("execution(* com.cc.service.impl.*.*(..))")
private void pt(){}
/**
* 前置通知,在切入点方法之前执行(切入点就是业务层方法)
*/
@Before("pt()")
public void beforeLog(){
System.out.println("before");
}
/**
* 后置通知,在切入点方法之前执行(切入点就是业务层方法)
*/
@AfterReturning("pt()")
public void afterLog(){
System.out.println("after");
}
/**
* 异常通知,在切入点方法之前执行(切入点就是业务层方法)
*/
@AfterThrowing("pt()")
public void throwLog(){
System.out.println("throw");
}
/**
* 最终通知,在切入点方法之前执行(切入点就是业务层方法)
*/
@After("pt()")
public void finalLog(){
System.out.println("final");
}
/**
* 环绕通知:可以在代码中手动控制增强方法何时执行的方式
* 需要调用切入点方法,不然只执行环绕通知,不执行方法
*/
//@Around("pt()")
public Object aroundLog(ProceedingJoinPoint pjp) {
Object rtvalue = null;
try {
System.out.println("前置环绕");
//得到方法执行参数
Object[] aegs =pjp.getArgs();
System.out.println("后置环绕");
rtvalue = pjp.proceed(aegs);//调用方法
return rtvalue;
}catch (Throwable t){
System.out.println("异常环绕");
throw new RuntimeException(t);
}finally {
System.out.println("最终环绕");
}
}
}
xml配置
<!--扫描的包-->
<context:component-scan base-package="com.cc"></context:component-scan>
<!-- 开启spring注解aop支持-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
纯注解
@Configuration
@ComponentScan(basePackages=“com.itheima”)
@EnableAspectJAutoProxy
public class SpringConfiguration {
}
问题:
基于注解的AOP在配置切入点表达式时,用全通配写法通知会执行两次,xml的则不会
使用第二种写法正常
//execution(* *..*.*(..))
//execution(* com.cc.service.impl.*.*(..))
基于注解的AOP执行顺讯问题
注解会先执行最终方法,在执行后置方法
注解会先执行最终方法,在执行异常方法