Spring框架完全掌握(下)
接着上一篇文章的内容Spring框架完全掌握(上),我们继续深入了解Spring框架。
Spring_AOP
考虑到AOP在Spring中是非常重要的,很有必要拿出来单独说一说。所以本篇文章基本上讲述的就是关于Spring的AOP编程。
简介
先看一个例子:
package com.itcast.spring.bean.calc;
public class ArithmeticCalculatorImpl implements ArithmeticCalculator {
@Override
public int add(int num1, int num2) {
int result = num1 + num2;
return result;
}
@Override
public int sub(int num1, int num2) {
int result = num1 - num2;
return result;
}
@Override
public int mul(int num1, int num2) {
int result = num1 * num2;
return result;
}
@Override
public int div(int num1, int num2) {
int result = num1 / num2;
return result;
}
}
这是一个实现四则运算接口的实现类,能够进行两个数之间的加减乘除。而这个时候,我们有一个需求,就是在每个方法执行前后都必须输出日志信息,那么我们就得在每个方法中都加上日志信息:
...
@Override
public int add(int num1, int num2) {
System.out.println("add method start with[" + num1 + "," + num2 + "]");
int result = num1 + num2;
System.out.println("add method start with[" + num1 + "," + num2 + "]");
return result;
}
...
这样所带来的问题是什么呢?
- 代码混乱:越来越多的非业务需求(例如日志、参数验证等)加入后,原有的业务方法急剧膨胀,每个方法在处理核心逻辑的同时还必须兼顾其它多个关注点。
- 代码分散:以日志需求为例,只是为了满足这个单一需求,就不得不在多个模块里多次重复相同的日志代码,如果日志需求发生变化,必须修改所有模块中的日志代码。
既然问题出现了,该如何解决呢?(使用动态代理)
public class ArithmeticCalculatorLoggingProxy {
private ArithmeticCalculator target;
public ArithmeticCalculator getLoggingProxy() {
ArithmeticCalculator proxy = null;
ClassLoader loader = target.getClass().getClassLoader();
Class[] interfaces = new Class[] { ArithmeticCalculator.class };
InvocationHandler h = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(method.getName() + "method start with[ " + Arrays.asList(args) + "]");
Object result = method.invoke(target, args);
System.out.println(method.getName() + "method end with[ " + result + "]");
return result;
}
};
proxy = (ArithmeticCalculator) Proxy.newProxyInstance(loader, interfaces, h);
return proxy;
}
}
这样我们就可以去获取代理对象从而实现日志业务却不改变基本业务代码。
其实这样实现还是略显麻烦,但不用担心,Spring框架为我们提供了一种实现方式——AOP。
AOP(Aspect-Oriented Programming,面向切面编程):这是一种新的方法论,是对传统OOP(Object-Oriented Programming,面向对象编程)的补充,AOP的主要编程对象是切面。
在应用AOP编程时,仍然需要定义公共功能,但可以明确地定义这个功能在哪里,以什么方式应用,并且不必修改受影响的类,这样一来,横切关注点就被模块化到特殊的对象里。
好处:
- 每个事物逻辑位于一个位置,代码不分散,便于维护和升级
- 业务模块更简洁,只包含核心业务代码
这样来看,AOP能够非常精准地解决我们遇到了问题。
前置通知
在Spring中,可以使用基于AspectJ注解或基于XML配置的AOP。AspectJ是Java社区里最完整最流行的AOP框架,所以我们以AspectJ注解方式为例进行讲解。
首先导入AOP框架的jar包:

然后我们在上面的案例中进行修改:
@Component
public class ArithmeticCalculatorImpl implements ArithmeticCalculator {
@Override
public int add(int num1, int num2) {
int result = num1 + num2;
return result;
}
@Override
public int sub(int num1, int num2) {
int result = num1 - num2;
return result;
}
@Override
public int mul(int num1, int num2) {
int result = num1 * num2;
return result;
}
@Override
public int div(int num1, int num2) {
int result = num1 / num2;
return result;
}
}
这里在实现类的开头加上了一个注解,目的是将该类交由Spring容器管理,其它代码不作改动。
//将该类声明为一个切面
@Aspect
@Component
public class LoggingAspect {
// 声明该方法是一个前置通知:在目标方法开始之前执行
@Before("execution(public int com.itcast.aop.impl.ArithmeticCalculatorImpl.add(int,int))")
public void beforeMethd(JoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
List<Object> args = Arrays.asList(joinPoint.getArgs());
System.out.println(methodName + " method start with" + args);
}
}
接着我们将输出日志的业务看成一个切面,创建一个类,然后任意地定义一个方法,该方法要添加一个注解:Before。用于声明该方法是一个前置通知,前置通知方法会在目标方法开始之前执行。所以我们还需要在Before中声明目标方法。该方法可以添加一个参数为JoinPoint类型,执行方法的方法名和参数都封装在该对象中。其次,该类必须也交由Spring容器管理,所以添加注解@Component,且该类为一个切面,添加注解@Aspect。
然后要在配置文件中进行配置:
<?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.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">
<!-- 配置自动扫描的包 -->
<context:component-scan
base-package="com.itcast.aop.impl"></context:component-scan>
<!-- 使AspjectJ注解起作用:自动为匹配的类生成代理对象 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
这样,框架会去自动寻找匹配的类并生成代理对象。
最后编写测试代码:
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
ArithmeticCalculator ac = ctx.getBean(ArithmeticCalculator.class);
int result = ac.add(1, 1);
System.out.println("result:" + result);
}
运行结果:
add method start with[1, 1]
result:2
但是当你调用其它的运算方法时发现日志信息又无法打印了,这是因为你在配置目标方法的时候配置的仅仅是add()方法,所以可以采用通配符的方式将类中的所有方法都配置进去。
@Before("execution(public int com.itcast.aop.impl.ArithmeticCalculatorImpl.*(int,int))")
这里的exeution是执行的意思,也就是说,该属性的括号内填写的是目标方法,对于该目标方法,可以更加抽象地进行表示,例如权限修饰符、返回值等等都可以用通配符进行替换。
到这里,SpringAOP就轻松实现了我们开始遇到的问题。
后置通知
既然有前置通知,那肯定就会有后置通知,后置通知的实现方式和前置通知类似:
@After("execution(public int com.itcast.aop.impl.ArithmeticCalculatorImpl.*(int,int))")
public void afterMetohd(JoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
List<Object> args = Arrays.asList(joinPoint.getArgs());
System.out.println(methodName + " method ends with" + args);
}
运行测试代码,结果如下:
add method start with[1, 1]
add method ends with[1, 1]
result:2
后置通知是在目标方法执行后执行,但需要注意的是,后置通知不管目标方法是否成功执行,就算目标方法在执行过程中产生了异常,后置通知仍然会执行,而且在后置通知中无法访问到目标方法的执行结果。
返回通知
返回通知和后置通知类似,但是返回通知只在目标方法正确执行完成后才执行,如果目标方法在执行过程中产生了错误,返回通知将不起作用。所以返回通知能够获取目标方法的执行结果:
// 声明该方法是一个返回通知:在方法正常执行结束后执行
// 返回通知是可以访问到目标方法的返回值的
@AfterReturning(value = "execution(public int com.itcast.aop.impl.ArithmeticCalculatorImpl.*(int,int))", returning = "result")
public void afterReturning(JoinPoint joinPoint, Object result) {
String methodName = joinPoint.getSignature().getName();
List<Object> args = Arrays.asList(joinPoint.getArgs());
System.out.println(methodName + " method ends with" + result);
}
运行结果:
add method start with[1, 1]
add method ends with[1, 1]
add method ends with2
result:2
异常通知
异常通知是在目标方法执行过程中产生了异常后才会执行,异常通知能够获取到目标方法产生的异常信息:
// 声明该方法是一个异常通知:在方法执行产生异常时执行
// 异常通知可以获取到产生的异常信息
@AfterThrowing(value = "execution(public int com.itcast.aop.impl.ArithmeticCalculatorImpl.*(int,int))", throwing = "ex")
public void afterThrowing(JoinPoint joinPoint, Exception ex) {
String methodName = joinPoint.getSignature().getName();
List<Object> args = Arrays.asList(joinPoint.getArgs());
System.out.println(methodName + " method's exception is " + ex);
}
我们人为产生一个异常来测试一下:
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
ArithmeticCalculator ac = ctx.getBean(ArithmeticCalculator.class);
result = ac.div(10, 0);
System.out.println("result:" + result);
}
运行结果:
div method start with[10, 0]
div method ends with[10, 0]
div method's exception is java.lang.ArithmeticException: / by zero
环绕通知
对于环绕通知,这在所有通知中是功能最强大的通知,其实它并不常用,但是我们还是得了解一下它的用法:
// 声明该方法是一个环绕通知,环绕通知需要携带ProceedingJoinPoint类型的参数
// 环绕通知类似于动态代理的全过程
// ProceedingJoinPoint类型的参数可以决定是否执行目标方法
// 且环绕通知必须有返回值,返回的是目标方法的返回值
@Around(value = "execution(public int com.itcast.aop.impl.ArithmeticCalculatorImpl.*(int,int))")
public Object aroundMethod(ProceedingJoinPoint point) {
Object result = null;
String methodName = point.getSignature().getName();
// 执行目标方法
try {
// 前置通知
System.out.println(methodName + " method' start with" + Arrays.asList(point.getArgs()));
result = point.proceed();
// 返回通知
System.out.println(methodName + " method' end with " + result);
} catch (Throwable e) {
// 异常通知
System.out.println(methodName + " method's exception is " + e);
}
// 后置通知
System.out.println(methodName + " method' end with");
return result;
}
环绕通知能够实现其它所有通知的功能,但是它有很多限制。
- 必须要携带ProceedingJoinPoint类型的参数
- 环绕通知必须有返回值,返回的是目标方法的返回值
测试代码:
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
ArithmeticCalculator ac = ctx.getBean(ArithmeticCalculator.class);
int result = ac.add(1, 1);
System.out.println("result:" + result);
}
运行结果:
add method' start with[1, 1]
add method' end with 2
add method' end with
result:2
切面的优先级
在具有多个切面的项目中,我们可以指定切面的优先级,决定切面的先后执行顺序。使用@Order()注解来配置优先级(在类开头注解),括号里填入一个整数,值越小优先级越高。
例如:
@Order(1)
public class LoggingAspect {
......
......
}
关于SpringAOP的相关内容就说到这里,如有错误,欢迎指正。
Spring框架完全掌握(下)的更多相关文章
- Spring框架基础(下)
log4J 导入log4J.jar 创建log4J.properties # Create a file called log4j.properties as shown below and plac ...
- Spring框架下的定时任务quartz框架的使用
手头的这个项目需要用到定时任务,但之前没接触过这东西,所以不太会用,从网上找资料,大致了解了一下,其实也不难.Java的定时任务实现有三种,一种是使用JDK自带的Timer那个类来实现,另一种是使用q ...
- 手撸Spring框架,设计与实现资源加载器,从Spring.xml解析和注册Bean对象
作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 你写的代码,能接的住产品加需求吗? 接,是能接的,接几次也行,哪怕就一个类一片的 i ...
- 最新 Eclipse IDE下的Spring框架配置及简单实例
前段时间开始着手学习Spring框架,又是买书又是看视频找教程的,可是鲜有介绍如何配置Spring+Eclipse的方法,现在将我的成功经验分享给大家. 本文的一些源代码来源于码农教程:http:// ...
- Spring 框架下 (增 删 改 )基本操作
//applicationContext.xml 配置文件 <?xml version="1.0" encoding="UTF-8"?><be ...
- Eclipse IDE下的Spring框架使用简单实例
Eclipse IDE下的Spring框架使用简单实例 1 准备Java jdk安装. Eclipse软件安装.根据系统安装32/64版本,选择Eclipse IDE for Java Develop ...
- Spring框架下Junit测试
Spring框架下Junit测试 一.设置 1.1 目录 设置源码目录和测试目录,这样在设置产生测试方法时,会统一放到一个目录,如果没有设置测试目录,则不会产生测试代码. 1.2 增加配置文件 Res ...
- Spring框架找不到 applicationContext.xml文件,可能是由于applicationContext.xml文件的路径没有放在根目录下造成的
Spring框架找不到 applicationContext.xml文件,可能是由于applicationContext.xml文件的路径没有放在根目录下造成的
- 深入剖析 RabbitMQ —— Spring 框架下实现 AMQP 高级消息队列协议
前言 消息队列在现今数据量超大,并发量超高的系统中是十分常用的.本文将会对现时最常用到的几款消息队列框架 ActiveMQ.RabbitMQ.Kafka 进行分析对比.详细介绍 RabbitMQ 在 ...
随机推荐
- Excel催化剂开源第16波-VSTO开发之脱离传统COM交互以提升性能
在VSTO开发或其他COM技术开发过程中,甚至VBA也是,在和Excel交互中,难免会遇到性能瓶颈问题,COM技术的交互实在太慢,对大量数据读写等操作,耗时太长,容易卡用户界面以为是程序死机等等. 在 ...
- linux作业控制和文件系统
一.作业控制 [root@tianyun ~]# sleep 2000运行一个程序,当前终端无法输入. 1 直接运行后台程序.暂停一个前台程序.[root@tianyun ~]# sleep 300 ...
- [剑指offer] 3. 从头到尾打印链表
题目描述 输入一个链表,按链表值从尾到头的顺序返回一个ArrayList. 思路: 利用容器,遍历一遍加入到一个新容器里,然后反置输出. vector 用 reverse stack 则直接一个个出栈 ...
- 关于Object.defineProperty 的基础知识
Object.defineProperty 这个方法大家耳熟能详,可以对 对象的属性进行添加或修改的操作.即可以进行 数据劫持 .vue就是通过这个方法来劫持数据的. 平时我们创建对象的时候,一般通 ...
- Linux系统安装jdk——.tar.gz版
1.rpm.deb.tar.gz的区别: rpm格式的软件包适用于基于Red Hat发行版的系统,例如Red Hat Linux.SUSE.Fedora. deb格式的软件包则是适用于基于Debian ...
- 《VR入门系列教程》之6---VR硬件介绍及DK1
第二章 VR硬件介绍 本章主要介绍当前比较流行的消费版VR设备,包括VR头显以及应用运行的PC和手机平台. 即使是在这工业高速发展的时代,一些大厂(比如Facebook的Oculus ...
- springboot-权限控制shiro(一)
1. 场景描述 (1)权限控制是IT项目特别是企业项目,绕不开的重要模块,接下来结合springboot介绍下权限控制框架shiro. (2)springboot集成shiro的东西有点多,一篇博客完 ...
- HTML--表格与表单(练习做注册页面)
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...
- python找质数对
python找质数对 编写python脚本,输入一个正整数,输出有几对质数的和等于这个正整数. 例如输入一个正整数10,可以找出有“3+7=10”.“5+5=10”两个质数对的和为10. 要实现这个功 ...
- 神奇的 SQL 之 CASE表达式,妙用多多 !
前言 历史考试选择题:黄花岗起义第一枪谁开的? A宋教仁 B孙中山 C黄兴 D徐锡麟,考生选C. 又看第二题:黄花岗起义第二枪谁开的? 考生傻了,就选了个B. 接着看第三题:黄花岗起义中,第三枪谁开的 ...