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).本文在此基 ...
随机推荐
- iOS开发遇见的坑之二:工程文件中插件和自身工程命名冲突
在升级cocoapod后,我重新管理了一下工程,其实也就是把各个类分类进行管理 类似于这样 然后编译就发现不能运行 1.其中一个错误是工程文件缺失,根据提示添加进来进行 2.有一个是pch的相对路径变 ...
- iOS 面试集锦2
4.写一个setter方法用于完成@property (nonatomic,retain)NSString *name,写一个setter方法用于完成@property(nonatomic,copy) ...
- [LUOGU] P1551 亲戚
题目背景 若某个家族人员过于庞大,要判断两个是否是亲戚,确实还很不容易,现在给出某个亲戚关系图,求任意给出的两个人是否具有亲戚关系. 题目描述 规定:x和y是亲戚,y和z是亲戚,那么x和z也是亲戚.如 ...
- 【Java_基础】java中static与final关键字的区别
1.static关键字 经static关键字修饰的成员被该类的所有对象所共享,任意一对象对静态变量的修改其它对象都是可见的.通常通过类名来引用static成员.类加载的连接阶段将会为静态成员变量在jv ...
- nginx 无法加载css/js图片等文件 404 not fund
刚配置Nginx反向代理,Nginx可能会出现无法加载css.js或者图片等文件,这里需要在配置文件*.conf里面加上如下配置项. location ~ .*\.(js|css|png|jpg)$ ...
- NFS网络共享服务 挂载参数及优化 内核优化建议
配置NFS服务端 nfs01上安装软件 [root@nfs01 ~]# yum install nfs-utils rpcbind -y nfs-utils:NFS服务的主程序,包括rpc.nfsd. ...
- CodeForces 699C - Vacations
题目链接:http://codeforces.com/problemset/problem/699/C C. Vacations time limit per test1 second memory ...
- 笔记-python-字符串格式化-format()
笔记-python-字符串格式化-format() 1. 简介 本文介绍了python 字符串格式化方法format()的常规使用方式. 2. 使用 2.1. Accessi ...
- Repo command reference
Repo command reference In this document init sync upload diff download forall prune start status Rep ...
- [android开发篇]activity组件篇
https://developer.android.com/guide/components/activities.html Activity 是一个应用组件,用户可与其提供的屏幕进行交互,以执行拨打 ...