本节主要内容:
一、Spring 通过XML配置文件形式来AOP 来实现前置,环绕,异常通知
1. Spring AOP 前置通知 XML配置使用案例
2. Spring AOP 环绕通知 XML配置使用案例
3. Spring AOP 抛出异常后通知 XML配置使用案例
4. Spring AOP 返回后通知 XML配置使用案例
5. Spring AOP 后通知 XML配置使用案例
二、Spring 通过注解形式来AOP 来实现前置,环绕,异常通知
1. Spring AOP 前置通知 注解使用案例
2. Spring AOP 环绕通知 注解使用案例
3. Spring AOP 抛出异常后通知 注解使用案例
4. Spring AOP 返回后通知 注解使用案例
5. Spring AOP 后通知 注解使用案例
AOP是Aspect Oriented Programming的缩写,意思是面向方面编程,AOP实际是GoF设计模式的延续
关于Spring AOP的一些术语
通知类型
Spring 实现AOP是依赖JDK动态代理和CGLIB代理实现的。以下是JDK动态代理和CGLIB代理简单介绍
JDK动态代理:其代理对象必须是某个接口的实现,它是通过在运行期间创建一个接口的实现类来完成对目标对象的代理。 CGLIB代理:实现原理类似于JDK动态代理,只是它在运行期间生成的代理对象是针对目标类扩展的子类。CGLIB是高效的代码生成包,底层是依靠ASM(开源的java字节码编辑类库)操作字节码实现的,性能比JDK强。
在Spring中,有接口时将采用JDK的方式实现proxy代理对象,当没有接口时,将采用cglib中的方式实现prixy代理对象。
一、 Spring 通过XML配置文件形式来AOP 来实现前置,环绕,异常通知
使用Spring AOP前置通知,在访问Controller中每个方法前,记录用户的操作日志。
Spring AOP使用步骤:
实现此案例需要按照如下步骤进行。
步骤一:创建Controller,创建新项目SpringAOP。
导入Spring 环境的jar包 :
如果没有jar包,那么可以上去上面下一个。下载地址:http://yunpan.cn/cdXTcJtZfJqQk 访问密码 6c96
创建员工业务控制器EmpController,并实现员工查询,代码如下:
package com.souvc.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
/**
* 控制类
*
*/
@Controller
@RequestMapping("/emp")
public class EmpController {
/**
* 方法名:find</br>
* 详述: 查询员工 </br>
* 开发人员:hgf </br>
* 创建时间:2019年08月19日 </br>
* @param userid
* @param password
* @return
* @throws
*/
@RequestMapping("/findEmp.do")
public String find(String userid,String password) {
// 模拟查询员工数据
System.out.println("执行,find()方法,查询员工数据,发送至列表页面.");
return "emp/emp_list.jsp";
}
}
步骤二:创建方面组件
创建方面组件OperateLogger,并在该类中创建记录用户操作日志的方法,代码如下:
package com.souvc.aspect;
/**
* 用于记录日志的方面组件,演示Spring AOP的各种通知类型。
*/
public class OperateLogger {
/**
* 方法名:log1</br>
* 详述:测试前置通知 </br>
* 开发人员:hgf </br>
* 创建时间:2019年08月19日 </br>
* @throws
*/
public void log1() {
// 记录日志
System.out.println("进入log1()方法");
}
}
步骤三:声明方面组件
在applicationContext.xml中,声明该方面组件,关键代码如下:
<!-- 声明方面组件 -->
<bean id="operateLogger" class="com.souvc.aspect.OperateLogger"/>
步骤四:将方面组件作用到目标组件上
在applicationContext.xml中,将声明的方面组件作用到com.souvc.controller包下所有类的所有方法上,关键代码如下:
<!-- 声明方面组件 -->
<bean id="operateLogger" class="com.souvc.aspect.OperateLogger"/>
<!-- 配置AOP -->
<aop:config>
<aop:aspect ref="operateLogger">
<aop:before method="log1" pointcut="within(com.souvc.controller..*)"/>
</aop:aspect>
</aop:config>
步骤五:测试
创建Junit测试类TestEmpController,并增加测试查询员工的方法,代码如下:
package com.souvc.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
/**
* 控制类
*
*/
@Controller
@RequestMapping("/emp")
public class EmpController {
/**
* 方法名:find</br>
* 详述: 查询员工 </br>
* 开发人员:hgf </br>
* 创建时间:2019年08月19日 </br>
* @param userid
* @param password
* @return
* @throws
*/
@RequestMapping("/findEmp.do")
public String find(String userid,String password) {
// 模拟查询员工数据
System.out.println("执行,find()方法,查询员工数据,发送至列表页面.");
return "emp/emp_list.jsp";
}
}
执行该测试方法,控制台输出效果:
进入log1()方法
执行,find()方法,查询员工数据,发送至列表页面.
可见,在执行EmpController.find()方法之前,执行了方面组件的记录日志的方法,由于该方法采用AOP面向对象的思想实现的,因此不需要对Controller类做任何改动。
使用Spring AOP环绕通知,在访问Controller中每个方法前,记录用户的操作日志。
Spring AOP使用步骤:
实现此案例需要按照如下步骤进行。
步骤一:创建方面组件
复用方面组件OperateLogger,在该类中创建新的记录日志的方法log2,代码如下:
/**
* 方法名:log2</br>
* 详述:环绕通知使用的方法 </br>
* 开发人员:hgf </br>
* 创建时间:2019年08月19日 </br>
* @param p
* @return
* @throws Throwable
* @throws
*/
public Object log2(ProceedingJoinPoint p) throws Throwable {
// 目标组件的类名
String className = p.getTarget().getClass().getName();
// 调用的方法名
String method = p.getSignature().getName();
// 当前系统时间
String date = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss") .format(new Date());
// 拼日志信息
String msg = "-->用户在" + date + ",执行了" + className + "." + method + "()";
// 记录日志
System.out.println(msg);
// 执行目标组件的方法
Object obj = p.proceed();
// 在调用目标组件业务方法后也可以做一些业务处理
System.out.println("-->调用目标组件业务方法后...");
return obj;
}
步骤二:声明方面组件
由于复用的方面组件已经声明,因此该步骤可以省略。
步骤三:将方面组件作用到目标组件上
在applicationContext.xml中,声明方面组件的log2方法,关键代码如下:
<aop:aspect ref="operateLogger">
<aop:around method="log2" pointcut="within(com.souvc.controller..*)"/>
</aop:aspect>
步骤四:测试
执行测试方法TestEmpController.test1(),控制台输出效果如下:
进入log1()方法
-->用户在2019-008-19 17:06:54,执行了com.souvc.controller.EmpController.find()
执行,find()方法,查询员工数据,发送至列表页面.
-->调用目标组件业务方法后..
项目详细代码:
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:context="http://www.springframework.org/schema/context"
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:jee="http://www.springframework.org/schema/jee"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:jpa="http://www.springframework.org/schema/data/jpa"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd
http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-3.2.xsd
http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.2.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.2.xsd
http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa-1.3.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd">
<!-- 开启注解扫描 -->
<context:component-scan base-package="com.souvc" />
<!-- 支持@RequestMapping请求和Controller映射 -->
<mvc:annotation-driven />
<bean
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/" />
<property name="suffix" value=".jsp" />
</bean>
<!-- 声明方面组件-->
<bean id="operateLogger" class="com.souvc.aspect.OperateLogger" />
<!-- 配置AOP -->
<aop:config>
<!-- 测试前置通知 -->
<aop:aspect ref="operateLogger">
<aop:before method="log1" pointcut="within(com.souvc.controller..*)"/>
</aop:aspect>
<!-- 测试环绕通知 -->
<aop:aspect ref="operateLogger">
<aop:around method="log2" pointcut="within(com.souvc.controller..*)"/>
</aop:aspect>
</aop:config>
</beans>
OperateLogger.java
package com.souvc.aspect;
import java.text.SimpleDateFormat;
import java.util.Date;
import org.aspectj.lang.ProceedingJoinPoint;
/**
* 用于记录日志的方面组件,演示Spring AOP的各种通知类型。
*/
public class OperateLogger {
/**
* 方法名:log1</br>
* 详述:测试前置通知 </br>
* 开发人员:hgf </br>
* 创建时间:2019年08月19日 </br>
* @throws
*/
public void log1() {
// 记录日志
System.out.println("进入log1()方法");
}
/**
* 方法名:log2</br>
* 详述:环绕通知使用的方法 </br>
* 开发人员:hgf </br>
* 创建时间:2019年08月19日 </br>
* @param p
* @return
* @throws Throwable
* @throws
*/
public Object log2(ProceedingJoinPoint p) throws Throwable {
// 目标组件的类名
String className = p.getTarget().getClass().getName();
// 调用的方法名
String method = p.getSignature().getName();
// 当前系统时间
String date = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss") .format(new Date());
// 拼日志信息
String msg = "-->用户在" + date + ",执行了" + className + "." + method + "()";
// 记录日志
System.out.println(msg);
// 执行目标组件的方法
Object obj = p.proceed();
// 在调用目标组件业务方法后也可以做一些业务处理
System.out.println("-->调用目标组件业务方法后...");
return obj;
}
}
使用Spring AOP异常通知,在每个Controller的方法发生异常时,记录异常日志。
Spring AOP使用步骤:
实现此案例需要按照如下步骤进行。
步骤一:创建方面组件
复用方面组件OperateLogger,在该类中创建新的记录日志的方法log3,代码如下:
/**
* 方法名:log3</br>
* 详述:测试异常通知使用的方法</br>
* 开发人员:hgf </br>
* 创建时间:2019年08月19日 </br>
* @param e
* @throws
*/
public void log3(Exception e) {
StackTraceElement[] elems = e.getStackTrace();
// 将异常信息记录
System.out.println("-->" + elems[0].toString());
}
步骤二:声明方面组件
由于复用的方面组件已经声明,因此该步骤可以省略。
步骤三:将方面组件作用到目标组件上
在applicationContext.xml中,声明方面组件的log3方法,关键代码如下:
<aop:aspect ref="operateLogger">
<aop:after-throwing method="log3" throwing="e" pointcut="within(com.souvc.controller..*)"/>
</aop:aspect>
关键配置代码:
<!-- 开启注解扫描 -->
<context:component-scan base-package="com.souvc" />
<!-- 支持@RequestMapping请求和Controller映射 -->
<mvc:annotation-driven />
<bean
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/" />
<property name="suffix" value=".jsp" />
</bean>
<!-- 声明方面组件-->
<bean id="operateLogger" class="com.souvc.aspect.OperateLogger" />
<!-- 配置AOP -->
<aop:config>
<!-- 测试前置通知 -->
<aop:aspect ref="operateLogger">
<aop:before method="log1" pointcut="within(com.souvc.controller..*)"/>
</aop:aspect>
<!-- 测试环绕通知 -->
<aop:aspect ref="operateLogger">
<aop:around method="log2" pointcut="within(com.souvc.controller..*)"/>
</aop:aspect>
<!-- 测试异常通知 -->
<aop:aspect ref="operateLogger">
<aop:after-throwing method="log3" throwing="e" pointcut="within(com.souvc.controller..*)"/>
</aop:aspect>
</aop:config>
步骤四:测试
为了便于测试,在EmpController.find()方法中制造一个异常,代码如下:
主要代码:
// 制造一个异常,便于测试异常通知
//Integer.valueOf("abc");
五、测试效果
进入log1()方法
-->用户在2019-08-19 17:12:07,执行了com.souvc.controller.EmpController.find()
执行,find()方法,查询员工数据,发送至列表页面.
-->java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
使用Spring AOP后返回通知类型。
Spring AOP使用步骤:
实现此案例需要按照如下步骤进行。
使用Spring AOP 执行后通知类型。
Spring AOP使用步骤:
实现此案例需要按照如下步骤进行。
步骤一:
在 OperateLogger.java 添加以下代码:
/**
* 方法名:log5</br>
* 详述:测试 执行后使用的方法</br>
* 开发人员:hgf </br>
* 创建时间:2019年08月19日 </br>
* @param result
* @return
* @throws
*/
public void log5() {
// 记录日志
System.out.println("进入log5()方法");
}
步骤二:
在 applicationContext.xml 添加以下代码:
<!-- 测试后通知 -->
<aop:aspect ref="operateLogger">
<aop:after method="log5" pointcut="within(com.souvc.controller..*)"/>
</aop:aspect>
步骤三:
运行测试类
步骤四:
效果如下:
进入log1()方法
-->用户在2019-08-19 17:53:10,执行了com.souvc.controller.EmpController.find()
执行,find()方法,查询员工数据,发送至列表页面.
进入log5()方法
-->java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
二、 Spring 通过注解形式形式来AOP 来实现前置,环绕,异常通知
Spring AOP相关注解及含义如下:
@Aspect:用于声明方面组件
@Before:用于声明前置通知
@AfterReturning:用于声明后置通知
@After:用于声明最终通知
@Around:用于声明环绕通知
@AfterThrowing:用于声明异常通知
使用Spring AOP注解替代XML配置,重构上面的3个案例。
分别在对应的方法上面加上注解。
实现此案例需要按照如下步骤进行。
步骤一:开启AOP注解扫描
在applicationContext.xml中,去掉方面组件声明及作用的XML配置,并开启AOP注解扫描,关键代码如下:
<!-- 声明方面组件 -->
<!-- <bean id="operateLogger" class="com.souvc.aspect.OperateLogger" />-->
<!-- 配置AOP -->
<!-- <aop:config>
<aop:aspect ref="operateLogger">
<aop:before method="log1" pointcut="within(com.souvc.controller..*)"/>
</aop:aspect>
<aop:aspect ref="operateLogger">
<aop:around method="log2" pointcut="within(com.souvc.controller..*)"/>
</aop:aspect>
<aop:aspect ref="operateLogger">
<aop:after-throwing method="log3" throwing="e" pointcut="within(com.souvc.controller..*)"/>
</aop:aspect>
</aop:config> -->
<!-- 开启AOP注解扫描 -->
<aop:aspectj-autoproxy proxy-target-class="true"/>
或者:
<!-- 启用spring对AspectJ注解的支持 -->
<aop:aspectj-autoproxy/>
步骤二:使用注解声明方面组件
在OperateLogger中,使用@Aspect注解声明方面组件,并分别用@Before、@Around、@AfterThrowing注解声明log1、log2、log3方法,将方面组件作用到目标组件上,代码如下:
package com.souvc.aspect;
import java.text.SimpleDateFormat;
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.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.springframework.stereotype.Component;
/**
* 用于记录日志的方面组件,演示Spring AOP的各种通知类型。
*/
@Component
@Aspect
public class OperateLogger {
/**
* 方法名:log1</br>
* 详述:测试前置通知 </br>
* 开发人员:hgf </br>
* 创建时间:2019年08月19日 </br>
* @throws
*/
@Before("within(com.souvc.controller..*)")
public void log1() {
// 记录日志
System.out.println("进入log1()方法");
}
/**
* 方法名:log2</br>
* 详述:环绕通知使用的方法 </br>
* 开发人员:hgf </br>
* 创建时间:2019年08月19日 </br>
* @param p
* @return
* @throws Throwable
* @throws
*/
@Around("within(com.souvc.controller..*)")
public Object log2(ProceedingJoinPoint p) throws Throwable {
// 目标组件的类名
String className = p.getTarget().getClass().getName();
// 调用的方法名
String method = p.getSignature().getName();
// 当前系统时间
String date = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss") .format(new Date());
// 拼日志信息
String msg = "-->用户在" + date + ",执行了" + className + "." + method + "()";
// 记录日志
System.out.println(msg);
// 执行目标组件的方法
Object obj = p.proceed();
// 在调用目标组件业务方法后也可以做一些业务处理
System.out.println("-->调用目标组件业务方法后...");
return obj;
}
/**
* 方法名:log3</br>
* 详述:测试异常通知使用的方法</br>
* 开发人员:hgf </br>
* 创建时间:2019年08月19日 </br>
* @param e
* @throws
*/
@AfterThrowing(pointcut = "within(com.souvc.controller..*)", throwing = "e")
public void log3(Exception e) {
StackTraceElement[] elems = e.getStackTrace();
// 将异常信息记录
System.out.println("-->" + elems[0].toString());
}
/**
* 方法名:log4</br>
* 详述:测试 返回后通知使用的方法</br>
* 开发人员:hgf </br>
* 创建时间:2019年08月19日 </br>
* @param result
* @return
* @throws
*/
@AfterReturning(value="execution(* com.souvc.controller.*.*(..))",returning="result")
public void log4(JoinPoint joinPoint,Object result ){
Object object = joinPoint.getSignature();//方法返回值
System.out.println("joinPoint.getKind():"+ joinPoint.getKind());
System.out.println("joinPoint.getTarget():"+joinPoint.getTarget());
System.out.println("joinPoint.getThis():"+joinPoint.getThis());
System.out.println("joinPoint.getArgs():"+joinPoint.getArgs().length);
Object [] args=joinPoint.getArgs();
for (int i = 0; i < args.length; i++) {
System.out.println("参数:"+args[i]);
}
System.out.println("joinPoint.getSignature():"+joinPoint.getSignature());
System.out.println("joinPoint.getSourceLocation():"+joinPoint.getSourceLocation());
System.out.println("joinPoint.getStaticPart():"+joinPoint.getStaticPart());
String rightnow=new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date());
System.out.println(rightnow+"执行了【"+object+"方法正常执行结束......】"+"【返回结果:"+result+"】");
}
/**
* 方法名:log5</br>
* 详述:测试 执行后使用的方法</br>
* 开发人员:hgf </br>
* 创建时间:2019年08月19日 </br>
* @param result
* @return
* @throws
*/
// @After(value="execution(com.souvc.controller.*.*(..))")
// public void log5() {
// // 记录日志
// System.out.println("进入log5()方法");
// }
}
步骤三:测试
执行测试方法TestEmpController.test1(),结果如下:
无异常的时候:
-->用户在2019-08-19 18:30:22,执行了com.souvc.controller.EmpController.find()
-->记录用户操作信息
查询员工数据,发送至列表页面.
-->调用目标组件业务方法后...
有异常的时候:
-->用户在2019-08-19 18:32:27,执行了com.souvc.controller.EmpController.find()
-->记录用户操作信息
查询员工数据,发送至列表页面.
-->java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
1)JoinPoint
java.lang.Object[] getArgs():获取连接点方法运行时的入参列表; Signature getSignature() :获取连接点的方法签名对象; java.lang.Object getTarget() :获取连接点所在的目标对象; java.lang.Object getThis() :获取代理对象本身;
2)ProceedingJoinPoint
ProceedingJoinPoint继承JoinPoint子接口,它新增了两个用于执行连接点方法的方法: java.lang.Object proceed() throws java.lang.Throwable:通过反射执行目标对象的连接点处的方法; java.lang.Object proceed(java.lang.Object[] args) throws java.lang.Throwable:通过反射执行目标对象连接点处的方法,不过使用新的入参替换原来的入参。