此前对于AOP的使用仅限于声明式事务,除此之外在实际开发中也没有遇到过与之相关的问题。最近项目中遇到了以下几点需求,仔细思考之后,觉得采用AOP 来解决。一方面是为了以更加灵活的方式来解决问题,另一方面是借此机会深入学习Spring AOP相关的内容。本文是权当本人的自己AOP学习笔记,以下需求不用AOP肯定也能解决,至于是否牵强附会,仁者见仁智者见智。
- 对部分函数的调用进行日志记录,用于观察特定问题在运行过程中的函数调用情况
- 监控部分重要函数,若抛出指定的异常,需要以短信或邮件方式通知相关人员
- 金控部分重要函数的执行时间
事实上,以上需求没有AOP也能搞定,只是在实现过程中比较郁闷摆了。
- 需要打印日志的函数分散在各个包中,只能找到所有的函数体,手动添加日志。然而这些日志都是临时的,待问题解决之后应该需要清除打印日志的代码,只能再次手动清除^_^!
- 类 似1的情况,需要捕获异常的地方太多,如果手动添加时想到很可能明天又要手动清除,只能再汗。OK,该需求相对比较固定,属于长期监控的范畴,并不需求临 时添加后再清除。然而,客户某天要求,把其中20%的异常改为短信提醒,剩下的80%改用邮件提醒。改之,两天后,客户抱怨短信太多,全部改成邮件提 醒...
- 该需求通常用于监控某些函数的执行时间,用以判断系统执行慢的瓶颈所在。瓶颈被解决之后,烦恼同情况1
终于下定决心,采用AOP来解决!代码如下:
切面类TestAspect
- package com.spring.aop;
- /**
- * 切面
- *
- */
- public class TestAspect {
- public void doAfter(JoinPoint jp) {
- System.out.println("log Ending method: "
- + jp.getTarget().getClass().getName() + "."
- + jp.getSignature().getName());
- }
- public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
- long time = System.currentTimeMillis();
- Object retVal = pjp.proceed();
- time = System.currentTimeMillis() - time;
- System.out.println("process time: " + time + " ms");
- return retVal;
- }
- public void doBefore(JoinPoint jp) {
- System.out.println("log Begining method: "
- + jp.getTarget().getClass().getName() + "."
- + jp.getSignature().getName());
- }
- public void doThrowing(JoinPoint jp, Throwable ex) {
- System.out.println("method " + jp.getTarget().getClass().getName()
- + "." + jp.getSignature().getName() + " throw exception");
- System.out.println(ex.getMessage());
- }
- private void sendEx(String ex) {
- //TODO 发送短信或邮件提醒
- }
- }
- package com.spring.service;
- /**
- * 接口A
- */
- public interface AService {
- public void fooA(String _msg);
- public void barA();
- }
- package com.spring.service;
- /**
- *接口A的实现类
- */
- public class AServiceImpl implements AService {
- public void barA() {
- System.out.println("AServiceImpl.barA()");
- }
- public void fooA(String _msg) {
- System.out.println("AServiceImpl.fooA(msg:"+_msg+")");
- }
- }
- package com.spring.service;
- /**
- * Service类B
- */
- public class BServiceImpl {
- public void barB(String _msg, int _type) {
- System.out.println("BServiceImpl.barB(msg:"+_msg+" type:"+_type+")");
- if(_type == 1)
- throw new IllegalArgumentException("测试异常");
- }
- public void fooB() {
- System.out.println("BServiceImpl.fooB()");
- }
- }
ApplicationContext
- <?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-2.0.xsd
- http://www.springframework.org/schema/aop
- http://www.springframework.org/schema/aop/spring-aop-2.5.xsd"
- default-autowire="autodetect">
- <aop:config>
- <aop:aspect id="TestAspect" ref="aspectBean">
- <!--配置com.spring.service包下所有类或接口的所有方法-->
- <aop:pointcut id="businessService"
- expression="execution(* com.spring.service.*.*(..))" />
- <aop:before pointcut-ref="businessService" method="doBefore"/>
- <aop:after pointcut-ref="businessService" method="doAfter"/>
- <aop:around pointcut-ref="businessService" method="doAround"/>
- <aop:after-throwing pointcut-ref="businessService" method="doThrowing" throwing="ex"/>
- </aop:aspect>
- </aop:config>
- <bean id="aspectBean" class="com.spring.aop.TestAspect" />
- <bean id="aService" class="com.spring.service.AServiceImpl"></bean>
- <bean id="bService" class="com.spring.service.BServiceImpl"></bean>
- </beans>
测试类AOPTest
- public class AOPTest extends AbstractDependencyInjectionSpringContextTests {
- private AService aService;
- private BServiceImpl bService;
- protected String[] getConfigLocations() {
- String[] configs = new String[] { "/applicationContext.xml"};
- return configs;
- }
- /**
- * 测试正常调用
- */
- public void testCall()
- {
- System.out.println("SpringTest JUnit test");
- aService.fooA("JUnit test fooA");
- aService.barA();
- bService.fooB();
- bService.barB("JUnit test barB",0);
- }
- /**
- * 测试After-Throwing
- */
- public void testThrow()
- {
- try {
- bService.barB("JUnit call barB",1);
- } catch (IllegalArgumentException e) {
- }
- }
- public void setAService(AService service) {
- aService = service;
- }
- public void setBService(BServiceImpl service) {
- bService = service;
- }
- }
运行结果如下:
- log Begining method: com.spring.service.AServiceImpl.fooA
- AServiceImpl.fooA(msg:JUnit test fooA)
- log Ending method: com.spring.service.AServiceImpl.fooA
- process time: 0 ms
- log Begining method: com.spring.service.AServiceImpl.barA
- AServiceImpl.barA()
- log Ending method: com.spring.service.AServiceImpl.barA
- process time: 0 ms
- log Begining method: com.spring.service.BServiceImpl.fooB
- BServiceImpl.fooB()
- log Ending method: com.spring.service.BServiceImpl.fooB
- process time: 0 ms
- log Begining method: com.spring.service.BServiceImpl.barB
- BServiceImpl.barB(msg:JUnit test barB type:0)
- log Ending method: com.spring.service.BServiceImpl.barB
- process time: 0 ms
- log Begining method: com.spring.service.BServiceImpl.barB
- BServiceImpl.barB(msg:JUnit call barB type:1)
- log Ending method: com.spring.service.BServiceImpl.barB
- method com.spring.service.BServiceImpl.barB throw exception
- 测试异常
《Spring参考手册》中定义了以下几个AOP的重要概念,结合以上代码分析如下:
- 切面(Aspect) :官方的抽象定义为“一个关注点的模块化,这个关注点可能会横切多个对象”,在本例中,“切面”就是类TestAspect所关注的具体行为,例如,AServiceImpl.barA()的调用就是切面TestAspect所关注的行为之一。“切面”在ApplicationContext中<aop:aspect>来配置。
- 连接点(Joinpoint) :程序执行过程中的某一行为,例如,AServiceImpl.barA()的调用或者BServiceImpl.barB(String _msg, int _type)抛出异常等行为。
- 通知(Advice) :“切面”对于某个“连接点”所产生的动作,例如,TestAspect中对com.spring.service包下所有类的方法进行日志记录的动作就是一个Advice。其中,一个“切面”可以包含多个“Advice”,例如TestAspect
- 切入点(Pointcut) :匹配连接点的断言,在AOP中通知和一个切入点表达式关联。例如,TestAspect中的所有通知所关注的连接点,都由切入点表达式execution(* com.spring.service.*.*(..))来决定
- 目标对象(Target Object) :被一个或者多个切面所通知的对象。例如,AServcieImpl和BServiceImpl,当然在实际运行时,Spring AOP采用代理实现,实际AOP操作的是TargetObject的代理对象。
-
AOP代理(AOP Proxy) 在Spring AOP中有两种代理方式,JDK动态代理和CGLIB代理。默认情况下,TargetObject实现了接口时,则采用JDK动态代理,例如,AServiceImpl;反之,采用CGLIB代理,例如,BServiceImpl。强制使用CGLIB代理需要将
<aop:config>
的proxy-target-class
属性设为true
通知(Advice)类型
- 前置通知(Before advice) :在某连接点(JoinPoint)之前执行的通知,但这个通知不能阻止连接点前的执行。ApplicationContext中在<aop:aspect>里面使用<aop:before>元素进行声明。例如,TestAspect中的doBefore方法
- 后通知(After advice) :当某连接点退出的时候执行的通知(不论是正常返回还是异常退出)。ApplicationContext中在<aop:aspect>里面使用<aop:after>元素进行声明。例如,TestAspect中的doAfter方法,所以AOPTest中调用BServiceImpl.barB抛出异常时,doAfter方法仍然执行
- 返回后通知(After return advice) :在某连接点正常完成后执行的通知,不包括抛出异常的情况。ApplicationContext中在<aop:aspect>里面使用<after-returning>元素进行声明。
- 环绕通知(Around advice) :包围一个连接点的通知,类似Web中Servlet规范中的Filter的doFilter方法。可以在方法的调用前后完成自定义的行为,也可以选择不执行。ApplicationContext中在<aop:aspect>里面使用<aop:around>元素进行声明。例如,TestAspect中的doAround方法。
- 抛出异常后通知(After throwing advice) : 在方法抛出异常退出时执行的通知。 ApplicationContext中在<aop:aspect>里面使用<aop:after-throwing>元素进行声明。例如,TestAspect中的doThrowing方法。
切入点表达式
- 通常情况下,表达式中使用”execution“就可以满足大部分的要求。表达式格式如下:
- execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?)
modifiers-pattern:方法的操作权限
ret-type-pattern:返回值
declaring-type-pattern:方法所在的包
name-pattern:方法名
parm-pattern:参数名
throws-pattern:异常
其中,除ret-type-pattern和name-pattern之外,其他都是可选的。上例中,execution(* com.spring.service.*.*(..))表示com.spring.service包下,返回值为任意类型;方法名任意;参数不作限制的所有方法。
- 通知参数
可以通过args来绑定参数,这样就可以在通知(Advice)中访问具体参数了。例如,<aop:aspect>配置如下
- <aop:config>
- <aop:aspect id="TestAspect" ref="aspectBean">
- <aop:pointcut id="businessService"
- expression="execution(* com.spring.service.*.*(String,..)) and args(msg,..)" />
- <aop:after pointcut-ref="businessService" method="doAfter"/>
- </aop:aspect>
- </aop:config>
TestAspect的doAfter方法中就可以访问msg参数,但这样以来AService中的barA()和BServiceImpl中的barB()就不再是连接点,因为execution(* com.spring.service.*.*(String,..))只配置第一个参数为String类型的方法。其中,doAfter方法定义如下:
- public void doAfter(JoinPoint jp,String msg)
- 访问当前的连接点
任何通知(Advice)方法可以将第一个参数定义为 org.aspectj.lang.JoinPoint
类型。JoinPoint
接口提供了一系列有用的方法, 比如 getArgs()
(返回方法参数)、getThis()
(返回代理对象)、getTarget()
(返回目标)、getSignature()
(返回正在被通知的方法相关信息)和 toString()
(打印出正在被通知的方法的有用信息。
相关推荐
**Spring AOP 管理XML版详解** 在Spring框架中,面向切面编程(Aspect Oriented Programming,简称AOP)是一种重要的设计模式,它扩展了传统的面向对象编程(OOP),使得我们可以将关注点分离,特别是那些横切关注...
**Spring AOP 配置实现详解** Spring AOP(Aspect Oriented Programming,面向切面编程)是Spring框架的重要组成部分,它允许我们通过分离关注点来简化应用程序的开发。在传统的面向对象编程中,业务逻辑与日志记录...
### Spring之AOP配置文件详解 #### 一、前言 在Java开发中,Spring框架因其强大的功能和灵活的配置而被广泛应用于企业级应用的开发。其中,面向切面编程(Aspect Oriented Programming,简称AOP)是Spring框架的...
在Spring XML配置中,这可以通过以下方式完成: ```xml <aop:aspectj-autoproxy /> ``` 在Spring Boot项目中,无需显式配置XML,只需在主配置类上添加`@EnableAspectJAutoProxy`注解即可: ```java @...
Spring AOP的配置可以通过XML或注解方式进行: - **XML配置**: - 在`<aop:config>`标签内定义切面,`<aop:pointcut>`定义切入点,`<aop:advisor>`定义通知。 - `<aop:aspect>`标签用于定义完整的切面,包括切入...
在Spring框架中,AOP支持多种配置方式,如基于XML的配置、基于Java注解的配置以及使用特定的Bean来实现。本文主要介绍几种常见的Spring AOP配置方式,并通过具体的示例来说明每种配置的特点。 #### 二、AOP配置所需...
**Spring AOP 依赖 Jar 包详解** 在 Spring 框架中,AOP(面向切面编程)是一种强大的设计模式,它允许开发者定义“切面”,这些切面可以封装跨多个对象的行为或责任。Spring AOP 提供了在运行时实现切面的功能,...
本文将详细介绍SpringAOP中的注解配置详解,包括使用注解实现SpringAOP的功能、XML配置等方面的内容。 一、使用注解实现SpringAOP的功能 在SpringAOP中,注解是实现AOP功能的主要方式之一。通过使用注解,开发者...
### Spring AOP 概念与实践 #### 一、AOP 概述 ...Spring AOP 提供了灵活的机制来实现这一目标,无论是通过 AspectJ 注解还是 XML 配置,开发者都可以根据项目的需求选择最适合的方式来实现 AOP。
总之,Spring.NET 的 AOP 功能通过配置文件提供了一种灵活的方式来管理和组织横切关注点,提高了代码的可读性和可维护性。理解和熟练使用这一特性,对于提升 .NET 应用程序的质量和可扩展性至关重要。
3. **基于XML配置的AOP**:在Spring的配置文件中,可以通过`<aop:config>`、`<aop:advisor>`、`<aop:pointcut>`等标签定义切面、通知和切入点。 ### 三、Spring AOP实战 1. **经典代理模式**:通过JDK动态代理或...
《Spring AOP 实践:基于XML的面向切面编程详解》 在软件开发中,Spring框架因其强大的功能和灵活性而备受推崇,其中AOP(Aspect Oriented Programming,面向切面编程)是其重要特性之一。Spring AOP允许我们定义横...
2. 配置Spring的XML配置文件,声明AOP的相关bean,包括切面类和通知。 3. 使用`@Aspect`注解标记切面类,并在类中定义通知方法。 4. 使用`@Before`、`@After`、`@Around`、`@AfterReturning`、`@AfterThrowing`等...
### Spring AOP 入门详解 #### 一、Spring AOP 概述 Spring AOP(Aspect Oriented Programming,面向切面编程)是Spring框架的一个关键特性,它为开发者提供了在运行时动态添加代码(即横切关注点或切面)到已有...
在Spring中配置AOP,主要有两种方式:基于XML和基于注解。这里我们主要介绍基于注解的方式,因为它是现代Spring应用的首选方法。 1. **启用AOP代理**:在Spring配置文件中,通过`<aop:aspectj-autoproxy>`元素启用...
在Spring框架中,实现AOP有两种主要方式:XML配置和AspectJ注解。本文主要讨论的是XML配置方式。在XML配置中,你需要在Spring配置文件中定义目标对象、切面类以及具体的切点和通知。 首先,创建目标对象。在例子中...
`springAOP2`可能是一个包含具体示例代码的目录。`基于@AspectJ配置Spring AOP之一 - 飞扬部落编程仓库-专注编程,网站,专业技术.htm`和其关联的`_files`目录可能包含了一个详细的教程或演示如何配置和运行@AspectJ的...