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

一、什么是面向切面编程

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、示例:选秀节目的观众类

我们把选秀节目表演作为核心业务逻辑,设置切面
前置通知:观众入座,关闭手机
返回通知:观众鼓掌
异常通知:观众要求退票
<bean id="audience" class="com.javaee.spring.chineseIdol.Audience" />

1、声明前置和后置通知

<aop:config>
    <aop:aspect ref="audience">
        <aop:before pointcut="execution(* com.javaee.spring.chineseIdol.Performer.perform(..))" method="takeSeats"/>
        <aop:before pointcut="execution(* com.javaee.spring.chineseIdol.Performer.perform(..))" method="turnOffCellPhones"/>
        <aop:after-returning pointcut="execution(* com.javaee.spring.chineseIdol.Performer.perform(..))" method="applaud"/>
        <aop:after-throwing pointcut="execution(* com.javaee.spring.chineseIdol.Performer.perform(..))" method="demandRefund"/>
    </aop:aspect>
</aop:config>
简化上面的配置,单独定义切入点:
<aop:config>
    <aop:aspect ref="audience">
        <aop:pointcut id="performance" expression="execution(* com.javaee.spring.chineseIdol.Performer.perform(..))"/>
        
        <aop:before pointcut-ref="performance" method="takeSeats"/>
        <aop:before pointcut-ref="performance" method="turnOffCellPhones"/>
        <aop:after-returning pointcut-ref="performance" method="applaud"/>
        <aop:after-throwing pointcut-ref="performance" method="demandRefund"/>
    </aop:aspect>
</aop:config>
业务逻辑和通知逻辑:
业务逻辑是OOP的,通知逻辑是AOP的
          业务逻辑                                     切面                                                       通知逻辑

2、声明环绕通知

使用环绕通知可以完成前置和后置通知所实现的相同功能,只需在一个方法中实现

配置文件:
<aop:config>
	<!-- 引用audience这个Bean -->
	<aop:aspect ref="audience">
		<!-- 切入点 -->
		<aop:pointcut id="performance2" expression="execution(* com.javaee.spring.chineseIdol.Performer.perform(..))"/>
		<!-- 环绕通知 -->
		<aop:around pointcut-ref="performance2" method="watchPerformance"/>
	</aop:aspect>
</aop:config>

3、为通知传递参数

3.1、有时通知不仅对方法进行简单包装(拦截),还需要校验传递给方法的参数值,此时需要为通知传递参数
<aop:config>
    <aop:aspect ref="beanName">
        <aop:pointcut id="pcID" expression="execution(* com.javaee.spring.chineseIdol.TargetName.methodName(String)) and args(something)" />
        <aop:before pointcut-ref="pcID" method="interceptMethod" arg-names="something" />
    </aop:aspect>
</aop:config>
示例中切入点定义和<aop:before>的arg-names属性为关键

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

3.3、<aop:before>元素引用了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引入,可以不需要为设计妥协或侵入性地改变现有的实现,只需使用<aop:declare-parents>元素
<aop:aspect>
    <aop:declare-parents
     types-matching="com.javaee.spring.chineseIdol.Performer+"
     implement-interface="com.javaee.spring.chineseIdol.Contestant"
     default-impl="com.javaee.spring.chineseIdol.GeneralContestant"
     />
</aop:aspect>
types-matching:匹配现有的接口,匹配到接口的所有实现者
implement-interface:引入一个新的接口
default-impl:新接口的实现类

4.6、<aop:declare-parents>声明了此切面所通知的Bean在它的对象层次结构中拥有的父类型

4.7、示例中类型匹配Performer接口(由types-matching属性指定)的那些Bean会实现Contestant接口(由implement-interface属性指定)

4.8、有两种方式标识所引入接口的实现
1)使用default-impl属性通过完全限定类名显式指定Contestant的实现
2)使用delegate-ref属性,引用一个Spring Bean作为引入的委托
<aop:declare-parents 
    types-matching="com.javaee.spring.chineseIdol.Performer+" 
    implement-interface="com.javaee.spring.chineseIdol.Contestant" 
    delegate-ref="contestantDelegate" 
    />
注册这个类:
<bean id="contestantDelegate" class="com.javaee.spring.chineseIdol.GeneralContestant"/>

四、例子程序

下载地址:https://pan.baidu.com/s/1c3STMW4
部分代码
chinese-Idol.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:p="http://www.springframework.org/schema/p"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    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/context
        http://www.springframework.org/schema/context/spring-context-3.0.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
        ">  <!-- 引入aop空间 -->

    <!-- Bean declarations go here -->
	<bean id="audience" class="com.javaee.spring.chineseIdol.aop.Audience"/>
	
	<bean id="performer" class="com.javaee.spring.chineseIdol.aop.Juggler"/>
	
	<bean id="geberal" class="com.javaee.spring.chineseIdol.aop.GeneralContestant"/>
	
	<aop:config>
		<aop:aspect ref="audience">
			<!-- 定义切入点 -->
			<aop:pointcut expression="execution(* com.javaee.spring.chineseIdol.aop.Performer.perform(..))" id="performance"/>
			<!-- 定义通知 -->
			<aop:before pointcut-ref="performance" method="takeSeats"/>
			<aop:before pointcut-ref="performance" method="turnOffCellPhones"/>
			<aop:after-returning pointcut-ref="performance" method="applaud"/>
			<aop:after-throwing pointcut-ref="performance" method="demandRefund"/>
		</aop:aspect>
		<!-- 引入一个新功能 -->
		<aop:aspect>
			<!-- +号表示匹配的所有实现了Perform接口的类型 -->
			<aop:declare-parents types-matching="com.javaee.spring.chineseIdol.aop.Performer+" 
			implement-interface="com.javaee.spring.chineseIdol.aop.Contestant" 
			delegate-ref="geberal"/>
		</aop:aspect>
		<!-- 定义环绕通知 -->
		<aop:aspect ref="audience">
			<aop:pointcut expression="execution(* com.javaee.spring.chineseIdol.aop.Performer.perform(..))" id="performance2"/>
			<aop:around pointcut-ref="performance2" method="watchPerformance"/>
		</aop:aspect>
	</aop:config>
	
</beans>
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 毫秒
观众鼓掌.啪啪啪
接收观众表彰!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值