AOP的一些基本概念
- 面向对象编程(OOP)的弊端?
——当需要为多个不具有继承关系的而对喜爱那个引入同一个公共行为时,例如日志、安全监测等,我们必须在每个对象里引入公共行为,这样程序中就产生了大量的重复代码。
- 如何理解AOP?
——简单来说,AOP可以理解为对原方法在不改动原码的情况下的一种增强手段。
AOP(Aspect Oriented Programming)面向切面编程是OOP(Object Oriented Programming,面向对象编程)的补充和完善。
OOP引入封装、继承、多态等概念来建立一种对象层次结构,用于模拟公共行为的一个集合符,但是存在一定的弊端(比方说引入日志、安全监测等公共行为会出现大量的重复代码)。
AOP利用一种称为"横切"的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为(比如说日志)封装到一个可重用模块,并将其命名为"Aspect",即切面,那么,我们可以统一指定切面的执行时机。
- 什么是切面?
——所谓"切面",简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性。
- AOP的原理?
——AOP的底层原理是动态代理模式,与java自带的代理模式不同,java自带的动态代理模式只能代理接口,而Spring下的AOP可以代理普通类。
- Spring AOP的代理类从何而来?
——Spring中的AOP是由IoC容器创建和管理的。Spring创建代理的规则为:默认使用Java动态代理来创建AOP代理,当需要代理的类不是代理接口的时候,Spring会切换为使用CGLIB代理(也可以强制指定)。
- 程序员需要做什么事情?
- 定义核心业务逻辑
- 定义切入点
- 定义通知
- AOP术语
术语 | 解释 |
---|---|
Aspect(切面) | 通常是一个类,数量不限。用来定义Pointcut和Advice |
Joint point(连接点) | 程序执行期间的一个点,例如执行方法或处理异常。在Spring AOP中,连接点始终表示方法执行。 |
Advice(通知/增强) | 特定连接点的某个方面采取的操作。这是在程序执行期间通过 Spring AOP 框架实际被调用的代码。 |
Pointcut(切点) | 表示一组 joint point,在这里Advice将被执行。 |
Introduction(引入) | 在不修改代码的前提下,引入可以在运行期为类动态地添加一些方法或字段 |
Target(目标对象) | 织入 Advice 的目标对象 |
Weaving(织入) | 将 Aspect 和其他对象连接起来, 并创建 Adviced object 的过程,可以在编译时,类加载时和运行时完成 |
- Advice的分类
类型 | 解释 |
---|---|
Before advice(前置通知) | 在方法执行之前执行 |
After returning advice(返回后通知) | 在方法正常执行并返回结果后执行 |
After throwing advice(抛出异常后通知) | 如果执行方法抛出了异常就会执行 |
After (finally) advice(后置通知) | 方法执行之后执行,不受方法执行结果影响 |
Around advice(环绕通知) | 最常用的,在方法执行前或者执行后执行 |
- 如何定义一个切面(Aspect)?
——Spring支持两种自定义切面的方式:基于模式和使用@AspectJ注解
方法 | 描述 |
---|---|
XML Schema based | 方面是使用常规类以及基于配置的 XML 来实现的。 |
@AspectJ based | @AspectJ 引用一种声明方面的风格作为带有 Java 5 注释的常规 Java 类注释。 |
- 通知执行的顺序?
——分为正常情况和异常抛出情况。
- 切入点类型?
——Spring AOP支持以下AspectJ切入点指示符(PCD)用于切入点表达式:
execution:用于匹配方法执行连接点。这是使用Spring AOP时使用的主要切入点指示符。
within:限制匹配某些类型中的连接点(使用Spring AOP时在匹配类型中声明的方法的执行)。
this:限制与连接点的匹配(使用Spring AOP时执行方法),其中bean引用(Spring AOP代理)是给定类型的实例。
target:限制与连接点的匹配(使用Spring AOP时执行方法),其中目标对象(被代理的应用程序对象)是给定类型的实例。
args:限制与连接点的匹配(使用Spring AOP时执行方法),其中参数是给定类型的实例。
@target:限制与连接点的匹配(使用Spring AOP时执行方法),其中执行对象的类具有给定类型的注释。
@args:限制与连接点的匹配(使用Spring AOP时执行方法),其中传递的实际参数的运行时类型具有给定类型的注释。
@within:限制匹配到具有给定注释的类型中的连接点(使用Spring AOP时在具有给定注释的类型中声明的方法的执行)。
@annotation:限制连接点的匹配,其中连接点的主题(在Spring AOP中执行的方法)具有给定的注释。
- 切入点表达式?
——Spring AOP用户可能execution最常使用切入点指示符。执行表达式的格式如下:
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern)
throws-pattern?)
- 常见的切入点表达式示例:
- 执行任何公共方法
execution(public * *(..))
- 执行任何以set开头的方法
execution(* set*(..))
- 执行AccountService接口定义的任何方法
execution(* com.xyz.service.AccountService.*(..))
- 执行service包中定义的任何方法
execution(* com.xyz.service.*.*(..))
- 执行service包下或者其子包下的任何方法
execution(* com.xyz.service..*.*(..))
- 服务包中的任何连接点(仅在Spring AOP中执行方法)
within(com.xyz.service.*)
- 代理实现AccountService接口的任何连接点(仅在Spring AOP中执行方法)
this(com.xyz.service.AccountService)
使用@AspectJ注解的AOP实例
- 准备工作:添加依赖
<!--AOP的依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.1.7.RELEASE</version>
</dependency>
<dependency>
<groupId>aopalliance</groupId>
<artifactId>aopalliance</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.1</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.1.7.RELEASE</version>
</dependency>
- 步骤一:创建用于拦截的bean
在实际工作中,这个bean里面往往封装着满足业务需要的核心逻辑。但是,我想想在逻辑执行的前后加入日志来跟踪调试,直接修改原码显然是十分危险的,也不符合面向对象的设计方法,使用面向方面思路就是专门来解决这种问题的。
我们写一个简单的bean来模拟核心代码:
package com.xx.aop;
import org.springframework.stereotype.Component;
@Component
public class TestBean {
private String testStr = "it is a test";
public String getTestStr() {
return testStr;
}
public void setTestStr(String testStr) {
this.testStr = testStr;
}
public void doTest() {
System.out.println(this + testStr);
}
}
- 步骤二:创建Advisor(增强器)
使用@AspectJ注解对POJO进行标注来创建一个Advisior。
我们要做的就是在所有类(包括TestBean )的doTest()方法执行前后来进行一些操作,这里用打印before或者after来代替。
package com.xx.aop;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class AspectTest {
@Pointcut(value = "execution(* com.xx.aop.TestBean.doTest(..))")
public void test() {
// 声明一个名叫test的切入点
// 该切入点与TestBean类下的doTest()方法匹配
}
@Before("test()")
public void beforeTest() {
System.out.println("beforeTest...");
}
@After("test()")
public void afterTest() {
System.out.println("afterTest...");
}
@Around("test()")
public Object aroundTest(ProceedingJoinPoint p) {
System.out.println("around-before...");
Object o = null;
try {
// 执行方法
o = p.proceed();
} catch (Throwable e) {
e.printStackTrace();
}
System.out.println("around-after...");
return o;
}
}
- 步骤三:修改配置文件
XML是Spring的基础,要在这里开启AOP功能和开启注解配置功能。
<?xml version="1.0" encoding="utf-8" ?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/beans"
xmlns:mvc="http://www.springframework.org/schema/mvc"
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/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--启用注解-->
<context:annotation-config/>
<!--开启注解扫描-->
<context:component-scan base-package="com.xx.aop"/>
<!--自动创建代理-->
<aop:aspectj-autoproxy/>
</beans>
- 测试
验证AOP的神奇效果:
package com.xx.aop;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class AopTest {
public static void main(String[] args) {
ApplicationContext context =
new ClassPathXmlApplicationContext("Beans.xml");
TestBean test = (TestBean) context.getBean("testBean");
test.doTest();
}
}
不出意外的情况下控制台会打印:
around-before... # 环绕通知
beforeTest... # 前置通知
com.xx.aop.TestBean@7c83dc97it is a test # 方法执行
around-after... # 环绕通知
afterTest... # 后置通知
这样我们利用Spring实现了对doTest()方法的增强,辅助功能可以独立于核心业务之外,方便程序的扩展和解耦。
基于XML配置的AOP
我们仿照上面的aspect,改为在xml文件中定义aspect:
<aop:config>
<!--一、声明一个Aspect,这个类我们已经定义好了-->
<aop:aspect id="xmlAspect" ref="aspectTestXml">
<!--定义一个切点-->
<aop:pointcut id="testPoint" expression="execution(* com.xx.aop.TestBean.doTest(..))"/>
<!--定义advice-->
<aop:before method="beforeTest" pointcut-ref="testPoint"/>
<aop:after method="afterTest" pointcut-ref="testPoint"/>
<aop:around method="aroundTest" pointcut-ref="testPoint"/>
</aop:aspect>
</aop:config>
<bean id="aspectTestXml" class="com.xx.aop.AspectTestXml"></bean>
<bean id="testBean2" class="com.xx.aop.TestBean"></bean>
测试:
package com.xx.aop;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class AopTest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
TestBean test = (TestBean) context.getBean("testBean2");
test.doTest();
}
}
结果:
beforeTest...
around-before...
com.xx.aop.TestBean@6ad82709it is a test
around-after...
afterTest...