在阅读本文之前,大家可先行参阅《简单理解Spring之IOC和AOP及代码示例》一文,简单了解下ioc和aop的相关内容。下面进入正题。
本文将会一步一步创建一个最简单的例子,来使用Spring的AOP特性,算是一个Spring AOP的入门Demo。作为一个初学者,运行出这么简单的一个Demo也踩了很多的坑。
OOP的问题,AOP的补充
当我们需要为分散的对象引入公共行为的时候,OOP则显得无能为力。也就是说,OOP允许你定义从上到下的关系,但并不适合定义从左到右的关系。例如日志功能。日志代码往往水平地散布在所有对象层次中,而与它所散布到的对象的核心功能毫无关系。对于其他类型的代码,如安全性、异常处理和透明的持续性也是如此。这种散布在各处的无关的代码被称为横切(cross-cutting)代码,在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用。
所谓“方面”,简单地说,就是将那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可操作性和可维护性。
Spring中对AOP的支持
Spring中AOP代理由Spring的IoC容器负责生成、管理,其依赖关系也由IoC容器负责管理。因此,AOP代理可以直接使用容器中的其他Bean实例作为目标,这种关系可由IoC容器的依赖注入提供。Spring默认使用Java动态代理来创建AOP代理,这样就可以为任何接口实例创建代理了。当需要代理的类不是代理接口的时候,Spring自动会切换为使用CGLIB代理,也可强制使用CGLIB。
本例子的逻辑如下:有一个Car类(业务类),在Car类中的go方法运行之前和之后,都会有相应的日志记录,但Car类本身并不知道日志的任何逻辑。
创建Maven项目并添加依赖
首先,新建一个Maven项目,使用 maven‐archetype‐quickstart模板,然后打开pom.xml文件,加入Spring AOP运行需要的依赖包
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
< dependency > < groupId >org.springframework</ groupId > < artifactId >spring-core</ artifactId > < version >4.0.5.RELEASE</ version > </ dependency > < dependency > < groupId >org.springframework</ groupId > < artifactId >spring-beans</ artifactId > < version >4.0.5.RELEASE</ version > </ dependency > < dependency > < groupId >org.springframework</ groupId > < artifactId >spring-context</ artifactId > < version >4.0.5.RELEASE</ version > </ dependency > < dependency > < groupId >org.springframework</ groupId > < artifactId >spring-aop</ artifactId > < version >4.0.5.RELEASE</ version > </ dependency > < dependency > < groupId >org.aspectj</ groupId > < artifactId >aspectjweaver</ artifactId > < version >1.8.1</ version > </ dependency > |
编写业务代码
新增一个业务类Car,包含一个go()方法
1
2
3
4
5
6
|
package com.wowo.spring_aop_demo1; public class Car { public void go(){ System.out.println( "go go go!" ); } } |
编写切面类
日志类会记录下系统的运行情况,但日志的逻辑不会在业务类中写的到处都是,而是作为一个切面类存在。
1
2
3
4
5
6
7
8
9
|
package com.wowo.spring_aop_demo1; public class CarLogger { public void beforeRun(){ System.out.println( "car is going to run" ); } public void afterRun(){ System.out.println( "car is running" ); } } |
该切面类包含两个方法,他们分别是前置通知和后置通知。
通过bean来配置关联
新增一个配置文件,本例命名为bean.xml,在配置文件中来关联切面与通知
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
<? 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:p = "http://www.springframework.org/schema/p" 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-2.5.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd" > < bean id = "car" class = "com.wowo.spring_aop_demo1.Car" /> < bean id = "logger" class = "com.wowo.spring_aop_demo1.CarLogger" /> < aop:config > < aop:aspect ref = "logger" > < aop:pointcut expression = "execution(* com.wowo.spring_aop_demo1.Car.go(..))" id = "go" /> < aop:before pointcut-ref = "go" method = "beforeRun" /> < aop:after pointcut-ref = "go" method = "afterRun" /> </ aop:aspect > </ aop:config > </ beans > |
注意:这个配置文件中,aop的命名空间,以及xsi:schemaLocation中包含的几个地址都是必须的。
execution(* com.wowo.spring_aop_demo1.Car.go(..))是一个AspectJ切点表达式,execution表示在执行时触发,后面的*表示任意类型的返回值,com.wowo.spring_aop_demo1.Car指的是切点所在的类,go(..)是方法名,..表示任意参数。
Spring切面可以应用5种类型的通知:
·Before——在方法被调用之前调用通知
·After——在方法完成之后调用通知,无论方法是否执行成功
·After-returning——在方法成功执行之后调用通知
·After-throwing——在方法抛出异常后调用通知
·Around——通知包裹了被通知的方法,在被通知的方法调用之前和调用之后都执行自定义的行为
运行业务代码
下面创建一个包含main()方法的类,来运行业务代码
1
2
3
4
5
6
7
8
9
10
11
12
|
package com.wowo.spring_aop_demo1; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class App { public static void main( String[] args ) { ApplicationContext context = new ClassPathXmlApplicationContext( "bean.xml" ); Car car=(Car) context.getBean( "car" ); car.go(); } } |
在上面的代码中,由Spring创建了一个car对象。Spring在创建该对象时,发现它的一个方法被配置成了切点(pointcut),所以,在实例化该对象时,会创建一个代理对象,当切点方法go()执行时,会被Spring创建的代理对象所拦截,运行go方法之前,会调用所对应的切面类CarLogger的前置方法beforeRun(),然后调用Car.go()方法,再然后就调用切面类CarLogger的后置方法afterRun()。
注意:必须使用Spring创建包含切点的对象,如果自己创建的话,Spring是监测不到的,它的运行也不会被应用任何通知。
项目输出结果为
1
2
3
|
car is going to run go go go! car is running |
使用环绕通知
如果想使用环绕通知,我们需要修改切面类中的通知方法以及配置文件,业务类无需做任何修改,因为他们是完全解耦的。首先修改切面类CarLogger
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
import org.aspectj.lang.ProceedingJoinPoint; public class CarLogger { public void aroundRun(ProceedingJoinPoint joinpoint){ System.out.println( "car is going to run" ); try { //调用被代理的对象的目标方法,本例中指向Car.go()方法 joinpoint.proceed(); } catch (Throwable e) { e.printStackTrace(); } System.out.println( "car is running" ); } } |
环绕通知的方法,需要接受ProceedingJoinPoint类型的参数,其proceed()方法将会调用被代理对象的目标方法,所以,正常情况下,这个方法一定要调用。我们也可以通过不调用该方法来组织被代理对象的运行。
接下来将配置文件的aop:config部分修改为如下所示
1
2
3
4
5
6
|
<aop:config> <aop:aspect ref= "logger" > <aop:pointcut expression= "execution(* com.wowo.spring_aop_demo1.Car.go(..))" id= "go" /> <aop:around method= "aroundRun" pointcut-ref= "go" /> </aop:aspect> </aop:config> |
注意:环绕通知不能和前置/后置通知同时存在。运行代码后,输出结果不变。
总结
以上就是本文关于Spring AOP入门Demo分享的全部内容,希望对大家有所帮助。感兴趣的朋友可以继续参阅本站其他相关专题,如有不足之处,欢迎留言指出。感谢朋友们对本站的支持!
原文链接:http://blog.csdn.net/daguanjia11/article/details/49278003