Spring之AOP
AOP
1.面向切面编程(Aspect Oriented Programming)提供了另一种角度来思考程序的结构,通过这种方式弥补面向对象编程(Object Oriented Programming)的不足。除了类以外,AOP提供了切面,切面对关注点进行模块化,例如横切多个类型和对象的事务管理(这些关注点术语通常称作横切(crosscutting)关注点)。Spring AOP是Spring的一个重要组件,但是Spring IOC并不依赖于Spring AOP,这意味着你可以自由选择是否使用AOP,AOP提供了强大的中间件解决方案,这使得Spring IOC更加完善。我们可以通过AOP来实现日志监听,事务管理,权限控制等等。
简而言之: AOP面向切面编程是OOP的延续,它是一种可以通过预编译方式和运行期间动态代 理来实现在不修改源代码的情况下给程序动态 地统一添加功能的一种技术,它是软件工程“开-闭”原则的一种实现.体现AOP的地方:事务管理,日志管理
继续进一步:AOP实现的原理:代理–>动态代理---->jdk代理或者cglib代理
回顾备注:事务的的原理的是依靠AOP ,AOP的原理是依靠代理
Spring系列之AOP实现的两种方式
AOP常用的实现方式有两种,一种是采用声明的方式来实现(基于XML),一种是采用注解的方式来实现(基于AspectJ)。
通知(Advice)的类型:
前置通知(Before advice):在某个连接点(Join point)之前执行的通知,但这个通知不能阻止连接点的执行(除非它抛出一个异常)。
返回后通知(After returning advice):在某个连接点(Join point)正常完成后执行的通知。例如,一个方法没有抛出任何异常正常返回。
抛出异常后通知(After throwing advice):在方法抛出异常后执行的通知。
后置通知(After(finally)advice):当某个连接点(Join point)退出的时候执行的通知(不论是正常返回还是发生异常退出)。
环绕通知(Around advice):包围一个连接点(Join point)的通知,如方法调用。这是最强大的一种通知类型。环绕通知可以在方法前后完成自定义的行为。它也会选择是否继续执行连接点或直接返回它们自己的返回值或抛出异常来结束执行。
采用声明的方式来实现AOP(基于XML)
1.创建一个项目AOP02(非web项目)
2.创建一个lib文件夹,并把相关的jar包放在该目录下并构建路径
3.模拟一个M层的接口(IUserManagerService),一个接口的实现类(UserManagerServiceImpl),具体代码见子标题中的内容
IUserManagerService类
package com.rj.bd.user;
/**
* @desc 模拟一个M层的接口
*/
public interface IUserManagerService {
//查找用户
public String findUser();
//添加用户
public void addUser();
}
UserManagerServiceImpl类
package com.rj.bd.user;
/**
* @desc 模拟一个M层的接口的实现类
*/
public class UserManagerServiceImpl implements IUserManagerService {
private String name;
public void setName(String name){
this.name=name;
}
public String getName(){
return this.name;
}
public String findUser(){
System.out.println("============执行业务方法findUser,查找的用户是:"+name+"=============");
return name;
}
public void addUser(){
System.out.println("============执行业务方法addUser=============");
//throw new RuntimeException();
}
}
4.创建一个切面类,在里面使用AOP中常见的几种的通知AopAspect表
package com.rj.bd.aops;
/**
* @desc 切面類
*/
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
public class AopAspect {
/**
* 前置通知:目标方法调用之前执行的代码
* @param jp
*/
public void doBefore(JoinPoint jp){
System.out.println("===========执行前置通知============");
}
/**
* 后置返回通知:目标方法正常结束后执行的代码
* 返回通知是可以访问到目标方法的返回值的
* @param jp
* @param result
*/
public void doAfterReturning(JoinPoint jp,String result){
System.out.println("===========执行后置通知============");
System.out.println("返回值result==================="+result);
}
/**
* 最终通知:目标方法调用之后执行的代码(无论目标方法是否出现异常均执行)
* 因为方法可能会出现异常,所以不能返回方法的返回值
* @param jp
*/
public void doAfter(JoinPoint jp){
System.out.println("===========执行最终通知============");
}
/**
*
* 异常通知:目标方法抛出异常时执行的代码
* 可以访问到异常对象
* @param jp
* @param ex
*/
public void doAfterThrowing(JoinPoint jp,Exception ex){
System.out.println("===========执行异常通知============");
}
/**
* 环绕通知:目标方法调用前后执行的代码,可以在方法调用前后完成自定义的行为。
* 包围一个连接点(join point)的通知。它会在切入点方法执行前执行同时方法结束也会执行对应的部分。
* 主要是调用proceed()方法来执行切入点方法,来作为环绕通知前后方法的分水岭。
*
* 环绕通知类似于动态代理的全过程:ProceedingJoinPoint类型的参数可以决定是否执行目标方法。
* 而且环绕通知必须有返回值,返回值即为目标方法的返回值
* @param pjp
* @return
* @throws Throwable
*/
public Object doAround(ProceedingJoinPoint pjp) throws Throwable{
System.out.println("======执行环绕通知开始=========");
// 调用方法的参数
Object[] args = pjp.getArgs();
// 调用的方法名
String method = pjp.getSignature().getName();
// 获取目标对象
Object target = pjp.getTarget();
// 执行完方法的返回值
// 调用proceed()方法,就会触发切入点方法执行
Object result=pjp.proceed();
System.out.println("输出,方法名:" + method + ";目标对象:" + target + ";返回值:" + result);
System.out.println("======执行环绕通知结束=========");
return result;
}
}
5.创建applicationContext.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:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
<!-- 声明一个业务类 -->
<bean id="userManager" class="com.rj.bd.user.UserManagerServiceImpl">
<property name="name" value="lixiaoxi"></property>
</bean>
<!-- 声明通知类 -->
<bean id="aspectBean" class="com.rj.bd.aops.AopAspect" />
<aop:config>
<aop:aspect ref="aspectBean">
<aop:pointcut id="pointcut" expression="execution(* com.rj.bd.user.*.*(..))"/>
<aop:before method="doBefore" pointcut-ref="pointcut"/>
<aop:after-returning method="doAfterReturning" pointcut-ref="pointcut" returning="result"/>
<aop:after method="doAfter" pointcut-ref="pointcut" />
<aop:around method="doAround" pointcut-ref="pointcut"/>
<aop:after-throwing method="doAfterThrowing" pointcut-ref="pointcut" throwing="ex"/>
</aop:aspect>
</aop:config>
</beans>
6.运行测试类Test.java
package com.rj.bd.test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.rj.bd.user.IUserManagerService;
/**
* @desc 測試類:測試基於XML配置的AOP
*/
public class Test {
public static void main(String[] args) {
ApplicationContext act = new ClassPathXmlApplicationContext("applicationContext.xml");
IUserManagerService userManager = (IUserManagerService)act.getBean("userManager");
userManager.findUser();
System.out.println("\n");
userManager.addUser();
}
}
补充讲解:applicationContext.xml中aop节点的含义
1.aop:pointcut如果位于aop:aspect元素中,则命名切点只能被当前aop:aspect内定义的元素访问到,为了能被整个aop:config元素中定义的所有增强访问,则必须在aop:config下定义切点。
2.如果在aop:config元素下直接定义aop:pointcut,必须保证aop:pointcut在aop:aspect之前定义。aop:config下还可以定义aop:advisor,三者在aop:config中的配置有先后顺序的要求:首先必须是aop:pointcut,然后是aop:advisor,最后是aop:aspect。而在aop:aspect中定义的aop:pointcut则没有先后顺序的要求,可以在任何位置定义。
.aop:pointcut:用来定义切入点,该切入点可以重用;
.aop:advisor:用来定义只有一个通知和一个切入点的切面;
.aop:aspect:用来定义切面,该切面可以包含多个切入点和通知,而且标签内部的通知和切入点定义是无序的;和advisor的区别就在此,advisor只包含一个通知和一个切入点。
3.在使用spring框架配置AOP的时候,不管是通过XML配置文件还是注解的方式都需要定义pointcut"切入点"
例如定义切入点表达式 execution(* com.sample.service.impl….(…))
execution()是最常用的切点函数,其语法如下所示:
整个表达式可以分为五个部分:
(1)、execution(): 表达式主体。
(2)、第一个*号:表示返回类型,号表示所有的类型。
(3)、包名:表示需要拦截的包名,后面的两个句点表示当前包和当前包的所有子包,com.sample.service.impl包、子孙包下所有类的方法。
(4)、第二个号:表示类名,号表示所有的类。
(5)、(…):最后这个星号表示方法名,*号表示所有的方法,后面括弧里面表示方法的参数,两个句点表示任何参数。
AOP通知的优先级
Spring AOP-通知顺序一共分为两种情况:
1) 同一切面中通知执行顺序:19-AOP通知优先级之同一切面中不同通知的优先级的示意图.png
2)不同切面中的通知执行顺序:默认是根据配置的先后顺序,但是我们可以自己设定
第一种情况我们之前的讲解中已经涵盖了,我们就不做过多的赘述了,我们直接以第二种情况作为例子进行讲解
1.先创建一个项目AOP04,除了AopAspect.java这个类没有之前其他的都跟AOP03一样
2.在文件夹aops下创建ArgsAspect.java LogAspect.java ,具体代码见子标题中的内容
3.在applicationContext.xml中配置:
<!-- 声明通知类-->
<bean id="logAspect" class="com.rj.bd.aops.LogAspect" />
<bean id="argsAspect" class="com.rj.bd.aops.ArgsAspect" />
4.执行之后会发现日志处理切面的优先级要高于参数处理切面的优先级,为了实现我们自己设定优先级,我们在ArgsAspect类中添加注解@Order(1),重新运行即可发现
LogAspect表
package com.rj.bd.aops;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@Aspect
public class LogAspect {
/**
* 必须为final String类型的,注解里要使用的变量只能是静态常量类型的
*/
public static final String EDP="execution(* com.rj.bd.user.*.*(..))";
/**
* 切面的前置方法 即方法执行前拦截到的方法
* 在目标方法执行之前的通知
* @param jp
*/
@Before(EDP)
public void doBefore(JoinPoint jp){
System.out.println("=========日志打印的通知==========");
}
}
ArgsAspect表
package com.rj.bd.aops;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.core.annotation.Order;
@Order(1)
/**
*
* 1)@Order该注解的作用为设定切面的优先级
* 2)通过阅读@Order源代码发现其里面只有一个value属性且默认值为最大值,
* 3)但是最大值其优先级为最低的,1为最小值但是优先级为最高的
*
*/
@Aspect
public class ArgsAspect {
/**
* 必须为final String类型的,注解里要使用的变量只能是静态常量类型的
*/
public static final String EDP="execution(* com.rj.bd.user.*.*(..))";
/**
* 切面的前置方法 即方法执行前拦截到的方法
* 在目标方法执行之前的通知
* @param jp
*/
@Before(EDP)
public void doBefore(JoinPoint jp){
System.out.println("=========参数验证的通知==========");
}
}
applicationContext.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:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
<!-- 声明spring对@AspectJ的支持 -->
<aop:aspectj-autoproxy/>
<!-- 声明一个业务类 -->
<bean id="userManager" class="com.rj.bd.user.UserManagerServiceImpl">
<property name="name" value="CHF"></property>
</bean>
<!-- 声明通知类-->
<bean id="logAspect" class="com.rj.bd.aops.LogAspect" />
<bean id="argsAspect" class="com.rj.bd.aops.ArgsAspect" />
</beans>