上两周写了两篇Spring IOC相关的文章:Spring使用之IOC、Spring之IOC补充主要讲Spring的核心功能IOC。Spring作为一个轻量级容器,其核心功能就是IOC。但也不仅仅是IOC,它提供的另一个核心功能就是AOP(Aspect Oriented Programming)即面向切面编程。
一、Spring AOP概述
(一)、描述
面向对象编程已经给我们提供了对现实业务很好的抽象,也实现数据的封装,既然我们已经有OOP,那么我们为什么还需要AOP呢??你想一想这样一个业务场景,假如我们银行有500个业务,假如有一天有这么一个需求,要统计每天所有业务被操作的总数??你怎么实现??每个业务逻辑方法加上一个统计??那如果我们有成千上万的业务呢??你每个业务方法都去修改,这样的工作量会有多大,况且统计好像不是业务,不应该写在业务方法里面,还有你修改了所有业务方法,那是不是所有的业务都得重新测试,有了AOP这个问题就迎刃而解。
Spring AOP就如同在选定逻辑方法上统一织入一段逻辑,我们可织入统计的逻辑,也可做安全验证。
(二)、切面编程相关的概念
1、 Joinpoint 连接点(《Spring 实战》中的翻译),可被织入操作的系统点即为Joinpoint,即可被加入逻辑代码的点,常见Joinpoint类型:
(1)、方法调用(方法调用处,调用对象上的执行点)。
(2)、方法调用执行(方法内,被调用方法逻辑执行时点,Spring仅支持该类JoinPoint)。
(3)、构造方法调用。
(4)、类初始化(静态类型,静态块初始化时点)
2、PointCut(切点) 用来描述一组Joinpoint,指定系统中符合条件的一组Joinpoint。spring通常通过AspectJ的切点表达式来描述。
在Spring中所有的方法方法都可以作为连接点,但是我们不需要统计所有方法的调用次数,我们可能只需要统计用户业务的总数,这是我们就得通过表达式来指定我们需要统计的业务方法,通过表达式指定的的连接点就是切点,我们要织入的统计代码就应用在这些切点上。
3、Advice(通知) :织入PointCut的逻辑内容 。
显然我们上面的业务需求中需要做的是统计,Advice根据织入内容在PointCut执行位置又可分为一下几种
(1)、Before Advice 指定PointCut方法执行之前执行。
(2)、After Advice 指定位置之后执行,After Advice包括后面三种,执行位置如下方法展示(After Advice、After Returning Advice 、After throwing Advice)。
执行位置分别对应如下:
public void mockMethod(){
//before Advice 织入逻辑执行点
try{
...........
retrun;
//after Returning Advice 织入逻辑执行点
}catch(Exception e){
//After throwing Advice 织入逻辑执行点
}finally{
//After Advice 织入逻辑执行点
}
}复制代码
(3)AroundAdvice ,方法执行前后,这是比较特殊的通知,之前的两种通知都不能阻止调用业务方法,但这个通知可阻止对业务方法的调用,比如,我们可使用AroudAdvice在逻辑方法前加一道安全验证,验证不通过则不让调相应的逻辑方法。(这里不理解不要紧,后面会给例子)。
二、Spring切面编程初印象
接下来,我们就上面说的银行的例子来写个demo,见识见识AOP。现在我们需要统计业务办理数,我们可直接在业务方法执行前做一个统计,所以我们通知使用Before Advice,Spring使用切面也又两种方式:XML配置、注解。
(一)、使用XML配置方式
使用AOP除了之前第一篇文章里说的需要引进Spring核心包外,还需要引进新的两个包
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.6.8</version>
</dependency>
复制代码
项目结构如下
我们通过一个静态的int变量来统计所有业务的使用数,CountBusiness如下:
package cn.springstudy.inspectadvice;
import org.springframework.aop.BeforeAdvice;
public class CountBusiness{
public static int count;
public void beforeBusinessCount(){
count++;
}
}
复制代码
我们建所有银行业务都放在cn.springstudy.bank下,我们现在加上两个银行业务处理类:
package cn.springstudy.bank;
//账号业务
public class AccountBusiness {
//更改用户账号绑定电话
public void chgAccountPhone(){
System.out.println("Change Account Phone");
}
//开通新账号
public void createNewAccount(){
System.out.println("Create New Account");
}
//更改用户信息
public void chgAccountInfo(){
System.out.println("Change Account Info Success");
}
//销户
public void delAccount(){
System.out.println("Delete Account");
}
}复制代码
package cn.springstudy.bank;
//转账业务
public class TransferBusiness {
//转账
public void transfer(){
System.out.println("Transfer A Account To B Account");
}
}
复制代码
接下来,需要在配置文件中配置
<?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">
<!-- 使用Spring AOP -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
<bean id="accountBusiness" class="cn.springstudy.bank.AccountBusiness"></bean>
<bean id="transferBusiness" class="cn.springstudy.bank.TransferBusiness"></bean>
<!--Advice类 -->
<bean id="countBusiness" class="cn.springstudy.inspectadvice.CountBusiness"></bean>
<!--AOP配置 -->
<aop:config>
<!-- 配置切面 -->
<aop:aspect ref="countBusiness">
<!--切点,表示执行cn.springstudy.bank下的任意类的任意方法,expression中的表单
是即为AspectJ表达式,指定需要被织入逻辑的切点,这里表达式可先不深究,后面会讲解 -->
<aop:pointcut id="bankbusinesspointcut"
expression="execution(* cn.springstudy.bank.*.*(..))">
</aop:pointcut>
<!--配置BeforeAdvice 执行织入逻辑为bean
countBusiness的beforeBusinessCount方法,应用在bankbusinesspointcut切点上-->
<aop:before method="beforeBusinessCount"
pointcut-ref="bankbusinesspointcut" >
</aop:before>
</aop:aspect>
</aop:config>
</beans>复制代码
接下来就可以做测试了
package cn.springstudy.spring;
import cn.springstudy.bank.AccountBusiness;
import cn.springstudy.bank.TransferBusiness;
import cn.springstudy.inspectadvice.CountBusiness;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class SpringStudy {
public static void main(String arg[]){
//方式一
ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");;
AccountBusiness accountBusiness = (AccountBusiness) classPathXmlApplicationContext.getBean("accountBusiness");
//办理修改账号信息业务
accountBusiness.chgAccountInfo();
//办理修改绑定电话业务
accountBusiness.chgAccountPhone();
TransferBusiness transferBusiness = (TransferBusiness) classPathXmlApplicationContext.getBean("transferBusiness");
//办理转账业务
transferBusiness.transfer();
System.out.println(CountBusiness.count);
}
}复制代码
OK,执行结构如我们想的一样,统计出来的业务办理量是3:
可以看到整个AOP功能的使用还是很清爽的,我们只是增加了一个统计的类CountBusiness,还有增加了点配置,可以看出Spring的理念之一:非侵入式。有一天你不想用Spring了,那些业务类、CountBusiness依旧是Java类,还是可以使用。
(二)、使用注解。
我们只需建CountBusiness类做下修改
package cn.springstudy.inspectadvice;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.aop.BeforeAdvice;
//这是一个切面类
@Aspect
public class CountBusiness implements BeforeAdvice {
public static int count;
//定义切点
@Pointcut("execution(* cn.springstudy.bank.*.*(..))")
public void declarePointCut(){}
//before Advice 执行逻辑
@Before("declarePointCut()")
public void beforeBusinessCount(){
count++;
}
}
复制代码
此时我们的XML配置就不在需要AOP配置了,只需将CountBusiness配置成一个普通Bean即可,配置文件改为一下,即可执行测试方法,执行结果同上。
<?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:c="http://www.springframework.org/schema/c"
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/c
http://www.springframework.org/schema/c/spring-c.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
<bean id="accountBusiness" class="cn.springstudy.bank.AccountBusiness"></bean>
<bean id="transferBusiness" class="cn.springstudy.bank.TransferBusiness"></bean>
<!--切面类-->
<bean id="countBusiness" class="cn.springstudy.inspectadvice.CountBusiness"></bean>
</beans>
复制代码
三、更多Spring AOP知识
(一)、Around Advice
Around Advice:前面我们说过使用Around Advice可以可阻止对业务方法的调用。接下来我们就来体验一下。我们在操作用户业务前面加个验证。
我们增加一个用户类:
package cn.springstudy.vo;
public class User {
private String name;
private String passWord;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassWord() {
return passWord;
}
public void setPassWord(String passWord) {
this.passWord = passWord;
}
}复制代码
再更改下账号业务的类,办理业务,顺便把要办理的用户传过来
package cn.springstudy.bank;
import cn.springstudy.vo.User;
//账号业务
public class AccountBusiness {
//更改用户账号绑定电话
public void chgAccountPhone(User user){
System.out.println("Change Account Phone");
}
//开通新账号
public void createNewAccount(User user){
System.out.println("Create New Account");
}
//更改用户信息
public void chgAccountInfo(User user){
System.out.println("Change Account Info Success");
}
//销户
public void delAccount(User user){
System.out.println("Delete Account");
}
}复制代码
接下来是Advice 类:
package cn.springstudy.inspectadvice;
import cn.springstudy.vo.User;
import org.aspectj.lang.ProceedingJoinPoint;
public class Authenticate {
public void aroundAdvice(ProceedingJoinPoint joinPoint){
//拿到调用切点方法的参数
Object[] arg = joinPoint.getArgs();
if (arg[0] instanceof User){
//如果用户名是zhangsan,不让调切点方法,直接return
if ("zhangsan".equals(((User)arg[0]).getName())){
System.out.println("zhangsan 异常用户,不让办理业务");
return;
}
try {
joinPoint.proceed();//执行切点方法,即逻辑方法
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}
}
}复制代码
配置文件
<?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:c="http://www.springframework.org/schema/c"
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/c
http://www.springframework.org/schema/c/spring-c.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
<bean id="accountBusiness" class="cn.springstudy.bank.AccountBusiness"></bean>
<bean id="transferBusiness" class="cn.springstudy.bank.TransferBusiness"></bean>
<bean id="authenticate" class="cn.springstudy.inspectadvice.Authenticate"></bean>
<aop:config>
<aop:aspect ref="authenticate">
<aop:around method="aroundAdvice" pointcut-ref="authenticatepointcut"></aop:around>
</aop:aspect>
</aop:config>
</beans>
复制代码
接下来就可以测试了
public class SpringStudy {
public static void main(String arg[]){
AccountBusiness accountBusiness = (AccountBusiness) classPathXmlApplicationContext.getBean("accountBusiness");
User user = new User();
user.setName("zhangsan");
user.setPassWord("123456");
//zhangsan 过来办理更改用户信息的业务
accountBusiness.chgAccountInfo(user);
}
}
复制代码
执行结果,可以看到chgAccountInfo方法里东西并没有执行。
如果是李四办理业务呢,没错李四可以正常调用业务。
public class SpringStudy {
public static void main(String arg[]){
AccountBusiness accountBusiness = (AccountBusiness) classPathXmlApplicationContext.getBean("accountBusiness");
User user = new User();
user.setName("lisi");
user.setPassWord("123456");
//zhangsan 过来办理更改用户信息的业务
accountBusiness.chgAccountInfo(user);
}
}复制代码
(二)、ASpectJ表达式
前面说到Apring AOP支持部分SpectJ切点语言来指定要应用切面的切点,那么ASpectJ切点语言究竟是怎样的语法。
支持的部分指示符包括:execution、within、this、target、args
1、execution是最常用的了,用来指定切点方法,接下来就看看之前我们的配置:
execution(* cn.springstudy.bank.*.*(..))复制代码
(1)、其中第一个*表示返回类型可以是任意类型,当然我们也可以直接指定,比如:
execution(int cn.springstudy.bank.*.*(..)) 表示匹配的返回类型为int的方法。execution(cn.springstudy.vo.User cn.springstudy.bank.AccountBusiness.*(..)) 匹配的是返回类型为User的方法。
(2)、第二个*号表示任意类,第三个*号表示任意方法,括号里面两个点表示参数可以是任意个数、任意类型。
所以execution(* cn.springstudy.bank.*.*(..))这个表达式表示的就是cn.springstudy.bank包下任意类,类中任意方法,返回类型,和参数都不做限定,所以指定的切点就是AccountBusiness、TransferBusiness这两个类中的所有方法。
2、within匹配所以持有指定注解类型内的方法
within(cn.springstudy.bank.*) 效果同 execution(* cn.springstudy.bank.*.*(..))
within(cn.springstudy.bank..*) 其中..表示包括子包。所以这个表示cn.springstudy.bank下,包括子包中的所有类下的方法。
3、args:使用“args(参数类型列表)”匹配当前执行的方法传入的参数为指定类型的执行方法,例:args(cn.springstudy.vo.User) 表示参数为User的方法。
4、target,用于匹配类型,例:target(cn.springstudy.bank.VIPBusiness)实现了VIPBusiness接口的类都会被匹配上
以上的所有指示器还可以通过 and 和 or这些逻辑运算符结合在一起,例:表示匹配cn.springstudy.bank包下类的方法,且方法参数类型为User
within(cn.springstudy.bank.*) and args(cn.springstudy.vo.User)复制代码
5、 this(),这个我测试,没看出跟target有什么区别,暂时不清楚跟target的区别。
未完,待续...............