Spring框架之AOP
概念
-
AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
-
SpringAOP是继spring ioc的 spring 的另一大特点。他也是spring框架的核心内容。
-
Spring AOP的实现是基于Java的代理机制,从JDK1.3开始就支持代理功能,但是性能成为一个很大问题,为了解决JDK代理性能问题,出现了CGLIB代理机制(继承你的代理类)。它可以生成字节码,所以它的性能会高于JDK代理。Spring支持这两种代理方式。但是,随着JVM(Java虚拟机)的性能的不断提高,这两种代理性能的差距会越来越小。
-
AOP适合于那些具有横切(切面)逻辑的应用:如性能监测,访问控制,事务管理、缓存、对象池管理以及日志记录。AOP将这些分散在各个业务逻辑中的代码通过横向切割的方式抽取到一个独立的模块中。AOP 实现的关键就在于 AOP 框架自动创建的 AOP 代理,AOP 代理则可分为静态代理和动态代理两大类,其中静态代理是指使用 AOP 框架提供的命令进行编译,从而在编译阶段就可生成 AOP 代理类,因此也称为编译时增强;而动态代理则在运行时借助于 JDK 动态代理、CGLIB 等在内存中“临时”生成 AOP 动态代理类,因此也被称为运行时增强。
-
应用: Aop就是在不增加代码的情况下,还添加了新的功能。日志,事务,等等。
-
Spring AOP 则采用运行时(动态代理)生成 AOP 代理类,因此无需使用特定编译器进行处理。由于 Spring AOP 需要在每次运行时生成 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):其实就是共有功能的实现。如日志切面、权限切面、事务切面等。在实际应用中通常是一个存放共有功能实现的普通Java类,之所以能被AOP容器识别成切面,是在配置中指定的。
- 通知(Advice):是切面的具体实现。以目标方法为参照点,根据放置的地方不同,可分为前置通知(Before)、后置通知(AfterReturning)、异常通知(AfterThrowing)、最终通知(After)与环绕通知(Around)5种。在实际应用中通常是切面类中的一个方法,具体属于哪类通知,同样是在配置中指定的。—其实也就是一个拦截器
- 连接点(Joinpoint):就是程序在运行过程中能够插入切面的地点。例如,方法调用、异常抛出或字段修改等,但Spring只支持方法级的连接点。
- 切入点(Pointcut):用于定义通知应该切入到哪些连接点上。不同的通知通常需要切入到不同的连接点上,这种精准的匹配是由切入点的正则表达式来定义的。
- 目标对象(Target):就是那些即将切入切面的对象,也就是那些被通知的对象。这些对象中已经只剩下干干净净的核心业务逻辑代码了,所有的共有功能代码等待AOP容器的切入。
- 代理对象(Proxy):将通知应用到目标对象之后被动态创建的对象。可以简单地理解为,代理对象的功能等于目标对象的核心业务逻辑功能加上共有功能。代理对象对于使用者而言是透明的,是程序运行过程中的产物。
- 织入(Weaving):将切面应用到目标对象从而创建一个新的代理对象的过程。这个过程可以发生在编译期、类装载期及运行期,当然不同的发生点有着不同的前提条件。譬如发生在编译期的话,就要求有一个支持这种AOP实现的特殊编译器;发生在类装载期,就要求有一个支持AOP实现的特殊类装载器;只有发生在运行期,则可直接通过Java语言的反射机制与动态代理机制来动态实现。
-
Spring 的aop通知(advice)类型如下:
- 前置通知[Before advice]:在连接点前面执行,前置通知不会影响连接点的执行,除非此处抛出异常。 (就像你进入自动取款机之前,他会通知你欢迎光临)
-后置返回通知[After returning advice]:在连接点正常执行完成后执行,如果连接点抛出异常,则不会执行。事务操作等等 (就是你作完自己的业务后,他就会提醒欢迎下次光临) - 异常返回通知[After throwing advice]:在连接点抛出异常后执行。 (取款过程中出现了异常,停电报警)
- 最终通知[After (finally) advice]:在连接点执行完成后执行,不管是正常执行完成,还是抛出异常,都会执行返回通知中的内容。 (就是你取款成功或者不成功他都会谢谢光临)
- 环绕通知[Around advice]:环绕通知围绕在连接点前后,比如一个方法调用的前后。这是最强大的通知类型,能在方法调用前后自定义一些操作。环绕通知还需要负责决定是继续处理join point(调用ProceedingJoinPoint的proceed方法)还是中断执行。
- 前置通知[Before advice]:在连接点前面执行,前置通知不会影响连接点的执行,除非此处抛出异常。 (就像你进入自动取款机之前,他会通知你欢迎光临)
-
Spring提供了4种实现AOP的方式:
- 经典的基于代理的AOP
- .@AspectJ注解驱动的切面
- 纯POJO切面
- 注入式AspectJ切面
举例说明AOP的通知(advice)
-
这是前置通知类
import org.springframework.aop.MethodBeforeAdvice; import java.lang.reflect.Method; /* * 这是前置通知 * */ public class BeforeAdvice implements MethodBeforeAdvice { @Override public void before(Method method, Object[] objects, Object o) throws Throwable { System.out.println("这是前置通知,触发它的是:"+method.getName()); } }
-
后置通知类
import org.springframework.aop.AfterReturningAdvice; import java.lang.reflect.Method; public class Afteradvice implements AfterReturningAdvice { @Override public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable { System.out.println("这是后置通知:"+method.getName()); } }
-
环绕通知类
import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; public class Aroundavice implements MethodInterceptor { @Override public Object invoke(MethodInvocation methodInvocation) throws Throwable { System.out.println("执行前!!!!!!!!!!!!!"); methodInvocation.proceed(); System.out.println("执行后@@@@@@@@@@@@@@@@@@@@@"); return null; } }
-
异常通知类
import org.springframework.aop.ThrowsAdvice; import java.lang.reflect.Method; public class Throwsadvice implements ThrowsAdvice { public void afterThrowing(Method m, Object[] os, Object target, Exception e) { System.out.println("出异常了..."+e.getMessage()); } }
-
service层规范接口
/* * service层的规范接口 * */ public interface Servers { void sayHello(); }
-
service实现类
public class ServerImpl implements Servers { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public void sayHello() { System.out.println("你好呀"+name); // System.out.println(10/0);这个语句是为了制造异常,出发异常通知。 } }
-
测试类
``` import org.springframework.context.support.ClassPathXmlApplicationContext; public class Test { public static void main(String[] args) { ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("adviceBean.xml"); Servers proxyInterfaces = (Servers) classPathXmlApplicationContext.getBean("ProxyFactoryBean"); proxyInterfaces.sayHello(); } }
-
xml文件的配置
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.springframework.org/schema/beans" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"> <!-- 注入bean的值--> <bean id="serversImpl" class="com.wdhcr.advice.ServerImpl"> <property name="name" value="张三"></property> </bean> <!-- 注入我们的切面--> <!-- 前置通知注入--> <bean id="beforeadvice" class="com.wdhcr.advice.BeforeAdvice"></bean> <!-- 后置通知注入--> <bean id="afteradvice" class="com.wdhcr.advice.Afteradvice"></bean> <!-- 环绕通知注入--> <bean id="aroundadvice" class="com.wdhcr.advice.Aroundavice"></bean> <!-- 异常通知注入--> <bean id="thorw" class="com.wdhcr.advice.Throwsadvice"></bean> <!-- 配置代理对象--> <bean id="ProxyFactoryBean" class="org.springframework.aop.framework.ProxyFactoryBean"> <!-- 配置代理接口集--> <property name="proxyInterfaces" > <list> <value>com.wdhcr.advice.Servers</value> </list> </property> <!-- 将通知织入代理对象--> <property name="interceptorNames"> <list> <value>beforeadvice</value> <value>afteradvice</value> <value>aroundadvice</value> <value>thorw</value> </list> </property> <!-- 指定要代理的对象--> <property name="target" ref="serversImpl"></property> </bean> </beans>
-
这是没有触发异常通知时的运行结果
-
这是触发了异常通知的运行结果