`
wangyanlong0107
  • 浏览: 486723 次
  • 性别: 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_AOP笔记Spring_AOP笔记

    Spring_AOP笔记Spring_AOP笔记Spring_AOP笔记Spring_AOP笔记Spring_AOP笔记Spring_AOP笔记Spring_AOP笔记

    spring in action 中文版6-11.zip

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

    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中文版+英文版+文中源码

    《Spring In Action》是一本深度剖析Spring框架的权威著作,无论是中文版还是英文版,都是IT开发者们深入理解和掌握Spring框架的重要参考资料。该书详细介绍了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 是 ...

    spring ioc aop mvc boot-学习笔记.docx

    自己学习spring课程的笔记。笔记都是根据尚硅谷的课程(spring ioc,spring aop,spring mvc,spring boot等)写的。 主要内容:spring ioc,spring aop,spring mvc,spring boot

    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. **学习资源**:提供的不同语言版本(英文版、中文版、中文扫描版)和配套源码...

    AspectJ in Action: Enterprise AOP with Spring Applications

    When to use Spring AOP and AspectJ AOP? Expert author Ramnivas Laddad shows how to combine technologies such as Spring, Hibernate, Swing, and JDBC with AspectJ. The book fully covers the latest ...

Global site tag (gtag.js) - Google Analytics