面向切面(AOP,Aspect Orient Programming):通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。AOP 为 Spring IoC 提供一个非常强大的中间件解决方案,是函数式编程的一种衍生泛型。
利用AOP编程可以对业务部分进行分离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
AOP 在 Spring 框架中用于:
OOP:面对对象编程
面向方面编程 (AOP) 通过提供另一种思考程序结构的方式来补充面向对象编程 (OOP)。
区别:OOP 中模块化的关键单位是类,而 AOP 中模块化的单位是方面。
名词 | 说明 | 备注 |
---|---|---|
横切关注点 | 跨越应用程序中多个模块的方法或功能 | 方法拦截处 |
切面(Aspect) | 是一个关注点的模块化,一个切面能包含多个增强方法 | 如事务处理、日志处理 |
连接点(Joint point) | 程序执行过程中的一个点 | 通知开始执行的地方 |
切入点(Point Cut) | 对连接点进行拦截的条件定义 | 匹配连接点的谓词 |
引介(Introduction) | 代表类型声明额外的方法或字段 | 添加新的方法 |
通知(Advice) | 连接点后要执行的代码 | 完成功能的实现 |
目标(Target) | 被一个或多个切面所通知的对象 | 被通知(代理)的对象 |
织入(Weave) | 将增强方法添加到目标的具体连接点上的过程 | AOP有三种织入方式 |
通知类型 | 说明 | 实现 |
---|---|---|
前置通知(Before Advice) | 在连接点之前执行的通知 | <aop:before> |
后置通知(After Advice) | 在连接点结束的时候执行的通知 | <aop:after> |
返回后通知(After Return Advice) | 在连接点正常完成后执行的通知 | <aop:after-returning > |
环绕通知(Around Advice) | 包围一个连接点的通知 | <aop:around> |
异常通知(After Throwing Advice) | 在方法异常退出时执行的通知 | <aop:after-throwing> |
Spring AOP 是用纯 Java 实现的。不需要特殊的编译过程。Spring AOP 不需要控制类加载器层次结构,因此适用于 servlet 容器或应用程序服务器。
Spring 中的 AOP 是通过动态代理实现的。
Spring AOP 目前仅支持方法执行连接点。
Spring AOP 默认为 AOP 代理使用标准的 JDK 动态代理。Spring AOP 也可以使用 CGLIB 代理。
默认情况下,如果业务对象未实现接口,则使用 CGLIB。
需要导入aspectjweaver
依赖:
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver --> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.4</version> </dependency>
利用之前在代理模式中用到的增删查改的业务,同样完成添加日志的功能。
基本业务:
public interface UserService { public void add(); public void delete(); public void update(); public void query(); }
public class UserServiceImpl implements UserService { @Override public void add() { System.out.println("添加一个用户"); } @Override public void delete() { System.out.println("删除一个用户"); } @Override public void update() { System.out.println("更改一个用户"); } @Override public void query() { System.out.println("查询用户"); } }
需要实现的接口有:
MethodBeforeAdvice
:在调用方法之前调用的通知。这样的通知不能阻止方法调用继续进行,除非它们抛出Throwable。AfterReturningAdvice
:返回通知仅在正常方法返回时调用,而不是在抛出异常时调用。这样的建议可以看到返回值,但不能更改它。public interface MethodBeforeAdvice extends BeforeAdvice { /** * method:被调用的方法 * args:方法的参数 * target:方法调用的目标(可能是null) */ void before(Method method, Object[] args, @Nullable Object target) throws Throwable; }
public interface AfterReturningAdvice extends AfterAdvice { /** * returnValue:方法返回的值(如果有的话) * method:被调用的方法 * args:方法的参数 * target:方法调用的目标(可能是null) */ void afterReturning(@Nullable Object returnValue, Method method, Object[] args, @Nullable Object target) throws Throwable; }
我们需要两个增强类,一个前置增强、一个后置增强。
public class Before_log implements MethodBeforeAdvice { @Override public void before(Method method, Object[] args, Object target) throws Throwable { System.out.println("执行 " + target.getClass().getName() + " 的 " + method.getName() + " 方法"); } }
public class After_log implements AfterReturningAdvice { @Override public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable { System.out.println("该方法的返回值为:" + returnValue); } }
在xml文件中注册bean,并配置aop。(需要aop约束)
<?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 https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd"> <bean id="UserService" class="com.service.UserServiceImpl"/> <bean id="before_log" class="com.service.Before_log"/> <bean id="after_log" class="com.service.After_log"/> <!--配置AOP--> <aop:config> <!--配置切入点--> <!--execution(要执行的位置!修饰词 返回值 类名 方法名 参数)--> <aop:pointcut id="pointcut" expression="execution(* com.service.UserServiceImpl.*(..))"/> <!--配置环绕增强--> <aop:advisor advice-ref="before_log" pointcut-ref="pointcut"/> <aop:advisor advice-ref="after_log" pointcut-ref="pointcut"/> </aop:config> </beans>
public class MyTest { @Test public void TestUserService(){ ApplicationContext applicationContext = new ClassPathXmlApplicationContext("ApplicationContext.xml"); UserService userService = applicationContext.getBean("UserService", UserService.class); userService.add(); } } /* 输出: 执行 com.service.UserServiceImpl 的 add 方法 添加一个用户 该方法的返回值为:null */
代理中需要提供两个增强类,一个前置增强、一个后置增强。
public class DiyPoint { public void After(){ System.out.println("===========开始前=============="); } public void Before(){ System.out.println("===========开始后=============="); } }
<bean id="UserService" class="com.service.UserServiceImpl"/> <bean id="diyPoint" class="com.DiyPointcut.DiyPoint"/> <aop:config> <!--ref:切面要引用的类--> <aop:aspect ref="diyPoint"> <aop:pointcut id="pointcut" expression="execution( * com.service.UserServiceImpl.*(..))"/> <!--前置通知--> <aop:before method="before" pointcut-ref="pointcut"/> <!--后置通知--> <aop:after method="after" pointcut-ref="pointcut"/> </aop:aspect> </aop:config>
测试类不变!
/* 输出: ===========开始后============== 添加一个用户 ===========开始前============== */
@Configuration // Java配置类 @Aspect // 标识一个切面 public class AnnotationPoint { @After("execution(* com.service.UserServiceImpl.*(..))") public void After(){ System.out.println("===========开始前=============="); } @Before("execution(* com.service.UserServiceImpl.*(..))") public void Before(){ System.out.println("===========开始后=============="); } }
<!--开启 context 注解支持--> <context:annotation-config/> <!--配置扫描--> <context:component-scan base-package="com.Anno"/> <!--开启 aop 注解支持--> <aop:aspectj-autoproxy/>
测试类不变!
/* 输出: ===========开始后============== 添加一个用户 ===========开始前============== */
通过上面三种实现AOP的例子,我们可以看到AOP是基于动态代理实现的。在实际应用中,我们应当选择前两种,而酌情(小项目)选择第三种。