基础知识部分
一、概念
官方解释:
面向切面编程(也叫面向方面编程):Aspect Oriented Programming(AOP),通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
我的理解:
举个例子:
生产环境中,我们需要对一些方法(几乎所有方法)的调用都要做一些日志统计,即在方法调用前打印日志“XXXX方法开始运行”,方法调用结束后打印“xxx方法运行结束”,假设需要对所有的方法都执行这样的操作,传统的方法是将打日志的这个操作封装成一个util类,里面分别写上方法调用前后打印日志的对应方法。这样能够满足要求,但人不可避免的是,我们仍然需要在业务demo中嵌入Util类的代码,这些代码是与业务无关的,我们需要将这些与业务无关的代码抽出来封装成一个切面(一个类),当对这个切面有使用需求时,只需在Spring的配置文件中进行配置即可,而无需在业务代码中嵌入与业务无关的代码。(这是aop的一个应用场景)
主要的意图是:
将日志记录,性能统计,安全控制,事务处理,异常处理等代码从业务逻辑代码中划分出来,通过对这些行为的分离,我们希望可以将它们独立到非指导业务逻辑的方法中,进而改变这些行为的时候不影响业务逻辑的代码。
二、AOP基本术语
1、通知、增强(Advice)
通知就是你想要的功能,在特定的连接点,AOP框架执行的动作。
Spring切面可以应用5种类型的通知:
前置通知(Before):在目标方法被调用之前调用通知功能;
后置通知(After):在目标方法完成之后调用通知,此时不会关心方法的输出是什么;
返回通知(After-returning):在目标方法成功执行之后调用通知;
异常通知(After-throwing):在目标方法抛出异常后调用通知;
环绕通知(Around):通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为。
2、连接点(JoinPoint)
程序执行过程中明确的点,如方法调用前,方法调用后,方法返回,方法抛出异常等。一个类或一段程序代码拥有一些具有边界性质的特定点,这些点中的特定点就称为“连接点”。Spring仅支持方法的连接点。允许使用通知的地方,都算连接点。
3、切点(PointCut)
相当于特定的连接点(JoinPoint),AOP通过“切点”定位特定的连接点。连接点相当于数据库中的记录,而切点相当于查询条件。切点和连接点不是一对一的关系,一个切点可以匹配多个连接点。
上面说的连接点的基础上,来定义切入点,你的一个类里,有15个方法,那就有几十个连接点了对把,但是你并不想在所有方法附近都使用通知(使用叫织入,下面说),你只想让其中的几个,在调用这几个方法之前,之后或者抛出异常时干点什么,那么就用切点来定义这几个方法,让切点来筛选连接点,选中那几个你想要的方法。
4、切面(Aspect)
通知和切入点共同组成了切面。说到这里,其实发现连接点的概念就是给你理解切点用的。通知说明了干什么和什么时候干,而切点说明了在哪儿干,两个加起来,何时、何处、做什么。
在spring里可以利用xml或者@Aspect注解定义切面
从下面的xml定义就可以看到切面是咋回事了,包含了pointcut和adivce(before)。
<aop:aspect id="aspectDemo" ref="aspectBean">
<aop:pointcut id="myPointcut" expression="execution(* package1.Foo.handle*(..)"/>
<aop:before pointcut-ref="myPointcut" method="doLog" />
</aop:aspect>
5、引入(Introduction)
引入是指给一个现有类添加方法或字段属性,引入还可以在不改变现有类代码的情况下,让现有的Java类实现新的接口(以及一个对应的实现)。相对于Advice可以动态改变程序的功能或流程来说,引介(Introduction)则用来改变一个类的静态结构。
Spring允许引入新的接口到任何被通知的对象。例如,你可以使用一个引入使任何对象实现 IsModified接口,来简化缓存。Spring中要使用Introduction, 可有通过DelegatingIntroductionInterceptor来实现通知,通过DefaultIntroductionAdvisor来配置Advice和代理类要实现的接口
(引入允许我们向业务类中业务方法添加新的方法和属性【spring提供了一个方法注入的功能】)
6、目标(Target)
引入中所提到的目标类,也就是要被通知的对象,也就是真正的业务逻辑,他可以在毫不知情的情况下,被咱们织入切面。而自己专注于业务本身的逻辑。
7、代理(Proxy)
应用通知的对象,详细内容参见设计模式里面的代理模式
(换句话:就是代理做了切面的业务,也做了真正的业务。)
AOP代理(AOP Proxy): AOP框架创建的对象,包含通知。 在Spring中,AOP代理可以是JDK动态代理或者CGLIB代理。
Spring缺省使用J2SE 动态代理(dynamic proxies)来作为AOP的代理。这样任何接口都可以被代理。
Spring也支持使用CGLIB代理. 对于需要代理类而不是代理接口的时候CGLIB代理是很有必要的。 如果一个业务对象并没有实现一个接口,默认就会使用CGLIB。 作为面向接口编程的最佳实践,业务对象通常都会实现一个或多个接口。
8、织入(Weaving)
把切面应用到目标对象来创建新的代理对象的过程,织入一般发生在如下几个时机:
编译时:当一个类文件被编译时进行织入,这需要特殊的编译器才可以做的到,例如AspectJ的织入编译器
类加载时:使用特殊的ClassLoader在目标类被加载到程序之前增强类的字节代码
运行时:切面在运行的某个时刻被织入,SpringAOP就是以这种方式织入切面的,原理应该是使用了JDK的动态代理技术
织入是将通知添加对目标类具体连接点上的过程。AOP像一台织布机,将目标类、通知或引介通过AOP这台织布机天衣无缝地编织到一起。
把切面应用到目标对象来创建新的代理对象的过程,Spring采用动态代理织入,而AspectJ采用编译期织入和类装载期织入。
基本术语参考博客:https://www.jianshu.com/p/22a29acddae9
简单实例部分
两种方式:
1、XML方式配置springAOP:工作中不常用,因为配置过程过于繁琐,后被注解方式取代,学习xml配置有助于理解AOP。
2、注解方式:如上。
一、XML方式配置AOP
1、搭建maven工程(不赘述)。
2、导依赖包
<!-- 切面编程依赖包 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>4.3.10.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>4.3.10.RELEASE</version>
</dependency>
<dependency>
<groupId>aopalliance</groupId>
<artifactId>aopalliance</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.8.10</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.10</version>
</dependency>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib-nodep</artifactId>
<version>3.2.5</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>
<!-- Spring依赖 -->
<!-- 1.Spring核心依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>4.3.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>4.3.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.3.7.RELEASE</version>
</dependency>
3、切面类
package com.aop.config;
import org.aspectj.lang.ProceedingJoinPoint;
public class XmlAop {
public void doBefore(){
System.out.println("before。。。");
}
public void doAfter(){
System.out.println("after。。。");
}
public void doAround(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("环绕通知开始");
Object proceed = joinPoint.proceed();
System.out.println("环绕通知结束");
}
}
4、业务代码类
public class UserDao {
public void saySomthing(){
System.out.println("this is DAO module");
}
}
5、配置文件AopBeans.xml(重点)
<?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:context="http://www.springframework.org/schema/context"
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-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">
<!--业务类bean-->
<bean id="userDaoAop" class="com.aop.dao.UserDao"></bean>
<!--切面bean-->
<bean id="myPoint" class="com.aop.config.XmlAop"></bean>
<!--配置aop-->
<aop:config>
<!--定义切点-->
<!--execution(* com.aop.dao.UserDao.*(..)) 表示:任意返回类型 com.aop.dao.UserDao下=任意方法任意参数-->
<aop:pointcut id="pointcut1" expression="execution(* com.aop.dao.UserDao.saySomthing(..))"/>
<!-- 切面 -->
<aop:aspect ref="myPoint">
<!-- doBefore doAfter doAround 分别对应 com.aop.config.XmlAop中的 doBefore()doAfter()doAround() -->
<aop:before method="doBefore" pointcut-ref="pointcut1"></aop:before>
<aop:after method="doAfter" pointcut-ref="pointcut1"></aop:after>
<aop:around method="doAround" pointcut-ref="pointcut1"></aop:around>
</aop:aspect>
</aop:config>
</beans>
6、测试类UserController.java
public class UserController {
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("AopBeans.xml");
UserDao userDao = (UserDao)applicationContext.getBean("userDao");
userDao.saySomthing();
}
}
运行结果:
before。。。
环绕通知开始
this is DAO module
环绕通知结束
after。。。
二、注解方式配置AOP
只有上面第3步和配置文件不一样:
1、切面类
package com.aop.config;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
/**
* @Description:
* @Author: renxin.tang
* @Date: 2019-08-19 16:40
*/
//声明这是一个切面bean
@Aspect
//声明这是一个组件
@Component
public class AnnotationAop {
//配置切入点,该方法无方法体,主要为方便同类中其他方法使用此处配置的切入点
@Pointcut("execution(* com.aop.dao.UserDao.*(..))")
public void aspect(){ }
/*
* 配置前置通知,使用在方法aspect()上注册的切入点
* 同时接受JoinPoint切入点对象,可以没有该参数
*/
@Before("aspect()")
public void doBefore() {
System.out.println("annotation before。。。");
}
//配置后置通知,使用在方法aspect()上注册的切入点
@After("aspect()")
public void doAfter() {
System.out.println("annotation after。。。");
}
//配置环绕通知,使用在方法aspect()上注册的切入点
@Around("aspect()")
public void doAround(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("annotation 环绕通知开始");
Object proceed = joinPoint.proceed();
System.out.println("annotation 环绕通知结束");
}
}
2、配置文件AnnocationAopBeans.xml
<?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:context="http://www.springframework.org/schema/context"
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-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">
<!-- 激活组件扫描功能,在包com.aop及其子包下面自动扫描通过注解配置的组件 -->
<context:component-scan base-package="com.aop"/>
<!-- 激活自动代理功能 -->
<aop:aspectj-autoproxy proxy-target-class="true"/>
<!-- 用户服务对象 -->
<bean id="userDao" class="com.aop.dao.UserDao"></bean>
</beans>
3、测试类
package com.aop.controller;
import com.aop.dao.UserDao;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* @Description:
* @Author: renxin.tang
* @Date: 2019-08-19 08:54
*/
public class AnnocationAopTest {
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("AnnocationAopBeans.xml");
UserDao userDao = (UserDao)applicationContext.getBean("userDao");
userDao.saySomthing();
}
}
运行结果:
annotation 环绕通知开始
annotation before。。。
this is DAO module
annotation 环绕通知结束
annotation after。。。