`
wangyanlong0107
  • 浏览: 499722 次
  • 性别: Icon_minigender_1
  • 来自: 沈阳
社区版块
存档分类
最新评论

【转】Spring in Action 4th 学习笔记 之 AOP

 
阅读更多

http://www.cnblogs.com/larryzeal/tag/spring/

先说说为什么需要AOP

 

最简单的一个例子就是日志记录,如果想记录一些方法的执行情况,最笨的办法就是修改每一个需要记录的方法。但这,真的很笨。。。

好的方法,应该是通过反射获取方法,然后去匹配,如果需要记录日志,那就调用日志方法即可。

这就是AOP 的Weaving,俗称编织、织入,就是将需要添加的功能编织到现有功能中,而不需要修改现有代码。

 

另一个例子,不那么大众的需求:我想给一个对象添加方法,怎么实现?

如果有学过js、Python等动态语言,你肯定知道它们支持给对象添加方法,直接添加即可。

但是Java不行,因为Java的类型是封闭的。

Spring给出办法就是通过代理,拦截请求,然后去调用实际拥有该方法的对象的该方法!(略绕) 这就是Introduction,俗称引入。

如图:

这是书中自带的图片,很形象。

如图所示,如果调用Advised bean的Existing method,那就是Weaving(织入);如果调用introduced method,那就是Introduction。

但是,无论那种,Spring都是通过其代理功能实现的。(如果你已经知道Spring的代理功能仅限于method,那你也可以想到Spring AOP仅限于method --- 稍后讨论)

 

以上,记住一点就行:Spring AOP中给方法添加功能就是织入,给对象添加功能就是引入

(至于为什么强调是Spring AOP,这是因为还有其他的AOP框架,稍后讨论。)

 

再列一下其他概念:

复制代码
Weaving织入部分:

  Advice : 需要添加的功能,例如日志功能、权限功能等,以及什么时候添加(目标方法执行前、后、异常等时候)。

  Join-point : 目标类中能够添加功能的地方!

  Pointcut : 指定目标类中添加功能的地方!因为不可能给所有Join-point织入Advice!(Spring AOP仅限于方法,因为它基于代理实现。其他的框架还可以针对字段添加功能!了解就行。)

  需要注意的是,Advice定义了什么时候做、做什么,而Pointcut则定义了在哪里做。

  Aspect = Advices + Pointcuts     // Aspect可以认为是一堆Advice的类,且其中指定了每个Advice执行的Pointcut。
复制代码

 

Introduction引入部分:

  暂无

 

以上,Pointcut是关键,它决定了一个AOP框架的行为。

因为Pointcut意味着where(field?method?exception?)和when(编译时?加载时?运行时?)。

【】通常,使用class和method name来定义Pointcut,或者使用正则表达式匹配class和method name来定义Pointcut!!!

 

 

Weaving应用部分

Spring AOP和AspectJ有很多协同。Spring AOP借鉴了AspectJ很多理念。

复制代码
Spring对AOP的支持有四种形式:
    ① 经典的Spring基于代理的AOP。
    ② 纯POJO aspect。
    ③ @AspectJ注解驱动的aspect。
    ④ 注入的AspectJ aspect。
以上,前三种是Spring自有AOP的变体,由于都是基于代理,所以,仅限于方法拦截!!!
复制代码

Spring AOP引用了AspectJ EL。

AspectJ EL表达式:核心就是execution,其他的都是用于限制各种参数的。【】【】

例如:

execution(* concert.Performance.perform(..)) && within(concert.*)     // 这里就定义了一个pointcut,而且仅限于被concert包下的aspect使用。   

上面的AspectJ EL是由两部分组成:execution定义切入点,within限定切入点。见下图:

 

 

 

上面,可以使用&&或and、||或or、!或not。 类似EL或JSTL

Spring还增加一个bean(),意思是仅限于该bean的Pointcut。    

例如:execution(* concert.Performance.perform()) and bean('woodstock')    这里就定义了一个woodstock的pointcut。
例如:execution(* concert.Performance.perform()) and !bean('woodstock')    注意这里!!!很有意思的用法。

 

AspectJ 注解开发:

AspectJ 从 5 开始引入了注解开发,Spring AOP同样引入AspectJ的注解。
但是,Spring AOP仅仅是利用AspectJ的注解名词,底层仍然是Spring AOP的代理实现。

 

注解开发过程:

@Aspect注解到aspect所在的类上,然后@Before等注解到advice(aspect对应的方法)上。如下:

复制代码
@Component  // 这个是必须的!!
@Aspect
public class Audience {
    @Before("execution(** concert.Performance.perform(..))")    // 该注解声明了silenceCellPhones()需要应用到的Pointcut。
    public void silenceCellPhones() {
        System.out.println("Silencing cell phones");
    }
    @Before("execution(** concert.Performance.perform(..))")
    public void takeSeats() {
        System.out.println("Taking seats");
    }
    @AfterReturning("execution(** concert.Performance.perform(..))")
    public void applause() {
        System.out.println("CLAP CLAP CLAP!!!");
    }
    @AfterThrowing("execution(** concert.Performance.perform(..))")
    public void demandRefund() {
        System.out.println("Demanding a refund");
    }
}
复制代码

但是,上面这种写法很不方便,因为Pointcut是重复的。

解决办法:使用@Pointcut一次性定义好一个Pointcut。如下:

复制代码
@Component  // 这个是必须的!!!
@Aspect
public class Audience {
    @Pointcut("execution(** concert.Performance.perform(..))")
    public void perform(){};    // 必须要定义一个方法,用于承载pointcut!

    // 其他的正常代码,略
}    
复制代码

 但是,到目前为止,AOP仍然是无法执行的,因为Spring AOP不知道这些注解代表什么,所以需要先开启AspectJ自动代理。

开启方法:@EnableAspectJAutoProxy注解到JavaConfig上面。或者,如果使用XML,<aop:aspectj-autoproxy> 。注意导入名称空间。

 

现在,上面的内容可以直接进行测试了:

复制代码
package aop.performance;

/**
 * 用这个演示join-point和pointcut。
 * perform()就是join-point!
 * 
 * @author Larry
 */
public interface Performance {
    void perform();
}
复制代码
复制代码
package aop.performance;
import org.springframework.stereotype.Component;

@Component
public class PerformanceImpl implements Performance{
    @Override
    public void perform() {
        System.out.println(this.getClass()+"正在演出~~~");
    }
}
复制代码
复制代码
package aop;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

/**
 * 用Audience类来掩饰AspectJ 5的注解用法。
 * 
 * @author Larry
 *
 */
@Component
@Aspect
public class Audience {
    @Before("execution(** aop.performance.Performance.perform(..))")
    public void takeSeat() {
        System.out.println("演出之前要入座~");
    }

    @Before("execution(** aop.performance.Performance.perform(..))")
    public void silenceCellPhones() {
        System.out.println("演出之前要静音~");
    }

    @After("execution(** aop.performance.Performance.perform(..))")
    public void applause() {
        System.out.println("演出之后要鼓掌!");
    }
    
    // TODO: 貌似不能这样用??而且会导致大BUG!!!阻止访问Pointcut!!!见下面
    //@Around("execution(** aop.performance.Performance.perform(..))")
    public void greet() {
        System.out.println("演出前后要致意~");    
    }

    @AfterReturning("execution(** aop.performance.Performance.perform(..))")
    public void leave() {
        System.out.println("结束后,goodbye~");
    }

    @AfterThrowing("execution(** aop.performance.Performance.perform(..))")
    public void demandRefund(){
        System.out.println("退钱!!!");
    }

    //上面,不好的地方是每次都要写相同的pointcut!解决办法如下:
    @Pointcut("execution(** aop.performance.Performance.perform(..))")
    public void perform(){}
    // 这样就定义了一个pointcut:performance(),然后就可以直接使用了!如下:
    @Before("perform()")
    public void wave(){
        System.out.println("挥挥手~");
    }
    
    // TODO: 务必注意,@Around必须手动调用Pointcut,否则会阻止对Pointcut的访问!!!
    @Around("perform()")
    public void greet2(ProceedingJoinPoint jp) {
        try {
            System.out.println("演出前后要致意~A");    
            jp.proceed();//TODO:这里还可以调用带参数的!
            System.out.println("演出前后要致意~B");    
        } catch (Throwable e) {
            e.printStackTrace();
        }
    }
}
复制代码
复制代码
package aop;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import aop.performance.PerformanceImpl;

@Configuration
@ComponentScan(basePackageClasses={ Audience.class,PerformanceImpl.class,AudienceB.class,IntroductionEncoreable.class })
@EnableAspectJAutoProxy    //激活AspectJ
public class JavaConfig {
    
}
复制代码
复制代码
package aop.test;

import java.util.Arrays;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.core.env.Environment;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import aop.JavaConfig;
import aop.performance.Performance;

@RunWith(SpringJUnit4ClassRunner.class)      
@ContextConfiguration(classes = { JavaConfig.class })
public class PerformanceAOPTest {
    @Autowired
    Environment env;
    @Autowired
    ApplicationContext ac;

    @Autowired
    Performance p;

    @Test
    public void run() {
        String[] activeProfiles = env.getActiveProfiles();
        System.out.println("activeProfiles的长度"+activeProfiles.length);
        for (String string : activeProfiles) {
            System.out.println("activeProfiles:" + string);
        }

        System.out.println("-------------------------------------");

        String applicationName = ac.getApplicationName();
        System.out.println("applicationName:"+applicationName);
        String[] beanDefinitionNames = ac.getBeanDefinitionNames();
        String beans = Arrays.toString(beanDefinitionNames);
        System.out.println("applicationContext中的beans:"+beans);

    }

    @Test
    public void run1() {
        p.perform();    // 注意有没有激活AspectJ!
    }
}
复制代码

上面的代码就是一个测试的全过程,其中遇到的一个问题就是环绕通知@Around,这个注解要求必须手动调用Pointcut(方法),否则Spring代理会丢失该方法!

丢失该方法,就意味着后面的代理无法继续!!!(类似拦截器拦截请求,拦截之后还要手动放行,否则后面的程序无法接收到该请求,也就是 丢失请求!)

需要注意的是,还可以多次调用该方法!!!应用场景:异常后重新执行。

复制代码
@Around("performance()")
public void watchPerformance(ProceedingJoinPoint jp) {
    try {
        System.out.println("Silencing cell phones");   // 相当于@Before
        System.out.println("Taking seats");  // 相当于@Before
        jp.proceed();    // 【】【】这个,就是调用pointcut。可能忘记调用,也可能重复调用。。。
        System.out.println("CLAP CLAP CLAP!!!");  // 相当于@After 【奇怪,那@AfterReturning呢】
    } catch (Throwable e) {
        System.out.println("Demanding a refund");   // 相当于@AfterThrowing
    }
}
复制代码

 

到目前为止,介绍的都是无参数的Pointcut(是指Advice不使用Pointcut的参数),下面开始带参数的Pointcut。

带参数的Pointcut(Advice使用Pointcut的参数)

// 样式
execution(* soundsystem.CompactDisc.playTrack(int)) && args(trackNumber)

注意,需要在Pointcut中给定参数类型,以及形参名。然后,再给Advice添加相同的形参即可(类型和形参名)。如下:

复制代码
/*
    注意,这里实现的功能是统计trackNumber的播放次数!
*/
@Aspect
@Component
public class TrackCounter {
    private Map<Integer, Integer> trackCounts = new HashMap<Integer, Integer>();

    // 定义Pointcut
    @Pointcut("execution(* soundsystem.CompactDisc.playTrack(int)) && args(trackNumber)")
    public void trackPlayed(int trackNumber) {}
    
 // Advice 
    @Before("trackPlayed(trackNumber)")        // trackNumber就是pointcut方法的形参名!!!
    public void countTrack(int trackNumber) {
        int currentCount = getPlayCount(trackNumber);
        trackCounts.put(trackNumber, currentCount + 1);
    }
    
 // 普通的方法 
    public int getPlayCount(int trackNumber) {
        return trackCounts.containsKey(trackNumber) ? trackCounts.get(trackNumber) : 0;
    }
}
复制代码

 

 

Introduction应用部分

Introduction就是给对象(Bean)引入需要的功能,而不修改原有代码。(例如你拿不到源代码的情况~)

Spring AOP的实现方法就是拦截请求,再转而调用实现了所需方法的对象即可。

示例:

  现在需要给Performance引入一个performEncore功能(再来一个、加演、额外演出 的意思)。

  根据Spring AOP的原理,我们需要一个拥有该方法的Bean,所以我们先定义一个接口,再去实现它。

  

复制代码
package aop.performance;

/**
 * Encore,加演。延长演出的意思。
 * @author Larry
 *
 */
public interface Encoreable {
    void performEncore();
}
复制代码
复制代码
package aop.performance;

import org.springframework.stereotype.Component;

@Component
public class EncoreableImpl implements Encoreable{
    @Override
    public void performEncore() {
        System.out.println("加演一场~~~");
    }
}
复制代码
复制代码
package aop;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.DeclareParents;
import org.springframework.stereotype.Component;
import aop.performance.Encoreable;
import aop.performance.EncoreableImpl;

/**
 * AOP应用之Introduction,就是给对象(bean)添加功能,类似js之类的动态语言给对象添加方法。。
 * @author Larry
 *
 */
@Component
@Aspect
public class IntroductionEncoreable {
    
    @DeclareParents(value="aop.performance.Performance+",defaultImpl=EncoreableImpl.class)   // 稍后讲
    public static Encoreable encoreable; // 先引入需要引入的方法所在的接口
}
复制代码
复制代码
package aop;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

import aop.performance.PerformanceImpl;

@Configuration
@ComponentScan(basePackageClasses={ PerformanceImpl.class,IntroductionEncoreable.class })
@EnableAspectJAutoProxy    //激活AspectJ
public class JavaConfig {
    
}
复制代码
复制代码
package IntroductionEncoreable;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import aop.JavaConfig;
import aop.performance.Encoreable;
import aop.performance.Performance;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes={JavaConfig.class})
public class IntroductionAOPTest {
    @Autowired
    ApplicationContext ac;
    @Autowired
    Performance p;

    @Test
    public void run1(){
        ((Encoreable)p).performEncore(); // 通过类型强转调用Introduction的方法!!!
    }
}
复制代码

上面就是测试Introduction的全部代码。

需要注意两点:

  ① @DeclareParents Field,其value为Pointcut所在的类(这里是接口,+表示其所有实现类或子类),defaultImpl则是接口的默认实现类,而Field则是所需方法所在的接口。

  ② 通过类型强转,将目标Bean转成@DeclareParents Field类型,再去调用方法!

 

 

最后,XML中配置Weaving织入,懒得弄了,直接上图吧

 

在XML中,一样可以定义Pointcut,然后在其他地方引用:

复制代码
<aop:config>
    <aop:aspect ref="audience">
        <aop:pointcut id="performance" expression="execution(** aop.performance.Performance.perform(..))" />
        <aop:before pointcut-ref="performance"  method="silenceCellPhones"/>
        <aop:before pointcut-ref="performance" method="takeSeats"/>
        <aop:after-returning pointcut-ref="performance" method="applause"/>
        <aop:after-throwing pointcut-ref="performance" method="demandRefund"/>
    </aop:aspect>
</aop:config>   
复制代码

XML配置和注解配置类似,唯一需要注意的是环绕通知@Around,还是需要指定一个方法,该方法接收ProceedingJoinPoint对象。

就是说,实际上同@Aspect Class的@Around Method一样,只不过现在去掉@Aspect和@Around,改为XML配置。

复制代码
package aop;

import org.aspectj.lang.ProceedingJoinPoint;

public class Audience {
    
    public void greet3(ProceedingJoinPoint jp) {
        try {
            System.out.println("演出前后要致意~A");    
            jp.proceed(); // TODO:这里还可以调用带参数的!
            System.out.println("演出前后要致意~B");    
        } catch (Throwable e) {
            e.printStackTrace();
        }
    }
}
复制代码
复制代码
<aop:config>
    <aop:aspect ref="audience">
        <aop:pointcut id="performance" expression="execution(** aop.performance.Performance.perform(..))" />   
        <aop:around pointcut-ref="performance" method="greet3"/>
    </aop:aspect>
</aop:config>
复制代码

 

另外,XML配置中的带参数Pointcut,略。见Spring in Action, 4th Edition   p147。

 

XML中Introduction引入配置

 

<aop:aspect>
    <aop:declare-parents types-matching="aop.performance.Performance+" implement-interface="aop.performance.Encoreable" default-impl="aop.performance.DefaultEncoreable" />
</aop:aspect>

 

或者,不使用default-impl,而使用delegate-ref

<bean id="encoreableDelegate" class="aop.performance.DefaultEncoreable" />
<aop:aspect>
    <aop:declare-parents types-matching="aop.performance.Performance+" implement-interface="aop.performance.Encoreable" delegate-ref="encoreableDelegate" />
</aop:aspect>

 

 

未完待续

https://www.cnblogs.com/larryzeal/p/5423411.html

分享到:
评论

相关推荐

    Spring in Action 4th 英文版及完整各章源代码

    《Spring in Action 4th》是一本深入探讨Spring框架的权威指南,专为有经验的Java开发者设计。这本书详尽地介绍了如何利用Spring框架构建高效、灵活的企业级应用。第4版更新了与Spring 4.x版本相匹配的内容,涵盖了...

    spring in action 4th 源码

    《Spring in Action 4th 源码解析》 ...总的来说,通过对Spring in Action 4th的源码学习,开发者不仅可以掌握Spring框架的基本用法,还能深入理解其内部机制,提高解决问题的能力,并为成为Spring专家打下坚实的基础。

    spring aop 学习笔记

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

    Spring in Action 第四版英文原版+源码

    总之,《Spring in Action》第四版结合其源码,为Java开发者提供了一个全面学习和实践Spring框架的平台,无论你是初学者还是经验丰富的开发者,都能从中受益匪浅。通过深入阅读和实践,你可以提升自己的技能,更好地...

    spring in action 中文版6-11.zip

    通过学习《Spring in Action》中文版6-11章节,读者将能够掌握Spring框架的核心功能,理解如何在实际项目中运用Spring进行高效开发,并具备解决复杂问题的能力。同时,了解Spring生态中的其他组件如Spring Boot和...

    Manning.Spring.in.Action.4th.Edition.2016.8

    根据所提供的文件信息,可以看出这是一本关于Spring框架的书籍,具体为《Spring in Action, Fourth Edition》的介绍。这本书由Craig Walls编写,出版日期为2016年8月,由Manning Publications出版。本书被赞誉为...

    spring in action的jar包

    《Spring in Action》是关于Spring框架的一本经典书籍,它深入浅出地介绍了Spring的核心概念和技术。这个"spring in action的jar包"很可能是书中提到的一些必要的库文件,用于配合书中的实例代码运行。这些jar包是...

    Spring in Action 中文版 pdf

    《Spring in Action》是Spring框架领域的一本经典著作,它以深入浅出的方式介绍了Spring框架的核心概念和技术。这本书的中文版对于中国的Java开发者来说是一份非常宝贵的资源,它帮助我们理解并掌握Spring框架,从而...

    Spring in action 4 + 代码

    在《Spring in Action 4》这本书中,读者可以通过详细的章节和配套的代码示例,逐步学习如何使用Spring框架来开发实际的Java应用。从基本的环境设置、配置,到高级的AOP、WebSocket和Spring Boot应用,这本书覆盖了...

    Manning.Spring.in.Action.4th.Edition

    《Spring in Action》第四版是Manning出版社发布的一本关于Spring框架的权威指南,涵盖了Spring框架的最新版本和核心概念。这本书深入浅出地讲解了如何利用Spring进行企业级Java应用开发,旨在帮助开发者充分利用...

    Spring Aop 学习笔记

    Spring Aop 学习笔记

    spring_in_action-sixth-edition.pdf

    4. Spring Boot Spring Boot 是 Spring 框架的子项目,提供了一个快速构建生产级别应用程序的方式。Spring Boot 提供了许多默认配置和自动配置项,帮助开发者快速构建应用程序。 5. Spring Data Spring Data 是 ...

    xmljava系统源码-SpringInAction4:《SpringInAction4th》学习笔记

    4th》学习笔记 第一部分 Spring的核心 1. Spring之旅 依赖注入 AOP bean的初始化过程 spring容器 2. 装配Bean “initialization on demand holder”创建单例模式的理解,参考 Spring中单例的概念限于Spring上下文中,...

    Spring_in_Action_4.pdf-第四版-英文原版

    《Spring in Action》是关于Spring框架的一本权威指南,特别是第四版,它深入浅出地介绍了如何使用Spring框架构建高效、灵活的Java应用。这本书涵盖了从基础到高级的多个Spring核心概念和技术,包括依赖注入、AOP...

    SpringAop学习笔记以及实现Demo

    **Spring AOP 学习笔记及实现Demo** Spring AOP(Aspect Oriented Programming,面向切面编程)是Spring框架中的一个重要组成部分,它提供了一种在不修改源代码的情况下,对程序进行功能增强的技术。AOP的主要目的...

    Spring in Action英文版4+中文版4+中文扫描版4+代码4

    9. **源码分析**:书中的代码4部分提供了Spring in Action 4th的源码,读者可以通过阅读和实践这些代码来加深对Spring框架的理解。 10. **学习资源**:提供的不同语言版本(英文版、中文版、中文扫描版)和配套源码...

    spring in action 第二版中文版

    《Spring in Action》第二版中文版是一本深受开发者欢迎的Spring框架入门与进阶书籍,旨在帮助读者深入理解和掌握Spring框架的核心概念和技术。这本书详细介绍了如何利用Spring进行企业级Java应用开发,包括依赖注入...

    Spring AOP学习笔记

    NULL 博文链接:https://linres.iteye.com/blog/281221

Global site tag (gtag.js) - Google Analytics