Spring AOP切面编程学习笔记
1 概述
AOP(Aspect Oriented Programming),即面向切面编程,可以说是OOP(Object Oriented Programming,面向对象编程)的补充和完善。OOP引入封装、继承、多态等概念来建立一种对象层次结构,用于模拟公共行为的一个集合。不过OOP允许开发者定义纵向的关系,但并不适合定义横向的关系,例如日志功能。
日志代码往往横向地散布在所有对象层次中,而与它对应的对象的核心功能毫无关系对于其他类型的代码,如安全性、异常处理和透明的持续性也都是如此,这种散布在各处的无关的代码被称为横切(cross cutting),在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用。
AOP技术恰恰相反,它利用一种称为"横切"的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名为"Aspect",即切面。所谓"切面",简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性。
1.1AOP好处
使用"横切"技术,AOP把软件系统分为两个部分:核心关注点和横切关注点。
业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。
横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处基本相似,比如权限认证、日志、事物。
AOP的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来。
2 关于AOP
2.1关于AOP核心概念
1、切面(aspect)
类是对物体特征的抽象,切面就是对横切关注点的抽象
2、切入点(pointcut)
对连接点进行拦截的定义
3、通知(advice)
所谓通知指的就是指拦截到连接点之后要执行的代码,通知分为Before前置、After后置、after-throwing异常、最终、Around环绕通知五类。
<!--3.AOP代理配置-->
<!--proxy-target-class:true:那么基于类的代理,CGLIB-->
<!--proxy-target-class:false,或这个属性被省略,那么基于JDK接口的代理-->
<!--<aop:config proxy-target-class="false">-->
<aop:config>
<!--定义切面1 -->
<!--通过 order控制横切关注点的顺序,越大,优先级越高,越先执行-->
<aop:aspect id="myAspect1" ref="myTimerDisplayHandler" order="2">
<!--定义切入点-->
<aop:pointcut id="showCurrentTime"
expression="execution(* com.kikop.myspringcglib2.service.IHelloWorld.simple*(..))"></aop:pointcut>
<!--定义切入点:执行前通知-->
<aop:before method="printTime" pointcut-ref="showCurrentTime"></aop:before>
<!--定义切入点:执行后通知-->
<aop:after method="printTime" pointcut-ref="showCurrentTime"></aop:after>
</aop:aspect>
4、横切关注点
对哪些方法进行拦截,拦截后怎么处理,这些关注点称之为横切关注点
5、连接点(joinpoint)
被拦截到的点,因为Spring只支持方法类型的连接点,所以在Spring中连接点指的就是被拦截到的方法,实际上连接点还可以是字段或者构造器
6、目标对象
代理的目标对象
7、织入(weave)
将切面应用到目标对象并导致代理对象创建的过程
8、引入(introduction)
在不修改代码的前提下,引入可以在运行期为类动态地添加一些方法或字段。
2.2AOP pointcut切点函数
// execution(<修饰符模式>?<返回类型模式><方法名模式>(<参数模式>)(<异常模式>))
/**
\* 匹配 com.aop.learn.service 包下所有类的所有方法(service+一个点)
*/
@Before("execution(* com.aop.learn.service.*(..))")
public void beforeAspect1(){
}
/**
\* 匹配 com.aop.learn.service 包、子孙包下所有类的所有方法(两service+个点)
*/
@Before("execution(* com.aop.learn.service..*(..))")
public void beforeAspect2(){
}
2.2.1切点定义示例
package com.kikop.myspringcglib2;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
/**
* @author kikop
* @version 1.0
* @project Name: TechnicalAbilityToolBox
* @file Name: MyTimerDisplayHandler
* @desc 切点定义
* @date 2019/8/25
* @time 14:29
* @by IDE: IntelliJ IDEA
*/
//等价于:xml中的<aop:config>
@Aspect
@Component
public class AspectPointCutDef {
/**
* 匹配所有目标类的public方法
*/
@Before("execution(public * *(..))")
public void beforeAspect() {
}
/**
* 匹配所有以To为后缀的方法
*/
@Before("execution(* *To(..))")
public void beforeAspect1() {
}
/**
* 匹配Waiter接口中的所有方法
*/
@Before("execution(* com.kikop.myspringcglib2.service.Writer.*(..))")
public void beforeAspect2() {
}
/**
* 匹配Waiter接口中及其实现类的方法
*/
@Before("execution(* com.kikop.myspringcglib2.service.Writer+.*(..))")
public void beforeAspect3() {
}
/**
* 匹配 com.kikop.myspringcglib2.service 包下所有类的所有方法
*/
@Before("execution(* com.kikop.myspringcglib2.service.*(..))")
public void beforeAspect4() {
}
/**
* 匹配 com.kikop.myspringcglib2.service 包,子孙包下所有类的所有方法
*/
@Before("execution(* com.kikop.myspringcglib2.service..*(..))")
public void beforeAspect5() {
}
/**
* 匹配 包名前缀为com的任何包下类名后缀为ive的方法,方法必须以Smart为前缀
*/
@Before("execution(* com..*.*ive.Smart*(..))")
public void beforeAspect6() {
}
/**
* 匹配 save(String name,int age) 函数
*/
@Before("execution(* save(String,int))")
public void beforeAspect7() {
}
/**
* 匹配 save(String name,*) 函数 第二个参数为任意类型
*/
@Before("execution(* save(String,*))")
public void beforeAspect8() {
}
/**
* 匹配 save(String name,..) 函数 除第一个参数固定外,接受后面有任意个入参且入参类型不限
*/
@Before("execution(* save(String,..))")
public void beforeAspect9() {
}
/**
* 匹配 save(String+) 函数 String+ 表示入参类型是String的子类
*/
@Before("execution(* save(String+))")
public void beforeAspect10() {
}
}
3AOP最佳实践
3.1Spring对aop支持
3.1.1Spring创建代理的规则
Spring中AOP代理由Spring的IOC容器负责生成、管理,其依赖关系也由IOC容器负责管理。因此,AOP代理可以直接使用容器中的其它bean实例作为目标,这种关系可由IOC容器的依赖注入提供。Spring创建代理的规则为:
1、默认使用Java动态代理来创建AOP代理,这样就可以为任何接口实例创建代理了
2、当需要代理的类不是代理接口的时候,Spring会切换为使用CGLIB代理,也可强制使用CGLIB。
3.1.2AOP编程步骤三个部分
1、定义普通业务组件XXXHandler
2、定义切入点PointCut,一个切入点可能横切多个业务组件
3、定义切点需要的增强处理,增强处理就是在AOP框架为普通业务组件织入的处理动作
所以进行AOP编程的关键就是定义切入点和定义切点的增强处理,一旦定义了合适的切入点和增强处理,AOP框架将自动生成AOP代理,即:代理对象的方法=增强处理+被代理对象的方法。
4代码实战
4.1maven依赖
spring-aop-5.0.0.RELEASE.jar、spring-aspects-5.0.0.RELEASE.jar(aspectjweaver-1.8.9.jar)
aopalliance-1.0.jar(好像用不到)
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.kikop</groupId>
<artifactId>myspringdemo</artifactId>
<version>1.0-SNAPSHOT</version>
<name>myspringdemo</name>
<!-- FIXME change it to the project's website -->
<url>http://www.example.com</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.7</maven.compiler.source>
<maven.compiler.target>1.7</maven.compiler.target>
<!--springframework-->
<springframework.version>5.0.0.RELEASE</springframework.version>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<!--2.spring对web的支持,依赖 spring-webmvc-->
<!--此时jar包会自动下载(spring-context、spring-web、spring-webmvc)-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${springframework.version}</version>
</dependency>
<!--2.2.spring dao层依赖(jdbc 和 tx)-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>${springframework.version}</version>
</dependency>
<!--2.4.spring aop依赖(aop 和 aspect)-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${springframework.version}</version>
</dependency>
<!--spring-aspects内部依赖:aspectjweaver,另外需手动添加:aopalliance-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>${springframework.version}</version>
</dependency>
<!--2.3.spring test相关依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${springframework.version}</version>
</dependency>
</dependencies>
<build>
<pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
<plugins>
<!-- clean lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#clean_Lifecycle -->
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<version>3.1.0</version>
</plugin>
<!-- default lifecycle, jar packaging: see https://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_jar_packaging -->
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>3.0.2</version>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.1</version>
</plugin>
<plugin>
<artifactId>maven-jar-plugin</artifactId>
<version>3.0.2</version>
</plugin>
<plugin>
<artifactId>maven-install-plugin</artifactId>
<version>2.5.2</version>
</plugin>
<plugin>
<artifactId>maven-deploy-plugin</artifactId>
<version>2.8.2</version>
</plugin>
<!-- site lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#site_Lifecycle -->
<plugin>
<artifactId>maven-site-plugin</artifactId>
<version>3.7.1</version>
</plugin>
<plugin>
<artifactId>maven-project-info-reports-plugin</artifactId>
<version>3.0.0</version>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
4.2上下文配置文件
<?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 http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--1.定义业务逻辑类-->
<bean id="helloWorldImpl1" class="com.kikop.myspringcglib2.service.impl.HelloWorldImpl1"></bean>
<bean id="helloWorldImpl2" class="com.kikop.myspringcglib2.service.impl.HelloWorldImpl2"></bean>
<!--2.定义动态代理类-->
<bean id="myTimerDisplayHandler" class="com.kikop.myspringcglib2.handler.MyTimerDisplayHandler"></bean>
<bean id="myLoggerHandler" class="com.kikop.myspringcglib2.handler.MyLoggerHandler"></bean>
<!--3.AOP代理配置-->
<!--proxy-target-class:true:那么基于类的代理,CGLIB-->
<!--<aop:config proxy-target-class="true">-->
<!--proxy-target-class:false,或这个属性被省略,那么基于JDK接口的代理-->
<aop:config>
<!--定义切面1 -->
<!--通过 order控制横切关注点的顺序,越大,优先级越高,越先执行-->
<aop:aspect id="myAspect1" ref="myTimerDisplayHandler" order="2">
<!--定义方法XXX切入点-->
<aop:pointcut id="showCurrentTime"
expression="execution(* com.kikop.myspringcglib2.service.IHelloWorld.prefixDo*(..))"></aop:pointcut>
<!--定义切入点:执行前通知-->
<aop:before method="printTimeBefore" pointcut-ref="showCurrentTime"></aop:before>
<!--定义切入点:执行后通知-->
<aop:after method="printTimeEnd" pointcut-ref="showCurrentTime"></aop:after>
</aop:aspect>
<!--定义切面2 -->
<aop:aspect id="myAspect2" ref="myLoggerHandler" order="1">
<!--定义方法YYY切入点-->
<aop:pointcut id="showCurrentLog"
expression="execution(* com.kikop.myspringcglib2.service.IHelloWorld.do*(..))"></aop:pointcut>
<!--定义切入点:执行前通知-->
<aop:before method="loggerBefore" pointcut-ref="showCurrentLog"></aop:before>
<!--定义切入点:执行后通知-->
<aop:after method="loggerEnd" pointcut-ref="showCurrentLog"></aop:after>
</aop:aspect>
</aop:config>
</beans>
4.3业务接口
package com.kikop.myspringcglib2.service;
/**
* @author kikop
* @version 1.0
* @project Name: TechnicalAbilityToolBox
* @file Name: IHelloWorld
* @desc 功能描述
* @date 2019/8/25
* @time 14:26
* @by IDE: IntelliJ IDEA
*/
public interface IHelloWorld {
void prefixDoWelcome() throws Exception;
void doBusinessPrint();
}
4.4业务接口实现类
package com.kikop.myspringcglib2.service.impl;
import com.kikop.myspringcglib2.service.IHelloWorld;
import java.util.concurrent.TimeUnit;
/**
* @author kikop
* @version 1.0
* @project Name: TechnicalAbilityToolBox
* @file Name: HelloWorldImpl1
* @desc 功能描述
* @date 2019/8/25
* @time 14:27
* @by IDE: IntelliJ IDEA
*/
public class HelloWorldImpl1 implements IHelloWorld {
@Override
public void prefixDoWelcome() throws Exception {
System.out.println(this.getClass().toString() + ":prefixDoWelcome");
TimeUnit.SECONDS.sleep(5);
}
@Override
public void doBusinessPrint() {
System.out.println(this.getClass().toString() + ":doBusinessPrint");
}
}
4.5增强逻辑Handler
package com.kikop.myspringcglib2.handler;
import java.util.Date;
/**
* @author kikop
* @version 1.0
* @project Name: TechnicalAbilityToolBox
* @file Name: MyTimerDisplayHandler
* @desc 横切关注点:时间
* @date 2019/8/25
* @time 14:29
* @by IDE: IntelliJ IDEA
*/
public class MyTimerDisplayHandler {
public void printTimeBefore() {
System.out.println("业务系统执行开始时间:" + new Date());
}
public void printTimeEnd() {
System.out.println("业务系统执行结束时间:" + new Date());
}
}
package com.kikop.myspringcglib2.handler;
/**
* @author kikop
* @version 1.0
* @project Name: TechnicalAbilityToolBox
* @file Name: MyTimerDisplayHandler
* @desc 横切关注点2:日志
* @date 2019/8/25
* @time 14:29
* @by IDE: IntelliJ IDEA
*/
public class MyLoggerHandler {
public void loggerBefore() {
System.out.println("业务方法执行,日志记录开始...");
}
public void loggerEnd() {
System.out.println("业务方法执行,日志记录完成!");
}
}
4.6代码测试
package com.kikop.myspringcglib2;
import com.kikop.myspringcglib2.service.IHelloWorld;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* @author kikop
* @version 1.0
* @project Name: TechnicalAbilityToolBox
* @file Name: MySpringCGLIBTest
* @desc 功能描述
* @date 2019/8/25
* @time 14:40
* @by IDE: IntelliJ IDEA
*/
public class MySpringCGLIBTest {
public static void springAOPTest() throws Exception {
ApplicationContext ctx =
new ClassPathXmlApplicationContext("springframework/spring-aop.xml");
IHelloWorld helloWorld = (IHelloWorld) ctx.getBean("helloWorldImpl1");
//显示时间
helloWorld.prefixDoWelcome();
//显示Log
helloWorld.doBusinessPrint();
}
public static void main(String[] args) throws Exception {
springAOPTest();
}
}
4.7结果输出
业务系统执行开始时间:Sun Feb 28 12:38:07 CST 2021
class com.kikop.myspringcglib2.service.impl.HelloWorldImpl1:prefixDoWelcome
业务系统执行结束时间:Sun Feb 28 12:38:12 CST 2021
业务方法执行,日志记录开始...
class com.kikop.myspringcglib2.service.impl.HelloWorldImpl1:doBusinessPrint
业务方法执行,日志记录完成!