Spring框架(JavaEE开发进阶Ⅲ)—AOP

一、什么是面向切面编程

0、AOP导言

业务功能需求,需要正交的横切
Spring框架(JavaEE开发进阶Ⅲ)—AOP_第1张图片
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中
在调用者调用目标方法的时候,自动生成代理,代理会帮你实现比如:日志、权限、安全、事务
Spring框架(JavaEE开发进阶Ⅲ)—AOP_第2张图片
3)Spring只支持方法连接点

3、Spring对AOP的支持(2)

3.1、切入点、通知、横切关注点等在权限系统中的技术实现
Spring框架(JavaEE开发进阶Ⅲ)—AOP_第3张图片
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)的一个子集
Spring框架(JavaEE开发进阶Ⅲ)—AOP_第4张图片
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声明切面非常复杂
Spring框架(JavaEE开发进阶Ⅲ)—AOP_第5张图片
0.2、示例:选秀节目的观众类
Spring框架(JavaEE开发进阶Ⅲ)—AOP_第6张图片
我们把选秀节目表演作为核心业务逻辑,设置切面
前置通知:观众入座,关闭手机
返回通知:观众鼓掌
异常通知:观众要求退票

1、声明前置和后置通知


    
        
        
        
        
    
简化上面的配置,单独定义切入点:

    
        
        
        
        
        
        
    
业务逻辑和通知逻辑:
业务逻辑是OOP的,通知逻辑是AOP的
          业务逻辑                                     切面                                                       通知逻辑
Spring框架(JavaEE开发进阶Ⅲ)—AOP_第7张图片

2、声明环绕通知

使用环绕通知可以完成前置和后置通知所实现的相同功能,只需在一个方法中实现
Spring框架(JavaEE开发进阶Ⅲ)—AOP_第8张图片
配置文件:

	
	
		
		
		
		
	

3、为通知传递参数

3.1、有时通知不仅对方法进行简单包装(拦截),还需要校验传递给方法的参数值,此时需要为通知传递参数

    
        
        
    
示例中切入点定义和的arg-names属性为关键

3.2、切入点表示了TargetName的methodName方法,指定String参数,然后在args参数中标识将something作为参数

3.3、元素引用了something参数,标识该参数必须传递给beanName所属类型的interceptMethod方法

4、通过切面引入新功能

4.1、利用“引入introduction”这个AOP概念,切面可以为Spring Bean添加新方法
Spring框架(JavaEE开发进阶Ⅲ)—AOP_第9张图片
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 毫秒
观众鼓掌.啪啪啪
接收观众表彰!

你可能感兴趣的:(spring3框架)