1.Spring AOP(面向切面编程)是什么?
面向切面编程(AOP)和面向对象编程(OOP)类似,也是一种编程模式。Spring AOP 是基于 AOP 编程模式的一个框架,它的使用有效减少了系统间的重复代码,达到了模块间的松耦合目的。
AOP 的全称是“Aspect Oriented Programming”,即面向切面编程,它将业务逻辑的各个部分进行隔离,使开发人员在编写业务逻辑时可以专心于核心业务,从而提高了开发效率。
AOP 采取横向抽取机制,取代了传统纵向继承体系的重复性代码,其应用主要体现在事务处理、日志管理、权限控制、异常处理等方面。
目前最流行的 AOP 框架有两个,分别为 Spring AOP 和 AspectJ。
Spring AOP 使用纯 Java 实现,不需要专门的编译过程和类加载器,在运行期间通过代理方式向目标类植入增强的代码。
AspectJ 是一个基于 Java 语言的 AOP 框架,从 Spring 2.0 开始,Spring AOP 引入了对 AspectJ 的支持。AspectJ 扩展了 Java 语言,提供了一个专门的编译器,在编译时提供横向代码的植入。
为了更好地理解 AOP,就需要对 AOP 的相关术语有一些了解,这些专业术语主要包含 Joinpoint、Pointcut、Advice、Target、Weaving、Proxy 和 Aspect,它们的含义如下表所示。
名称 | 说明 |
Joinpoint(连接点) | 指那些被拦截到的点,在 Spring 中,可以被动态代理拦截目标类的方法。 |
Pointcut(切入点) | 指要对哪些 Joinpoint 进行拦截,即被拦截的连接点。 |
Advice(通知) | 指拦截到 Joinpoint 之后要做的事情,即对切入点增强的内容。 |
Target(目标) | 指代理的目标对象。 |
Weaving(植入) | 指把增强代码应用到目标上,生成代理对象的过程。 |
Proxy(代理) | 指生成的代理对象。 |
Aspect(切面) | 切入点和通知的结合。 |
2.Spring AOP的基本概念和原理
Spring AOP(Aspect-Oriented Programming)是Spring框架中的一个重要特性,用于实现面向切面编程。它通过在不修改原有代码的情况下,将横切关注点(Cross-cutting Concerns)从业务逻辑中分离出来,实现了模块化的开发和更好的代码重用。
AOP的概念和作用
AOP是一种编程范式,旨在解决横切关注点的代码重复问题。
AOP通过将横切关注点从主业务逻辑中分离出来,实现了更好的代码模块化和可维护性。
AOP可以在不改变原有代码的情况下,通过织入(Weaving)切面(Aspect)来实现对横切关注点的处理。
Spring AOP的基本原理和实现方式
Spring AOP基于动态代理(Dynamic Proxy)实现。
Spring AOP提供了两种代理方式:JDK动态代理和CGLIB代理。
JDK动态代理适用于基于接口的代理,而CGLIB代理适用于类级别的代理。
Spring AOP通过代理对象将切面织入到目标对象的方法调用中,实现对横切关注点的处理。
AOP中的主要概念
切面(Aspect):横切关注点的模块化单元,它包含了通知和切点。
连接点(Join Point):在程序执行过程中能够被切面织入的特定点。
切点(Pointcut):用于定义连接点的表达式,指定了哪些连接点将被切面织入。
通知(Advice):切面在连接点上执行的动作,包括前置通知、后置通知、异常通知、环绕通知和引入通知。
织入(Weaving):将切面应用到目标对象中的过程,可以在编译时、类加载时或运行时进行。
总结:Spring AOP通过代理和织入的方式,实现了对横切关注点的处理。它的基本原理是通过动态代理来生成代理对象,并将切面织入到目标对象的方法调用中。在AOP中,切面、连接点、切点、通知和织入是重要的概念,它们共同构成了Spring AOP的基本框架。
3.Spring AOP的实现方式
Spring AOP的实现方式主要有三种:
基于动态代理的方式:利用Java反射机制,在运行时动态生成代理对象,对被代理对象的方法进行增强。Spring默认采用JDK动态代理实现AOP。
基于CGLIB的方式:利用CGLIB库,在运行时动态生成被代理对象的子类,对需要增强的方法进行重写。CGLIB方式比JDK动态代理方式更加强大,可代理不实现任何接口的普通Java类。
基于AspectJ的方式:AspectJ是一个独立的AOP框架,Spring可以通过整合AspectJ实现AOP。AspectJ提供了更加灵活、强大的AOP功能,支持更多的切面表达式和增强方式。
在实际开发中,一般采用JDK动态代理方式实现AOP。当需要增强的对象没有实现接口时,才会考虑CGLIB方式。而使用AspectJ方式则较少见,一般用于需要特定的AOP功能的场景。
4.Spring AOP的注意事项
AOP只是一种编程思想,需要和具体的框架结合使用。
Spring AOP只支持方法级别的切面,不支持构造函数级别的切面。
了解切点表达式的语法和用法,以便编写出正确的切点表达式。
在进行AOP配置时,需要注意切面的优先级和顺序,以确保切面的正确顺序。
在使用AOP时,需要注意性能问题。过多的切面会增加方法调用的开销,影响应用程序的性能。
在进行AOP配置时,需要考虑到目标对象的生命周期,以避免出现因切面的生命周期问题导致的应用程序异常。
AOP可以用来解耦代码,但是过度的切面会将应用程序的控制权交给AOP框架,影响应用程序的可读性和可维护性。
5.Spring AOP的扩展
Spring AOP的扩展有:
1. 自定义切点:可以创建自定义切点,让切面在更细粒度地地方切入。
2. 异常通知:可以在方法抛出异常时执行通知,让代码更健壮。
3. 环绕通知:可以在方法调用前后执行通知,可以对方法的参数和返回值进行修改。
4. 注解驱动:可以使用注解来定义切面和通知,让代码更简洁和易于维护。
5. AspectJ集成:可以使用AspectJ的注解和表达式来定义切面和通知,更加灵活和强大。
6. 注入切面:可以将切面作为Spring的Bean注入到容器中,让切面更加可重用和可定制。
6.Spring AOP的应用场景
Spring AOP的应用场景包括但不限于以下几个方面:
日志记录:可以使用AOP拦截方法,记录方法的执行时间、参数、返回值等信息,实现日志的自动记录。
安全控制:可以使用AOP拦截访问操作,实现对用户访问权限的控制,例如检查用户是否有权限访问某些资源。
性能监控:可以使用AOP拦截方法,监控方法的执行时间、调用次数等信息,实现对系统性能的监控和优化。
缓存管理:可以使用AOP拦截方法,实现缓存管理,例如缓存对象的读取、写入、删除等操作。
事务管理:可以使用AOP拦截方法,实现事务管理,例如对数据库操作进行事务控制。
异常处理:可以使用AOP拦截方法,实现异常处理,例如捕获异常并记录日志或者发送通知。
总之,AOP可以在很多领域进行应用,可以简化程序的编写和维护,提高程序的可重用性和可扩展性。
7.Spring AOP的优缺点
Spring AOP是Spring框架提供的一种面向切面编程的实现方式,它有以下优点和缺点:
优点:
1. 降低了代码的重复性:通过将横切关注点(如日志记录、事务管理等)从业务逻辑中分离出来,可以减少代码的重复性,提高代码的可维护性和可读性。
2. 提高了代码的模块化和可重用性:将横切关注点封装成切面,可以将其应用到多个目标对象中,提高了代码的模块化和可重用性。
3. 简化了业务逻辑的编写:通过使用切面,可以将与业务逻辑无关的代码(如事务管理、异常处理等)从业务逻辑中抽离出来,使业务逻辑更加清晰和简洁。
4. 提供了更好的代码结构和可扩展性:通过使用切面,可以将不同关注点的代码分离,使代码结构更加清晰,并且可以灵活地添加、修改或删除切面,以满足不同的需求。
缺点:
1. 仅支持方法级别的切面:Spring AOP只能对方法进行切面织入,对于其他级别的切面(如字段访问、对象创建等),需要使用其他的AOP框架。
2. 无法拦截私有方法和静态方法:由于Spring AOP基于动态代理实现,无法拦截私有方法和静态方法。
3. 对性能有一定的影响:由于Spring AOP使用动态代理来生成代理对象,并在方法调用时进行切面织入,会增加一定的性能开销。
4. 需要依赖Spring框架:使用Spring AOP需要依赖Spring框架,对于不使用Spring框架的项目来说,引入Spring框架可能会增加项目的复杂性。
总结:Spring AOP具有降低代码重复性、提高代码模块化和可重用性、简化业务逻辑编写和提供更好的代码结构和可扩展性等优点。然而,它也存在仅支持方法级别的切面、无法拦截私有方法和静态方法、对性能有一定的影响和需要依赖Spring框架等缺点。在使用Spring AOP时,需要根据具体的需求和场景来权衡其优缺点。
8.Spring AOP与其他技术的比较
Spring AOP与其他常见的技术进行比较如下:
1. Java原生的动态代理:
- Spring AOP基于动态代理实现,可以在目标对象的方法调用前后织入切面逻辑。而Java原生的动态代理只能代理接口,无法代理类,且只能在方法调用前后进行拦截,无法实现更复杂的切面逻辑。
2. AspectJ:
- AspectJ是一个功能更强大的AOP框架,与Spring AOP相比,AspectJ支持更多的切面织入方式,如字段访问、对象创建等,且支持更丰富的切点表达式。但AspectJ需要在编译时进行织入,需要修改源代码或使用特定的编译器。
3. JPA(Java Persistence API):
- JPA是Java持久化API的一种标准规范,它提供了一种面向对象的持久化解决方案。与Spring AOP不同,JPA主要关注数据持久化层面的切面,如事务管理、缓存管理等,而不是应用层面的切面。
4. Spring Transaction Management:
- Spring的事务管理模块提供了一种声明式的事务管理方式,可以通过配置来管理事务。与Spring AOP相比,Spring的事务管理更加专注于事务相关的切面,如事务的开始、提交、回滚等,而不涉及其他横切关注点。
总结:Spring AOP相对于其他技术来说,更加专注于应用层面的切面编程,提供了一种简单、灵活的切面实现方式。与Java原生的动态代理相比,Spring AOP在功能上更加丰富,可以代理类而不仅仅是接口。与AspectJ相比,Spring AOP的配置更加简单,且不需要修改源代码或使用特定的编译器。与JPA和Spring的事务管理相比,Spring AOP关注的切面范围更广,可以应用于更多的横切关注点。在选择使用哪种技术时,需要根据具体的需求和场景来进行权衡和选择。
9.AOP通知
Spring AOP(面向切面编程)是一种编程范式,它允许我们在应用程序运行时动态地添加行为或切面。其中,前置通知、后置通知、环绕通知、异常通知和过滤通知是 Spring AOP 中的常见类型。
前置通知
前置通知(Before advice):在目标方法执行前,执行的通知。可以用来在方法执行前进行一些准备或检查操作。
spring-context.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" default-autowire="byType" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!--目标对象--> <bean class="com.aop.biz.impl.BookBizImpl" name="bookBiz"></bean> <!--通知--> <bean class="com.aop.biz.advice.MyMethodBeforeAdvice" id="methodBeforeAdvice"></bean> <!--代理--> <bean class="org.springframework.aop.framework.ProxyFactoryBean" id="bookProxy"> <!-- 配置目标对象--> <property name="target" ref="bookBiz"></property> <!-- 配置代理的接口--> <property name="proxyInterfaces"> <list> <value>com.aop.biz.IBookBiz</value> </list> </property> <!--配置通知--> <property name="interceptorNames"> <list> <value>methodBeforeAdvice</value> </list> </property> </bean> </beans>
package com.aop.biz.advice; import org.springframework.aop.MethodBeforeAdvice; import java.lang.reflect.Method; import java.util.Arrays; /** * 买书、评论前加系统日志 * @author Administrator * */ public class MyMethodBeforeAdvice implements MethodBeforeAdvice { @Override public void before(Method arg0, Object[] arg1, Object arg2) throws Throwable { // 在这里,可以获取到目标类的全路径及方法及方法参数,然后就可以将他们写到日志表里去 String target = arg2.getClass().getName(); String methodName = arg0.getName(); String args = Arrays.toString(arg1); System.out.println("【前置通知:系统日志】:"+target+"."+methodName+"("+args+")被调用了"); } }
后置通知
后置通知(After advice):在目标方法执行后,执行的通知。可以用来在方法执行后进行一些清理操作,例如释放资源或记录执行结果。
package com.aop.biz.advice; import org.springframework.aop.AfterReturningAdvice; import java.lang.reflect.Method; import java.util.Arrays; /** * 买书返利 * @author Administrator * */ public class MyAfterReturningAdvice implements AfterReturningAdvice { @Override public void afterReturning(Object arg0, Method arg1, Object[] arg2, Object arg3) throws Throwable { String target = arg3.getClass().getName(); String methodName = arg1.getName(); String args = Arrays.toString(arg2); System.out.println("【后置通知:买书返利】:"+target+"."+methodName+"("+args+")被调用了,"+"该方法被调用后的返回值为:"+arg0); } }
环绕通知
环绕通知(Around advice):在目标方法执行前和执行后,执行的通知。可以用来在目标方法执行前后做一些预处理和后处理操作。
package com.zking.aop.advice; import java.util.Arrays; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; /** * 环绕通知 * 包含了前置和后置通知 * * @author Administrator * */ public class MyMethodInterceptor implements MethodInterceptor { @Override public Object invoke(MethodInvocation arg0) throws Throwable { String target = arg0.getThis().getClass().getName(); String methodName = arg0.getMethod().getName(); String args = Arrays.toString(arg0.getArguments()); System.out.println("【环绕通知调用前:】:"+target+"."+methodName+"("+args+")被调用了"); // arg0.proceed()就是目标对象的方法 Object proceed = arg0.proceed(); System.out.println("【环绕通知调用后:】:该方法被调用后的返回值为:"+proceed); return proceed; } }
异常通知
异常通知(After throwing advice):在目标方法抛出异常时,执行的通知。可以用来做一些异常处理操作。
package com.aop.biz.advice; import org.springframework.aop.ThrowsAdvice; import com.aop.biz.Exception.PriceException; /** * 出现异常执行系统提示,然后进行处理。价格异常为例 * @author Administrator * */ public class MyThrowsAdvice implements ThrowsAdvice { public void afterThrowing(PriceException ex) { System.out.println("【异常通知】:当价格发生异常,那么执行此处代码块!!!"); } }
过滤通知
过滤通知(After returning advice):在目标方法执行成功并正常返回时,执行的通知。可以用来做一些返回结果的处理。
所有用到的bean <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" default-autowire="byType" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!--目标对象--> <bean class="com.aop.biz.impl.BookBizImpl" name="bookBiz"></bean> <!--通知--> <bean class="com.aop.biz.advice.MyMethodBeforeAdvice" id="methodBeforeAdvice"></bean> <bean class="com.aop.biz.advice.MyAfterReturningAdvice" id="myAfterReturningAdvice"></bean> <bean class="com.aop.biz.advice.MyMethodInterceptor" id="myMethodInterceptor"></bean> <bean class="com.aop.biz.advice.MyThrowsAdvice" id="myThrowsAdvice"></bean> <bean class="org.springframework.aop.support.RegexpMethodPointcutAdvisor" id="regexpMethodPointcutAdvisor"> <property name="advice" ref="myAfterReturningAdvice"></property> <property name="pattern" value=".*buy"></property> </bean> <!--代理--> <bean class="org.springframework.aop.framework.ProxyFactoryBean" id="bookProxy"> <!-- 配置目标对象--> <property name="target" ref="bookBiz"></property> <!-- 配置代理的接口--> <property name="proxyInterfaces"> <list> <value>com.aop.biz.IBookBiz</value> </list> </property> <!--配置通知--> <property name="interceptorNames"> <list> <value>methodBeforeAdvice</value> <!-- <value>myAfterReturningAdvice</value>--> <value>regexpMethodPointcutAdvisor</value> <value>myMethodInterceptor</value> <value>myThrowsAdvice</value> </list> </property> </bean> </beans>