一、简介
- AOP(Aspect Oriented Programming)面向切面编程。将横向分布在系统中的与业务功能无关的代码,如日志,抽取出来,单独管理,减少重复。
二、AOP核心概念
- 横切关注点:对哪些方法进行拦截,拦截后怎么处理,这些关注点称之为横切关注点
- 切面(Aspect):切面是一个类,是对横切关注点的抽象
- 连接点(Join point):被拦截到的点,因为Spring只支持方法类型的连接点,所以在Spring中连接点指的就是被拦截到的方法,实际上连接点还可以是字段或者构造器
- 切入点(Pointcut):对连接点进行拦截的定义
- 通知(Advice):所谓通知指的就是指拦截到连接点之后要执行的代码,通知分为前置、后置、异常、最终、环绕通知五类
- 前置通知(Before advice):在某个连接点(Join point)之前执行的通知,但这个通知不能阻止连接点的执行(除非它抛出一个异常;如果抛出了异常,连接点会不会执行,但后续的通知会继续执行)
- 后置通知(After(finally)advice):当某个连接点(Join point)退出的时候执行的通知(不论是正常返回还是发生异常退出)(如果连接点的方法发生了异常,后续通知会继续执行)
- 异常通知:在方法抛出异常后执行的通知
- 返回后通知(After returning advice):在某个连接点(Join point)正常完成后执行的通知。例如,一个方法没有抛出任何异常正常返回
- 环绕通知(Around advice):包围一个连接点(Join point)的通知,如方法调用。这是最强大的一种通知类型。环绕通知可以在方法前后完成自定义的行为。它也会选择是否继续执行连接点或直接返回它们自己的返回值或抛出异常来结束执行
- 目标对象(Target):代理的目标对象
- 织入(Weaving):将切面应用到目标对象并导致代理对象创建的过程
- 引入(introduction):在不修改代码的前提下,引入可以在运行期为类动态地添加一些方法或字段
三、Spring对AOP的支持
1. Spring创建代理的规则为
- Spring默认使用Java的动态代理来创建AOP代理
- Java动态代理:
- 当需要代理的类不是代理接口的时候,Spring会使用CGLIB代理,也可以强制使用CGLIB代理
- CGLIB(Code Generation Library):是一个强大的,高性能,高质量的Code生成类库,它可以在运行期扩展Java类与实现Java接口
四、案例
1. xml配置文件形式
项目结构
- 引入相关jar(需要单独引入合适的aspectj,aopalliance)
<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.lt.aop</groupId>
<artifactId>spring_aop</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
<properties>
<spring-version>4.3.23.RELEASE</spring-version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${spring-version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring-version}</version>
</dependency>
<!-- The AspectJ weaver introduces advices to java classes -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.8</version>
</dependency>
<!-- AOP Alliance -->
<dependency>
<groupId>aopalliance</groupId>
<artifactId>aopalliance</artifactId>
<version>1.0</version>
</dependency>
</dependencies>
</project>
- 配置文件(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"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.2.xsd">
<bean id="printer" class="com.lt.aop01.Printer"/>
<bean id="logger" class="com.lt.aop01.Log"/>
<aop:config>
<aop:aspect ref="logger">
<aop:pointcut id="handler" expression="execution(* com.lt.aop01..*.doPrint(..))"/>
<aop:before pointcut-ref="handler" method="doLog"/>
<aop:after pointcut-ref="handler" method="doLog"/>
</aop:aspect>
</aop:config>
</beans>
- 业务类
package com.lt.aop01;
/**
* @author lt
* @date 2019年4月29日
* @version v1.0
*/
public class Printer {
public void doPrint() {
System.out.println("进入PrintImpl,并调用doPrint()方法!");
}
}
- 日志类
package com.lt.aop01;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* @author lt
* @date 2019年4月29日
* @version v1.0
*/
public class Log {
public void doLogBefore(){
System.out.println("before:"+new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
}
public void doLogAfter(){
System.out.println("after:"+new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
}
public void doLogException(){
System.out.println("exception:"+new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
}
}
- 客户端测试
package com.lt.aop01;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* @author lt
* @date 2019年4月29日
* @version v1.0
*/
@SuppressWarnings("all")
public class Client {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("aop_01.xml");
Printer print = (Printer) ctx.getBean("printer");
print.doPrint();
}
}
- 测试结果
五月 05, 2019 10:20:39 上午 org.springframework.context.support.ClassPathXmlApplicationContext prepareRefresh
信息: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@6576fe71: startup date [Sun May 05 10:20:39 CST 2019]; root of context hierarchy
五月 05, 2019 10:20:39 上午 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
信息: Loading XML bean definitions from class path resource [aop_01.xml]
before:2019-05-05 10:20:40
进入PrintImpl,并调用doPrint()方法!
after:2019-05-05 10:20:40
AOP中切入点表达式:execution
1. execution(* com.lt.aop01..*.doPrint(..))
- execution():表达式主体
- 第一个*表示返回返回类型,*表示返回所有类型
- com.lt.aop01..表示需要拦截的包名,..表示拦截当前包,子包,子孙包
- *.doPrint(..)*表示所有类,表示拦截doPrint()方法,方法中的两个圆点表示拦截任何参数
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" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.2.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!-- IOC自动扫包 --> <context:component-scan base-package="com.lt.aop02" /> <!-- 使用AOP注解 --> <!-- <aop:aspectj-autoproxy />有一个proxy-target-class属性,默认为false, 表示使用jdk动态代理织入增强,当配为<aop:aspectj-autoproxy poxy-target-class="true"/>时, 表示使用CGLib动态代理技术织入增强。不过即使proxy-target-class设置为false, 如果目标类没有声明接口,则spring将自动使用CGLib动态代理。 --> <aop:aspectj-autoproxy /> </beans>
CGLIB与jdk动态代理
1. 深入理解JDK动态代理机制
2. Spring AOP中的JDK和CGLib动态代理哪个效率更高?
- 业务类
package com.lt.aop02; import org.springframework.stereotype.Component; /** * @author lt * @date 2019年4月29日 * @version v1.0 */ @Component public class Printer { public void doPrint() { System.out.println("进入PrintImpl,并调用doPrint()方法!"); } }
- 日志类
package com.lt.aop02; import java.text.SimpleDateFormat; import java.util.Arrays; import java.util.Date; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.AfterThrowing; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.springframework.stereotype.Component; /** * @author lt * @date 2019年4月29日 * @version v1.0 */ @Component @Aspect public class Log { /** * 1.此空方法用于声明切点表达式,定义切点需要拦截的内容 * 2.@Pointcut("execution(* com.lt.aop02..*.*)")表示要拦截的内容 * 3.通知直接使用此切点方法名即可引入切点表达式 * @author lt * @date 2019年5月6日 */ @Pointcut("execution(* com.lt.aop02..*.*(..))") public void pointcutDeclaration(){} /** * 前置通知 * @author lt * @date 2019年5月6日 * @param point */ @Before("pointcutDeclaration()") public void doLogBefore(JoinPoint point){ String method = point.getSignature().getName(); System.out.print("before:"+new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())); System.out.println(",调用方法:"+method+",参数:"+Arrays.asList(point.getArgs())); } /** * 后置通知 * @author lt * @date 2019年5月6日 * @param point */ @After("pointcutDeclaration()") public void doLogAfter(JoinPoint point){ String method = point.getSignature().getName(); System.out.print("after:"+new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())); System.out.println(",调用方法:"+method+",参数:"+Arrays.asList(point.getArgs())); } /** * 异常通知 * @author lt * @date 2019年5月6日 * @param point */ @AfterThrowing("pointcutDeclaration()") public void doLogException(JoinPoint point){ String method = point.getSignature().getName(); System.out.print("exception:"+new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())); System.out.println(",调用方法:"+method+",参数:"+Arrays.asList(point.getArgs())); } /** * 环绕通知 * @author lt * @date 2019年5月6日 * @param point */ @Around("pointcutDeclaration()") public void doLogAround(ProceedingJoinPoint point){ String method = point.getSignature().getName(); try { //环绕通知-前置通知 System.out.print("around-before:"+new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())); System.out.println(",调用方法:"+method+",参数:"+Arrays.asList(point.getArgs())); point.proceed(); //环绕通知-后置通知 System.out.print("around-after:"+new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())); System.out.println(",调用方法:"+method+",参数:"+Arrays.asList(point.getArgs())); } catch (Throwable e) { System.out.print("around-exception:"+new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())); System.out.println(",调用方法:"+method+",参数:"+Arrays.asList(point.getArgs())); e.printStackTrace(); } } }
- 客户端测试类
package com.lt.aop02; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.support.ClassPathXmlApplicationContext; /** * @author lt * @date 2019年4月29日 * @version v1.0 */ @SuppressWarnings("all") public class Client { public static void main(String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext("aop_02.xml"); Printer print = (Printer) ctx.getBean("printer"); print.doPrint(); } }
- 测试结果
五月 06, 2019 2:47:16 下午 org.springframework.context.support.ClassPathXmlApplicationContext prepareRefresh 信息: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@6576fe71: startup date [Mon May 06 14:47:16 CST 2019]; root of context hierarchy 五月 06, 2019 2:47:16 下午 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions 信息: Loading XML bean definitions from class path resource [aop_02.xml] before:2019-05-06 14:47:17,调用方法:doPrint,参数:[] 进入PrintImpl,并调用doPrint()方法! after:2019-05-06 14:47:17,调用方法:doPrint,参数:[]
Spring中常用注解
1. Spring系列之Spring常用注解总结
参考
【1】Spring3:AOP
【2】spring AspectJ的Execution表达式
- 项目结构