前言
Spring 的aop技术,个人理解 主要解决代码复用,避免重复性编写类似代码问题。比较典型的三种场景就是 日志打印、权限验证、事务处理。其实远不至于这三种场景,在编码过程中如果发现某些类似的代码频繁的出现在各个方法中,就可以考虑是否可以用aop统一进行处理,而不是在每个方法都进行一次。
Spring aop相关术语
连接点:判断是否需要使用spring aop技术,首先提取某一类的业务方法进行分析,所有这些方法就是连接点。
切点:进一步在所有的连接点中进行分析,提取出需要进行统一处理的方法,是连接点的子集。解决 where的问题,主要通过切点表达式进行过滤,如典型的配置方式execution(* com.xxx.xxx.*(..))。
通知:简单的说,就是首先从切点中提取出来的共同操作:以前这些操作分布在各个方法体中,现在提取到同一个类中统一管理,解决how(执行什么)的问题; Spring aop中定义了5种通知,解决when(什么时候执行)的问题,根据自己的业务场景选择使用:
前置通知(Before):在目标方法执行前,首先调用该方法。
后置通知(After):在目标方法执行完成后,再调用该方法。不管是目标方法执行成功,还是抛出异常,都会调用。
返回通知(AfterReturning):在目标方法执行成功后,再调用该方法。
异常通知(AfterThrowing):在目标方法执行抛出异常后,调用该方法。
环绕通知(Around):对目标方法进行包裹,理论上可以在环绕通知里,实现上述4种通知。
切面:用于承载 通知+切点的类。把where、when and how (在哪儿执行、什么时候执行、执行什么)执行整合在一起。
引入:向现有的目标类添加新的方法和属性,只需要在切面中添加即可。反过来讲,也可以把目标类中共同的属性和方法抽取到切面中,方便统一管理。
织入:是把切面应用到目标对象并创建新的代理对象的过程。创建代理对象又分为静态代理 和动态代理,使用AspectJ是静态代理,在编译期或者类加载器创建代理对象;使用spring aop是动态代理,在运行期动态的为目标对象创建代理对象。
Spring aop支持jdk动态代理和CGLIB动态代理。默认情况下,如果目标对象实现了接口,采用JDK的动态代理实现AOP,否则使用CGLIB动态代理。当然也可以通过配置指定。
本章主要讲解spring支持的三种aop使用方式(本章是基于spring4.3进行讲解,对于spring3.0以前的版本,还请使用经典方式,这里不再讲解):
1、基于注解方式(使用AspectJ注解,但本质上还是基于动态代理,这里只是用到AspectJ注解)。
2、基于xml配置方法。
3、注入AspectJ切面的静态代理方式。
在spring中我们通常使用前两种方式(这里暂且称之为spring aop),第三种方式作为补充(这里暂且称之为 AspectJ aop)。Spring aop只能支持在普通方法上织入切面,但无法使用在构造方法上。主要原因 前面也提到过,spring aop是在运行期动态的创建代理对象,此时目标对象创建完成,不会再有构造方法的调用。作为补充,这种情况下只能使用AspectJ静态代理,在编译期货类加载期就进行代理对象的创建。
下面分别对三种切面注入方式进行讲解。
基于注解方式
从spring3.0开始,就比较推崇用注解代替xml配置,所以我们首先对基于注解方式的spring aop用法进行讲解,首先看下切面类:
/** * 统一日志aop */ @Component //标记为一个 @Aspect //标记为切面 public class LogAop { private static final Log log = LogFactory.getLog(LogAop.class); //定义切点 方便复用 @Pointcut("execution(* com.sky.aop.service.*.*.*(..))") public void log(){}; //前置通知 @Before("log()") public void beforeLog(JoinPoint jp){ log.info(jp.getSignature().getDeclaringTypeName()+"类的"+jp.getSignature().getName()+ "方法Before日志"); } //环绕通知 @Around("log()") public void aroundLog(ProceedingJoinPoint jp) { try { log.info(jp.getSignature().getDeclaringTypeName()+"类的"+jp.getSignature().getName()+"方法Around通知开始"); jp.proceed(); log.info(jp.getSignature().getDeclaringTypeName() + "方法Around通知结束"); }catch (Throwable throwable) { Object[] args = jp.getArgs(); System.out.println("参数列表值为:"); for (Object one: args){ log.error(one.toString()); } log.error(jp.getSignature().getDeclaringTypeName() + "类的" + jp.getSignature().getName() + "调用异常", throwable); } } //后置通知 @After("log()") public void after(JoinPoint jp){ log.info(jp.getSignature().getDeclaringTypeName()+"类的"+jp.getSignature().getName()+ "方法after日志"); } //返回通知 @AfterReturning("log()") public void afterRet(JoinPoint jp){ log.info(jp.getSignature().getDeclaringTypeName()+"类的"+jp.getSignature().getName()+ "方法AfterReturning日志"); } //异常通知 @AfterThrowing("log()") public void afterError(JoinPoint jp){ log.info(jp.getSignature().getDeclaringTypeName()+"类的"+jp.getSignature().getName()+ "方法AfterThrowing日志"); } }
该类模拟的是统一的日志打印,我们业务场景通常为:在目标方法调用之前、之后、或异常时需要进行日志打印,LogAop切面类定义spring aop支持的5种通知类型(前面已经讲解),分别对应5类注解:@Before、@Around、@After、@AfterReturning、@AfterThrowing。
再来看下切点定义:
//定义切点 方便复用 @Pointcut("execution(* com.sky.aop.service.*.*.*(..))") public void log(){};
log()方法表示切点,方法体为空,只是做切点标记使用。
execution(* com.sky.aop.service.*.*.*(..) 表达式:
第一个*:表示返回任意类型的方法。
com.sky.aop.service:表示包路径。
第二个*:表示com.sky.aop.service包下所有的子包(不包含子包的子包)。
第三个*: 表示子包下的任意类。
第四个*: 表类里的任意方法。
(..): 表示任意参数的方法。
本示例中,会匹配到下列ProductService、UserService类中的所有方法:
ProductService、UserService类都是接口类,其实现类在impl包下,这时spring aop会默认使用 JDK动态代理。这里只以ProductService为例,代码为:
public interface ProductService { void add(int id); }
再看下其实现类ProductServiceImpl的代码:
@Component public class ProductServiceImpl implements ProductService{ @Override public void add(int id) { System.out.println("ProductService的add方法调用,参数为:"+id); } }
至此,统一日志添加的coding工作已经完成,可以看到业务实现类ProductServiceImpl跟普通spring bean没有任何区别。实际上只是多了一个切面类LogAop。
下面我们使用Junit进行单元测试,看看效果,首先创建spring bean自动装配类SpringConfig,代码如下:
@ComponentScan(basePackages = "com.sky.aop") @EnableAspectJAutoProxy public class SpringConfig { }
@EnableAspectJAutoProxy 注解的作用为: 启动自动代理。
最后再来看下Junit单元测试类:
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes=SpringConfig.class) public class SpringAopTest { @Autowired private ProductService productService; @Test public void productTest(){ productService.add(1); } }
执行测试方法productTest,打印信息如下:
六月 26, 2017 4:58:30 下午 com.sky.aop.LogAop aroundLog 信息: com.sky.aop.service.product.ProductService类的add方法Around通知开始 ProductService的add方法调用,参数为:1 六月 26, 2017 4:58:30 下午 com.sky.aop.LogAop beforeLog 信息: com.sky.aop.service.product.ProductService类的add方法Before日志 六月 26, 2017 4:58:30 下午 com.sky.aop.LogAop aroundLog 信息: com.sky.aop.service.product.ProductService方法Around通知结束 六月 26, 2017 4:58:30 下午 com.sky.aop.LogAop after 信息: com.sky.aop.service.product.ProductService类的add方法after日志 六月 26, 2017 4:58:30 下午 com.sky.aop.LogAop afterRet 信息: com.sky.aop.service.product.ProductService类的add方法AfterReturning日志
这里没有异常抛出,所以只有“异常通知”未执行,其余通知均已执行。测试通过。
基于xml配置方法
业务类不变,为了区分,这里新建一个切面类LogXmlAop,通知方法与上述的LogAop切面类相同,只是去打掉了相关注解,内容如下:
@Component public class LogXmlAop { private static final Log log = LogFactory.getLog(LogAop.class); //前置通知 public void beforeLog(JoinPoint jp){ log.info(jp.getSignature().getDeclaringTypeName()+"类的"+jp.getSignature().getName()+ "方法Before日志"); } //环绕通知 public void aroundLog(ProceedingJoinPoint jp) { try { log.info(jp.getSignature().getDeclaringTypeName()+"类的"+jp.getSignature().getName()+"方法Around通知开始"); jp.proceed(); log.info(jp.getSignature().getDeclaringTypeName() + "方法Around通知结束"); }catch (Throwable throwable) { Object[] args = jp.getArgs(); System.out.println("参数列表值为:"); for (Object one: args){ log.error(one.toString()); } log.error(jp.getSignature().getDeclaringTypeName() + "类的" + jp.getSignature().getName() + "调用异常", throwable); } } //后置通知 public void after(JoinPoint jp){ log.info(jp.getSignature().getDeclaringTypeName()+"类的"+jp.getSignature().getName()+ "方法after日志"); } //返回通知 public void afterRet(JoinPoint jp){ log.info(jp.getSignature().getDeclaringTypeName()+"类的"+jp.getSignature().getName()+ "方法AfterReturning日志"); } //异常通知 public void afterError(JoinPoint jp){ log.info(jp.getSignature().getDeclaringTypeName()+"类的"+jp.getSignature().getName()+ "方法AfterThrowing日志"); } }
可以看到这个类就是一个普通的bean 类,我们通过xml配置可以把这个普通的bean类定义为一个切面类,在classpath下新增配置文件spring-aop.xml,内容如下:
<?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: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.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <context:component-scan base-package="com.sky.aop" /> <aop:config> <aop:aspect ref="logXmlAop"> <aop:pointcut id="log" expression="execution(* com.sky.aop.service.*.*.*(..))"/> <aop:before pointcut-ref="log" method="beforeLog"/> <aop:around pointcut-ref="log" method="aroundLog"/> <aop:after pointcut-ref="log" method="after" /> <aop:after-returning pointcut-ref="log" method="afterRet" /> <aop:after-throwing pointcut-ref="log" method="afterError" /> </aop:aspect> </aop:config> </beans>
可以看到与基于注解的方式差不多,标记切面、定义切点、定义通知:
aop:config:表示该包裹体内部 是spring aop配置;
aop:aspect:定义切面,ref表示引用的spring bean id,这里引用的自动装配bean类LogXmlAop的实例;
aop:pointcut:定义切点,以便定义通知时复用;
aop:before:前置通知
aop:around:环绕通知
aop:after:后置通知
aop:after-returning:返回通知
aop:after-throwing:异常通知
至此,基于“基于xml配置方法”的spring aop实现方式已经完成,主要工作就是通过xml配置把一个普通bean变成一个切面。
下面我们重新编写一个Junit测试类,通过该xml配置文件注入bean,进行测试,代码如下:
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = "classpath:spring-aop.xml") public class SpringAopXmlTest { @Autowired private ProductService productService; @Test public void productTest(){ productService.add(1); } }
可以看到内容基本与“基于注解方法”的测试类相同,只是把@ContextConfiguration注解的参数改为xml配置文件,执行测试方法,打印结果如下:
信息: JSR-330 'javax.inject.Inject' annotation found and supported for autowiring ProductService的add方法调用,参数为:1 六月 26, 2017 5:15:39 下午 com.sky.aop.LogAop beforeLog 信息: com.sky.aop.service.product.ProductService类的add方法Before日志 六月 26, 2017 5:15:39 下午 com.sky.aop.LogAop aroundLog 信息: com.sky.aop.service.product.ProductService类的add方法Around通知开始 六月 26, 2017 5:15:39 下午 com.sky.aop.LogAop aroundLog 信息: com.sky.aop.service.product.ProductService方法Around通知结束 六月 26, 2017 5:15:39 下午 com.sky.aop.LogAop after 信息: com.sky.aop.service.product.ProductService类的add方法after日志 六月 26, 2017 5:15:39 下午 com.sky.aop.LogAop afterRet 信息: com.sky.aop.service.product.ProductService类的add方法AfterReturning日志
测试通过。
注入AspectJ切面
这种方式属于静态代理方法,严格的讲这种方式不属于spring aop,但spring支持基于AspectJ静态代理的方式。使用场景:作为spring aop的补充,当需要创建 目标对象构造方法调用时的切面,此时可以使用spring注入AspectJ切面,分两步即可完成:
第一步,首先创建切面类:
public aspect MyAspectJ { private static final Log log = LogFactory.getLog(MyAspectJ.class); public MyAspectJ(){} //定义构造方法调用切面 pointcut newCreate():execution(com.sky.aop.aspectj.impl.AspectJServiceImpl.new()); Object around():newCreate(){ log.info("调用构造方法开始"); Object result = proceed(); log.info("调用构造方法结束"); return result; } //定义普通方法调用切面 pointcut runLog():execution(* com.sky.aop.aspectj.impl.AspectJServiceImpl.run()); before():runLog(){ log.info("调用普通方法前打印日志"); } }
这里定义了两个切面:一个是目标方法是构造方法,另一个是普通方法。定义了两个通知:一个是环绕通知,一个是前置通知。由于项目中使用较少,这里不做讲解,其他具体用法参考官方文档:http://www.eclipse.org/aspectj/doc/released/progguide/index.html
第二步:把切面注入spring,配置如下:
<?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:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <context:component-scan base-package="com.sky.aop.aspectj" /> <bean class="com.sky.aop.MyAspectJ" factory-method="aspectOf" /> </beans>
我们再来看下,目标方法所在的业务类AspectJServiceImpl:
@Component public class AspectJServiceImpl implements AspectJService{ private static final Log log = LogFactory.getLog(AspectJServiceImpl.class); public AspectJServiceImpl(){ log.info("AspectJServiceImpl构造方法执行"); } @Override public void run() { log.info("AspectJService run方法执行"); } }
注入AspectJ切面 完成,下面创建Junit单元测试,新建测试类SpringAspectJTest,内容如下:
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = "classpath:spring-aspectj-aop.xml") public class SpringAspectJTest { @Autowired private AspectJService aspectJService; @Test public void ajTest(){ aspectJService.run(); } }
执行测试方法报错,信息如下:
java.lang.IllegalStateException: Failed to load ApplicationContext ………..省略日志.......... Caused by: java.lang.ClassNotFoundException: com.sky.aop.MyAspectJ
主要原因是因为切面类MyAspectJ不是class修饰,而是aspect修饰。普通maven编译无法编译AspectJ切面类,必须采用专用的编译器。具体Maven配置如下:
<build> <plugins> <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>aspectj-maven-plugin</artifactId> <version>1.8</version> <executions> <execution> <goals> <goal>compile</goal> </goals> </execution> </executions> <configuration> <complianceLevel>1.8</complianceLevel> <source>1.8</source> <target>1.8</target> </configuration> </plugin> </plugins> </build>
再次执行上述测试方法,打印信息如下:
信息: 调用构造方法开始 六月 26, 2017 9:01:30 下午 com.sky.aop.aspectj.impl.AspectJServiceImpl init$_aroundBody0 信息: AspectJServiceImpl构造方法执行 六月 26, 2017 9:01:30 下午 com.sky.aop.MyAspectJ init$_aroundBody1$advice 信息: 调用构造方法结束 六月 26, 2017 9:01:31 下午 com.sky.aop.MyAspectJ ajc$before$com_sky_aop_MyAspectJ$2$2f971b7a 信息: 调用普通方法前打印日志 六月 26, 2017 9:01:31 下午 com.sky.aop.aspectj.impl.AspectJServiceImpl run 信息: AspectJService run方法执行
测试成功。
至此两种常用的spring aop实现方法,以及一种spring注入AspectJ切面的方式 讲解完毕。
以上示例代码地址:https://github.com/gantianxing/spring-aop.git
相关推荐
在Spring XML配置文件中,我们可以定义以下元素来实现AOP配置: - `<aop:config>`:声明AOP配置。 - `<aop:pointcut>`:定义切入点表达式,例如`execution(* com.example.service.*.*(..))`表示匹配...
Spring AOP 提供了一种灵活的方式来实现事务管理,通过配置事务特性和事务管理切面来实现事务管理。 配置事务管理切面: 在 Spring AOP 中,事务管理切面是通过 `<aop:config>` 元素来配置的。该元素用于定义一个...
这种方式虽然相比注解方式略显繁琐,但对于大型项目或者需要精细控制AOP配置的情况,仍然是一个很好的选择。通过深入理解和实践,我们可以更好地利用Spring AOP来优化我们的应用程序,提高代码的可读性和可维护性。
### Spring之AOP配置文件详解 #### 一、前言 在Java开发中,Spring框架因其强大的功能和灵活的配置而被广泛应用于企业级应用的开发。其中,面向切面编程(Aspect Oriented Programming,简称AOP)是Spring框架的...
2. **注解配置**:Spring 2.5引入了基于注解的AOP配置,可以在切面类上使用@Aspect注解,@Before、@After、@AfterReturning、@AfterThrowing和@Around定义通知,@Pointcut定义切点。例如: ```java @Aspect ...
在使用Spring AOP时,我们可以通过XML配置或注解的方式来定义切面。例如,可以使用`@Aspect`注解定义一个切面类,`@Before`、`@After`等注解来声明通知,`@Pointcut`定义切点表达式。 在实际开发中,Spring AOP广泛...
**Spring AOP XML方式配置通知** 在Java世界中,Spring框架是广泛应用的IoC(Inversion of Control)和AOP(Aspect Oriented Programming)容器。AOP允许开发者定义“方面”,这些方面可以封装关注点,如日志、事务...
**Spring AOP配置实例** Spring AOP(Aspect Oriented Programming,面向切面编程)是Spring框架的核心组件之一,它提供了一种在不修改源代码的情况下,对程序进行功能增强的技术。AOP允许开发者定义“切面”,这些...
本文主要介绍几种常见的Spring AOP配置方式,并通过具体的示例来说明每种配置的特点。 #### 二、AOP配置所需基本元素 配置AOP时需要以下三个基本元素: 1. **Advice**:这是实际执行的代码,即我们所说的“切面”...
Spring支持两种AOP的实现方式:Spring AspectJ注解风格和Spring XML配置风格。使用AspectJ注解风格是最常见的,它允许开发者直接在方法上使用注解来定义切面。 Spring AOP中有五种不同类型的的通知(Advice): 1....
Spring AOP(面向切面编程)是Spring框架的重要组成部分,它提供了一种模块化和声明式的方式来处理系统中的交叉关注点问题,如日志、事务管理、安全性等。本示例将简要介绍如何在Spring应用中实现AOP,通过实际的...
下面将详细介绍Spring AOP的注解方式和XML配置方式。 ### 注解方式 #### 1. 定义切面(Aspect) 在Spring AOP中,切面是包含多个通知(advisors)的类。使用`@Aspect`注解标记切面类,例如: ```java @Aspect ...
总结一下,Spring AOP提供了一种优雅的方式来处理系统的横切关注点,如日志记录、事务管理或性能监控。通过定义切点、创建切面和配置通知,我们可以实现代码的解耦,提高可维护性和复用性。这个例子提供了学习Spring...
Spring AOP,全称Spring Aspect-Oriented Programming(面向切面编程),是Spring框架的重要组成部分,它提供了一种模块化和声明式的方式来处理系统中的交叉关注点,如日志、事务管理、性能监控等。在Spring AOP中,...
现在,我们回到主题——"springaop依赖的jar包"。在Spring 2.5.6版本中,使用Spring AOP通常需要以下核心jar包: - `spring-aop.jar`:这是Spring AOP的核心库,包含了AOP相关的类和接口。 - `spring-beans.jar`:...
Spring AOP配置 Spring AOP的配置可以通过XML或注解方式进行: - **XML配置**: - 在`<aop:config>`标签内定义切面,`<aop:pointcut>`定义切入点,`<aop:advisor>`定义通知。 - `<aop:aspect>`标签用于定义完整...
通过以上介绍,我们可以看到Spring的注解AOP配置是如何让代码更简洁、更易于理解和维护的。结合实际的项目需求,我们可以灵活地使用这些注解来实现各种企业级功能,如日志、事务控制等,从而提高代码的复用性和模块...
在本示例中,"springaop.zip" 包含了一个使用XML配置的Spring AOP应用实例,可以直接运行,配合相关的博客文章学习效果更佳。 在Spring AOP中,我们首先需要了解几个核心概念: 1. **切面(Aspect)**:切面是关注...
本示例提供了一种通过注解和配置文件两种方式实现Spring AOP的方法。 首先,我们来详细讲解通过注解实现Spring AOP。在Spring中,我们可以使用`@Aspect`注解来定义一个切面,这个切面包含了多个通知(advice),即...
Spring AOP,全称Aspect-Oriented Programming(面向切面编程),是Spring框架的一个重要模块,它通过提供声明式的方式来实现面向切面编程,从而简化了应用程序的开发和维护。在Spring AOP中,我们无需深入到每个...