`
frank-liu
  • 浏览: 1681540 次
  • 性别: 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
分享到:
评论

相关推荐

    Spring AOP面向方面编程原理:AOP概念

    ### Spring AOP面向方面编程原理:AOP概念详解 #### 一、引言 随着软件系统的日益复杂,传统的面向对象编程(OOP)逐渐暴露出难以应对某些横切关注点(cross-cutting concerns)的问题。为了解决这一挑战,面向方面编程...

    Spring基础:AOP编程(4)

    总结来说,Spring的AOP编程为我们提供了一种优雅的方式来处理系统中的横切关注点,通过切面、通知、切点和连接点等概念,我们可以将关注点分离,提高代码的可读性和可维护性。无论是基于代理还是注解,Spring AOP都...

    Spring基础:AOP编程(1)

    Spring框架是Java开发中不可或缺的一部分,它以其模块化、易用性和灵活性著称。在Spring中,面向切面...通过学习和理解AOP的概念、代理模式以及注解的使用,开发者能够更好地利用Spring框架构建高效、灵活的应用程序。

    Spring基础:AOP编程(2)

    AOP编程(2)”的文章中,我们将深入探讨Spring框架中的面向切面编程(Aspect-Oriented Programming, AOP),这是一种强大的设计模式,它允许我们分离关注点,尤其是那些横切关注点,如日志、事务管理和安全控制。...

    Spring基础:AOP编程(3)

    在Java开发中,Spring框架因其强大的功能和易用性而备受推崇,其中AOP(Aspect-Oriented Programming,面向切面编程)是其核心特性之一。AOP允许开发者将关注点从核心业务逻辑中分离出来,如日志记录、事务管理等,...

    Spring基础:Spring AOP简单使用

    1. **AOP概念** - **切面(Aspect)**:切面是关注点的模块化,比如日志、安全检查等,它将分散在代码各处的相同关注点集中在一起。 - **连接点(Join Point)**:程序执行过程中的某个特定点,如方法调用、异常抛...

    Spring_AOP_学习小结 Spring_AOP_学习小结 Spring_AOP_学习小结

    一、AOP概念 1. Joinpoint(连接点):在Spring AOP中,Joinpoint指的是程序执行的某个特定点,如方法的执行。在Spring中,Joinpoint主要指的是方法的调用。 2. Advice(通知):Advice是在特定Joinpoint上执行的...

    spring-boot aop

    1. **引入依赖**:在`pom.xml`或`build.gradle`文件中添加Spring AOP和AspectJ的依赖。对于Maven,添加以下依赖: ```xml &lt;groupId&gt;org.springframework.boot &lt;artifactId&gt;spring-boot-starter-aop ``` 2. ...

    hualinux spring 3.15:Spring AOP.pdf

    通过上述知识点,可以对Spring AOP以及AspectJ有一个全面的认识,并理解AOP在企业级应用中的重要性和应用方式。文档中提到的实践示例,例如前置通知、后置通知、返回通知、异常通知和环绕通知的具体编码实现,都是...

    spring源代码分析:aop的实现

    在编译时,AspectJ编译器会将这些注解转换为AspectJ语言的切面,然后在运行时由Spring AOP加载和应用。 总的来说,Spring的AOP机制通过动态代理技术,实现了对横切关注点的透明化处理,降低了系统的复杂度。通过对...

    spring aop 学习笔记

    本学习笔记将深入探讨Spring AOP的核心概念、工作原理以及实际应用。 1. **核心概念** - **切面(Aspect)**:切面是关注点的模块化,包含业务逻辑之外的横切关注点,如日志、事务管理。 - **连接点(Join Point...

    spring-aop-jar

    在IT领域,Spring框架是一个广泛使用的Java应用框架,它提供了许多功能,包括依赖注入、面向切面编程(AOP)等。"spring-aop-jar"这个主题涉及到Spring框架中的核心组件之一——Spring AOP。这里我们将深入探讨...

    spring ioc和aop原理流程图(详细)

    Spring 框架是Java开发中的核心框架,它主要由两个关键部分组成:IOC(Inversion of Control,控制反转)和AOP(Aspect Oriented Programming,面向切面编程)。这两个概念是Spring框架的核心特性,极大地简化了企业...

    spring 应用aop 实例

    在Spring框架中,AOP(面向切面编程)是一种强大的设计模式,它允许开发者将关注点从核心业务逻辑中分离出来,比如日志...通过运行这些测试,你可以验证AOP配置是否按照预期工作,从而加深对Spring AOP的理解和应用。

    Java核心知识体系4:AOP原理和切面应用.pdf

    【Java核心知识体系4:AOP原理和切面应用】 面向切面编程(Aspect-Oriented Programming,简称AOP)是Java开发中的一个重要概念,它旨在解决程序中的横切关注点,如日志记录、事务管理等。AOP将这些关注点从核心...

    Spring 2.5 AOP 例子

    Spring 2.5 AOP(面向切面编程)是Java应用程序中的一个重要概念,它允许开发者在不修改原有代码的情况下插入新的行为或监控。这个例子旨在帮助我们理解和应用Spring框架的AOP特性。以下是对该主题的详细解释: 一...

    spring-aop.rar_aop1270_spring_spring aop

    本文将围绕Spring AOP的源码分析,探讨其核心概念、工作原理以及在实际开发中的应用。 一、AOP核心概念 1. 切面(Aspect):切面是关注点的模块化,通常包含一组通知(advises)和一个切入点(pointcut)定义。 2...

    SPRING_AOP_概念解析以及例子示范.docx

    Spring AOP,全称Spring面向切面编程,是一种强大的设计模式,它允许程序员在不修改原有代码...在实际开发中,Spring AOP可以广泛应用于事务管理、权限控制、性能监控等多个场景,大大提高了软件的灵活性和模块化程度。

    spring aop spring aop

    在Spring AOP中,主要有以下几个核心概念: 1. **切面(Aspect)**:切面是关注点的模块化,包含pointcut(切点)和advice(通知)两部分。例如,在上述代码中的`PersonProxy`类就是一个切面,它定义了`before()`和...

Global site tag (gtag.js) - Google Analytics