再谈Spring AOP
1、AOP的基本概念
在进行AOP开发前,先熟悉几个概念:
连接点(Jointpoint):表示需要在程序中插入横切关注点的扩展点,连接点可能是类初始化、方法执行、方法调用、字段调用或处理异常等等,Spring只支持方法执行连接点。程序执行过程中明确的点,一般是方法的调用。在AOP中表示为“在哪里干”;
切入点(Pointcut):选择一组相关连接点的模式,即可以认为连接点的集合。就是带有通知的连接点,在程序中主要体现为书写切入点表达式。Spring支持perl5正则表达式和AspectJ切入点模式,Spring默认使用AspectJ语法,在AOP中表示为“在哪里干的集合”;
通知(Advice):在连接点上执行的行为,通知提供了在AOP中需要在切入点所选择的连接点处进行扩展现有行为的手段;包括前置通知(before advice)、后置通知(after advice)、环绕通知(around advice)。AOP在特定的切入点上执行的增强处理。在Spring中通过代理模式实现AOP,并通过拦截器模式以环绕连接点的拦截器链织入通知;在AOP中表示为“干什么”;
方面/切面(Aspect):横切关注点的模块化,比如上边提到的日志组件。通常是一个类,里面可以定义切入点和通知。可以认为是通知、引入和切入点的组合;在Spring中可以使用Schema和@AspectJ方式进行组织实现;在AOP中表示为“在哪干和干什么集合”;
引入(inter-type declaration):也称为内部类型声明,为已有的类添加额外新的字段或方法,Spring允许引入新的接口(必须对应一个实现)到所有被代理对象(目标对象), 在AOP中表示为“干什么(引入什么)”;
目标对象(Target Object):需要被织入横切关注点的对象,即该对象是切入点选择的对象,需要被通知的对象,从而也可称为“被通知对象”;由于Spring AOP 通过代理模式实现,从而这个对象永远是被代理对象,在AOP中表示为“对谁干”;
AOP代理(AOP Proxy):AOP框架使用代理模式创建的对象,从而实现在连接点处插入通知(即应用切面),就是通过代理来对目标对象应用切面。在Spring中,AOP代理可以用JDK动态代理或CGLIB代理实现,而通过拦截器模型应用切面。AOP框架创建的对象,代理就是目标对象的加强。Spring中的AOP代理可以使JDK动态代理,也可以是CGLIB代理,前者基于接口,后者基于子类
织入(Weaving):织入是一个过程,是将切面应用到目标对象从而创建出AOP代理对象的过程,织入可以在编译期、类装载期、运行期进行。
在AOP中,通过切入点选择目标对象的连接点,然后在目标对象的相应连接点处织入通知,而切入点和通知就是切面(横切关注点),而在目标对象连接点处应用切面的实现方式是通过AOP代理对象,如下图所示。

接下来再让我们具体看看Spring有哪些通知类型:
1. 前置通知(Before Advice):在切入点选择的连接点处的方法之前执行的通知,该通知不影响正常程序执行流程(除非该通知抛出异常,该异常将中断当前方法链的执行而返回)。
2. 后置通知(After Advice):在切入点选择的连接点处的方法之后执行的通知,包括如下类型的后置通知
2.1 后置返回通知(After returning Advice):在切入点选择的连接点处的方法正常执行完毕时执行的通知,必须是连接点处的方法没抛出任何异常正常返回时才调用后置通知
2.2 后置异常通知(After throwing Advice): 在切入点选择的连接点处的方法抛出异常返回时执行的通知,必须是连接点处的方法抛出任何异常返回时才调用异常通知
2.3 后置最终通知(After finally Advice): 在切入点选择的连接点处的方法返回时执行的通知,不管抛没抛出异常都执行,类似于Java中的finally块。
3. 环绕通知(Around Advices):环绕着在切入点选择的连接点处的方法所执行的通知,环绕通知可以在方法调用之前和之后自定义任何行为,并且可以决定是否执行连接点处的方法、替换返回值、抛出异常等等。
2、基于注解的AOP配置方式
2.1 启用@AsjectJ支持
在applicationContext.xml中配置下面一句:
<aop:aspectj-autoproxy />
2.2 通知类型介绍
(1)Before:在目标方法被调用之前做增强处理,@Before只需要指定切入点表达式即可
(2)AfterReturning:在目标方法正常完成后做增强,@AfterReturning除了指定切入点表达式后,还可以指定一个返回值形参名returning,代表目标方法的返回值
(3)AfterThrowing:主要用来处理程序中未处理的异常,@AfterThrowing除了指定切入点表达式后,还可以指定一个throwing的返回值形参名,可以通过该形参名来访问目标方法中所抛出的异常对象
(4)After:在目标方法完成之后做增强,无论目标方法时候成功完成。@After可以指定一个切入点表达式
(5)Around:环绕通知,在目标方法完成前后做增强处理,环绕通知是最重要的通知类型,像事务,日志等都是环绕通知,注意编程中核心是一个ProceedingJoinPoint
2.3 例子:

(1)Operator.java --> 切面类
@Component
@Aspect
public class Operator { @Pointcut("execution(* com.aijava.springcode.service..*.*(..))")
public void pointCut(){} @Before("pointCut()")
public void doBefore(JoinPoint joinPoint){
System.out.println("AOP Before Advice...");
} @After("pointCut()")
public void doAfter(JoinPoint joinPoint){
System.out.println("AOP After Advice...");
} @AfterReturning(pointcut="pointCut()",returning="returnVal")
public void afterReturn(JoinPoint joinPoint,Object returnVal){
System.out.println("AOP AfterReturning Advice:" + returnVal);
} @AfterThrowing(pointcut="pointCut()",throwing="error")
public void afterThrowing(JoinPoint joinPoint,Throwable error){
System.out.println("AOP AfterThrowing Advice..." + error);
System.out.println("AfterThrowing...");
} @Around("pointCut()")
public void around(ProceedingJoinPoint pjp){
System.out.println("AOP Aronud before...");
try {
pjp.proceed();
} catch (Throwable e) {
e.printStackTrace();
}
System.out.println("AOP Aronud after...");
} }
(2)UserService.java --> 定义一些目标方法
@Service
public class UserService { public void add(){
System.out.println("UserService add()");
} public boolean delete(){
System.out.println("UserService delete()");
return true;
} public void edit(){
System.out.println("UserService edit()");
int i = 5/0;
} }
(3).applicationContext.xml
<context:component-scan base-package="com.aijava.springcode"/> <aop:aspectj-autoproxy />
(4).Test.java
public class Test {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
UserService userService = (UserService) ctx.getBean("userService");
userService.add();
}
}
上面是一个比较简单的测试,基本涵盖了各种增强定义。注意:做环绕通知的时候,调用ProceedingJoinPoint的proceed()方法才会执行目标方法。
2.4 通知执行的优先级
进入目标方法时,先织入Around,再织入Before,退出目标方法时,先织入Around,再织入AfterReturning,最后才织入After。
注意:Spring AOP的环绕通知会影响到AfterThrowing通知的运行,不要同时使用!同时使用也没啥意义。
2.5 切入点的定义和表达式
切入点表达式的定义算是整个AOP中的核心,有一套自己的规范
Spring AOP支持的切入点指示符:
execution:用来匹配执行方法的连接点
A:@Pointcut("execution(* com.aijava.springcode.service..*.*(..))")
第一个*表示匹配任意的方法返回值,..(两个点)表示零个或多个,上面的第一个..表示service包及其子包,第二个*表示所有类,第三个*表示所有方法,第二个..表示方法的任意参数个数
B:@Pointcut("within(com.aijava.springcode.service.*)")
within限定匹配方法的连接点,上面的就是表示匹配service包下的任意连接点
C:@Pointcut("this(com.aijava.springcode.service.UserService)")
this用来限定AOP代理必须是指定类型的实例,如上,指定了一个特定的实例,就是UserService
D:@Pointcut("bean(userService)")
bean也是非常常用的,bean可以指定IOC容器中的bean的名称
2.6 基于XML形式的配置方式
开发中如果选用XML配置方式,通常就是POJO+XML来开发AOP,大同小异,无非就是在XML文件中写切入点表达式和通知类型
(1)Log.java
public class Log {
private Integer id;
//操作名称,方法名
private String operName;
//操作人
private String operator;
//操作参数
private String operParams;
//操作结果 成功/失败
private String operResult;
//结果消息
private String resultMsg;
//操作时间
private Date operTime = new Date();
setter,getter
}
(2)Logger.java
/**
* 日志记录器 (AOP日志通知)
*/
public class Logger { @Resource
private LogService logService; public Object record(ProceedingJoinPoint pjp){ Log log = new Log();
try {
log.setOperator("admin");
String mname = pjp.getSignature().getName();
log.setOperName(mname); //方法参数,本例中是User user
Object[] args = pjp.getArgs();
log.setOperParams(Arrays.toString(args)); //执行目标方法,返回的是目标方法的返回值,本例中 void
Object obj = pjp.proceed();
if(obj != null){
log.setResultMsg(obj.toString());
}else{
log.setResultMsg(null);
} log.setOperResult("success");
log.setOperTime(new Date()); return obj;
} catch (Throwable e) {
log.setOperResult("failure");
log.setResultMsg(e.getMessage());
} finally{
logService.saveLog(log);
}
return null;
}
}
(3).applicationContext.xml
<aop:config>
<aop:aspect id="loggerAspect" ref="logger">
<aop:around method="record" pointcut="(execution(* com.aijava.distributed.ssh.service..*.add*(..))
or execution(* com.aijava.distributed.ssh.service..*.update*(..))
or execution(* com.aijava.distributed.ssh.service..*.delete*(..)))
and !bean(logService)"/>
</aop:aspect>
</aop:config>
2.7 JDK动态代理介绍
例子:
(1)UserService.java
public interface UserService {
public void add();
}
(2)UserServiceImpl.java
public class UserServiceImpl implements UserService{
public void add() {
System.out.println("User add()...");
}
}
(3)ProxyUtils.java
public class ProxyUtils implements InvocationHandler{
private Object target;
public ProxyUtils(Object target){
this.target = target;
}
public Object getTarget() {
return target;
}
public void setTarget(Object target) {
this.target = target;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("do sth before...");
method.invoke(target, args);
System.out.println("do sth after...");
return null;
}
}
(4)Test.java
public class Test {
public static void main(String[] args) {
UserService userService = new UserServiceImpl();
ProxyUtils proxyUtils = new ProxyUtils(userService);
UserService proxyObject = (UserService) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),UserServiceImpl.class.getInterfaces(), proxyUtils);
proxyObject.add();
}
}
JDK动态代理核心还是一个InvocationHandler,记住这个就行了。
再谈Spring AOP的更多相关文章
- 浅谈Spring AOP 面向切面编程 最通俗易懂的画图理解AOP、AOP通知执行顺序~
简介 我们都知道,Spring 框架作为后端主流框架之一,最有特点的三部分就是IOC控制反转.依赖注入.以及AOP切面.当然AOP作为一个Spring 的重要组成模块,当然IOC是不依赖于Spring ...
- 再谈spring的循环依赖是怎么造成的?
老生常谈,循环依赖!顾名思义嘛,就是你依赖我,我依赖你,然后就造成了循环依赖了!由于A中注入B,B中注入A导致的吗? 看起来没毛病,然而,却没有说清楚问题!甚至会让你觉得你是不清楚spring的循环依 ...
- 再谈Spring Boot中的乱码和编码问题
编码算不上一个大问题,即使你什么都不管,也有很大的可能你不会遇到任何问题,因为大部分框架都有默认的编码配置,有很多是UTF-8,那么遇到中文乱码的机会很低,所以很多人也忽视了. Spring系列产品大 ...
- 关于 Spring AOP (AspectJ) 该知晓的一切
关联文章: 关于Spring IOC (DI-依赖注入)你需要知道的一切 关于 Spring AOP (AspectJ) 你该知晓的一切 本篇是年后第一篇博文,由于博主用了不少时间在构思这篇博文,加上 ...
- 关于 Spring AOP (AspectJ) 你该知晓的一切
版权声明:本文为CSDN博主「zejian_」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明.原文链接:https://blog.csdn.net/javazej ...
- Spring AOP 源码分析 - 筛选合适的通知器
1.简介 从本篇文章开始,我将会对 Spring AOP 部分的源码进行分析.本文是 Spring AOP 源码分析系列文章的第二篇,本文主要分析 Spring AOP 是如何为目标 bean 筛选出 ...
- 求求你,下次面试别再问我什么是 Spring AOP 和代理了!
https://mbd.baidu.com/newspage/data/landingsuper?context=%7B%22nid%22%3A%22news_9403056301388627935% ...
- TinyFrame再续篇:整合Spring AOP实现日志拦截
上一篇中主要讲解了如何使用Spring IOC实现依赖注入的.但是操作的时候,有个很明显的问题没有解决,就是日志记录问题.如果手动添加,上百个上千个操作,每个操作都要写一遍WriteLog方法,工作量 ...
- Spring AOP 用法浅谈(Day_18)
当你这一天没有在进步,那你就是在退步! [简述] Aspect Oriented Programming :面向切面编程 所谓面向切面编程,是一种通过预编译和运行期动态代理的方式实现在不修改源代码的情 ...
随机推荐
- 1.Anaconda安装Tensorflow报错UnicodeDecodeError: 'utf-8' codec can't decode ## invalid start byte的问题之解决
安装TensorFlow pip install --ignore-installed --upgrade tensorflow 报错: UnicodeDecodeError: 'utf-8' cod ...
- 【工具】代码生成器-python脚本
我觉得造轮子这件事情,是谁都可以做的.只不过做得好或者不好而已,用心了做得就要优雅一点. 之前用过java的代码生成器,什么pojodobodbo都能生成,于是我也来自己造一个轮子. 造轮子的事情是没 ...
- Jquery 简明介绍
http://www.cnblogs.com/luotianshuai/p/5196997.html http://www.cnblogs.com/liujianzuo888/articles/568 ...
- VS2010/MFC编程入门之十八(对话框:字体对话框)
鸡啄米在上一节为大家讲解了文件对话框的使用,本节则主要介绍字体对话框如何应用. 字体对话框的作用是用来选择字体.我们也经常能够见到.MFC使用CFontDialog类封装了字体对话框的所有操作.字体对 ...
- hdu5057 分块处理,当数值大于数据范围时树状数组 真是巧 将大数据分为小数据来处理
这题说的给了100000个数有100000次操作 询问 L和R 区间内 在D位上为P的个数,用树状数组存 要开[10][10][100000]的int 开不了但是能开 这么大的unsign short ...
- SQLServer中char、varchar、nchar、nvarchar比较
转自:http://www.cnblogs.com/bluesky_blog/archive/2009/07/31/1535722.html 对于程序中的string型字段,SQLServer中有ch ...
- Python Missing parentheses in call to 'print'
原来是因为Python2.X和Python3.X不兼容. 我安装的是Python3.X,但是我试图运行的却是Python2.X 的代码. 所以上面的语法在python3中是错误的.在python3中, ...
- js正则表达式的使用详解
本文转自:http://www.jb51.net/article/39623.htm 1定义正则表达式2关于验证的三个这则表达式方法3正则表达式式的转义字符 1定义正则表达式在js中定义正则表达式很简 ...
- 几种Memcache的状态监控的工具,以及安装和使用【linux系统】
1.Memcache-top的简介及安装和用法 简介:memcache-top是用perl语言编写的,可以运行在term下.它能够像top一样显示各个memcached节点的状态变化,其中包括系统管理 ...
- 《Java入门第二季》第二章 封装
什么是java中的封装1.封装的概念:隐藏信息.隐藏具体的实现细节. 2.封装的实现步骤: 1)修改属性的可见性,private.2)创建修改器方法和访问器方法,getXXX/setXXX.(未必一定 ...