什么是AOP
AOP(Aspect-OrientedProgramming,面向方面编程),可以说是OOP(Object-Oriented Programing,面向对象编程)的补充和完善。OOP引入封装、继承和多态性等概念来建立一种对象层次结构,用以模拟公共行为的一个集合。当我们需要为分散的对象引入公共行为的时候,OOP则显得无能为力。也就是说,OOP允许你定义从上到下的关系,但并不适合定义从左到右的关系。例如日志功能。日志代码往往水平地散布在所有对象层次中,而与它所散布到的对象的核心功能毫无关系。对于其他类型的代码,如安全性、异常处理和透明的持续性也是如此。这种散布在各处的无关的代码被称为横切(cross-cutting)代码,在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用。
而AOP技术则恰恰相反,它利用一种称为“横切”的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其名为“Aspect”,即方面。然后通过一些技术将模块在合适时机注入到目标对象中去。。。。。。。
使用“横切”技术,AOP把软件系统分为两个部分:核心关注点和横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处都基本相似。比如权限认证、日志、事务处理。Aop 的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来。正如Avanade公司的高级方案构架师Adam Magee所说,AOP的核心思想就是“将应用程序中的商业逻辑同对其提供支持的通用服务进行分离。”
实现AOP的技术,主要分为两大类:一是采用动态代理技术,利用截取消息的方式,对该消息进行装饰,以取代原有对象行为的执行;二是采用静态织入的方式,引入特定的语法创建“方面”,从而使得编译器可以在编译期间织入有关“方面”的代码。
JDK动态代理,要实现接口;Cglib动态代理,就代理类,无须实现接口
AOP使用场景
AOP用来封装横切关注点,具体可以在下面的场景中使用:
Authentication 权限
Caching 缓存
Context passing 内容传递
Error handling 错误处理
Lazy loading 懒加载
Debugging 调试
logging, tracing, profiling and monitoring 记录跟踪 优化 校准
Performance optimization 性能优化
Persistence 持久化
Resource pooling 资源池
Synchronization 同步
Transactions 事务
AOP相关概念
方面(Aspect):切面由切点和增强(引介)组成,类似于Class。一个关注点的模块化,这个关注点实现可能另外横切多个对象。事务管理是J2EE应用中一个很好的横切关注点例子。方面用Spring的 Advisor或拦截器实现。
连接点(Joinpoint): 程序执行过程中明确的点,如方法的调用或特定的异常被抛出。
通知(Advice): 在特定的连接点,AOP框架执行的动作。各种类型的通知包括“around”、“before”和“throws”通知。通知类型将在下面讨论。许多AOP框架包括Spring都是以拦截器做通知模型,维护一个“围绕”连接点的拦截器链。Spring中定义了四个advice: BeforeAdvice, AfterAdvice, ThrowAdvice和DynamicIntroductionAdvice
切入点(Pointcut): 指定一个通知将被引发的一系列连接点的集合。AOP框架必须允许开发者指定切入点:例如,使用正则表达式。 Spring定义了Pointcut接口,用来组合MethodMatcher和ClassFilter,可以通过名字很清楚的理解, MethodMatcher是用来检查目标类的方法是否可以被应用此通知,而ClassFilter是用来检查Pointcut是否应该应用到目标类上
引入(Introduction): 添加方法或字段到被通知的类。 Spring允许引入新的接口到任何被通知的对象。例如,你可以使用一个引入使任何对象实现 IsModified接口,来简化缓存。Spring中要使用Introduction, 可有通过DelegatingIntroductionInterceptor来实现通知,通过DefaultIntroductionAdvisor来配置Advice和代理类要实现的接口
目标对象(Target Object): 包含连接点的对象。也被称作被通知或被代理对象。POJO
AOP代理(AOP Proxy): AOP框架创建的对象,包含通知。 在Spring中,AOP代理可以是JDK动态代理或者CGLIB代理。
织入(Weaving): 组装方面来创建一个被通知对象。这可以在编译时完成(例如使用AspectJ编译器),也可以在运行时完成。Spring和其他纯Java AOP框架一样,在运行时完成织入。
Spring AOP使用动态代理技术,JDK动态代理,针对接口的;Cglib针对没有实现接口的类。
- 调用者Bean尝试调用目标方法,但是被生成的代理截了胡
- 代理根据Advice的种类(本例中是@Before Advice),对Advice首先进行调用,如果Advice种类是其他的,代理会在合适时机加上执行Advice。
- 代理调用目标方法
- 返回调用结果给调用者Bean(由代理返回,没有体现在图中)
如何使用Spring AOP
可以通过配置文件或者编程的方式来使用Spring AOP。
配置可以通过xml文件来进行,大概有四种方式:
1. 配置ProxyFactoryBean,显式地设置advisors, advice, target等
2. 配置AutoProxyCreator,这种方式下,还是如以前一样使用定义的bean,但是从容器中获得的其实已经是代理对象
3. 通过<aop:config>来配置
4. 通过<aop: aspectj-autoproxy>来配置,使用AspectJ的注解来标识通知及切入点,spring 2.0之后引入的。
一、基于XML配置的Spring AOP <aop:config>
采用声明的方式实现(在XML文件中配置),大致步骤为:配置文件中配置pointcut, 在java中用编写实际的aspect 类, 针对对切入点进行相关的业务处理。
业务接口:
package com.spring.service; public interface IUserManagerService { //查找用户 public String findUser(); //添加用户 public void addUser(); }
业务实现:
package com.spring.service.impl; import com.spring.service.IUserManagerService; public class UserManagerServiceImpl implements IUserManagerService{ private String name; public void setName(String name){ this.name=name; } public String getName(){ return this.name; } public String findUser(){ System.out.println("============执行业务方法findUser,查找的用户是:"+name+"============="); return name; } public void addUser(){ System.out.println("============执行业务方法addUser============="); //throw new RuntimeException(); } }
切面类:
package com.spring.aop; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; public class AopAspect { /** * 前置通知:目标方法调用之前执行的代码 * @param jp */ public void doBefore(JoinPoint jp){ System.out.println("===========执行前置通知============"); } /** * 后置返回通知:目标方法正常结束后执行的代码 * 返回通知是可以访问到目标方法的返回值的 * @param jp * @param result */ public void doAfterReturning(JoinPoint jp,String result){ System.out.println("===========执行后置通知============"); System.out.println("返回值result==================="+result); } /** * 最终通知:目标方法调用之后执行的代码(无论目标方法是否出现异常均执行) * 因为方法可能会出现异常,所以不能返回方法的返回值 * @param jp */ public void doAfter(JoinPoint jp){ System.out.println("===========执行最终通知============"); } /** * * 异常通知:目标方法抛出异常时执行的代码 * 可以访问到异常对象 * @param jp * @param ex */ public void doAfterThrowing(JoinPoint jp,Exception ex){ System.out.println("===========执行异常通知============"); } /** * 环绕通知:目标方法调用前后执行的代码,可以在方法调用前后完成自定义的行为。 * 包围一个连接点(join point)的通知。它会在切入点方法执行前执行同时方法结束也会执行对应的部分。 * 主要是调用proceed()方法来执行切入点方法,来作为环绕通知前后方法的分水岭。 * * 环绕通知类似于动态代理的全过程:ProceedingJoinPoint类型的参数可以决定是否执行目标方法。 * 而且环绕通知必须有返回值,返回值即为目标方法的返回值 * @param pjp * @return * @throws Throwable */ public Object doAround(ProceedingJoinPoint pjp) throws Throwable{ System.out.println("======执行环绕通知开始========="); // 调用方法的参数 Object[] args = pjp.getArgs(); // 调用的方法名 String method = pjp.getSignature().getName(); // 获取目标对象 Object target = pjp.getTarget(); // 执行完方法的返回值 // 调用proceed()方法,就会触发切入点方法执行 Object result=pjp.proceed(); System.out.println("输出,方法名:" + method + ";目标对象:" + target + ";返回值:" + result); System.out.println("======执行环绕通知结束========="); return result; } }
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:aop="http://www.springframework.org/schema/aop" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd"> <!-- 声明一个业务类 --> <bean id="userManager" class="com.spring.service.impl.UserManagerServiceImpl"> <property name="name" value="lixiaoxi"></property> </bean> <!-- 声明通知类 --> <bean id="aspectBean" class="com.spring.aop.AopAspect" /> <aop:config> <aop:aspect ref="aspectBean"> <aop:pointcut id="pointcut" expression="execution(* com.spring.service.impl.UserManagerServiceImpl..*(..))"/> <aop:before method="doBefore" pointcut-ref="pointcut"/> <aop:after-returning method="doAfterReturning" pointcut-ref="pointcut" returning="result"/> <aop:after method="doAfter" pointcut-ref="pointcut" /> <aop:around method="doAround" pointcut-ref="pointcut"/> <aop:after-throwing method="doAfterThrowing" pointcut-ref="pointcut" throwing="ex"/> </aop:aspect> </aop:config> </beans>
测试类:
package com.spring.test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import com.spring.service.IUserManagerService; public class TestAop { public static void main(String[] args) throws Exception{ ApplicationContext act = new ClassPathXmlApplicationContext("applicationContext3.xml"); IUserManagerService userManager = (IUserManagerService)act.getBean("userManager"); userManager.findUser(); System.out.println(" "); userManager.addUser(); } }
测试结果:
二、使用注解配置AOP
采用注解来做aop, 主要是将写在spring 配置文件中的连接点写到注解里面。
业务接口和业务实现与上边一样,具体切面类如下:
package com.spring.aop; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.AfterThrowing; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; @Aspect public class AopAspectJ { /** * 必须为final String类型的,注解里要使用的变量只能是静态常量类型的 */ public static final String EDP="execution(* com.spring.service.impl.UserManagerServiceImpl..*(..))"; /** * 切面的前置方法 即方法执行前拦截到的方法 * 在目标方法执行之前的通知 * @param jp */ @Before(EDP) public void doBefore(JoinPoint jp){ System.out.println("=========执行前置通知=========="); } /** * 在方法正常执行通过之后执行的通知叫做返回通知 * 可以返回到方法的返回值 在注解后加入returning * @param jp * @param result */ @AfterReturning(value=EDP,returning="result") public void doAfterReturning(JoinPoint jp,String result){ System.out.println("===========执行后置通知============"); } /** * 最终通知:目标方法调用之后执行的通知(无论目标方法是否出现异常均执行) * @param jp */ @After(value=EDP) public void doAfter(JoinPoint jp){ System.out.println("===========执行最终通知============"); } /** * 环绕通知:目标方法调用前后执行的通知,可以在方法调用前后完成自定义的行为。 * @param pjp * @return * @throws Throwable */ @Around(EDP) public Object doAround(ProceedingJoinPoint pjp) throws Throwable{ System.out.println("======执行环绕通知开始========="); // 调用方法的参数 Object[] args = pjp.getArgs(); // 调用的方法名 String method = pjp.getSignature().getName(); // 获取目标对象 Object target = pjp.getTarget(); // 执行完方法的返回值 // 调用proceed()方法,就会触发切入点方法执行 Object result=pjp.proceed(); System.out.println("输出,方法名:" + method + ";目标对象:" + target + ";返回值:" + result); System.out.println("======执行环绕通知结束========="); return result; } /** * 在目标方法非正常执行完成, 抛出异常的时候会走此方法 * @param jp * @param ex */ @AfterThrowing(value=EDP,throwing="ex") public void doAfterThrowing(JoinPoint jp,Exception ex) { System.out.println("===========执行异常通知============"); } }
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:aop="http://www.springframework.org/schema/aop" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd"> <!-- 声明spring对@AspectJ的支持 --> <aop:aspectj-autoproxy/> <!-- 声明一个业务类 --> <bean id="userManager" class="com.spring.service.impl.UserManagerServiceImpl"> <property name="name" value="lixiaoxi"></property> </bean> <!-- 声明通知类 --> <bean id="aspectBean" class="com.spring.aop.AopAspectJ" /> </beans>
测试类:
package com.spring.test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import com.spring.service.IUserManagerService; public class TestAop1 { public static void main(String[] args) throws Exception{ ApplicationContext act = new ClassPathXmlApplicationContext("applicationContext4.xml"); IUserManagerService userManager = (IUserManagerService)act.getBean("userManager"); userManager.findUser(); System.out.println(" "); userManager.addUser(); } }
测试结果与上面相同。
参考:https://blog.csdn.net/moreevan/article/details/11977115/ 、 https://www.cnblogs.com/xiaoxi/p/5981514.html