Spring AOP四种实现方式Demo详解与相关知识探究[通俗易懂]

Spring AOP四种实现方式Demo详解与相关知识探究[通俗易懂]一、前言在网络上看到一篇博客Spring实现AOP的4种方式,博主写的很通俗易懂,但排版实在抓狂,对于我这么一个对排版、代码格式有强迫症的人来说,实在是不能忍受~~~~(>_我亲手实现了一遍,重新整理,加上了一些不易关注到的细节、漏掉的知识,以及自己对AOP的一些理解,写成这篇博客。二、AOP相关概念(1)AOP是什么?AOP与拦截器的区别?太抽象的不说,如果你知道St

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全系列IDE使用 1年只要46元 售后保障 童叟无欺

一、前言

在网络上看到一篇博客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都是因为依赖问题引起的,这里也贴一下。

包结构:

Spring AOP四种实现方式Demo详解与相关知识探究[通俗易懂]

pom.xml:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>

	<groupId>com.springAOP</groupId>
	<artifactId>springAOP</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>jar</packaging>

	<name>springAOP</name>
	<url>http://maven.apache.org</url>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<org.springframework.version>3.0.5.RELEASE</org.springframework.version>
	</properties>


	<dependencies>

		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>3.8.1</version>
			<scope>test</scope>
		</dependency>

		<!-- Spring -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-context</artifactId>
			<version>${org.springframework.version}</version>
		</dependency>

		<!-- Spring AOP + AspectJ -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-aop</artifactId>
			<version>${org.springframework.version}</version>
		</dependency>
		
		<dependency>
			<groupId>org.aspectj</groupId>
			<artifactId>aspectjrt</artifactId>
			<version>1.8.9</version>
		</dependency>
		
		<dependency>
			<groupId>org.aspectj</groupId>
			<artifactId>aspectjweaver</artifactId>
			<version>1.8.9</version>
		</dependency>
	
	</dependencies>
</project>

下面开始正式的讲解:

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

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

public interface Sleepable {
	public void sleep();
}

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

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


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

public class SleepHelper implements MethodBeforeAdvice, AfterReturningAdvice {

	public void before(Method arg0, Object[] arg1, Object arg2) throws Throwable {
		System.out.println("睡觉前要脱衣服!");
	}

	public void afterReturning(Object arg0, Method arg1, Object[] arg2, Object arg3) throws Throwable {
		System.out.println("起床后要穿衣服!");
	}

}

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

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
<span style="white-space:pre">	</span>xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
<span style="white-space:pre">	</span>xmlns:aop="http://www.springframework.org/schema/aop"
<span style="white-space:pre">	</span>xsi:schemaLocation="http://www.springframework.org/schema/beans
<span style="white-space:pre">	</span>http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
<span style="white-space:pre">	</span>http://www.springframework.org/schema/aop
<span style="white-space:pre">	</span>http://www.springframework.org/schema/aop/spring-aop-3.0.xsd ">
   
   <!-- 定义被代理者 -->
   <bean id="me" class="com.springAOP.bean.Me"></bean>
   
   <!-- 定义通知内容,也就是切入点执行前后需要做的事情 -->
   <bean id="sleepHelper" class="com.springAOP.bean.SleepHelper"></bean>
   
   <!-- 定义切入点位置 -->
   <bean id="sleepPointcut" class="org.springframework.aop.support.JdkRegexpMethodPointcut">
		<property name="pattern" value=".*sleep"></property>
   </bean>
   
   <!-- 使切入点与通知相关联,完成切面配置 -->
   <bean id="sleepHelperAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
		<property name="advice" ref="sleepHelper"></property>   	
   		<property name="pointcut" ref="sleepPointcut"></property>
   </bean>
   
   <!-- 设置代理 -->
   <bean id="proxy" class="org.springframework.aop.framework.ProxyFactoryBean">
		<!-- 代理的对象,有睡觉能力 -->
		<property name="target" ref="me"></property>
		<!-- 使用切面 -->
		<property name="interceptorNames" value="sleepHelperAdvisor"></property>
		<!-- 代理接口,睡觉接口 -->
		<property name="proxyInterfaces" value="com.springAOP.bean.Sleepable"></property> 
   </bean>
   	
</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的效果!

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

执行结果:

Spring AOP四种实现方式Demo详解与相关知识探究[通俗易懂]

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

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

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

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

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

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

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

@Aspect
public class SleepHelper{

    public SleepHelper(){
        
    }
    
    @Pointcut("execution(* *.sleep())")
    public void sleeppoint(){}
    
    @Before("sleeppoint()")
    public void beforeSleep(){
        System.out.println("睡觉前要脱衣服!");
    }
    
    @AfterReturning("sleeppoint()")
    public void afterSleep(){
        System.out.println("睡醒了要穿衣服!");
    }
    
}

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

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

(3)然后修改配置为:

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


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

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


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

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

public class SleepHelper{

    public void beforeSleep(){
        System.out.println("睡觉前要脱衣服!");
    }
    
    public void afterSleep(){
        System.out.println("睡醒了要穿衣服!");
    }
   
}

(2)配置文件:

<?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:aop="http://www.springframework.org/schema/aop"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
	http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
	http://www.springframework.org/schema/aop
	http://www.springframework.org/schema/aop/spring-aop-3.0.xsd ">

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

	<aop:config>
		<aop:aspect ref="sleepHelper">
			<aop:before method="beforeSleep" pointcut="execution(* *.sleep(..))" />
			<aop:after method="afterSleep" pointcut="execution(* *.sleep(..))" />
		</aop:aspect>
	</aop:config>

</beans>

(3)配置的另一种写法

	<aop:config>
		<aop:aspect ref="sleepHelper">
            <aop:pointcut id="sleepHelpers" expression="execution(* *.sleep(..))" />
            <aop:before pointcut-ref="sleepHelpers" method="beforeSleep" />
            <aop:after pointcut-ref="sleepHelpers" method="afterSleep" />       	
        </aop:aspect>
	</aop:config>


五、AOP实现原理

学东西还是要深入进去的,推荐一篇网评还不错的博文,http://blog.csdn.net/moreevan/article/details/11977115/



版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/169622.html原文链接:https://javaforall.net

(0)
上一篇 2022年8月11日 下午1:36
下一篇 2022年8月11日 下午1:46


相关推荐

  • Eclipse如何安装svn插件及使用「建议收藏」

    Eclipse如何安装svn插件及使用「建议收藏」Eclipse中使用SVN此文章对Myeclipse同样适用。一.在Eclipse里下载Subclipse插件方法一:从EclipseMarketplace里面下载具体操作:打开Eclipse–&gt;Help–&gt;EclipseMarketplace–&gt;在Find中输入subclipse搜索–&gt;找到subclipse点击in…

    2026年3月12日
    6
  • c++面试选择题_北京易联达C语言面试咋样

    c++面试选择题_北京易联达C语言面试咋样1.new、delete、malloc、free关系delete会调用对象的析构函数,和new对应free只会释放内存,new调用构造函数。malloc与free是C++/C语言的标准库函数,new/delete是C++的运算符。它们都可用于申请动态内存和释放内存。对于非内部数据类型的对象而言,光用maloc/free无法满足动态对象的要求。对象在创建的同时要自动执行构造函数,对象在消亡之前要…

    2022年10月21日
    5
  • curl 模拟 GET\POST 请求,以及 curl post 上传文件

    curl 模拟 GET\POST 请求,以及 curl post 上传文件curl 模拟 GET POST 请求 以及 curlpost 上传文件一般情况下 我们调试数据接口 都会使用一个 postman 的工具 但是这个工具还是有点大了 事实上 我们在调试一些小功能的时候 完全没有必要使用它 在命令行中 我们使用 curl 这个工具 完全可以满足我们轻量的调试要求 下面 我们来简单的说一下 curl 的一些常见使用方法 curlGET 请求 cu

    2026年3月17日
    1
  • 我的世界怎么显示坐标_我的世界永久显示坐标

    我的世界怎么显示坐标_我的世界永久显示坐标在我的世界游戏中,坐标这个问题如果玩的很6的话能够帮助我们瞬间移动,去到任何想去的地方,这时有不少玩家不禁想问了,我的世界坐标怎么看,坐标指令又是什么呢?坐标(coordinates)在数字上反映了您在主世界中的位置。坐标基于一个由三条交于一点(即原点)的坐标轴而形成的网格。玩家会出生在距离原点数百方块的位置上。x轴反映了玩家距离原点在东()西(-)方向上的距离,如经度。z轴反映了玩家距离原点在…

    2022年4月19日
    1.4K
  • 【AI学习100天】Day04 对比AI工具—DeepSeek、Kimi、豆包、文心一言、通义千问

    【AI学习100天】Day04 对比AI工具—DeepSeek、Kimi、豆包、文心一言、通义千问

    2026年3月12日
    2

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号