跟着柴毛毛学Spring(4)——面向切面编程![这里写图片描述](http://img.blog.csdn.net/20171031111402095)
面向切面编程简介
1. 什么是面向切面编程?
面向切面编程是Spring的第二大特性,它能将一个函数中非主体但有很必要的代码封装到一个单独的类中,在程序运行的时候再把它们插入到函数中。这样能使程序猿只关注函数的主体功能,而且写出来的代码具有具有较强的可读性,简约明了。
2. 面向切面编程的优点
面向切面编程的优点有两个: 1. 一个函数中所有额外的功能都被封装在一个类中,而不是分散在函数的各处。 2. 由于将非主体功能的代码转移到其他类中,因此函数的代码将更佳简洁。
3. Spring的面向切面编程特点
目前面向切面(AOP)市场的三足鼎立格局是这样的: - AspectJ - JBoss Aspect - Spring Aspect Spring Aspect借鉴AspectJ。
所谓面向切面,前面已经介绍过,就是将函数的一部分代码抽取出来,封装到一个类中,然后在某一时刻将其插入到程序中去。 AOP世界中,将切面插入的到程序中的时机有以下三种: 1. 编译时插入 2. 类被加载的时候插入 3. 程序运行时被插入
Spring是在程序运行期间将切面插入到相关函数中去的。 此外,在编译时插入需要特殊的编译器,在类加载的时候插入需要特殊的类加载器,而在运行时插入不需要额外的软件支持。
开始使用
假设现在有一个函数,用来查询数据库中名字为name的Person对象:
public String queryPersons(String name){
//查询的具体操作……
//此处省略100行代码……
}
我们需要在查询开始前判断name是否为空,在查询结束后记录操作日志,在查询发生异常时记录异常日志。 在过去,我们把这些代码都直接写在这个函数中,这样会导致这个函数很长,可读性很差。下面我们使用面向切面编程的思想解决这个问题。
1. 定义切面代码
首先需要将这个函数中额外的功能封装到一个类中,这个类就是一个普通类,功能封装在函数中:
class PersonAspect(){
/**
* 判断参数是否为空
*/
public void verifyNull(String name){
//判断参数是否为空
}
/**
* 记录操作日志
*/
public void logInfo(){
}
/**
* 记录异常日志
*/
public void logError(){
}
}
2. 将切面声明为bean
在Spring的配置文件中添加bean的声明:
<bean id="personAspect" class="com.njupt.PersonAspect">
</bean>
或者直接在PersonAspect类上使用@Componet标注:
@Componet("personAspect")
class PersonAspect(){
/**
* 判断参数是否为空
*/
public void verifyNull(String name){
//判断参数是否为空
}
/**
* 记录操作日志
*/
public void logInfo(){
}
/**
* 记录异常日志
*/
public void logError(){
}
}
3. 声明通知
定义好了额外的功能之后,接下来我们需要告诉Spring,这些功能需要在何时添加到哪个函数的什么位置,因此我们需要在Spring的配置文件中作如下声明:
<aop:config>
<aop:aspect ref="personAspect">
<!-- 在queryPersons函数执行之前先运行函数verifyNull -->
<aop:before pointcut="excution(* com.njupt.Person.queryPersons(..))" method="verifyNull" />
<!-- 在queryPersons函数执行之后再运行函数logInfo -->
<aop:after-returning pointcut="excution(* com.njupt.Person.queryPersons(..))" method="logInfo" />
<!-- 在queryPersons函数发生异常时运行函数logError -->
<aop:throwing pointcut="excution(* com.njupt.Person.queryPersons(..))" method="logError" />
</aop:config>
到此为止,Spring AOP已经可以运行了!
4. 声明环绕通知
在上述示例中,函数前插入一段代码,函数后插入一段代码,函数异常时插入一段代码,这些代码块都是独立的,无法共享数据。如果需要实现函数前后代码块的数据共享,那就要使用环绕通知。 说来也很简单,首先需要在PersonAspect类中加上一个参数为ProceedingJoinPoint的函数,如下所示:
@Componet("personAspect")
class PersonAspect(){
/**
* 判断参数是否为空
*/
public void verifyNull(String name){
//判断参数是否为空
}
/**
* 记录操作日志
*/
public void logInfo(){
}
/**
* 记录异常日志
*/
public void logError(){
}
/**
* 环绕通知
*/
public void xxx(ProceedingJoinPoint joinPoint){
int num = 0;
//在函数前执行一些功能,修改num参数
//执行函数
joinPoint.proceed();
//在函数后处理num
num++;
//……
}
}
然后在配置文件中把这个函数声明为环绕通知即可:
<aop:config>
<aop:aspect ref="personAspect">
<!-- 在queryPersons函数执行之前先运行函数verifyNull -->
<aop:before pointcut="excution(* com.njupt.Person.queryPersons(..))" method="verifyNull" />
<!-- 在queryPersons函数执行之后再运行函数logInfo -->
<aop:after-returning pointcut="excution(* com.njupt.Person.queryPersons(..))" method="logInfo" />
<!-- 在queryPersons函数发生异常时运行函数logError -->
<aop:throwing pointcut="excution(* com.njupt.Person.queryPersons(..))" method="logError" />
<!-- 环绕通知 -->
<aop:around pointcut="excution(* com.njupt.Person.queryPersons(..))" method="xxx()" />
</aop:aspect>
</aop:config>
到此为止,Spring的AOP介绍完毕,下面介绍如何使用注解取代XML。
使用注解代替XML
1. 注解切面
首先需要在切面类上加上注解@Aspect,告诉Spring这个类是个切面类; 其次需要在切面类中创建一个函数,用于定义切点。该函数的名字即为切点名,函数返回值和参数均为空。
@Aspect
class PersonAspect(){
/**
* 定义切点
*/
@Pointcut("excution(* com.njupt.Person.queryPersons(..))")
public void personAspect(){
}
/**
* 判断参数是否为空
*/
@Before("personAspect()")
public void verifyNull(String name){
//判断参数是否为空
}
/**
* 记录操作日志
*/
@AfterReturning("personAspect()")
public void logInfo(){
}
/**
* 记录异常日志
*/
@AfterThrowing("personAspect()")
public void logError(){
}
/**
* 环绕通知
*/
@Around("personAspect()")
public void xxx(ProceedingJoinPoint joinPoint){
int num = 0;
//在函数前执行一些功能,修改num参数
//执行函数
joinPoint.proceed();
//在函数后处理num
num++;
//……
}
}
- 如何让所有实体类用相同名称的主键(很有力的问题,比如所有表实体主键都用ID)
- Codeforces 834D The Bakery【dp+线段树维护+lazy】
- memcached安装及.NET中的Memcached.ClientLibrary使用详解
- AtCoder Beginner Contest 069【A,水,B,水,C,数学,D,暴力】
- 2017"百度之星"程序设计大赛 - 资格赛【1001 Floyd求最小环 1002 歪解(并查集),1003 完全背包 1004 01背包 1005 打表找规律+卡特兰数】
- 洛谷 2634&&BZOJ 2152: 聪聪可可【点分治学习+超详细注释】
- 【经验总结】Java在ACM算法竞赛编程中易错点
- 【Java学习笔记之六】java三种循环(for,while,do......while)的使用方法及区别
- 类A是公共的,应在名为A.java的文件中声明错误
- 逆天通用水印支持Winform,WPF,Web,WP,Win10。支持位置选择(9个位置 ==》[X])
- 【Java学习笔记之七】java函数的语法规则总结
- BZOJ 3038: 上帝造题的七分钟2【线段树区间开方问题】
- BZOJ 3211: 花神游历各国【线段树区间开方问题】
- WP、Win10开发或者WPF开发时绘制自定义窗体~例如:一个手机
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- Linux共享库、静态库、动态库详解
- strace详解及实战
- 推荐一个比crontab更好用的东西:crongo
- Vue 自定义指令
- IDEA + Spring Boot 的三种热加载方案,看完弄懂,不用加班~
- 开篇讲--关于Kali的碎碎念
- Mysql服务器SQL模式 (官方精译)
- 如何用Prometheus和Grafana监控Kubernetes集群?
- linux实时文件事件监听--inotify
- MySQL事务原理&实战【官方精译】
- 俗话:MySQL索引
- 基于飞桨复现CVPR 2016 MCNN的过程解析:教你更精确估算人流密度
- mysql各种引擎对比、实战
- 接球小游戏玩腻了?换个姿势让PaddleX帮你吊打游戏系统
- mysql事务隔离级别详解和实战