一、前言

在网络上看到一篇博客Spring实现AOP的4种方式,博主写的很通俗易懂,但排版实在抓狂,对于我这么一个对排版、代码格式有强迫症的人来说,实在是不能忍受~~~~(>_<)~~~~。

我亲手实现了一遍,重新整理,加上了一些不易关注到的细节、漏掉的知识,以及自己对AOP的一些理解,写成这篇博客。

二、AOP相关概念

(1)AOP是什么?AOP与拦截器的区别?

太抽象的不说,如果你知道Struts2的拦截器,拦截器就是应用的AOP的思想,它用于拦截Action以进行一些预处理或结果处理。而Spring的AOP是一种更通用的模式,可以拦截Spring管理的Bean,功能更强大,适用范围也更广,它是通过动态代理与反射机制实现的。(更详细的解释可参看博客 http://blog.csdn.net/zhangliangzi/article/details/51648032 )

(2)使用AOP需要的一些概念。

1.通知(Advice)

通知定义了在切入点代码执行时间点附近需要做的工作。

Spring支持五种类型的通知:

Before(前)  org.apringframework.aop.MethodBeforeAdvice
After-returning(返回后) org.springframework.aop.AfterReturningAdvice
After-throwing(抛出后) org.springframework.aop.ThrowsAdvice
Arround(周围) org.aopaliance.intercept.MethodInterceptor
Introduction(引入) org.springframework.aop.IntroductionInterceptor

2.连接点(Joinpoint)

程序能够应用通知的一个“时机”,这些“时机”就是连接点,例如方法调用时、异常抛出时、方法返回后等等。

3.切入点(Pointcut)

通知定义了切面要发生的“故事”,连接点定义了“故事”发生的时机,那么切入点就定义了“故事”发生的地点,例如某个类或方法的名称,Spring中允许我们方便的用正则表达式来指定。

4.切面(Aspect)

通知、连接点、切入点共同组成了切面:时间、地点和要发生的“故事”。

5.引入(Introduction)

引入允许我们向现有的类添加新的方法和属性(Spring提供了一个方法注入的功能)。

6.目标(Target)

即被通知的对象,如果没有AOP,那么通知的逻辑就要写在目标对象中,有了AOP之后它可以只关注自己要做的事,解耦合!

7.代理(proxy)

应用通知的对象,详细内容参见设计模式里面的动态代理模式。

8.织入(Weaving)

把切面应用到目标对象来创建新的代理对象的过程,织入一般发生在如下几个时机:

(1)编译时:当一个类文件被编译时进行织入,这需要特殊的编译器才可以做的到,例如AspectJ的织入编译器;

(2)类加载时:使用特殊的ClassLoader在目标类被加载到程序之前增强类的字节代码;

(3)运行时:切面在运行的某个时刻被织入,SpringAOP就是以这种方式织入切面的,原理应该是使用了JDK的动态代理技术。

三、使用AOP的几种方式

1.经典的基于代理的AOP

2.@AspectJ注解驱动的切面

3.纯POJO切面

4.注入式AspectJ切面

四、Demo详解

在讲Demo之前,先把项目结构贴一下,我用的的一般的Java Project+Maven进行测试,用Web Project的小区别一会会说到。有一点很重要,jar依赖必须导入正确,我在测试过程中,很多bug都是因为依赖问题引起的,这里也贴一下。

包结构:

pom.xml:

  1. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  2. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  3. <modelVersion>4.0.0</modelVersion>
  4. <groupId>com.springAOP</groupId>
  5. <artifactId>springAOP</artifactId>
  6. <version>0.0.1-SNAPSHOT</version>
  7. <packaging>jar</packaging>
  8. <name>springAOP</name>
  9. <url>http://maven.apache.org</url>
  10. <properties>
  11. <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  12. <org.springframework.version>3.0.5.RELEASE</org.springframework.version>
  13. </properties>
  14. <dependencies>
  15. <dependency>
  16. <groupId>junit</groupId>
  17. <artifactId>junit</artifactId>
  18. <version>3.8.1</version>
  19. <scope>test</scope>
  20. </dependency>
  21. <!-- Spring -->
  22. <dependency>
  23. <groupId>org.springframework</groupId>
  24. <artifactId>spring-context</artifactId>
  25. <version>${org.springframework.version}</version>
  26. </dependency>
  27. <!-- Spring AOP + AspectJ -->
  28. <dependency>
  29. <groupId>org.springframework</groupId>
  30. <artifactId>spring-aop</artifactId>
  31. <version>${org.springframework.version}</version>
  32. </dependency>
  33. <dependency>
  34. <groupId>org.aspectj</groupId>
  35. <artifactId>aspectjrt</artifactId>
  36. <version>1.8.9</version>
  37. </dependency>
  38. <dependency>
  39. <groupId>org.aspectj</groupId>
  40. <artifactId>aspectjweaver</artifactId>
  41. <version>1.8.9</version>
  42. </dependency>
  43. </dependencies>
  44. </project>

下面开始正式的讲解:

1、经典的基于代理的AOP实现,以一个睡觉的例子实现。

(1)可睡觉的接口,任何可以睡觉的人或机器都可以实现它。

  1. public interface Sleepable {
  2. public void sleep();
  3. }

(2)接口实现类,“Me”可以睡觉,“Me”就实现可以睡觉的接口。

  1. public class Me implements Sleepable{
  2. public void sleep() {
  3. System.out.println("\n睡觉!不休息哪里有力气学习!\n");
  4. }
  5. }

(3)Me关注于睡觉的逻辑,但是睡觉需要其他功能辅助,比如睡前脱衣服,起床脱衣服,这里开始就需要AOP替“Me”完成!解耦!首先需要一个SleepHelper类。因为一个是切入点前执行、一个是切入点之后执行,所以实现对应接口。

  1. public class SleepHelper implements MethodBeforeAdvice, AfterReturningAdvice {
  2. public void before(Method arg0, Object[] arg1, Object arg2) throws Throwable {
  3. System.out.println("睡觉前要脱衣服!");
  4. }
  5. public void afterReturning(Object arg0, Method arg1, Object[] arg2, Object arg3) throws Throwable {
  6. System.out.println("起床后要穿衣服!");
  7. }
  8. }

(4)最关键的来了,Spring核心配置文件application.xml配置AOP。

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. <span style="white-space:pre">    </span>xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. <span style="white-space:pre">    </span>xmlns:aop="http://www.springframework.org/schema/aop"
  5. <span style="white-space:pre">    </span>xsi:schemaLocation="http://www.springframework.org/schema/beans
  6. <span style="white-space:pre">    </span>http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
  7. <span style="white-space:pre">    </span>http://www.springframework.org/schema/aop
  8. <span style="white-space:pre">    </span>http://www.springframework.org/schema/aop/spring-aop-3.0.xsd ">
  9. <!-- 定义被代理者 -->
  10. <bean id="me" class="com.springAOP.bean.Me"></bean>
  11. <!-- 定义通知内容,也就是切入点执行前后需要做的事情 -->
  12. <bean id="sleepHelper" class="com.springAOP.bean.SleepHelper"></bean>
  13. <!-- 定义切入点位置 -->
  14. <bean id="sleepPointcut" class="org.springframework.aop.support.JdkRegexpMethodPointcut">
  15. <property name="pattern" value=".*sleep"></property>
  16. </bean>
  17. <!-- 使切入点与通知相关联,完成切面配置 -->
  18. <bean id="sleepHelperAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
  19. <property name="advice" ref="sleepHelper"></property>
  20. <property name="pointcut" ref="sleepPointcut"></property>
  21. </bean>
  22. <!-- 设置代理 -->
  23. <bean id="proxy" class="org.springframework.aop.framework.ProxyFactoryBean">
  24. <!-- 代理的对象,有睡觉能力 -->
  25. <property name="target" ref="me"></property>
  26. <!-- 使用切面 -->
  27. <property name="interceptorNames" value="sleepHelperAdvisor"></property>
  28. <!-- 代理接口,睡觉接口 -->
  29. <property name="proxyInterfaces" value="com.springAOP.bean.Sleepable"></property>
  30. </bean>
  31. </beans>

其中:

<beans>是Spring的配置标签,beans里面几个重要的属性:

xmlns:

是默认的xml文档解析格式,即spring的beans。地址是http://www.springframework.org/schema/beans;通过设置这个属性,所有在beans里面声明的属性,可以直接通过<>来使用,比如<bean>等等。一个XML文件,只能声明一个默认的语义解析的规范。例如上面的xml中就只有beans一个是默认的,其他的都需要通过特定的标签来使用,比如aop,它自己有很多的属性,如果要使用,前面就必须加上aop:xxx才可以。类似的,如果默认的xmlns配置的是aop相关的语义解析规范,那么在xml中就可以直接写config这种标签了。

xmlns:xsi:

是xml需要遵守的规范,通过URL可以看到,是w3的统一规范,后面通过xsi:schemaLocation来定位所有的解析文件。

xmlns:aop:

这个是重点,是我们这里需要使用到的一些语义规范,与面向切面AOP相关。

xmlns:tx:

Spring中与事务相关的配置内容。

(5)测试类,Test,其中,通过AOP代理的方式执行Me的sleep()方法,会把执行前、执行后的操作执行,实现了AOP的效果!

  1. public class Test {
  2. public static void main(String[] args){
  3. @SuppressWarnings("resource")
  4. //如果是web项目,则使用注释的代码加载配置文件,这里是一般的Java项目,所以使用下面的方式
  5. //ApplicationContext appCtx = new ClassPathXmlApplicationContext("application.xml");
  6. ApplicationContext appCtx = new FileSystemXmlApplicationContext("application.xml");
  7. Sleepable me = (Sleepable)appCtx.getBean("proxy");
  8. me.sleep();
  9. }
  10. }

执行结果:



(6)通过org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator简化配置。

将配置文件中设置代理的代码去掉,加上:

  1. <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/>

然后,在Test中,直接获取me对象,执行sleep方法,就可以实现同样的功能!

通过自动匹配,切面会自动匹配符合切入点的bean,会被自动代理,实现功能!

2、更简单的方式,通过AspectJ提供的注解实现AOP。

(1)同样的例子,修改后的SleepHelper:

  1. @Aspect
  2. public class SleepHelper{
  3. public SleepHelper(){
  4. }
  5. @Pointcut("execution(* *.sleep())")
  6. public void sleeppoint(){}
  7. @Before("sleeppoint()")
  8. public void beforeSleep(){
  9. System.out.println("睡觉前要脱衣服!");
  10. }
  11. @AfterReturning("sleeppoint()")
  12. public void afterSleep(){
  13. System.out.println("睡醒了要穿衣服!");
  14. }
  15. }

(2)在方法中,可以加上JoinPoint参数以进行相关操作,如:

  1. //当抛出异常时被调用
  2. public void doThrowing(JoinPoint point, Throwable ex)
  3. {
  4. System.out.println("doThrowing::method "
  5. + point.getTarget().getClass().getName() + "."
  6. + point.getSignature().getName() + " throw exception");
  7. System.out.println(ex.getMessage());
  8. }

(3)然后修改配置为:

  1. <aop:aspectj-autoproxy />
  2. <!-- 定义通知内容,也就是切入点执行前后需要做的事情 -->
  3. <bean id="sleepHelper" class="com.springAOP.bean.SleepHelper"></bean>
  4. <!-- 定义被代理者 -->
  5. <bean id="me" class="com.springAOP.bean.Me"></bean>

(4)最后测试,一样的结果!

  1. public class Test {
  2. public static void main(String[] args){
  3. @SuppressWarnings("resource")
  4. //如果是web项目,则使用注释的代码加载配置文件,这里是一般的Java项目,所以使用下面的方式
  5. //ApplicationContext appCtx = new ClassPathXmlApplicationContext("application.xml");
  6. ApplicationContext appCtx = new FileSystemXmlApplicationContext("application.xml");
  7. Sleepable me = (Sleepable)appCtx.getBean("me");
  8. me.sleep();
  9. }
  10. }

3、使用Spring来定义纯粹的POJO切面(名字很绕口,其实就是纯粹通过<aop:fonfig>标签配置,也是一种比较简单的方式)。

(1)修改后的SleepHelper类,很正常的类,所以这种方式的优点就是在代码中不体现任何AOP相关配置,纯粹使用xml配置。

  1. public class SleepHelper{
  2. public void beforeSleep(){
  3. System.out.println("睡觉前要脱衣服!");
  4. }
  5. public void afterSleep(){
  6. System.out.println("睡醒了要穿衣服!");
  7. }
  8. }

(2)配置文件:

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
  4. xsi:schemaLocation="http://www.springframework.org/schema/beans
  5. http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
  6. http://www.springframework.org/schema/aop
  7. http://www.springframework.org/schema/aop/spring-aop-3.0.xsd ">
  8. <!-- 定义通知内容,也就是切入点执行前后需要做的事情 -->
  9. <bean id="sleepHelper" class="com.springAOP.bean.SleepHelper"></bean>
  10. <!-- 定义被代理者 -->
  11. <bean id="me" class="com.springAOP.bean.Me"></bean>
  12. <aop:config>
  13. <aop:aspect ref="sleepHelper">
  14. <aop:before method="beforeSleep" pointcut="execution(* *.sleep(..))" />
  15. <aop:after method="afterSleep" pointcut="execution(* *.sleep(..))" />
  16. </aop:aspect>
  17. </aop:config>
  18. </beans>

(3)配置的另一种写法

  1. <aop:config>
  2. <aop:aspect ref="sleepHelper">
  3. <aop:pointcut id="sleepHelpers" expression="execution(* *.sleep(..))" />
  4. <aop:before pointcut-ref="sleepHelpers" method="beforeSleep" />
  5. <aop:after pointcut-ref="sleepHelpers" method="afterSleep" />
  6. </aop:aspect>
  7. </aop:config>

五、AOP实现原理

学东西还是要深入进去的,推荐一篇网评还不错的博文,http://www.cnblogs.com/xiaolovewei/p/7919763.html

转载:https://blog.csdn.net/zhangliangzi/article/details/52334964

Spring AOP四种实现方式Demo详解与相关知识探究的更多相关文章

  1. ASP.NET Eval四种绑定方式 及详解

    1.1.x中的数据绑定语法 <asp:Literal id="litEval2" runat="server" Text='<%#DataBinde ...

  2. spring security四种实现方式

    spring security四种实现方式 spring(20) > 目录(?)[+] 最简单配置spring-securityxml实现1 实现UserDetailsService 实现动态过 ...

  3. Spring AOP两种实现方式

    一. AOP 概念: Spring AOP 即Aspect Oriented Programming(面向切面编程), 实现方式分为两种: 1. 注解(Annotation) 2. 配置(Config ...

  4. spring aop两种配置方式(1)

    第一种:注解配置AOP注解配置AOP(使用 AspectJ 类库实现的),大致分为三步: 1. 使用注解@Aspect来定义一个切面,在切面中定义切入点(@Pointcut),通知类型(@Before ...

  5. java框架篇---spring aop两种配置方式

    第一种:注解配置AOP 注解配置AOP(使用 AspectJ 类库实现的),大致分为三步: 1. 使用注解@Aspect来定义一个切面,在切面中定义切入点(@Pointcut),通知类型(@Befor ...

  6. 【Spring】12、Spring Security 四种使用方式

    spring security使用分类: 如何使用spring security,相信百度过的都知道,总共有四种用法,从简到深为:1.不用数据库,全部数据写在配置文件,这个也是官方文档里面的demo: ...

  7. spring aop两种配置方式

    基于注解的Spring AOP开发 简单案例快速入门 定义目标类接口和实现类 /** * Created by zejian on 2017/2/19.*/ //接口类 public interfac ...

  8. EF三种编程方式图文详解

    Entity Framework4.1之前EF支持“Database First”和“Model First”编程方式,从EF4.1开始EF开始支持支持“Code First”编程方式,今天简单看一下 ...

  9. JDK提供的四种线程池代码详解

    一.线程池什么时候使用,会给我们带来什么好处? 如果很多用户去访问服务器,用户访问服务器的时间是非常短暂的,那么有可能在创建线程和销毁线程上花费的时间会远远大于访问所消耗的时间,如果采用线程池会使线程 ...

随机推荐

  1. C++编译器对属性和方法的处理机制

    C++中的class从面向对象理论出发,将变量(属性)和函数(方法)集中定义在一起,用于描述现实世界中的类.从计算机的角度,程序依然由数据段和代码段构成. C++编译器如何完成面向对象理论到计算机程序 ...

  2. iOS开发基础block的形式讲解

    前几个星期,我利用通知写了一个仿京东选择地址的Demo(http://blog.csdn.net/hbblzjy/article/details/52212879),后来看过一篇文章说,尽量少用通知, ...

  3. findbugs, checkstyle, pmd的myeclipse7.5+插件安装(转:http://blog.csdn.net/priestmoon/article/details/63941)

    CheckStyle (1)下载net.sf.eclipsecs_5.3.0.201012121300-updatesite-.zip (2)打开MyEclipse,Help->Software ...

  4. iOS中 自定义系统相机 作者:韩俊强

    需要框架: #import <AVFoundation/AVFoundation.h> #import <AssetsLibrary/AssetsLibrary.h> 布局如下 ...

  5. mysql删除重复数据只保留一条

    mysql删除重复数据只保留一条 新建一张测试表: CREATE TABLE `book` ( `id` char(32) NOT NULL DEFAULT '', `name` varchar(10 ...

  6. FreeMarker 生成Java、mybatis文件

    FreeMarker 生成Java.mybatis文件 将mysql数据库表通过FreeMarker生成对应的Java文件和对应的mybatis文件. FreeMarker是一款模板引擎: 即一种基于 ...

  7. Html.java 存储页面信息类

    Html.java 存储页面信息类 package com.iteye.injavawetrust.miner; /** * 存储页面信息类 * @author InJavaWeTrust * */ ...

  8. 10_Android中通过HttpUrlConnection访问网络,Handler和多线程使用,读取网络html代码并显示在界面上,ScrollView组件的使用

     编写如下项目: 2 编写Android清单文件 <?xml version="1.0" encoding="utf-8"?> <mani ...

  9. 【一天一道LeetCode】#22. Generate Parentheses

    一天一道LeetCode (一)题目 Given n pairs of parentheses, write a function to generate all combinations of we ...

  10. 【Qt编程】Qt版扫雷

    学习要学会举一反三.在以前的<用matlab扫扫雷>一文中,我用matlab简单的编写了一个扫雷小程序.当然,与Windows自带的扫雷程序自然是不敢相提并论.今天我就用c++来写个扫雷程 ...