Spring 详解(二)------- AOP关键概念以及两种实现方式
### 1. AOP 关键词
- target:目标类,需要被代理的类。例如:ArithmeticCalculator
- Joinpoint(连接点):所谓连接点是指那些可能被拦截到的方法。例如:所有的方法
- PointCut 切入点:已经被增强的连接点。例如:add()
- advice:通知/增强,增强代码。例如:showRaram、showResult
- Weaving(织入):是指把增强 advice 应用到目标对象 target 来创建新的代理对象proxy的过程.
- proxy 代理类:通知+切入点
- Aspect(切面)::是切入点 pointcut 和通知 advice 的结合
2. AOP 的作用
当我们为系统做参数验证,登录权限验证或者日志操作等,为了实现代码复用,我们可能把日志处理抽离成一个新的方法。但是这样我们仍然必须手动插入这些方法,这样的话模块之间高耦合,不利于后期的维护和功能的扩展,有了 AOP 我们可以将功能抽成一个切面,代码复用好,低耦合。
3. AOP 的通知类型
Spring 按照通知 Advice 在目标类方法的连接点位置,可以分为5类
- 前置通知[Before advice]:在连接点前面执行,前置通知不会影响连接点的执行,除非此处抛出异常。
- 正常返回通知[After returning advice]:在连接点正常执行完成后执行,如果连接点抛出异常,则不会执行。
- 异常返回通知[After throwing advice]:在连接点抛出异常后执行。
- 返回通知[After (finally) advice]:在连接点执行完成后执行,不管是正常执行完成,还是抛出异常,都会执行返回通知中的内容。
- 环绕通知[Around advice]:环绕通知围绕在连接点前后,比如一个方法调用的前后。这是最强大的通知类型,能在方法调用前后自定义一些操作。环绕通知还需要负责决定是继续处理join point(调用ProceedingJoinPoint的proceed方法)还是中断执行。
Spring 中使用五种通知
1. 前置通知
<aop:before method="" pointcut="" pointcut-ref=""/>
method : 通知,及方法名
pointcut :切入点表达式,此表达式只能当前通知使用。
pointcut-ref : 切入点引用,可以与其他通知共享切入点。
通知方法格式:public void myBefore(JoinPoint joinPoint){
参数1:org.aspectj.lang.JoinPoint 用于描述连接点(目标方法),获得目标方法名等
2. 后置通知 目标方法后执行,获得返回值
<aop:after-returning method="" pointcut-ref="" returning=""/>
returning 通知方法第二个参数的名称
通知方法格式:public void myAfterReturning(JoinPoint joinPoint,Object result){
参数1:连接点描述
参数2:类型Object,参数名 returning="result" 配置的
3. 异常通知 目标方法发生异常后
<aop:after-throwing method="testException" throwing="e"
pointcut="execution(* com.anqi.testAop.ArithmeticCalculator.div(..))"/>
throwing 发生的异常
通知方法格式:public Object testRound(ProceedingJoinPoint pjp){
参数1:ProceedingJoinPoint
返回值为 reslut
### 4. 基于 xml 的配置方式
xml 配置文件
<context:component-scan base-package="com.anqi">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
<!--1、 创建目标类 -->
<bean id="arithmeticCalculator" class="com.anqi.testAop.ArithmeticCalculatorImpl"></bean>
<!--2、创建切面类(通知) -->
<bean id="logAspect" class="com.anqi.testAop.MyLogger"></bean>
<aop:config>
<aop:aspect ref="logAspect">
<!-- 切入点表达式 也可以在通知内部分别设置切入点表达式 -->
<aop:pointcut expression="execution(* com.anqi.testAop.*.*(..))" id="myPointCut"/>
<!-- 配置前置通知,注意 method 的值要和 对应切面的类方法名称相同 -->
<aop:before method="before" pointcut-ref="myPointCut" />
<aop:after method="after" pointcut-ref="myPointCut" />
<aop:after-returning method="testAfterReturn" returning="result" pointcut-ref="myPointCut"/>
<aop:after-throwing method="testException" throwing="e" pointcut="execution(* com.anqi.testAop.ArithmeticCalculator.div(..))"/>
<!--<aop:around method="testRound" pointcut-ref="myPointCut" /> 最强大,但是一般不使用-->
</aop:aspect>
</aop:config>
目标类
public interface ArithmeticCalculator {
int add(int i, int j);
int sub(int i, int j);
int mul(int i, int j);
int div(int i, int j);
}
public class ArithmeticCalculatorImpl implements ArithmeticCalculator {
@Override
public int add(int i, int j) {
int result = i + j;
return result;
}
@Override
public int sub(int i, int j) {
int result = i - j;
return result;
}
@Override
public int mul(int i, int j) {
int result = i * j;
return result;
}
@Override
public int div(int i, int j) {
int result = i / j;
return result;
}
}
切面类
``` java
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import java.util.Arrays;
/**
创建日志类
*/
public class MyLogger {public void before(JoinPoint joinPoint) {
System.out.println("前置通知 参数为["+joinPoint.getArgs()[0]+","+joinPoint.getArgs()[1]+"]");
}
public void after(JoinPoint joinPoint) {
System.out.println("后置通知 "+ joinPoint.getSignature().getName());
}public void testException(JoinPoint joinPoint, Throwable e) {
System.out.println("抛出异常: "+ e.getMessage());
}public void testAfterReturn(JoinPoint joinPoint, Object result) {
System.out.println("返回通知,返回值为 " + result);
}public Object testRound(ProceedingJoinPoint pjp) {
Object result = null;
String methodName = pjp.getSignature().getName();
Object[] args = pjp.getArgs();
try {
//前置通知
System.out.println("!!!前置通知 --> The Method"+methodName+" begins"+ Arrays.asList(args));
//执行目标方法
result = pjp.proceed();
//返回通知
System.out.println("!!!返回通知 --> The Method"+methodName+" ends"+ args);}catch(Throwable e) {
//异常通知
System.out.println("!!!异常通知 --> The Method"+methodName+" ends with"+ result);
}
//后置通知
System.out.println("!!!后置通知 --> The Method"+methodName+" ends"+ args);
return result;
}
}
<br/>
测试
``` java
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Main {
public static void main(String[] args) {
ApplicationContext application = new ClassPathXmlApplicationContext("spring-context.xml");
ArithmeticCalculator a = application.getBean(ArithmeticCalculator.class);
int result = a.add(1,2);
System.out.println(result);
System.out.println(a.div(5,0));
}
}
/*
前置通知 参数为[1,2]
后置通知 add
返回通知,返回值为 3
3
前置通知 参数为[5,0]
后置通知 div
抛出异常: / by zero
*/
5. 基于注解的配置方式
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="com.anqi">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
<!-- 使 AspectJ 注解起作用: 自动为匹配的类生成代理对象 -->
<aop:aspectj-autoproxy/>
</beans>
目标类
``` java
public interface ArithmeticCalculator {
int add(int i, int j);
int sub(int i, int j);
int mul(int i, int j);
int div(int i, int j);
}
import org.springframework.stereotype.Service;
@Service
public class ArithmeticCalculatorImpl implements ArithmeticCalculator {
@Override
public int add(int i, int j) {
int result = i + j;
return result;
}
@Override
public int sub(int i, int j) {
int result = i - j;
return result;
}
@Override
public int mul(int i, int j) {
int result = i * j;
return result;
}
@Override
public int div(int i, int j) {
int result = i / j;
return result;
}
}
<br>
切面
``` java
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import java.util.Arrays;
/**
* 创建日志类
*/
@Aspect
@Component
public class MyLogger {
@Before("execution(* com.anqi.testAop.*.*(..))")
public void before(JoinPoint joinPoint) {
System.out.println("前置通知 参数为["+joinPoint.getArgs()[0]+","+joinPoint.getArgs()[1]+"]");
}
@After("execution(* com.anqi.testAop.*.*(..))")
public void after(JoinPoint joinPoint) {
System.out.println("后置通知 "+ joinPoint.getSignature().getName());
}
@AfterThrowing(value="execution(* com.anqi.testAop.ArithmeticCalculator.div(..))", throwing = "e")
public void testException(JoinPoint joinPoint, Throwable e) {
System.out.println("抛出异常: "+ e.getMessage());
}
@AfterReturning(value="execution(* com.anqi.testAop.*.*(..))", returning = "result")
public void testAfterReturn(JoinPoint joinPoint, Object result) {
System.out.println("返回通知,返回值为 " + result);
}
@Around("execution(* com.anqi.testAop.*.*(..))")
public Object testRound(ProceedingJoinPoint pjp) {
Object result = null;
String methodName = pjp.getSignature().getName();
Object[] args = pjp.getArgs();
try {
//前置通知
System.out.println("!!!前置通知 --> The Method"+methodName+" begins"+ Arrays.asList(args));
//执行目标方法
result = pjp.proceed();
//返回通知
System.out.println("!!!返回通知 --> The Method"+methodName+" ends"+ args);
}catch(Throwable e) {
//异常通知
System.out.println("!!!异常通知 --> The Method"+methodName+" ends with"+ result);
}
//后置通知
System.out.println("!!!后置通知 --> The Method"+methodName+" ends"+ args);
return result;
}
}
输出结果与第一种方式一致,这里就不再赘述了。
6. 切面的优先级
可以使用@Order来指定切面的优先级
``` java
//参数验证切面
@Order(1)
@Aspect
@Component
public class ValidateAspect {
@Before("execution(public int com.anqi.spring.aop.order.ArithmeticCalculator.*(int, int))")
public void validateArgs(JoinPoint join) {
String methodName = join.getSignature().getName();
Object[] args = join.getArgs();
System.out.println("validate"+methodName+Arrays.asList(args));
}
}
//把这个类声明为一个切面:需要把该类放入到 IOC 容器中, 再声明为一个切面
@Order(2)
@Aspect
@Component
public class LoggingAspect2 {
/**
- 声明该方法是一个前置通知: 在目标方法开始之前执行
- @param join
/
@Before("execution(public int com.anqi.spring.aop.order.ArithmeticCalculator.(int, int))")
public void beforeMehod(JoinPoint join) {
String methodName = join.getSignature().getName();
List args = Arrays.asList(join.getArgs());
System.out.println("前置通知 --> The Method"+methodName+" begins"+ args);
}
}
### 7. 重用切点表达式
``` java
//把这个类声明为一个切面:需要把该类放入到 IOC 容器中, 再声明为一个切面
@Order(2)
@Aspect
@Component
public class LoggingAspect {
/**
* 定义一个方法, 用于声明切入点表达式, 一般地, 该方法中再不需要填入其他代码
*/
@Pointcut("execution(public int com.anqi.spring.aop.order.ArithmeticCalculator.*(int, int))")
public void declareJointPointExpression() {}
/**
* 声明该方法是一个前置通知: 在目标方法开始之前执行
* @param join
*/
@Before("declareJointPointExpression()")
public void beforeMehod(JoinPoint join) {
String methodName = join.getSignature().getName();
List<Object> args = Arrays.asList(join.getArgs());
System.out.println("前置通知 --> The Method"+methodName+" begins"+ args);
}
}
### 8. 两种方式的比较(摘自 spring 官方文档)
如果您选择使用Spring AOP,则可以选择@AspectJ或XML样式。需要考虑各种权衡。
XML样式可能是现有Spring用户最熟悉的,并且由真正的POJO支持。当使用AOP作为配置企业服务的工具时,XML可能是一个不错的选择(一个好的测试是你是否认为切入点表达式是你可能想要独立改变的配置的一部分)。使用XML样式,从您的配置可以更清楚地了解系统中存在哪些方面。
XML风格有两个缺点。首先,它没有完全封装它在一个地方解决的要求的实现。DRY原则规定,系统中的任何知识都应该有单一,明确,权威的表示。使用XML样式时,有关如何实现需求的知识将分支到支持bean类的声明和配置文件中的XML。使用@AspectJ样式时,此信息封装在单个模块中:方面。其次,XML样式在它所表达的内容方面比@AspectJ样式稍微受限:仅支持“单例”方面实例化模型,并且不可能组合在XML中声明的命名切入点。例如,
@Pointcut("execution(* get*())")
public void propertyAccess() {}
@Pointcut("execution(org.xyz.Account+ *(..))")
public void operationReturningAnAccount() {}
@Pointcut("propertyAccess() && operationReturningAnAccount()")
public void accountPropertyAccess() {}
在XML样式中,您可以声明前两个切入点:
<aop:pointcut id="propertyAccess"
expression="execution(* get*())"/>
<aop:pointcut id="operationReturningAnAccount"
expression="execution(org.xyz.Account+ *(..))"/>
XML方法的缺点是您无法 accountPropertyAccess通过组合这些定义来定义切入点。
@AspectJ 样式支持额外的实例化模型和更丰富的切入点组合。它具有将方面保持为模块化单元的优点。它还具有以下优点:Spring AOP 和 AspectJ 都可以理解(并因此消耗)@AspectJ 方面。因此,如果您以后决定需要 AspectJ 的功能来实现其他要求,则可以轻松迁移到基于 AspectJ 的方法。总而言之,只要您的方面不仅仅是简单的企业服务配置,Spring 团队更喜欢 @AspectJ 风格。
Spring 详解(二)------- AOP关键概念以及两种实现方式的更多相关文章
- spring事务详解(基于注解和声明的两种实现方式)
Spring事务( Transaction ) 事务的概念 事务是一些sql语句的集合,作为一个整体执行,一起成功或者一起失败. 使用事务的时机 一个操作需要多天sql语句一起完成才能成功 程序中事务 ...
- 前端跨域问题相关知识详解(原生js和jquery两种方法实现jsonp跨域)
1.同源策略 同源策略(Same origin policy),它是由Netscape提出的一个著名的安全策略.同源策略是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,则浏览器的正 ...
- Spring Boot2 系列教程(十六)定时任务的两种实现方式
在 Spring + SpringMVC 环境中,一般来说,要实现定时任务,我们有两中方案,一种是使用 Spring 自带的定时任务处理器 @Scheduled 注解,另一种就是使用第三方框架 Qua ...
- C#中迭代器的概念和两种实现方式
1.首先我们看下IEnumerable接口定义: namespace System.Collections { // Summary: // Expose ...
- Junit4使用详解一:测试失败的两种情况
Junit4最佳实践 1.把测试文件夹和代码文件夹分离,这两者的代码互不干扰,代码目录和测试目录是并列的关系 2.Java代码 3.创建单元测试代码文件 4.运行测试代码 5.查看测试结果 现在的情 ...
- JAVA高级架构师基础功:Spring中AOP的两种代理方式:动态代理和CGLIB详解
在spring框架中使用了两种代理方式: 1.JDK自带的动态代理. 2.Spring框架自己提供的CGLIB的方式. 这两种也是Spring框架核心AOP的基础. 在详细讲解上述提到的动态代理和CG ...
- Shiro 安全框架详解二(概念+权限案例实现)
Shiro 安全框架详解二 总结内容 一.登录认证 二.Shiro 授权 1. 概念 2. 授权流程图 三.基于 ini 的授权认证案例实现 1. 实现原理图 2. 实现代码 2.1 添加 maven ...
- Spring框架系列(9) - Spring AOP实现原理详解之AOP切面的实现
前文,我们分析了Spring IOC的初始化过程和Bean的生命周期等,而Spring AOP也是基于IOC的Bean加载来实现的.本文主要介绍Spring AOP原理解析的切面实现过程(将切面类的所 ...
- Spring框架系列(10) - Spring AOP实现原理详解之AOP代理的创建
上文我们介绍了Spring AOP原理解析的切面实现过程(将切面类的所有切面方法根据使用的注解生成对应Advice,并将Advice连同切入点匹配器和切面类等信息一并封装到Advisor).本文在此基 ...
随机推荐
- jquery的同步和异步
之前一直在写JQUERY代码的时候遇到AJAX加载数据都需要考虑代码运行顺序问题.最近的项目用了到AJAX同步.这个同步的意思是当JS代码加载到当前AJAX的时候会把页面里所有的代码停止加载,页面出去 ...
- clover 显卡注入功能详细讲解
13 March 2014 GraphicsInjector功能源于变色龙,不过比变色龙更加灵活,定制性更加强大.Intel的显卡 GMA950, X3100, HD300, HD4000被证实可 ...
- NOIP 2018数据点
链接: https://pan.baidu.com/s/14jXQGPSac3b9_m5h5x2wGQ 提取码: 1cbk 好文别忘点赞!!!
- verilog behavioral modeling--blocking and nonblocking
BLOCKIN ...
- PHP方法之 substr
简单描述: substr 主要用于字符串的截取,但是不适用于中文字符串,易出现乱码,中文字符串可使用mbstring. 方法申明: substr(string,start,length) string ...
- django 12天(跨域,文件上传,下载,cookie,session)
django 12天(跨域,文件上传,下载) 跨域 什么是跨域 1.协议不同 2.端口不同 3.主机不同 如何解决跨域 1.安装django-cors-headers模块 2.在settings.py ...
- Codeforces Round #879 (Div. 2) C. Short Program
题目链接:http://codeforces.com/contest/879/problem/C C. Short Program time limit per test2 seconds memor ...
- 获取class
使用原生JavaScript,获取类操作符时:即使使用getElementByClassName,在Firefox和IE9以下是不兼容的.Firefox下是可以用它获取的到元素而IE不行,一般框架都会 ...
- UIDatePicker 显示时间和打印时间不一样
默认是时区为0,如下格式化一下就好啦 UIDatePicker *datePiker = [[UIDatePicker alloc] initWithFrame:CGRectMake(0, 100, ...
- Virtualbox虚拟机相关
Virtualbox虚拟机相关 Virtualbox是我一直使用的虚拟机,由于需要一些测试环境等,会经常使用多个虚拟机.经常捣腾.之前有涉及到一些virtualbox方面的问题的处理,并没有记录下来, ...