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 在 ...
随机推荐
- 苹果IOS内购二次验证返回state为21002的坑
项目是三四年前的老项目,之前有IOS内购二次验证的接口,貌似很久都没用了,然而最近IOS的妹子说接口用不了,让我看看啥问题.接口流程时很简单的,就是前端IOS在购买成功之后,接收到receipt后进行 ...
- Python学习4——条件、循环及其他语句总结
多种语句 打印语句: 导入语句: 赋值语句: 代码块: 条件语句: 断言: 循环: 推导: pass.dal.exec和eval : 学习到的新函数:(以下函数的应用代码均在IDLE测试通过) ch ...
- 上传及下载github项目
1.上传本地项目 git init //把这个目录变成Git可以管理的仓库 git add README.md //文件添加到仓库 git add . //不但可以跟单 ...
- web-inf与meta-inf
/WEB-INF/web.xml Web应用程序配置文件,描述了 servlet 和其他的应用组件配置及命名规则. /WEB-INF/classes/包含了站点所有用的 class 文件,包括 ser ...
- 1.4.3 ID遍历爬虫(每天一更)
# -*- coding: utf-8 -*- ''' Created on 2019年5月7日 @author: 薛卫卫 ''' import itertools import urllib.req ...
- 使用Kubeadm创建k8s集群之节点部署(三十一)
前言 本篇部署教程将讲述k8s集群的节点(master和工作节点)部署,请先按照上一篇教程完成节点的准备.本篇教程中的操作全部使用脚本完成,并且对于某些情况(比如镜像拉取问题)还提供了多种解决方案.不 ...
- JavaScript数据结构——集合的实现与应用
与数学中的集合概念类似,集合由一组无序的元素组成,且集合中的每个元素都是唯一存在的.可以回顾一下中学数学中集合的概念,我们这里所要定义的集合也具有空集(即集合的内容为空).交集.并集.差集.子集的特性 ...
- Selenium模拟登陆百度贴吧
Selenium模拟登陆百度贴吧 from selenium import webdriver from time import sleep from selenium.webdriver.commo ...
- Java | Map排序,工具类改进
package util; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; ...
- ride.py在运行python3.×版本后导致无法运行及解决办法
最近一直在自学python自动化,网上看到rf框架挺适合初学自动化测试,于是通过虫师的搭建了rf框架, 但是在使用过程中遇到了一个问题,在网上没有找到明确解决办法于是想到记录一下 之前为了搭建rf框架 ...