AOP介绍
面向切面编程(AOP)和面向对象编程(OOP)类似,也是一种编程模式。Spring AOP 是基于 AOP 编程模式的一个框架,它的使用有效减少了系统间的重复代码,达到了模块间的松耦合目的。它将业务逻辑的各个部分进行隔离,使开发人员在编写业务逻辑时可以专心于核心业务,从而提高了开发效率。AOP 采取横向抽取机制,取代了传统纵向继承体系的重复性代码,其应用主要体现在事务处理、日志管理、权限控制、异常处理等方面。
目前最流行的 AOP 框架有两个,分别为 Spring AOP 和 AspectJ。这里介绍Spring AOP的,从 Spring 2.0 开始,Spring AOP 引入了对 AspectJ 的支持。
专业术语说明
- 通知(Advice): AOP 框架中的增强处理。通知描述了切面何时执行以及如何执行增强处理。
- 连接点(join point): 连接点表示应用执行过程中能够插入切面的一个点,这个点可以是方法的调用、异常的抛出。在 Spring AOP 中,连接点总是方法的调用。
- 切点(PointCut): 可以插入增强处理的连接点。
- 切面(Aspect): 切面是通知和切点的结合。
- 引入(Introduction):引入允许我们向现有的类添加新的方法或者属性。
- 织入(Weaving): 将增强处理添加到目标对象中,并创建一个被增强的对象,这个过程就是织入。
Spring AOP 的特点
Spring 中的 AOP 是通过动态代理实现的。不同的 AOP 框架支持的连接点也有所区别,例如,AspectJ 和 JBoss,除了支持方法切点,它们还支持字段和构造器的连接点。而 Spring AOP 不能拦截对对象字段的修改,也不支持构造器连接点,我们无法在 Bean 创建时应用通知。
AOP分类
- 静态 AOP 实现, AOP 框架在编译阶段对程序源代码进行修改,生成了静态的 AOP 代理类(生成的 *.class 文件已经被改掉了,需要使用特定的编译器),比如 AspectJ。
- 动态 AOP 实现, AOP 框架在运行阶段对动态生成代理对象(在内存中以 JDK 动态代理,或 CGlib 动态地生成 AOP 代理类),如 SpringAOP。
各种AOP实现方式比较
动态代理的介绍在设计模式那里会介绍,这个就不描述了。
Spring AOP使用
有两种使用方式:一种是使用Spring中的 org.springframework.aop.framework.ProxyFactoryBean 类,这个类对应的切入点和通知提供了完整的控制能力,并可以生成指定的内容,但在开发中很少使用这种方式。开发中一般使用第二种方式,Spring使用AspectJ开发AOP,这种方式主要用基于 xml 和 Annotation 来实现的,下面也主要讲的是这种实现方式。
Spring使用AspectJ开发AOP
一般使用注解方式来开发,AspectJ 允许使用注解定义切面、切入点和增强处理,而 Spring 框架则可以识别并根据这些注解生成 AOP 代理。
具体实现流程:引入spring的jar包——》创建切面类 ——》创建 Spring 配置文件(使用注解的话就不需要了)【切面类的内容就是指定在什么方法上加上什么通知】——》执行(测试)加上通知的方法
通知类型:
切入点表达式
切入点表达式主要就是来配置拦截哪些类的哪些方法,就是下面 @Pointcut 经常用到的。
如何设置切点、创建切面
下面介绍设置切点的方法:可以根据目标方法设置、标注了指定注解的方法设置等等。这两种是经常使用的两种。
语法:
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?)
参数讲解:
- modifiers-pattern?【修饰的类型,可以不写】
- ret-type-pattern【方法返回值类型,必写】
- declaring-type-pattern?【方法声明的类型,可以不写】
- name-pattern(param-pattern)【要匹配的名称,括号里面是方法的参数】
- throws-pattern?【方法抛出的异常类型,可以不写】
符号讲解:
- ?号代表0或1,可以不写
- “*”号代表任意类型,0或多
- 方法参数为…表示为可变参数
官方案例讲解:
注解方式
首先需要在Spring配置文件中开启AOP注解方式。
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:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!--扫描含com.mengma包下的所有注解-->
<context:component-scan base-package="com.mengma"/>
<!-- 使切面开启自动代理 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
注解介绍:
- @Aspect:指定一个类为切面类
- @Pointcut:指定切入点表达式,通知最后增强哪些方法,注解所在的方法必须是private
- @Before:前置通知: 目标方法之前执行
- @After:后置通知:目标方法之后执行(始终执行)
- @AfterReturning:返回后通知: 执行方法结束前执行(异常不执行)
- @AfterThrowing:异常通知: 出现异常时候执行
- @Around:环绕通知: 环绕目标方法执行(常用)
使用案例:主要提供切面类的代码案例,拦截哪个类、测试代码就不提供了。
@Component//需要将bean注入到Spring容器中
@Aspect//指定为切面类
public class AOP {
// 指定切入点表达式,拦截哪个类的哪些方法
@Pointcut("execution(* aa.*.*(..))")
public void pt() {
}
@Before("pt()")
public void begin() {
System.out.println("开始事务");
}
@After("pt()")
public void close() {
System.out.println("关闭事务");
}
}
xml方式的实现
基于 XML 的声明式是指通过 Spring 配置文件的方式定义切面、切入点及声明通知,而所有的切面和通知都必须定义在 aop:config 元素中。
下面提供一个案例:
<?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-2.5.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">
<!--目标类 -->
<bean id="customerDao" class="com.mengma.dao.CustomerDaoImpl" />
<!--切面类 -->
<bean id="myAspect" class="com.mengma.aspectj.xml.MyAspect"></bean>
<!--AOP 编程 -->
<aop:config>
<aop:aspect ref="myAspect">
<!-- 配置切入点,通知最后增强哪些方法 -->
<aop:pointcut expression="execution ( * com.mengma.dao.*.* (..))"
id="myPointCut" />
<!--前置通知,关联通知 Advice和切入点PointCut -->
<aop:before method="myBefore" pointeut-ref="myPointCut" />
<!--后置通知,在方法返回之后执行,就可以获得返回值returning 属性 -->
<aop:after-returning method="myAfterReturning"
pointcut-ref="myPointCut" returning="returnVal" />
<!--环绕通知 -->
<aop:around method="myAround" pointcut-ref="myPointCut" />
<!--抛出通知:用于处理程序发生异常,可以接收当前方法产生的异常 -->
<!-- *注意:如果程序没有异常,则不会执行增强 -->
<!-- * throwing属性:用于设置通知第二个参数的名称,类型Throwable -->
<aop:after-throwing method="myAfterThrowing"
pointcut-ref="myPointCut" throwing="e" />
<!--最终通知:无论程序发生任何事情,都将执行 -->
<aop:after method="myAfter" pointcut-ref="myPointCut" />
</aop:aspect>
</aop:config>
</beans>
切面类型总结
- AOP的底层实际上是动态代理,动态代理分成了JDK动态代理和CGLib动态代理。如果被代理对象没有接口,那么就使用的是CGLIB代理(也可以直接配置使用CBLib代理)
- 如果是单例的话,那我们最好使用CGLib代理,因为CGLib代理对象运行速度要比JDK的代理对象要快
- AOP既然是基于动态代理的,那么它只能对方法进行拦截,它的层面上是方法级别的
- 无论经典的方式、注解方式还是XML配置方式使用Spring AOP的原理都是一样的,只不过形式变了而已。一般我们使用注解的方式使用AOP就好了。
- 注解的方式使用Spring AOP就了解几个切点表达式,几个增强/通知的注解就完事了,是不是贼简单…使用XML的方式和注解其实没有很大的区别,很快就可以上手啦。
- 引介/引入切面也算是一个比较亮的地方,可以用代理的方式为某个对象实现接口,从而能够使用借口下的方法。这种方式是非侵入式的~
- 要增强的方法还可以接收与被代理方法一样的参数、绑定被代理方法的返回值这些功能…