一、什么是面向切面编程
0、AOP导言
业务功能需求,需要正交的横切
0.1、横切关注点被描述为多处影响应用的功能,可被模块化为特殊的类,称为切面(aspect)
0.2、OO中继承和委托(引用)是重用通用功能的手段,但继承会导致一个脆弱的对象体系,使用委托需要对委托对象进行复杂的调用(继承:父类和子类之间是一个紧密的耦合关系,委托:需要对委托的对象去主动的调用)
0.3、切面提供了取代继承和委托的另一种选择:在一处定义通用功能,可通过声明的方式定义这个功能以何种方式在何处应用,而无需修改受影响的类
0.4、OOP(面向对象编程)针对业务处理过程的实体及其属性和行为进行抽象封装,以获得更加清晰高效的逻辑单元划分
0.5、AOP针对业务处理过程中的切面进行提取,它所面对的是处理过程中的某个步骤或阶段,以获得逻辑过程中各部分之间低耦合性的隔离效果
0.6、一些OO设计模式解决了AOP希望解决的部分问题:
1)Decorator(装饰者)模式
2)Observer(观察者)模式
无法实现环绕通知
3)Chain of Responsibility(责任链)模式
以上模式不是解决横切性通用的一个办法
0.7、Spring AOP构建于IoC之上,和IoC浑然天成统一于Spring容器中
0.8、AOP代理是AOP框架创建的对象,AOP在JavaEE应用开发中的价值在于为业务对象提供代理
0.9、Spring有两种代理方式
1)默认使用J2SE动态代理实现AOP代理,主要用于代理接口
2)CGLIB代理(代码生成工具),实现类的代理,而不是接口
0.10、Spring重点关注AOP的一个子集:方法拦截(method interception)
0.11、AOP实现策略:
1)J2SE动态代理(JDK1.3引入动态代理dynamic proxies),局限:只能针对接口,不能针对类)
2)动态字节码生成(CGLIB:Code Generation Library工具,可针对类提供代理)
3)Java代码生成(不再流行)
4)使用定制的类加载器(改变new操作行为,偏离Java标准)
5)语言扩展(AspectJ)
1、定义AOP术语
1.1、Aspect(切面):横切关注点的抽象即切面,与类相似,只是两者的关注点不一样——类是对物体特征的抽象,而切面是横切关注点的抽象(横切业务功能实现)
1.2、joinpoint(连接点):所谓连接点是指那些被拦截到的点。在Spring中,这些点指的是方法,因为Spring只支持方法类型的连接点。实际上joinpoint还可以是field或类构造器
1.3、Pointcut(切入点):所谓切入点是指我们要对那些joinpoint进行拦截的定义
切入点是连接点当中的一个子集,Spirng可以对所有方法调用拦截
1.4、Advice(通知、增强):所谓通知是指拦截到joinpoint之后要做的事情。通知分为前置通知Before,后置通知After,异常通知Afer-throwing,最终通知After-returning,环绕通知Around
1.5、Advisor(通知器):Spring引入的更抽象的概念,由两部分组成:一个通知和一个用于说明“在何处进行通知”的切入点,通知器完整模块化了一个切面。这样,切入点和通知也可以各自独立的复用
通知 + 切入点就组成了通知器
1.6、Target(目标对象):代理的目标对象
1.7、Weave(织入):指将aspects应用到target对象并导致proxy对象创建的过程
把切面应用到目标对象,就产生了代理对象
在目标对象的生命周期中有多个点可以进行织入:编译期;类加载期;运行期
1.8、Introduction(引入):在不修改类代码的前提下,Introduction可以在运行期为类动态的添加一些方法或field字段
1.9、Interceptor(拦截器):很多AOP框架用它来实现字段和方法的拦截,随之而来的就是在连接点处挂接一条拦截器链,链条上的每个拦截器通常会调用下一个拦截器。实际上,拦截器是一种AOP的实现策略
1.10、AOP代理(AOP proxy):既被通知的对象的引用——也就是说,AOP通知将在其上执行的这样一个对象引用,对于基于拦截的AOP框架来说,AOP代理概念极为关键。AOP代理可能是J2SE的动态代理,也可能是借住字节码操作工具生成的
AOP代理是和目标对象相关联的,对目标对象进行切面的应用,就导致了AOP代理对象的创建
2、Spring对AOP的支持
2.1、AOP框架的基本功能:创建切入点来定义切面织入的连接点
2.2、AOP框架领域三足鼎立:
1)AspectJ
2)JBoss AOP
3)Spring AOP
2.3、Spring提供4种各具特色的AOP支持:
1)经典的Spring基于代理AOP
2)@AspectJ注解驱动的切面
3)纯POJO切面
4)注入式AspectJ切面
前三种都是Spring基于代理的AOP变体,第四种是AspectJ的功能
2.4、当引入了简单的声明式AOP和基于注解的AOP之后,直接使用ProxyFactoryBean的经典Spring AOP就过时了
2.5、Spring AOP框架的关键点
1)Spring通知(advice)是用Java编写的,定义通知所应用的切面通常在Spring配置文件中用XML编写,或用注解
2)Spring在运行期通知对象
通过使用代理类,Spring在运行期将切面织入Spring管理的Bean中
在调用者调用目标方法的时候,自动生成代理,代理会帮你实现比如:日志、权限、安全、事务
3)Spring只支持方法连接点
3、Spring对AOP的支持(2)
3.1、切入点、通知、横切关注点等在权限系统中的技术实现
join point:OrderManager、ProductManager(一系列方法,符合条件的连接点作为切入点)
advice:权限验证
通知加切入点组成了通知器,通知器是对切面的模块化
3.2、Spring AOP的优点
1)AOP框架与IoC容器整合。通知、通知器、切入点都是Bean,可以在同一个轻量级容器中配置
2)Spring不仅提供了AOP,还对常用的重要企业级服务进行了模块化,比如Spring提供了现成的事务管理拦截器,可以开箱即用
3)和Spring的其他部分一样,Spring AOP可以在不同应用服务器之间任意移植,因为不涉及自定义的类装载机制
3.3、Spring AOP的缺点
1)不支持字段拦截,只对方法拦截
2)只用通过Spring IoC容器获取的对象才能进行通知,不能在类装载器层面进行通知(不能让new操作符返回已经通知过的对象)
二、使用切入点选择连接点
0、导言
0.1、切入点和通知是切面的最基本元素
0.2、Spring AOP使用AspectJ的切入点表达式语言来定义切入点
告诉Spring容器,在哪些目标的哪些连接点方法被调用的时候,需要去织入一些切面,应用一些横切关注点的功能
0.3、Spring仅支持AspectJ切入点指示器(pointcut designator)的一个子集
args() 限制连结点匹配参数为指定类型的执行方法
@args() 限制连结点匹配参数由指定注解标注的执行方法
target() 限制连结点匹配目标对象为指定类型的类
@target() 限制连结点匹配特定对象的类要有指定的注解
execution指示器是我们在编写切入点定义时使用的最主要指示器,在此基础上,使用其他指示器来限制匹配的切入点
1、编写切入点
代码说明:
execution(* com.javaee.spring.chineseIdol.Instrument.play(..))
使用
execution()指示器选择Instrument的play()方法,方法表达式以*开始,标识不关心方法返回值类型。接着指定全限定类名和方法名。对于方法参数列表,使用(..)标识切入点选择所有的play()方法,入参随意
代码说明:
execution(* com.javaee.spring.chineseIdol.Instrument.play(..)) && within(com.javaee.spring.chineseIdol.*)
假定我们需要配置切入点仅匹配com.javaee.spring.chineseIdol包,可以使用
within()指示器来限制匹配
2、使用Spring的bean()指示器
Spring2.5还引入一个新的
bean(),允许在切入点表达式中使用Bean的ID来标识Bean
bean()使用Bean ID或Bean名称作为参数来限制切入点只匹配特定的Bean
execution(* com.javaee.spring.chineseIdol.Instrument.play()) and bean(piano)
还可以使用
非操作符作为,为除了指定ID的Bean之外的其他Bean应用通知
execution(* com.javaee.spring.chineseIdol.Instrument.play()) and ! bean(piano)
三、在XML中声明切面
0、引言
0.1、Spring在AOP配置命名空间中提供了声明式切面的选择
使用ProxyFactoryBean声明切面非常复杂
0.2、示例:选秀节目的观众类
我们把选秀节目表演作为核心业务逻辑,设置切面
前置通知:观众入座,关闭手机
返回通知:观众鼓掌
异常通知:观众要求退票
1、声明前置和后置通知
简化上面的配置,单独定义切入点:
业务逻辑和通知逻辑:
业务逻辑是OOP的,通知逻辑是AOP的
业务逻辑 切面 通知逻辑
2、声明环绕通知
使用环绕通知可以完成前置和后置通知所实现的相同功能,只需在一个方法中实现
配置文件:
3、为通知传递参数
3.1、有时通知不仅对方法进行简单包装(拦截),还需要校验传递给方法的参数值,此时需要为通知传递参数
示例中切入点定义和
的arg-names属性为关键
3.2、切入点表示了TargetName的methodName方法,指定String参数,然后在args参数中标识将something作为参数
3.3、元素引用了something参数,标识该参数必须传递给beanName所属类型的interceptMethod方法
4、通过切面引入新功能
4.1、利用“引入introduction”这个AOP概念,切面可以为Spring Bean添加新方法
4.2、利用Spring AOP,可以为Bean引入新的方法。代理拦截调用并委托给实现该方法的其他对象
4.3、示例演示让表演者Performer增加一个接受表彰的功能receiveAward()
4.4、通过接口声明该功能方法
public interface Contestant {
void receiveAward();
}
4.5、借助AOP引入,可以不需要为设计妥协或侵入性地改变现有的实现,只需使用元素
types-matching:匹配现有的接口,匹配到接口的所有实现者
implement-interface:引入一个新的接口
default-impl:新接口的实现类
4.6、声明了此切面所通知的Bean在它的对象层次结构中拥有的父类型
4.7、示例中类型匹配Performer接口(由types-matching属性指定)的那些Bean会实现Contestant接口(由implement-interface属性指定)
4.8、有两种方式标识所引入接口的实现
1)使用default-impl属性通过完全限定类名显式指定Contestant的实现
2)使用delegate-ref属性,引用一个Spring Bean作为引入的委托
注册这个类:
四、例子程序
下载地址:https://pan.baidu.com/s/1c3STMW4
部分代码
chinese-Idol.xml
Audience.java
package com.javaee.spring.chineseIdol.aop;
import org.aspectj.lang.ProceedingJoinPoint;
public class Audience {
public void takeSeats() {
System.out.println("观众入座.");
}
public void turnOffCellPhones() {
System.out.println("观众关闭手机.");
}
public void applaud() {
System.out.println("观众鼓掌.啪啪啪");
}
public void demandRefund() {
System.out.println("表演太糟糕了.要求退票");
}
public void watchPerformance(ProceedingJoinPoint joinPoint) {
try {
System.out.println("观众入座2。");
System.out.println("观众关闭手机2。");
long start = System.currentTimeMillis();
joinPoint.proceed();
long end = System.currentTimeMillis();
System.out.println("观众鼓掌2:啪 啪 啪 。");
System.out.println("表演持续的时间:" + (end-start) + " 毫秒");
} catch (Throwable e) {
System.out.println("表演太糟糕了.要求退票2。");
}
}
}
test.java
package com.javaee.spring.chineseIdol.aop;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class test {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("/com/javaee/spring/chineseIdol/aop/chinese-Idol.xml");
Performer performer = ctx.getBean("performer", Performer.class);
try {
performer.perform();
//aop引入新功能
Contestant con = (Contestant)performer;
con.receiveAward(); //强制转换,接受表彰
//原来的类没有这个功能
//在spring容器里通过aop引入了功能
//这个功能通过Contestant接口申明
//委托给另外一个Bean来实现
//然后就给表演者引入了接受表彰的功能!!!
} catch (PerformanceException e) {
e.printStackTrace();
}
}
}
执行结果:
观众入座2。
观众关闭手机2。
观众入座.
观众关闭手机.
JUGGLING 3 BALLS
观众鼓掌2:啪 啪 啪 。
表演持续的时间:0 毫秒
观众鼓掌.啪啪啪
接收观众表彰!