Spring MVC概念和使用

参考链接:

《Spring MVC 学习指南(第二版)》

Spring MVC 是 Spring 框架中用于Web应用快速开发的一个模块。Spring框架是一个开源框架,源码下载地址:

1
git clone git@github.com:spring-projects/spring-framework.git

控制反转(IOC)

DI和IOC是差不多的概念,一个重要特征是接口依赖,是把对象关系推迟到运行时去确定。Ioc意味着将你设计好的对象交给容器控制,而不是传统的在你的对象内部直接控制。

IoC 不是一种技术,只是一种思想,一个重要的面向对象编程的法则,它能指导我们如何设计出松耦合、更优良的程序。传统应用程序都是由我们在类内部主动创建依赖对象,从而导致类与类之间高耦合,难于测试;有了IoC容器后,把创建和查找依赖对象的控制权交给了容器,由容器进行注入组合对象,所以对象与对象之间是 松散耦合,这样也方便测试,利于功能复用,更重要的是使得程序的整个体系结构变得非常灵活。

谁控制谁,控制什么:传统Java SE程序设计,我们直接在对象内部通过new进行创建对象,是程序主动去创建依赖对象;而IoC是有专门一个容器来创建这些对象,即由Ioc容器来控制对 象的创建;谁控制谁?当然是IoC 容器控制了对象;控制什么?那就是主要控制了外部资源获取(不只是对象包括比如文件等)。

为何是反转,哪些方面反转了:有反转就有正转,传统应用程序是由我们自己在对象中主动控制去直接获取依赖对象,也就是正转;而反转则是由容器来帮忙创建及注入依赖对象;为何是反转?因为由容器帮我们查找及注入依赖对象,对象只是被动的接受依赖对象,所以是反转;哪些方面反转了?依赖对象的获取被反转了。

依赖注入(DI)

IoC和DI由什么关系呢?其实它们是同一个概念的不同角度描述,由于控制反转概念比较含糊(可能只是理解为容器控制对象这一个层面,很难让人想到谁来维护对象关系),所以2004年大师级人物Martin Fowler又给出了一个新的名字:“依赖注入”,相对IoC 而言,“依赖注入”明确描述了“被注入对象依赖IoC容器配置依赖对象”

谁依赖于谁:当然是应用程序依赖于IoC容器。

为什么需要依赖:应用程序需要IoC容器来提供对象需要的外部资源。

谁注入谁:IoC容器注入应用程序某个对象,应用程序依赖的对象。

注入了什么:就是注入某个对象所需要的外部资源(包括对象、资源、常量数据)。

假设类A依赖类B,A要使用B的实例必须先创建或者获取B的实例,如下:

1
2
3
4
5
6
7
8
class A{

public void importantMothod(){
B b = //... get an instance of B
b.usefulMethod();
//...
}
}

假设此时B不是一个具体的实体类而是一个抽象类或者接口,此时问题就复杂了,不能简单的在A内初始化B的实例。

依赖注入框架的引入就是来解决这个问题的,负责创建实例并将实例注入到A中,改写如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
class A{

private B b;

public void importantMothod(){
b.usefulMethod();
//...
}

public void setB(B b){
this.b = b;
}
}

类A中多了一个setB方法,通过依赖注入框架来注入B的实例进来(构造函数注入也可以)。

在Spring中会先创建B,然后再创建A的实例,最后将B注入到A中。使用Spring程序会将几乎所有对象创建的工作交给Spring,并配置如何注入依赖。Spring支持xml配置和注解两种方式来配置。

Spring依赖注入的方式有两种:set方法(推荐)、构造器。下面以set注入为例:

1
2
3
4
<bean id="a" class="com.test.irain.A">
<property name="hello" ref="b"/>
</bean>
<bean id="b" class="com.test.irain.B"></bean>
1
2
3
4
5
6
7
8
public class A {

private B hello;

public void setHello(B hello) {
this.hello = hello;
}
}

面向切面(AOP)

下面摘抄于知乎上的一段关于面向切面的解释:https://www.zhihu.com/question/24863332

面向切面编程(AOP是Aspect Oriented Program的首字母缩写) ,我们知道,面向对象的特点是继承、多态和封装。而封装就要求将功能分散到不同的对象中去,这在软件设计中往往称为职责分配。实际上也就是说,让不同的类设计不同的方法。这样代码就分散到一个个的类中去了。这样做的好处是降低了代码的复杂程度,使类可重用。

但是人们也发现,在分散代码的同时,也增加了代码的重复性。什么意思呢?比如说,我们在两个类中,可能都需要在每个方法中做日志。按面向对象的设计方法,我们就必须在两个类的方法中都加入日志的内容。也许他们是完全相同的,但就是因为面向对象的设计让类与类之间无法联系,而不能将这些重复的代码统一起来。

也许有人会说,那好办啊,我们可以将这段代码写在一个独立的类独立的方法里,然后再在这两个类中调用。但是,这样一来,这两个类跟我们上面提到的独立的类就有耦合了,它的改变会影响这两个类。那么,有没有什么办法,能让我们在需要的时候,随意地加入代码呢?这种在运行时,动态地将代码切入到类的指定方法、指定位置上的编程思想就是面向切面的编程。

一般而言,我们管切入到指定类指定方法的代码片段称为切面,而切入到哪些类、哪些方法则叫切入点。有了AOP,我们就可以把几个类共有的代码,抽取到一个切片中,等到需要时再切入对象中去,从而改变其原有的行为。这样看来,AOP其实只是OOP的补充而已。OOP从横向上区分出一个个的类来,而AOP则从纵向上向对象中加入特定的代码。有了AOP,OOP变得立体了。如果加上时间维度,AOP使OOP由原来的二维变为三维了,由平面变成立体了。从技术上来说,AOP基本上是通过代理机制实现的。AOP在编程历史上可以说是里程碑式的,对OOP编程是一种十分有益的补充。

面向切面AOP

接下来我们来看看AOP在Spring中的应用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public interface UserService {

public void update();
public void delete();
public void save();
}

public class UserServiceImpl implements UserService {

public void update() {
System.out.println("更新用户信息");
}

public void delete() {
System.out.println("删除用户信息");
}

public void save() {
System.out.println("保存用户信息");
}
}

public class Test {

public static void main(String[] args){
ApplicationContext ac = new ClassPathXmlApplicationContext("aop.xml");
UserService userService = (UserService) ac.getBean("userservice");
userService.update();
}
}

新建aop.xml配置bean

1
<bean id="userservice" class="com.test.irain.UserServiceImpl"></bean>

假设,我们现在要添加一个操作日志记录功能,这个时候就要用到面向切面来实现了。

新建日志类

1
2
3
4
5
6
public class OptLogger {

public void logger(){
System.out.println("记录操作日志了...");
}
}

配置切面和切点

1
2
3
4
5
6
7
8
9
<bean id="userservice" class="com.test.irain.UserServiceImpl"></bean>
<bean id="optlogger" class="com.test.irain.OptLogger"></bean>

<aop:config>
<aop:pointcut id="servicepointcut" expression="execution(* com.test.irain.*(..))"/>
<aop:aspect id="loggeraspect" ref="optlogger"> <!-- 配置切面 -->
<aop:before method="logger" pointcut-ref="servicepointcut"/>
</aop:aspect>
</aop:config>

实现原理

Spring AOP就是基于动态代理的,如果要代理的对象,实现了某个接口,那么Spring AOP会使用JDK Proxy,去创建代理对象,而对于没有实现接口的对象,就无法使用JDK Proxy去进行代理了(为啥?你写一个JDK Proxy的demo就知道了),这时候Spring AOP会使用Cglib,生成一个被代理对象的子类,来作为代理,放一张图出来就明白了:

SpringAOP过程

Spring MVC控制流程

SpringMVC流程图

从上面的图中我们可以大致明白springMVC的调度流程,下面详细说明:

  1. 发起请求到前端控制器(DispatcherServlet)。
  2. 前端控制器请求HandlerMapping查找Handler,可以根据xml配置、注解进行查找。

在web.xml中配置

1
2
3
4
5
6
7
8
9
10
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>

  1. 处理器映射器HandlerMapping向前端控制器返回Handler。HandlerMapping会把请求映射为HandlerExecutionChain对象(包含一个Handler处理器(页面控制器)对象,多个HandlerInterceptor拦截器对象),通过这种策略模式,很容易添加新的映射策略。

还记得我们前一篇 《JavaWeb开发快速上手篇》 的如下配置吗,这个SimpleUrlHandlerMapping就是一个HandlerMapping的子类。

1
2
3
4
5
6
7
8
9
<bean id="simpleUrlHandlerMapping"
class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="mappings">
<props>
<!-- /hello 路径的请求交给 id 为 helloController 的控制器处理-->
<prop key="/hello">helloController</prop>
</props>
</property>
</bean>
  1. 前端控制器调用处理器适配器(HandlerAdapter)去执行Handler。

Handler要实现Contoller接口,才能由处理器适配器执行

1
2
3
4
5
6
7
8
9
10
public class HelloController implements Controller {

@RequestMapping("/hello")
public ModelAndView handleRequest(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse) throws Exception {
ModelAndView mav = new ModelAndView("index.jsp");
mav.addObject("message", "Hello Spring MVC");
return mav;
}
}

配置Handler

1
<bean id="helloController" class="com.test.irain.controller.HelloController"></bean>
  1. 处理器适配器HandlerAdapter将会根据适配的结果去执行Handler。
  2. Handler执行完成后给适配器返回ModelAndView。
  3. 处理器适配器向前端控制器返回ModelAndView (ModelAndView是springmvc框架的一个底层对象,包括 Model和view)。
  4. 前端控制器请求视图解析器去进行视图解析 (根据逻辑视图名解析成真正的视图(jsp)),通过这种策略很容易更换其他视图技术,只需要更改视图解析器即可。
1
2
<!-- 视图解析器,解析jsp视图,默认使用jstl,classpath下需要有jstl的包 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"></bean>
  1. 视图解析器向前端控制器返回真正的视图View。
  2. 前端控制器请求进行视图渲染,视图渲染将模型数据(在ModelAndView)填充到request域。
  3. 前端控制器向用户响应结果。

Spring MVC 创建过程

Spring MVC的创建步骤如下:

第一步:加入相关jar包,必须用的包有:

1
2
3
4
5
6
7
spring-aop-xxx.RELEASE.jar
spring-beans-xxx.RELEASE.jar
spring-context-xxx.RELEASE.jar
spring-core-xxx.RELEASE.jar
spring-expression-xxx.RELEASE.jar
spring-web-xxx.RELEASE.jar
spring-webmvc-xxx.RELEASE.jar

另外可能还需要日志相关的jar包

1
common-logging-xxx.jar

第二步:在web.xml中配置DispatcherServlet.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!-- 配置DispatcherServlet -->
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 可选:配置DispatcherServlet 的一个初始化参数,目的是配置Spring MVC配置文件的位置和名称 -->
<!-- 默认的配置文件在/WEB-INFO/<servlet-name>-servlet.xml -->
<init-param>
<param-name>contextConfigLocation</param-name>
<!-- 在src的根目录创建springmvc.xml配置文件 -->
<param-value>classpath:springmvc.xml</param-value>
</init-param>
<!-- 设置加载的时候就创建spring mvc -->
<load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>

第三步:加入Spring MVC 的配置文件(上面配置了该配置文件的路径和名称)。

第四步:编写处理请求的处理器,并标识为处理器。

在springmvc.xml(Spring MVC的配置文件)中配置自动扫描的包和视图解析器:

1
2
3
4
5
6
7
8
9
<!-- 配置自动扫描的包 -->
<context:component-scan base-package="com.guohe.springmvc"></context:component-scan>

<!-- 配置视图解析器 -->
<!-- 对InternalResourceViewResolver解析器:解析为 profix + returnValue + suffix -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="profix" value="/WEB-INFO/views/"></property>
<property name="suffix" value=".jsp"></property>
</bean>

写一个控制器(使用@Controller标识):

1
2
3
4
5
6
7
8
9
10
@Controller
public class HelloWorld{
//使用@RequestMapping注解来映射请求的URL
@RequestMapping("/hello")
public String hello(){
System.out.println("hello world");
//这里对应的是/WEB-INFO/views/success.jsp
return "success";
}
}

第五步:编写视图(由上面配置可知,需要创建/WEB-INFO/views/success.jsp视图文件)。

RequestMapping用法

第一:@RequestMapping除了注解方法还可以注解类,这样请求路径就是 类的注解路径 + 方法注解路径

1
2
3
4
5
6
7
8
9
10
11
@RequestMapping("springmvc")
@Controller
public class SpringMVCTest{

@RequestMapping("/hello")
public String hello(){
System.out.println("hello world");
return "success";
}
}
//请求路径是 localhost:8080/projname/springmvc/hello

第二:@RequestMapping除了可以使用请求URL映射请求外,还可以使用请求方式、请求参数、请求头映射请求。

映射对象映射说明
value请求URL
method请求方式
params请求参数
heads请求头
1
2
3
4
5
6
//指定请求方式为POST
@RequestMapping(value="/testmethod", method=RequestMethod.POST)
public String testMethod(){
//...
return "success";
}
1
2
3
4
5
6
7
8
9

//指定请求必须包含username参数,而且参数age的值不等于10
//例如请求连接: springmvc/testparam?username=xiaoming&age=8
@RequestMapping(value="/testparam", params={"username", "age!=10"},
heads={"Accept-Language=zh-CN,zh;q=0.8"})
public String testParamsAndHeads(){
//...
return "success";
}

第三:@PathVariable注解可以将URL中占位符参数绑定到控制器处理方法的入参中,这使得Spring MVC支持REST风格的URL.

1
2
3
4
5
6

@RequestMapping("/testPathVariable/{id}")
public String testPathVariable(@PathVariable("id") Integer id){
System.out.println("id = " + id)
return "success";
}

第四:@RequestParam可以绑定请求参数,类似的还有@RequestHead

1
2
3
4
5
6
7
@RequestMapping(value="/testRequestParam")
public String testRequestParam(@RequestParam(value="username") String un,
@RequestParam(value="age", required=false, defaultValue="0") int age){
System.out.println("username = " + username + ", age=" + age);
return "success";
}
//上面username参数是必须的,age参数不是必须的(设置了required=false)

第五:@CookieValue注解可以映射得到cookie的值。

1
2
3
4
5
6

@RequestMapping("/testCookieValue")
public String testCookieValue(@CookieValue("JSESSIONID") String sessionId){
System.out.println("sessionId = " + sessionId);
return "success";
}

输出模型

Spring MVC提供了以下几种途径输出模型数据:

  • ModelAndView:处理方法返回值类型为ModelAndView时,返回视图并且可添加模型数据。
  • Map及Model:实际上返回的是ExtendedModelMap对象。
  • @SessionAttributes:只能注解类,可以将属性放入session.
  • @ModelAttribute:标注可被应用在方法或方法参数上.
1
2
3
4
5
6
7

@RequestMapping("/testModelAndView")
public ModelAndView testModelAndView(){
ModelAndView modelAndView = new ModelAndView("success");
modelAndView.addObject("name", "dp2px");
return modelAndView;
}
1
2
3
4
5
6

@RequestMapping("/testMap")
public String testMap(Map<String, Object> map){
map.put("name", "dp2px")
return "success";
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//可以通过制定属性名,还可以通过模型的对象类型制定那些对象放入session
@SessionAttributes(value={"user"}, types={String.class})
@RequestMapping("/springmvc")
@Controller
public class SpringMVCTest{

@RequestMapping("/testSessionAttributes")
public String testSessionAttributes(Map<String, Object> map){
User user = new User("dp2px", "http://dp2px.com");
map.put("user", user);
map.put("stringtype", "i am string")
return "success";
}
}

在控制器的处理器方法参数上添加 @ModelAttribute 注释可以访问模型中的属性,如果不存在这个模型,则会自动将其实例化,产生一个新的模型。 模型属性还覆盖了来自 HTTP Servlet 请求参数的名称与字段名称匹配的值,也就是请求参数如果和模型类中的域变量一致,则会自动将这些请求参数绑定到这个模型对象,这被称为数据绑定,从而避免了解析和转换每个请求参数和表单字段这样的代码。

1
2
3
4
@RequestMapping("/modelAttribute")
public void getUser(@ModelAttribute("user") User user){
return "success";
}

评论

Ajax Android AndroidStudio Animation Anroid Studio AppBarLayout Babel Banner Buffer Bulma ByteBuffer C++ C11 C89 C99 CDN CMYK COM1 COM2 CSS Camera Raw, 直方图 Chrome ContentProvider CoordinatorLayout C语言 DML DOM Dagger Dagger2 Darktable Demo Document DownloadManage ES2015 ESLint Element Error Exception Extensions File FileProvider Flow Fresco GCC Git GitHub GitLab Gradle Groovy HTML5 Handler HandlerThread Hexo Hybrid I/O IDEA IO ImageMagick IntelliJ Intellij Interpolator JCenter JNI JS Java JavaScript JsBridge Kotlin Lab Lambda Lifecycle Lint Linux Looper MQTT MVC MVP Maven MessageQueue Modbus Momentum MySQL NDK NIO NexT Next Nodejs ObjectAnimator Oracle VM Permission PhotoShop Physics Python RGB RS-232 RTU Remote-SSH Retrofit Runnable RxAndroid RxJava SE0 SSH Spring SpringBoot Statubar Task Theme Thread Tkinter UI UIKit UML VM virtualBox VS Code VUE ValueAnimator ViewPropertyAnimator Vue Web Web前端 Workbench api apk bookmark by关键字 compileOnly css c语言 databases demo hexo hotfix html iOS icarus implementation init jQuery javascript launchModel logo merge mvp offset photos pug query rxjava2 scss servlet shell svg tkinter tomcat transition unicode utf-8 vector virtual box vscode 七牛 下载 中介者模式 串口 临潼石榴 主题 书签 事件 享元模式 仓库 代理模式 位运算 依赖注入 修改,tables 光和色 内存 内核 内部分享 函数 函数式编程 分支 分析 创建 删除 动画 单例模式 压缩图片 发布 可空性 合并 同向性 后期 启动模式 命令 命令模式 响应式 响应式编程 图层 图床 图片压缩 图片处理 图片轮播 地球 域名 基础 增加 备忘录模式 外观模式 多线程 大爆炸 天气APP 太白山 头文件 奇点 字符串 字符集 存储引擎 宇宙 宏定义 实践 属性 属性动画 岐山擀面皮 岐山肉臊子 岐山香醋 工具 工厂模式 年终总结 开发技巧 异常 弱引用 恒星 打包 技巧 指针 插件 摄影 操作系统 攻略 故事 数据库 数据类型 数组 文件 新功能 旅行 旋转木马 时序图 时空 时间简史 曲线 杂谈 权限 枚举 架构 查询 标准库 标签选择器 样式 核心 框架 案例 桥接模式 检测工具 模块化 模板引擎 模板方法模式 油泼辣子 泛型 洛川苹果 浅色状态栏 源码 源码分析 瀑布流 热修复 版本 版本控制 状态栏 状态模式 生活 留言板 相册 相对论 眉县猕猴桃 知识点 码云 磁盘 科学 笔记 策略模式 类图 系统,发行版, GNU 索引 组件 组合模式 结构 结构体 编码 网易云信 网格布局 网站广播 网站通知 网络 美化 联合 膨胀的宇宙 自定义 自定义View 自定义插件 蒙版 虚拟 虚拟机 补码 补齐 表单 表达式 装饰模式 西安 观察者模式 规范 视图 视频 解耦器模式 设计 设计原则 设计模式 访问者模式 语法 责任链模式 贪吃蛇 转换 软件工程 软引用 运算符 迭代子模式 适配器模式 选择器 通信 通道 配置 链表 锐化 错误 键盘 闭包 降噪 陕西地方特产 面向对象 项目优化 项目构建 黑洞
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×