一、什么是AOP
AOP(Aspect-Oriented Programming, 面向切面编程): 是一种新的方法论, 是对传统 OOP(Object-Oriented Programming, 面向对象编程) 的补充。在程序开发中主要用来解决一些系统层面上的问题,比如日志,事务,权限等待,Struts2的拦截器设计就是基于AOP的思想,是个比较经典的例子。
AOP是spring框架中的一个重要内容。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。AOP可以分为静态织入与动态织入,静态织入即在编译前将需织入内容写入目标模块中,这样成本非常高。动态织入则不需要改变目标模块。Spring框架实现了AOP,使用注解配置完成AOP比使用XML配置要更加方便与直观。
AOP体系结构如下:
层次1底层编织实现模块:主要是将面向方面系统抽象封装的AOP模型编织进待增强的目标对象的实现技术。
层次2面向方面系统:配置模型,逻辑配置和AOP模型是为上策的语言和开发环境提供支持的,主要功能是将需要增强的目标对象、切面和配置使用AOP的API转换、抽象、封装成面向方面中的逻辑模型。
层次3语言和开发环境:基础是指待增加对象或者目标对象;切面通常包括对于基础的增加应用;配置是指AOP体系中提供的配置环境或者编织配置,通过该配置AOP将基础和切面结合起来,从而完成切面对目标对象的编织实现。
二、AOP的基本术语解释
1、横切关注点
对哪些方法进行拦截,拦截后怎么处理,这些关注点称之为横切关注点。
2、切面(aspect)
类是对物体特征的抽象,切面就是对横切关注点的抽象。
3、连接点(joinpoint)
被拦截到的点,因为Spring只支持方法类型的连接点,所以在Spring中连接点指的就是被拦截到的方法,实际上连接点还可以是字段或者构造器。
4、切入点(pointcut)
对连接点进行拦截的定义
5、通知(advice)
所谓通知指的就是指拦截到连接点之后要执行的代码,通知分为前置、后置、异常、最终、环绕通知五类。
6、目标对象
代理的目标对象
7、织入(weave)
将切面应用到目标对象并导致代理对象创建的过程
8、引入(introduction)
在不修改代码的前提下,引入可以在运行期为类动态地添加一些方法或字段
三、Spring AOP简介
AspectJ是Java 社区里最完整最流行的 AOP 框架。而在 Spring2.0 以上版本中, 可以使用基于 AspectJ 注解或基于 XML 配置的 AOP。
Spring中AOP代理由Spring的IOC容器负责生成、管理,其依赖关系也由IOC容器负责管理。因此,AOP代理可以直接使用容器中的其它bean实例作为目标,这种关系可由IOC容器的依赖注入提供。Spring创建代理的规则为:
1、默认使用Java动态代理来创建AOP代理,这样就可以为任何接口实例创建代理了。
2、当需要代理的类不是代理接口的时候,Spring会切换为使用CGLIB代理,也可强制使用CGLIB。
AOP编程其实是很简单的事情,纵观AOP编程,程序员只需要参与三个部分:
1、定义普通业务组件
2、定义切入点,一个切入点可能横切多个业务组件
3、定义增强处理,增强处理就是在AOP框架为普通业务组件织入的处理动作
所以进行AOP编程的关键就是定义切入点和定义增强处理,一旦定义了合适的切入点和增强处理,AOP框架将自动生成AOP代理,即:代理对象的方法=增强处理+被代理对象的方法。
四、AOP通知类型介绍
1、Before:在目标方法被调用之前做增强处理(前置通知)
2、AfterReturning:在目标方法正常完成后做增强(后置通知)
3、AfterThrowing:主要用来处理程序中未处理的异常(异常通知)
4、After:在目标方法完成之后做增强,无论目标方法是否成功完成(最终通知)
5、Around:环绕通知,在目标方法完成前后做增强处理,环绕通知是最重要的通知类型,像事务,日志等都是环绕通知,注意编程中核心是一个ProceedingJoinPoint(环绕通知)
五、Spring AOP配置的两种方式
1、基于注解的方式
(1)准备
要在Spring应用中使用AspectJ注解,必须在classpath下包含AspectJ类库: aopalliance.jar、aspectj.weaver.jar 和 spring-aspects.jar。同时要在 Spring IOC 容器中启用 AspectJ 注解支持, 只要在 Bean 配置文件中定义一个空的 XML 元素 <aop:aspectj-autoproxy />即可。
这样当 Spring IOC 容器侦测到Bean配置文件中的 <aop:aspectj-autoproxy/> 元素时, 会自动为与 AspectJ切面匹配的Bean创建代理。
(2)注解声明的使用
要在Spring中声明AspectJ切面,只需要在 IOC 容器中将切面声明为Bean实例。当在 Spring IOC 容器中初始化AspectJ切面之后, Spring IOC 容器就会为那些与 AspectJ 切面相匹配的 Bean 创建代理。
在 AspectJ 注解中, 切面只是一个带有 @Aspect 注解的Java类。
AspectJ 支持 5 种类型的通知注解:
@Before: 前置通知, 在方法执行之前执行
@After: 后置通知, 在方法执行之后执行
@AfterRunning: 返回通知, 在方法返回结果之后执行
@AfterThrowing: 异常通知, 在方法抛出异常之后
@Around: 环绕通知, 围绕着方法执行
/**
* @author 作者 Your-Name:
* @version 创建时间:2019年5月31日 上午10:37:25
* 类说明
*/
package com.sumeng.aop;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
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;
/**
* @author Administrator
*
*/
//将切面类交给Spring管理
@Aspect
public class UserInfoAop {
//前置通知
@Before(value="execution (* com.sumeng.aop.UserInfo.add(..))")
public void check(JoinPoint point){
System.out.println("添加数据之前执行的方法!!!!"+point);
}
//后置通知
@AfterReturning(value="execution (* com.sumeng.aop.UserInfo.de*(..))",returning="log")
public void checkDelect(Object log){
System.out.println("保存日志信息之后执行!!!"+log);
}
//环绕通知
@Around(value="execution (* com.sumeng.aop.UserInfo.up*(..))")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable{
System.out.println("环绕通知之前执行的方法!!!!");
Object obj = joinPoint.proceed();
System.out.println("环绕通知之后执行的方法!!!!");
return obj;
}
//异常通知
@AfterThrowing(value="execution (* com.sumeng.aop.UserInfo.che*(..))",throwing="ex")
public void checkQuery(JoinPoint ex){
System.out.println("异常错误"+ ex);
}
//始终通知
@After(value="UserInfoAop.pointcut()")
public void after(){
System.out.println("始终通知!!!!");
}
//切入点 简化注入 相当于给@给了个 id
@Pointcut(value="execution (* com.sumeng.aop.UserInfo.c*(..))")
public void pointcut(){}
}
2、基于XML的方式
(1)准备
当使用 XML 声明切面时, 需要在 <beans> 根元素中导入 aop Schema。
在 Bean 配置文件中, 所有的 Spring AOP 配置都必须定义在 <aop:config> 元素内部. 对于每个切面而言, 都要创建一个 <aop:aspect> 元素来为具体的切面实现引用后端 Bean 实例。
切面 Bean 必须有一个标示符, 供 <aop:aspect> 元素引用。
(2)XML配置方式
切入点使用 <aop:pointcut> ,必须定义在 <aop:aspect> 元素下, 或者直接定义在 <aop:config> 元素下。若定义在 <aop:aspect> 元素下: 只对当前切面有效;若定义在 <aop:config> 元素下: 对所有切面都有效。基于 XML 的 AOP 配置不允许在切入点表达式中用名称引用其他切入点。
在 aop Schema 中, 每种通知类型都对应一个特定的 XML 元素。 通知元素需要使用 <pointcut-ref> 来引用切入点, 或用 <pointcut> 直接嵌入切入点表达式,method 属性指定切面类中通知方法的名称。
<?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"
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-4.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.0.xsd">
<!--
init-method="getstart" 该类初始化执行的方法
destroy-method="getdestroy" 该类被销毁时执行的方法
scope="singleton" 默认为单列模式 可以 不写
scope="prototype" 多列模式
<property name="该类的属性名" value="给该类属性名注入的值"></property>
<import resource="user.xml"/> 若有多个配置文件可以用 inport
-->
<!-- <bean id="car" class="com.sumeng.web.Car" init-method="getstart" destroy-method="getdestroy" scope="">
<property name="carName" value="奥迪"></property>
</bean>
<import resource="user.xml"/>-->
<!-- 通过 注解 注入 类扫描器 -->
<!-- <context:component-scan base-package="com.sumeng.web"></context:component-scan>-->
<bean id="userInfo" class="com.sumeng.aop.UserInfo" ></bean>
<bean id="userInfoAop" class="com.sumeng.aop.UserInfoAop" ></bean>
<!-- 开启注解 -->
<context:annotation-config />
<!-- Aop配置 -->
<aop:config>
<!-- 配置切入点pointcut -->
<aop:pointcut expression="execution(* com.sumeng.aop.UserInfo.add(..))" id="userInfoPointcut"/>
<aop:pointcut expression="execution(* com.sumeng.aop.UserInfo.delete(..))" id="deletePointcut"/>
<aop:pointcut expression="execution(* com.sumeng.aop.UserInfo.update(..))" id="updatePointcut"/>
<aop:pointcut expression="execution(* com.sumeng.aop.UserInfo.check(..))" id="checkPointcut"/>
<!-- 配置切面 -->
<aop:aspect ref="userInfoAop">
<!-- 前置通知 -->
<aop:before method="check" pointcut-ref="userInfoPointcut"/>
<!-- 后置通知 -->
<aop:after-returning method="checkDelect" pointcut-ref="deletePointcut" returning="log"/>
<!-- 环绕通知 -->
<aop:around method="around" pointcut-ref="updatePointcut"/>
<!-- 异常通知 -->
<aop:after-throwing method="checkQuery" pointcut-ref="checkPointcut" throwing="ex"/>
<!-- 始终通知 -->
<aop:after method="checkQuery" pointcut-ref="checkPointcut"/>
</aop:aspect>
</aop:config>
</beans>