`
frank-liu
  • 浏览: 1666409 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

spring学习:AOP的概念和应用

 
阅读更多

简介

    在spring的编程和应用中,有一个很重要的 概念就是面向切面编程(aspect oriented programming) 。它使得我们将一些和业务逻辑不相关但是和应用逻辑又有紧密联系的东西给分离出来了。这么说来稍微显得有点抽象。在文章中会详细解释这个概念以及它们的详细应用。

 

aop的概念引入

    为什么在前面我提到分离出业务逻辑和应用逻辑呢?用一个具体的示例来说,在我们开发的很多应用里,我们希望自己实现的方法里面仅仅是包含针对这个应用业务本身的逻辑。而在某些情况下,我们同时也会有一些其他的应用必须的限制。比如说,我们对某些方法的执行和调用需要有一个安全认证,必须是经过鉴权的用户才能执行和访问。有时候,我们需要对一些方法的执行做一些日志记录,方便以后对程序的执行情况进行跟踪和分析。还有时候我们需要对一些数据的访问设置缓存。类似的示例还要很多。这个时候,我们该怎么办呢?一种办法是在每个必要的方法里加入这个逻辑,于是我们经常看到如下代码的存在:

 

methodA() {
    if(authenticated(user)) {
         // execute business 
    } else {
        // authentication
    }
}

    也许我们这个方法只是为了增删查改某个东西。可是代码里却需要搅和进来一个和鉴权、日志等相关的东西。而且很多情况下,在多个方法里都需要重复的执行这些判断。这种方式有不少问题。首先,在很多需要这样的方法里都重复了这些代码,从设计的角度来说,有大量代码重复。其次,随着代码的演化,这种方式很容易遗漏,很可能在某些需要这些验证的地方我们却忘了。而且它们还增加了代码的复杂程度。

    这些我们要在很多方法里织入的东西其本身是和业务没有直接关系的,但是在很多地方又重复用到了。能不能把它们像我们重构代码时提取公共方法那样给提出来作为一个公共的东西呢?这就是aop所关注的地方。和我们关注的常用方法不一样,这些业务逻辑更像是一个纵向的业务层面,它和业务逻辑是一种垂直的关系。如下图:

    针对aop的理解,有几个相关的概念如下:

 

 advice

    advice: 指的是我们在这个切面要做什么以及什么时候做这个切面的事。在spring里面有5种advice:

1. before: 在每个切面修饰的方法执行前执行advice的逻辑方法。

2. after: advice的逻辑方法在切面所修饰的方法执行结束之后执行。这里不管方法是不是正常执行完都会执行advice方法。

3. after-returning: 在修饰的方法成功执行结束后执行advice方法。

4. after-throwing: 在修饰的方法执行抛异常的情况下执行advice方法。

5. around: advice包装起来修饰方法,在修饰方法执行之前以及执行之后提供一些advice方法的特性。

 

join points

join points: 切入点. 既然我们前面讲到了一个切面是要干什么的,接着就是要在什么地方来织入切面的逻辑。因为我们可以考虑织入的点很多,可以是在一个方法执行的时候, 异常被抛出的时候甚至是某个子段被改变的时候。这里就相当于定义了有哪些点可以被用来执行切面逻辑。

 

pointcuts

在前面定义的这些切入点,相当于表示可以织入切面逻辑的地方。但是我们真正程序执行的地方并不是要在上面所定义的所有可织入的地方都织入,而是根据我们的需要来织入。所以说,前面的join points相当于定义一个可以织入的集合,而我们这里的切入点表示真正程序中使用到了指定切入的点。

 

aspect

aspect:也就是我们这里提到的切面。它其实是一个advice和pointcuts的组合。因为advice表示了要做什么和什么时候做。而pointcuts则表示了在哪里做。这样它们就定义了一个完整切面的所有内容。你看,时间地点人物事件齐全,典型的完整小学生作文结构啊:)

 

weaving

weaving(织入)。这个表示我们应用切面到目标对象以产生一个新的代理对象的过程。因为我们使用java编程语言的时候都知道。在我们定义好对象以及方法之后,它们就会被编译成字节码,然后再通过jit来解释执行。而默认的方式就是我们对象和方法里定义的行为是什么程序就是怎么执行的。如果我们要把这些切面的功能给加入进去,就然要使用一些手段。常用的手段如下:

编译时:在编译的时候把切面的逻辑给加进去,通常这需要特殊的编译器。aspectJ的weaving编译器就是通过这种方式实现织入的。

类装载时: 因为我们的程序是在被编译成class文件后再通过classloader装载进虚拟机执行的,如果有特殊的类装载器在这个过程中把织入的逻辑加载进来也是可能的。aspectJ的装载时织入就支持这种方式。

运行时:还要一种情况就是在程序运行的时候,通过aop的容器来动态的生成一系列的代理对象,在这些代理对象里将切面的逻辑织入。这种做法是spring aop所采用的方式,其实现方式如下图:

pointcut的定义

    在前面我已经提到过,pointcut是程序中切入的点。它可以在程序的方法、成员变量等地方产生。比如方法调用执行之前或者之后,也可以在一个变量变化的时候或者对象创建的时候。所以,既然有这么多中情形,我们就需要一个精确的方式来定义它们。比如该怎么来描述我们织入的点是在某个方法执行之前呢?如果这个方法有参数和返回值呢?这些都是应该要考虑的地方。因为目前spring只支持对方法的执行进行切入,所以主要支持的表达式都是和方法相关的。

    spring里对pointcut的定义主要是使用aspectJ里的pointcut表达式,最常用的一些表达式如下表:

 

表达式 描述
args() 限定切入点匹配方法的参数必须是指定的类型。
@args() 限定切入点匹配方法的参数必须用指定的参数标注。
execution() 指定匹配的切入点为方法。
this() 指定aop proxy所引用的bean的类型。
target() 指定切入点匹配的对象类型。
@target() 指定切入点匹配的类必须由指定的类型标注。
within() 限制切入点在某些类型范围内。
@within() 限定切入点的类型必须有指定的类型标注。
@annotation() 限定切入点必须匹配那些有特定标注的对象。

 

    为了理解前面pointcut表达式所表达的意思,我们可以结合一个示例来说明。假设我们有一个如下定义的接口:

package com.yunzero;

public interface Performance {
	public void perform();
}

 

 我们有一个如下的表达式:

execution(** com.yunzero.Performance.perform(..))

   我们从左到右一部分一部分的来看它们的定义。其中execution表示该切入点定义为一个方法的执行。所以我们后续定义的切面逻辑是针对某个方法的某个阶段来切入执行的。

    后面的两个**表示返回的类型,而这里指的是返回的类型为任意类型。

    后面的com.yunzero.Performance则是指的方法所在的类。perform则是方法名。

    顾名思义,后面括号里以及所带的..表示方法所带的参数。这里表示可以是任何参数。所以,前面表达式可以匹配的是com.yunzero.Performance.perform()方法。而且如果有多个同名重载的方法的话,它们是都可以匹配的,因为这里匹配的是任何参数以及任何返回类型。

  另外,如果我们需要限制切入点匹配的范围,比如限定在某个包的范围内,我们可以采用如下的方式:

execution(** com.yunzero.Performance.perform(..)) && within(com.yunzero.*)

   这样的话,切入点就仅仅匹配com.yunzero这个包下面的所有Performance.perform方法了。

    这样,我们就知道了怎么来定义切入点了。接着需要的就是定义aspect了。它决定了在什么时候来切入,以及每个切入的地方该做什么。

 

aspect的定义

    下面是我们定义的一个 aspect:

@Aspect
public class Audience {

	@Pointcut("execution(** com.yunzero.Performance.perform(..))")
	public void performance() {}

	@Before("performance()")
	public void silenceCellPhones() {
		System.out.println("Silencing cell phones");
	}
	
	@Before("performance()")
	public void takeSeats() {
		System.out.println("Taking seats.");
	}
	
	@AfterReturning("performance()")
	public void applause() {
		System.out.println("CLAP CLAP CLAP");
	}
	
	@AfterThrowing("performance()")
	public void demandRefund() {
		System.out.println("Demanding a refund");
	}
}

    在这部分代码里,我们用@Aspect来修饰这个类。然后用到了@Before, @After等标注。其中 @Pointcut里面就定义了一个我们熟悉的切入点表达式。它被关联到一个空的performance方法上。因为它仅仅是作为一个可以被其他几个切片重用的定义,这个performance方法必须为空。而我们定义的几个@Before,@After等修饰的方法,可以将它们当做普通的bean方法来使用。它们的意思也很直白,就是表示在某个定义的切入点执行之前或者之后所要做的事情。这几个最常用的标注的描述如下表:

annotation 描述
@After 切面方法在所织入的方法执行之后执行。不管方法是正常执行结束还是抛异常。
@AfterReturning 切面方法在织入方法正常退出后执行。
@AfterThrowing 切面方法在织入方法抛异常后执行。
@Around 切面方法包装织入方法,它可以在方法的执行前以及执行后定义自己的逻辑。
@Before 切面方法在织入方法执行前执行。

    在这一步定义好切面以及切入点之后,并不能让切面方法自动织入到目标方法。实际上我们还需要做一些配置或者设定。这和使用spring创建对象的方式很类似,可以使用javaconfig的方式,也可以使用xml配置的方式。两种方式的配置分别如下:

java config:

 

package com.yunzero;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@Configuration
@EnableAspectJAutoProxy
@ComponentScan
public class ConcertConfig {
	@Bean
	public Audience audience() {
		return new Audience();
	}
}

    这里就是创建了一个Audience对象。里面唯一的差别就是加入了@EnableAspectJAutoProxy这个标注。

 

xml:

 

<?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: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/aop
	http://www.springframework.org/schema/aop/spring-aop.xsd
	http://www.springframework.org/schema/context
	http://www.springframework.org/schema/context/spring-context.xsd">

	<context:component-scan base-package="com.yunzero"/>
	
	<aop:aspectj-autoproxy/>
	
	<bean class="com.yunzero.Audience"/>

</beans>

    在配置文件的方式里,我们需要定义一个前面指定的aspect类作为bean实例。

 

完整拼图

    在前面的描述里,我们已经给定了一个作为切入目标的接口Performance,以及一个切面定义类Audience。那么剩下的就是让这个切面的方法在Performance的示例执行的时候起作用。于是我们剩下的就是需要定义一个Performance的实现,然后通过在spring里创建这个示例并执行它的perform方法。

    现在我们有一个Performance的实现如下:

 

package com.yunzero;

import org.springframework.stereotype.Component;

@Component
public class OperaPerformance implements Performance {

	@Override
	public void perform() {
		System.out.println("Performance in opera!");
	}

}

    示例程序的启动方法如下:

    

package com.yunzero;

import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;


public class App 
{
    public static void main( String[] args )
    {
        AbstractApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
        Performance p = ctx.getBean(Performance.class);
        p.perform();
        ctx.close();
    }
}

     这个时候运行程序,我们将看到如下的输出:

 

Silencing cell phones
Taking seats.
Performance in opera!
CLAP CLAP CLAP

    所有详细的配置和实现可以参考附件里的完整工程。

 

Introduction

    aop还要一个强大的能力就是introduction。对于这个introduction的理解和我们常用的简介不一样。我们前面使用aop的功能,更多的时候只是在原有类型和方法的基础上做一些修饰和增强。而这里introduction则相当于是新功能的添加。

     该怎么来理解这个introduction呢?在java语言里,如果我们已经定义好了某些接口和它们的实现类,这个时候它们的功能是固定的。如果我们需要给它们增加一些新的功能的时候,我们可能就需要修改原来的接口和方法。这种方式会带来一些代码的变动,在工程中甚至会很困难。而一些其他的语言如C#, ruby等有自己的特性,可以动态定制和增加自己的新特性而不用修改原来的代码。像C++里也有自己的mixin特性。于是spring aop也对这种方式做了一个增强。它的实现过程如下图:

 

 

    所以,这里introduction可以理解为新特性的引入。 这种新特性引入该怎么来实现呢?我们看一个具体的示例。假设我们又定义了一个新的接口:

package com.yunzero;

public interface Encoreable {

	void performEncore();
}

 

    同时它有一个默认的实现:

 

package com.yunzero;

public class DefaultEncoreable implements Encoreable {

	@Override
	public void performEncore() {
		System.out.println("Default encoreable impl");
	}

}

   现在是我们希望在原有的Performance里面能够引入这个接口以及它实现的功能。那么该怎么做呢?

    这个时候我们需要再定义一个切面:

package com.yunzero;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.DeclareParents;

@Aspect
public class EncoreableIntroducer {

	@DeclareParents(value="com.yunzero.Performance+", 
			defaultImpl=DefaultEncoreable.class)
	public static Encoreable encoreable;
}

    这里和前面切面不一样的地方是它没有定义切入点以及切入的逻辑。仅仅是使用了一个@DeclareParents的标注,并设定了value, defaultImpl属性。它相当于将所描述的Encoreable接口作为一个Performance的子类。然后这个接口的默认实现也定义好。这样spring aop就会在后面运行的时候自动生成proxy类将它们给包装起来。

    定义好这部分之后,我们还需要在配置文件里设置这个切面的bean,其定义如下:

<bean class="com.yunzero.EncoreableIntroducer"/>

 现在,我们在应用程序的代码里稍微做一点修改:

package com.yunzero;

import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;


public class App 
{
    public static void main( String[] args )
    {
        AbstractApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
        Performance p = ctx.getBean(Performance.class);
        p.perform();
        ((Encoreable)p).performEncore();
        ctx.close();
    }
}

     这一点变化仅仅在((Encoreable)p).performEncore();这一句。很神奇的是,我们居然可以在这里将Performance接口的实例转换成Encoreable类型的实例了。前面代码运行的结果如下:

 

Silencing cell phones
Taking seats.
Performance in opera!
CLAP CLAP CLAP
Default encoreable impl

     在一些我们不方便修改原来源代码的情况下,采用这种方式来增强原有的特性也不失为一个可选项。

 

总结

    Spring aop是一个可以分离不同关注点,保证我们系统业务逻辑更清晰的特性。它除了最常用的对一些切面进行定义和对切入点进行织入之外。也提供一些对现有功能进行特性增强的选项。另外,对于切入点的定义以及描述因为本身java语言里面并没有提供默认的支持,它相当于使用了一种特定的语言来描述它们。总的来说,功能很强大,不过过程还是比较繁琐。

 

 

参考材料

spring in action

spring in practice

  • 大小: 23.6 KB
  • 大小: 25.6 KB
  • 大小: 49.7 KB
分享到:
评论

相关推荐

    开发者突击·精通AOP整合应用开发 源码

    《开发者突击:精通AOP整合应用开发(AspectWerkz+Aspectl+Spring)》以AOP基础理论为主线,首先讲解AOP的产生与发展、为什么要应用AOP、AOP的核心概念,然后再详细讲解AspectWerkz、AspectJ、Spdng框架的AOP应用开发...

    开发者突击 精通AOP整合应用开发AspectWerkz+AspectJ+Spring.zip

    本书以AOP基础理论为主线,首先讲解AOP的产生与发展、为什么要应用AOP、AOP的核心概念,然后详细讲解AspectWerkz、AspectJ、Spring框架的AOP应用开发技术。 随书附赠的光盘内容为本书开发的案例程序包。本书内容循序...

    Spring - 3 AOP

    Spring 自制教程,主要包括Spring的引入、IoC概念、AOP概念、以及Spring在Web开发中的应用,共分为4个文件

    25个经典的Spring面试问答

    25个经典的Spring面试问答,Spring...Spring AOP与AspectJ:理解Spring AOP的概念,以及如何使用AspectJ实现AOP。 Spring与其它技术集成:了解Spring与其它技术的集成,例如与JPA、Hibernate、Thymeleaf等的集成。 S

    Spring AOP实验

    1、了解AOP的概念和作用; 2、理解AOP中的相关术语; 3、了解Spring中两种动态代理方式的区别; 4、掌握基于XML和注解的AspectJ开发。 二、 实验内容 1、按图所示的类图结构,设计接口及其实现类,并完成另外两附加...

    开发者突击 精通AOP整合应用开发AspectWerkz+AspectJ+Spring.z01

    本书以AOP基础理论为主线,首先讲解AOP的产生与发展、为什么要应用AOP、AOP的核心概念,然后详细讲解AspectWerkz、AspectJ、Spring框架的AOP应用开发技术。 随书附赠的光盘内容为本书开发的案例程序包。本书内容循序...

    让aop概念更好的理解

    可以让你更好的理解spring下AOP的概念,分布式概念以及应用

    Spring+3.x企业应用开发实战光盘源码(全)

     第12章:讲解了如何在Spring中集成Hibernate、myBatis等数据访问框架,同时,读者还将学习到ORM框架的混用和DAO层设计的知识。  第13章:本章重点对在Spring中如何使用Quartz进行任务调度进行了讲解,同时还涉及...

    SpringAOP的实现机制(底层原理)、应用场景等详解,模拟过程的实例

    通过学习它们的原理和实际应用,您将能够更好地理解和利用Spring AOP来提高您的应用程序的可维护性和可扩展性。 内容亮点: JDK动态代理: 我们将详细介绍JDK动态代理的概念和工作原理。您将了解如何使用Java的...

    进击的编程思想!带你学Spring AOP核心编程思想教程 新角度学面向切面编程

    何谓Spring AOP? Spring AOP 是基于 AOP 编程模式的一个框架,它的...课程从Spring AOP概念开始引入,通过Spring AOP代理和判断模式进行,宝库各种模式,不断的深入学习,相信会给同学们带来不一样的Spring AOP技术

    spring基础进阶技巧200篇.zip

    《Spring基础进阶技巧200篇》是一本关于Spring框架的资源,它提供了200篇文章,涵盖了Spring框架的基础知识和...这本资源适合已经具备一定Spring基础知识的开发人员,希望进一步深入学习和应用Spring框架的技术人员。

    Apring AOP

    Spring AOP:概念、应用场景、实现、切面、通知、目标类、目标方法、AOP代理、代理对象的方法、连接点、说明、扩展

    SSM框架的学习与应用-Java EE企业级应用开发学习记录-(第六天)初识Spring框架

    本资源是一篇关于学习和应用SSM框架(Spring、SpringMVC、MyBatis)的学习记录系列文章中的第六天内容,主题为初识Spring框架。 在这一部分中,你将了解到Spring框架的基本概念和使用方法,并通过实例探讨了Spring ...

    三层架构设计模式MVC和AOP面向切面编程-SSM框架的学习与应用-Java EE企业级应用开发学习记录(第九天)

    本资源将深入探讨软件开发中两个关键的设计和编程概念:三层架构设计模式(MVC)和...AOP的实际应用: 我们将提供实际的示例和案例,展示如何使用AOP来改善代码的可维护性和可扩展性。您将了解如何在Spring框架等流行

    Spring开发指南

    Spring初探 准备工作 构建Spring基础代码 Spring 基础语义 Dependency Injection ... AOP 概念 AOP in Spring Dynamic Proxy 与Spring AOP CGLib 与 Spring AOP AOP 应用 DAO Support Remoting

    javaSpring学习笔记

    这份学习笔记提供了详细而系统的教程和实践指南,帮助初学者快速入门,并带领已经有一定经验的开发者深入理解和应用Spring框架的各种功能和特性。 在“Java Spring学习笔记”中,你将找到对Spring框架的全面介绍,...

    spring课堂笔记.docx

    "spring课堂笔记.docx" 是一份关于 Spring 框架的课堂笔记,它提供了对 Spring 框架的深入理解和学习资源。以下是对这份课堂笔记的大致描述: "spring课堂笔记.docx" 是一份详尽的学习材料,旨在帮助开发者掌握 ...

    Spring的学习笔记

    一、 AOP概念 19 二、 利用动态代理实现面向切面编程 20 第八课:Spring AOP配置选项 21 一、 AOP配置annotation方式 21 (一) 搭建annotation开发环境 21 (二) aspectJ类库 22 (三) AOP的annotation实例 22 (四) ...

    javaSpring-经典概念题-试题-中文

    从基础概念如Spring IoC容器和Spring Bean的定义,到高级主题如依赖注入和AOP的实现,以及Web开发方面的Spring MVC框架和表单数据处理等内容都有涉及。此外,还包括了Spring事务管理和Spring Boot框架的介绍,帮助...

Global site tag (gtag.js) - Google Analytics