Html是“名词”,CSS是“形容词”,JavaScript是“动词”,这三个兄弟凑在一起,就构成了 “静态” 页面,那么如何让他 “动态” 起来呢?这就需要后端相关技术的支持,这也是我们今天想要说的。
那么又怎么去理解 “静态” 和 “动态” 这两个词呢?
这两个词最大的不同就是在于其 交互性 ,静态页面不是指页面不能进行变化,而是指不能与后端进行交互,实现数据的传输与处理,也就是说,静态页面一旦做好后,基本就是这个样子了,更像一个单纯的展示,而动态页面却可以实现根据用户的要求和选择而动态的去改变和响应,浏览器客户端,成为了前后端动态交互的一个桥梁。
而随着现在用户需求的增加,以及数据量的增加,在Web开发中,能够及时、正确地响应用户的请求几乎已经可以说是必须的了
今天重点要学习的就是也就是——如何在获取请求后对其解析,然后执行相关的逻辑处理,最终跳转到页面,将数据回馈
上面我提到了,在前后端动态交互中,浏览器客户端,成为了前后端沟通的桥梁,这也就是常见的 B/S 架构方式,也就是 浏览器/服务器,在其中最为常用的就是三层架构的开发模式
大家在 JavaWeb 的学习过程中,基本上已经在用三层架构的模式来进行编程,哪三层呢?
注:以JavaWeb中为例
有两点需要强调一下:
例如表现层依赖业务层,在 JavaWeb 阶段实际上就是在 Servlet 中 new 了一个 Service ,当然,在Spring的 IOC 下我们只需要在控制层中添加Service的引用就可以了,并不需要再new了,耦合大大降低,我们上面说的依赖主要指两个层之间存在一定的关系
针对,一些简单的操作,例如单表数据的增删,实际上几乎没有任何业务,最多例如参数不合法一类的,能加个返回的错误码,但如果面对一些比较复杂的项目,就存在一些业务逻辑需要编写
例如:查询时需要的结果,并不是简单的一张表中,而查询条件也比较复杂,我们就可以通过对查询条件进行拆分,再组合,就可以查询到不同需求的数据。
再例如:以前文章中我常说的转账案例,为了避免在转账的整个过程中发生异常,导致资金发生问题,就需要保证事务的一致性,而这些事务我们就可以放在业务层来做,当然 Spring 的AOP 可以帮助我们更好的处理事务问题
MVC 也就是 model-view-controller,我们来看看它的每一部分
做了一张 MVC 模式下的工程结构图,方便大家理解
<div align="center">
<img src="https://user-gold-cdn.xitu.io/2020/3/21/170fafd3875e80cd?w=533&h=391&f=png&s=36877" style="zoom:80%">
</div>
实际上,如果是初次接触 Spring MVC 实际上,看个基本概念也就行了,比如下面我提到的,Spring MVC 的优点,Spring MVC 与 Struts 的区别,如果在没有进行过一些基本的使用,以及简单一些流程的简单分析,实际上没啥卵用,这些东西简单看看就行了,经过一定的学习以后,回过头来再看,会有感觉的多
<p align="right">—— Spring官网</p> Spring MVC属于SpringFrameWork的后续产品,已经融合在Spring Web Flow里面。Spring 框架提供了构建 Web 应用程序的全功能 MVC 模块。使用 Spring 可插入的 MVC 架构,从而在使用Spring进行WEB开发时,可以选择使用Spring的Spring MVC框架或集成其他MVC开发框架,如Struts1(现在一般不用),Struts 2(一般老项目使用)等。 <p align="right">—— 百度百科</p>
MVC 在上面我们已经进行了基本的介绍,而Spring MVC 就是一款基于 MVC架构模式的轻量级Web框架,我们所说的 Spring MVC 与 Spring Web MVC 是等价的,只不过人们更习惯前者的叫法,这一款框架,本质上也是基于 Servlet 的,如果你有 Servlet 以及 Spring 的基础,简单的上手这个Web框架是非常快的
② 清晰的模块化职能划分,各模块各司其职,清晰明了
Struts 也是一款基于 MVC 这种在开发模式的 JavaEE框架,近些年来,实际上开发者更多的选择使用 SpringMVC 这个框架,那么两者的区别是什么呢?Spring MVC 的过人之处又在哪里呢?
① Spring MVC 基于方法开发,Struts 基于类开发
② Spring MVC 支持单例开发模式,而 Struts 不支持
③ Spring MVC 的速度比 Struts 的速度稍微快一些
④ Spring MVC 使用更加简洁,同时还支持 JSR303,能够比较方便的处理 ajax
⑤ Struts2 的 OGNL 表达式使页面的开发效率相比 Spring MVC 更高一点,但是执行效率对于 JSTL 也没有很明显的提升
① 创建Maven项目 --> ② 选择JDK版本 --> ③ 勾选 create from archetype 即使用骨架创建项目 --> ④ 选择 maven-archetype-webapp 创建出一个web项目
<div align="center">
<img src="https://user-gold-cdn.xitu.io/2020/3/21/170fafd3885c8292?w=1049&h=683&f=png&s=94429" style="zoom:80%">
</div>
然后指定基本信息,点击下一步
<div align="center">
<img src="https://user-gold-cdn.xitu.io/2020/3/21/170fafd385647693?w=844&h=208&f=png&s=11031" style="zoom:80%">
</div>
但是,由于创建 maven archetype 的原因,在创建时,会执行 mvn archetype:generate这个命令,这样就需要指定一个 archetype-catalog.xml 文件,命令中参数 -DarchetypeCatalog 的值有三种
我们需要做的就是添加这样一组键值对,就可以加快创建项目的速度
<div align="center">
<img src="https://user-gold-cdn.xitu.io/2020/3/21/170fafd470208bcf?w=940&h=586&f=png&s=42669" style="zoom:80%">
</div>
这里没什么好说的,基本不需要更改,继续下一步
<div align="center">
<img src="https://user-gold-cdn.xitu.io/2020/3/21/170fafd389628c7d?w=861&h=468&f=png&s=29070" style="zoom:80%">
</div>
将版本从1.7改为1.8,接着又在 dependencies 中引入我们需要的一些 jar 包
定义 <spring.version>5.0.2.RELEASE</spring.version>
这样一个标签对,在下面就可以引用,这样相比于直接将版本信息写到每一个 dependencie 中,更利于后期的维护,方便更换版本,这种方式叫做锁定版本
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>cn.ideal</groupId> <artifactId>spring_mvc_01_basic</artifactId> <version>1.0-SNAPSHOT</version> <packaging>war</packaging> <name>spring_mvc_01_basic Maven Webapp</name> <!-- FIXME change it to the project's website --> <url>http://www.example.com</url> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> <spring.version>5.0.2.RELEASE</spring.version> </properties> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>servlet-api</artifactId> <version>2.5</version> <scope>provided</scope> </dependency> <dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>jsp-api</artifactId> <version>2.0</version> <scope>provided</scope> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency> </dependencies> <build> <finalName>spring_mvc_01_basic</finalName> <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) --> <plugins> <plugin> <artifactId>maven-clean-plugin</artifactId> <version>3.1.0</version> </plugin> <!-- see http://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_war_packaging --> <plugin> <artifactId>maven-resources-plugin</artifactId> <version>3.0.2</version> </plugin> <plugin> <artifactId>maven-compiler-plugin</artifactId> <version>3.8.0</version> </plugin> <plugin> <artifactId>maven-surefire-plugin</artifactId> <version>2.22.1</version> </plugin> <plugin> <artifactId>maven-war-plugin</artifactId> <version>3.2.2</version> </plugin> <plugin> <artifactId>maven-install-plugin</artifactId> <version>2.5.2</version> </plugin> <plugin> <artifactId>maven-deploy-plugin</artifactId> <version>2.8.2</version> </plugin> </plugins> </pluginManagement> </build> </project>
刚创建好的项目中,main文件夹下是空的,我们需要创建出 java 以及 resources 两个文件夹,并且分别设置为,源代码根目录 以及 资源根目录,设置方式如下图
<div align="center">
<img src="https://user-gold-cdn.xitu.io/2020/3/21/170fafd4b213ca20?w=652&h=176&f=png&s=18473" style="zoom:80%">
</div>
在以前 JavaWeb 阶段中,我们都很清楚,前端发出的请求,都会被映射到 Web.xml 中,然后匹配到对应的 Servlet 中,然后调用对应的 Servlet 类 来处理这个请求
由于现在我们使用了 Spring MVC,所以这些请求,我们就交给 Spring MVC 进行管理,所以需要在工程 webapp-WEB-INF 中找到 web.xml 进,在其中配置核心控制器,也就是 DispatcherServelt
<servlet ></servlet >
标签中指定了一个实现类为 DispatcherServelt ,名称为 dispatcherServlet 的 servlet 配置
<servlet-mapping></servlet-mapping>
标签中则指定了 dispatcherServlet 拦截请求的范围,使用 /
即代表所有请求都需要经过这里
<init-param></init-param>
标签对中放置 DispatcherServelt 所需要的初始化参数,配置的是 contextConfigLocation 上下文参数变量,其加载的配置文件为编译目录下的 springmvc.xml (下面创建)
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd" > <web-app> <display-name>Archetype Created Web Application</display-name> <servlet> <servlet-name>dispatcherServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <!--配置Servlet初始化参数,读取springmvc的配置文件,创建spring容器--> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:springmvc.xml</param-value> </init-param> <!-- 配置servlet启动时加载对象--> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>dispatcherServlet</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> </web-app>
在这里,一个是开启扫描,以及开启注解,还有就是配置视图解析器,它的作用就是执行方法后,根据返回的信息,来加载相应的界面,并且绑定反馈数据
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:context="http://www.springframework.org/schema/context" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!-- 配置spring创建容器时要扫描的包--> <context:component-scan base-package="cn.ideal"></context:component-scan> <!-- 配置视图解析器--> <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/pages/"></property> <property name="suffix" value=".jsp"></property> </bean> <!-- 配置spring开启注解mvc的支持 --> <mvc:annotation-driven></mvc:annotation-driven> </beans>
特别说明:一般开发我们都需要写上这个标签,即使或许现在还没怎么体现出来
package cn.ideal.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; @Controller public class ControllerDemo { @RequestMapping(path = "/test") public String methodTest(){ System.out.println("这是Controller测试方法"); return "testSuccess"; } }
写一个超链接,去请求test这个路径,也就是指向到了 Controller 下的 methodTest() 方法
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>Title</title> </head> <body> <h3>这是主页面</h3> <a href="test">访问test试试</a> </body> </html>
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>Title</title> </head> <body> <h3>跳转成功哈</h3> </body> </html>
我这里,配置了本地的tomcat,以及项目名称
<div align="center">
<img src="https://user-gold-cdn.xitu.io/2020/3/21/170fafd489d2362c?w=1031&h=682&f=png&s=52626" style="zoom:80%">
</div>
<div align="center">
<img src="https://user-gold-cdn.xitu.io/2020/3/21/170fafd4b223ff16?w=743&h=444&f=png&s=60869" style="zoom:80%">
</div>
注:我们开发人员真正需要进行开发的是处理器(Handler)和视图(View)
也就是,处理用户请求的具体逻辑代码,以及展示给用户的界面
@RequestMaspping 注解是指定控制器可以处理哪些URL请求,这个注解可以放在类或者方法上。
属性:
而一般不在 @RequestMaspping 中配置其他属性的时候,可以省去 value 参数名,直接写一个代表 URL 映射信息的字符串就可以了
例如: @RequestMaspping(/test)
在用户在页面中出发请求的时候,提交表单的数据一般都是 key/value 格式的数据
在传统JavaWeb 中我们所使用的一般是 request.getParameter() 等方法将请求参数获取到
而Spring MVC中可以通过参数绑定,将客户端请求的这个 key/value 格式的数据绑定到 Controller 处理器方法的形参上,支持的数据类型我们可以分为三类
注:只截取了部分
<h3>这是主页面</h3> <a href="user/testA?username=admin&password=admin888">测试一下</a>
<h3>跳转成功哈</h3>
@Controller @RequestMapping("/user") public class UserController { @RequestMapping("/testA") public String testA(String username, String password) { System.out.println("获取到的username: " + username); System.out.println("获取到的password: " + password); return "testSuccess"; } }
通过构建一个超链接的方式传递参数,例如 ?username=admin
而在后端中如果方法形参与这个username是一致的,这个提交的数据就会被绑定到参数username中
参数中使用 JavaBean 类型接收时,在提交表单的时候,就需要将其中的 name 属性中的值与实体类中的成员变量的值是一样的
如果一个JavaBean类中包含其他的引用类型,那么表单的name属性需要编写成:对象.属性例如:account.username
<form action="user/testB" method="post"> 昵称: <input type="text" name="nickname"><br/> 年龄: <input type="text" name="age"><br/> 住址: <input type="text" name="address"><br/> 用户名: <input type="text" name="account.username"><br/> 密码: <input type="text" name="account.password"><br/> <input type="submit" value="提交"> </form>
@Controller @RequestMapping("/user") public class UserController { @RequestMapping("/testB") public String testB(User user) { System.out.println(user); return "testSuccess"; } }
public class User implements Serializable { private String nickname; private int age; private String address; private Account account; ......省略 get set toString方法 }
public class Account implements Serializable { private String username; private String password; ......省略 get set toString方法 }
对于集合类型,仍然使用一个表达式的写法
<form action="user/testB" method="post"> 昵称: <input type="text" name="nickname"><br/> 年龄: <input type="text" name="age"><br/> 住址: <input type="text" name="address"><br/> 用户名1: <input type="text" name="list[0].username"><br/> 密码1: <input type="text" name="list[0].password"><br/> 用户名2: <input type="text" name="map['First'].username"><br/> 密码2: <input type="text" name="map['First'].password"><br/> <input type="submit" value="提交"> </form>
@Controller @RequestMapping("/user") public class UserController { @RequestMapping("/testB") public String testB(User user) { System.out.println(user); return "testSuccess"; } }
public class User implements Serializable { private String nickname; private int age; private String address; private List<Account> list; private Map<String, Account> map; ......省略 get set toString方法 }
public class Account implements Serializable { private String username; private String password; ......省略 get set toString方法 }
在 web.xml 中的 <web-app></web-app>
标签内配置过滤器类,达到解决请求参数中文乱码的问题
<!--配置解决中文乱码的过滤器--> <filter> <filter-name>characterEncodingFilter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> </filter> <filter-mapping> <filter-name>characterEncodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
属性
@RequestMapping(path="/hello") public String sayHello(@RequestParam(value="nick",required=false)String nickname) { System.out.println(nickname); return "success"; }
@RequestMapping("/testC") public String testC(@RequestBody String body) { System.out.println(body); return "testSuccess"; }
例如接收到这样的语句
nickname=BWH_Steven&age=666&address=beijing
属性:
@RequestMapping(path="/test/{uid}") public String testD(@PathVariable(value="uid") String id) { System.out.println(id); return "testSuccess"; }
<a href="user/test/66">访问test试试</a>
属性:
@RequestMapping("/testD") public String testD(@RequestHeader(value="Accept") String header) { System.out.println(header); return "testSuccess"; }
<a href="user/testD">访问test试试</a>
打印结果:
text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,/;q=0.8,application/signed-exchange;v=b3
属性:
@RequestMapping("/testF") public String testF(@CookieValue(value="JSESSIONID") String cookieValue) { System.out.println(cookieValue); return "testSuccess"; }
<a href="user/testF">访问test试试</a>
打印结果:
FCFDD389AC473F837266FC890E9E6F36
作用:
应用场景:
只提供修改年龄和地址的表单,同时传一个隐藏域中的id,方便去数据库查询(当然我们这里是模拟的)
<form action="user/testG" method="post"> <input type="hidden" name="uid" value="1"> 年龄: <input type="text" name="age"><br/> 住址: <input type="text" name="address"><br/> <input type="submit" value="提交"> </form>
实体类就不给出了,就是三个成员,nickname age address
如果没有下面这个增加了 @ModelAttribute 注解的 findUserByUid方法,当执行 testG 方法后,会获取到一个 nickname = null 的值
而我们下面的做法,在执行 testG 之前会先执行 findUserByUid,然后可以去数据库中根据uid查询,当然我们这里是模拟的,然后将这个user返回
接着执行 testG 方法的时候,就能将用户提交的 age 和 address 获取到,同时将用户没有提交的 nickname 使用数据库中的值
@RequestMapping("/testG") public String testG(User user) { System.out.println("这是testG方法"); System.out.println("用户修改后的: " + user); return "testSuccess"; } @ModelAttribute public User findUserByUid(Integer uid) { System.out.println("这是findUserByUid方法"); //模拟查询数据库 User user = new User(); user.setNickname("BWH_Steven"); user.setAge(66); user.setAddress("北京"); System.out.println("数据库中查询到: " + user); return user; }
如果没有返回值的方式就可以这样改写,增加一个 map ,然后存进去
@RequestMapping("/testG") public String testG(@ModelAttribute("first") User user) { System.out.println("这是testG方法"); System.out.println("用户修改后的: " + user); return "testSuccess"; } @ModelAttribute public void findUserByUid(Integer uid, Map<String,User> map) { System.out.println("这是findUserByUid方法"); //模拟查询数据库 User user = new User(); user.setNickname("BWH_Steven"); user.setAge(66); user.setAddress("北京"); System.out.println("数据库中查询到: " + user); map.put("first",user); }
属性
在存入方法跳转之前,会将数据保存到 nickname age address 中,因为注解@SessionAttribute中有这几个参数
@Controller @RequestMapping("/user") @SessionAttributes(value = {"nickname","age","address"}) public class UserController { /** * 向session中存入值 * @param model * @return */ @RequestMapping("/addUser") public String addUser(Model model) { model.addAttribute("nickname", "BWH_Steven"); model.addAttribute("age", 20); model.addAttribute("address", "北京"); return "testSuccess"; } /** * 从session中获取值 * @param modelMap * @return */ @RequestMapping("/findUser") public String findUser(ModelMap modelMap) { String nickname = (String) modelMap.get("nickname"); Integer age = (Integer)modelMap.get("age"); String address= (String) modelMap.get("address"); System.out.println(nickname + " " + age + " " + address); return "testSuccess"; } /** * 清除值 * @return */ @RequestMapping("/delete") public String delete(SessionStatus status) { status.setComplete(); return "testSuccess"; } }
<a href="user/addUser">访问addUser试试</a> <a href="user/findUser">访问findUser试试</a> <a href="user/delete">访问delete试试</a>
注意设置 isELIgnored="false"
<%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %> <html> <head> <title>Title</title> </head> <body> <h3>跳转成功哈</h3> ${nickname} ${age} ${address} ${sessionScope} </body> </html>
讲完了请求与参数绑定,以及一些常用的注解,接着就可以说一下响应的一些知识,也就是我们接受到用户的请求,并且进行一定的处理以后,如何进行正确的响应
其实在前面的讲解中,我们一直用的就是返回字符串的形式,而结果也是很直观的,也就是,进行了同名页面的跳转,例如返回 success 则跳转到 success.jsp 的页面中
这也就是说,Controller 方法返回字符串可以指定逻辑视图的名称,视图解析器会将其解析成物理视图的地址
演示一种常见的使用场景
<a href="user/testString">修改用户信息页面</a>
注:实体类就不谈了,只有 username 和 password 两个成员
模拟一个数据库查询到数据信息,然后将数据存到 request 域中
@Controller @RequestMapping("/user") public class UserController { @RequestMapping("/testString") public String testString(Model model) { //模拟从数据库中查询 User user = new User(); user.setUsername("张三"); user.setPassword("888666"); model.addAttribute("user", user); return "success"; } }
注意配置:isELIgnored="false"
当用户点击主页面中进行页面修改,就会跳转到这个用户名以及密码的修改界面,同时将数据进行回显,优化体验
<%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %> <html> <head> <title>Title</title> </head> <body> <h3>修改</h3> <form action="" method="post"> 用户名:<input type="text" name="username" value="${ user.username }"><br> 密码:<input type="text" name="password" value="${ user.password }"><br> <input type="submit" value="提交"> </form> </body> </html>
<a href="user/testForward">测试一下</a>
@RequestMapping("/testForward") public String testForward() throws Exception{ System.out.println("testForward 被执行了"); //转发 // return "forward:/WEB-INF/pages/success.jsp"; //重定向 return "redirect:/index.jsp"; }
如果说直接去掉返回值,以及修改返回类型为void,会报出一个404异常,可以看到地址栏中,去指向了一个 http://localhost:8080/springmvc-response/user/testVoid.jsp
的地址,也就是说它默认去查找了一个jsp页面(也就是 @RequestMapping("/testVoid") 值同名的 jsp),不过没有找到
如果想要在这种情况下,跳转页面可以使用请求转发,或者重定向跳转
@RequestMapping("/testVoid") public void testVoid(HttpServletRequest request,HttpServletResponse response) throws Exception { System.out.println("请求转发或者重定向被执行了"); // 1. 请求转发 // request.getRequestDispatcher("/WEB-INF/pages/test1.jsp").forward(request, response); // 2. 重定向 // response.sendRedirect(request.getContextPath()+"/test2.jsp"); response.setCharacterEncoding("UTF-8"); response.setContentType("text/html;charset=UTF-8"); // 3. 直接响应数据 response.getWriter().print("测试被执行了哈"); return; }
这种方式其实和String达到的效果基本是一致的
<a href="user/findAll">测试一下</a>
@RequestMapping("/findUser") public ModelAndView findUser() throws Exception{ ModelAndView modelAndView = new ModelAndView(); //跳转到jsp modelAndView.setViewName("success"); //模拟从数据库中查询用户信息 User user = new User(); user.setUsername("李四"); user.setPassword("888888"); modelAndView.addObject("user",user); return modelAndView; }
${user.username} ${user.password}
在 web.xml 中配置的 DispatcherServle(前端控制器),会拦截到所有的资源,在以后的开发中,一个特别显著的问题就是,静态资源 (img、css、js)这样的文件也被拦截了,也就无法使用,我们首先需要了解的就是如何不对静态资源进行拦截
非常简单,在springmvc.xml中配置就可以了
mvc:resources 标签就可以配置不过滤
<!--前端控制器--> <mvc:resources mapping="/css/**/" location="/css/"/> <mvc:resources mapping="/images/**/" location="/images/"/> <mvc:resources mapping="/js/**/" location="/js/"/>
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>Title</title> <%--引入jquery--%> <script src="js/jquery-2.1.0.min.js"></script> <script> $(function () { $("#btn").click(function () { alert("Just for test"); }); }); </script> </head> <body> <%--<a href="user/testString">修改用户信息页面</a>--%> <%--<a href="user/testForward">测试一下</a>--%> <button id="btn">发送ajax请求</button> </body> </html>
在 Javaweb 阶段,大家基本都是有了解过 ajax 的,所以我就直接用了,如果有不熟悉的,可以去查一下api或者找一下教程,格式还是非常好理解的
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>Title</title> <%--引入jquery--%> <script src="js/jquery-2.1.0.min.js"></script> <script> $(function () { $("#btn").click(function () { //发送ajax请求 $.ajax({ url:"user/testAjax", contentType:"application/json;charset=UTF-8", data:'{"username":"zhangsan","password":"888888"}', dataType:"json", type:"post", success:function (data) { //解析响应数据 } }) }); }); </script> </head> <body> <button id="btn">发送ajax请求</button> </body> </html>
参数中使用 @RequestBody 这个注解,就可以接收到请求体,然后变成这样一个串的形式
@Controller @RequestMapping("/user") public class UserController { @RequestMapping("/testAjax") public void testAjax(@RequestBody String body){ System.out.println("testAjax 被执行了"); System.out.println(body); } }
打印结果就是这样的
testAjax 被执行了 {"username":"zhangsan","password":"888888"}
@RequestMapping("/testAjax") public @ResponseBody User testAjax(@RequestBody User user){ System.out.println("testAjax 被执行了"); // 模拟数据库查询 System.out.println(user); user.setUsername("admin"); user.setPassword("admin888"); return user; }
使用 @RequestBody String body 接收到的是一个串,而想要直接将 Json 字符串和 JavaBean 对象相互转换,需要 jackson 的jar包,我们可以增加这样的依赖
<dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.9.0</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-core</artifactId> <version>2.9.0</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-annotations</artifactId> <version>2.9.0</version> </dependency>
<%--引入jquery--%> <script src="js/jquery-2.1.0.min.js"></script> <script> $(function () { $("#btn").click(function () { //发送ajax请求 $.ajax({ url:"user/testAjax", contentType:"application/json;charset=UTF-8", data:'{"username":"zhangsan","password":"888888"}', dataType:"json", type:"post", success:function (data) { //解析响应数据 alert(data); alert(data.username); alert(data.password); } }) }); }); </script>
<h3>文件上传</h3> <form action="user/fileupload" method="post" enctype="multipart/form-data"> 选择文件:<input type="file" name="upload"/><br/> <input type="submit" value="上传文件"/> </form>
form表单的enctype的默认值是:application/x-www-form-urlencoded
如果想要进行文件上传,就必须要改为 multipart/form-data(),同时method属性取值必须是Post
注意:当form表单的enctype取值不是application/x-www-form-urlencoded后,request.getParameter()方法就不能再使用
注意:想要实现文件上传,可以借助一些组件,需要导入该组件相应的支撑jar 包:Commons-fileupload 和commons-io
commons-io 不属于文件上传组件的开发jar文件,但Commons-fileupload 组件从1.1 版本开始,它使用需要commons-io包的支持
@Controller @RequestMapping("/user") public class UserController { @RequestMapping("/fileupload") public String fileupload(HttpServletRequest request) throws Exception { System.out.println("文件上传..."); // 使用fileupload组件完成文件上传 // 上传位置 String path = request.getSession().getServletContext().getRealPath("/uploads/"); // 判断,该路径是否存在 File file = new File(path); if (!file.exists()) { // 不存在则创建文件夹 file.mkdirs(); } // 解析request对象,获取上传文件项 DiskFileItemFactory factory = new DiskFileItemFactory(); ServletFileUpload upload = new ServletFileUpload(factory); // 解析request List<FileItem> items = upload.parseRequest(request); // 遍历 for (FileItem item : items) { // 进行判断,当前item对象是否是上传文件项 if (item.isFormField()) { // 普通表单项 } else { // 上传文件项 // 上传文件的名称 String filename = item.getName(); // 把文件的名称设置唯一值,UUID 防止重复覆盖问题 String uuid = UUID.randomUUID().toString().replace("-", ""); filename = uuid + "_" + filename; // 完成文件上传 item.write(new File(path, filename)); // 删除临时文件 item.delete(); } } return "success"; } }
request.getSession().getServletContext() 获取的是Servlet容器对象,就好比tomcat容器
getRealPath("/") 代表获取实际路径,“/”指代项目根目录
所以代码返回的是项目在容器中的实际发布运行的根路径
<h3>文件上传</h3> <form action="user/fileupload2" method="post" enctype="multipart/form-data"> 选择文件:<input type="file" name="upload"/><br/> <input type="submit" value="上传文件"/> </form>
<!--配置文件解析器对象--> <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> <property name="maxUploadSize" value="10485760" /> </bean>
注意:10485760 = 10x1024x1024
@Controller @RequestMapping("/user") public class UserController { @RequestMapping("/fileupload2") public String fileupload2(HttpServletRequest request, MultipartFile upload) throws Exception { System.out.println("文件上传..."); // 使用fileupload组件完成文件上传 // 上传位置 String path = request.getSession().getServletContext().getRealPath("/uploads/"); // 判断,该路径是否存在 File file = new File(path); if (!file.exists()) { // 不存在则创建文件夹 file.mkdirs(); } // 获取上传文件的名称 String filename = upload.getOriginalFilename(); // 把文件的名称设置唯一值,uuid String uuid = UUID.randomUUID().toString().replace("-", ""); filename = uuid+"_"+filename; // 完成文件上传 upload.transferTo(new File(path,filename)); return "success"; } }
很多时候会将整个工程部署到不同的服务器,例如:
应用服务器,数据库服务器,缓存和消息服务器,文件服务器等等,不过入门来说了解一下就可以了
想要测试下面的代码,可以配置两个 Tomcat 给不同端口等配置,模拟一下
<h3>文件上传</h3> <form action="user/fileupload3" method="post" enctype="multipart/form-data"> 选择文件:<input type="file" name="upload"/><br/> <input type="submit" value="上传文件"/> </form>
<dependency> <groupId>com.sun.jersey</groupId> <artifactId>jersey-core</artifactId> <version>1.18.1</version> </dependency> <dependency> <groupId>com.sun.jersey</groupId> <artifactId>jersey-client</artifactId> <version>1.18.1</version> </dependency>
@Controller @RequestMapping("/user") public class UserController { @RequestMapping("/fileupload3") public String fileupload3(MultipartFile upload) throws Exception { System.out.println("SpringMVC跨服务器方式的文件上传..."); // 定义图片服务器的请求路径 String path = "http://localhost:9090//springmvc-fileupload/uploads/"; // 获取到上传文件的名称 String filename = upload.getOriginalFilename(); String uuid = UUID.randomUUID().toString().replaceAll("-", "").toUpperCase(); // 把文件的名称唯一化 filename = uuid + "_" + filename; // 向图片服务器上传文件 // 创建客户端对象 Client client = Client.create(); // 连接图片服务器 WebResource webResource = client.resource(path + filename); // 上传文件 webResource.put(upload.getBytes()); return "success"; } }
异常处理也算一个老生常谈的问题,在上线项目或者运行项目的时候,总可能会出现一些无法预料的异常信息,对于开发者而言,自然需要看到具体的异常信息,然后进行排除,而对于用户,自然尽可能的出现一些简单,易于理解的语言或者提示
在 Spring MVC 中,提供了一个全局异常处理器,可以对异常进行统一处理
Dao、Service、Controller出现都通过 throws Exception 向上抛出,最后由Spring MVC前端
控制器交由全局异常处理器进行异常处理
对于预期的异常,通常定义一个自定义异常类,用来存储异常的信息
首先这个类继承了 Exception 类,用来描述程序能获取的异常,设置了一个成员message,就是用来存放异常信息的
package cn.ideal.exception; public class SysException extends Exception { private String message; @Override public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } public SysException(String message) { this.message = message; } }
全局异常处理器实现的是 Spring MVC 的 HandlerExceptionResolver 接口
这是接口的源码
public interface HandlerExceptionResolver { @Nullable ModelAndView resolveException(HttpServletRequest var1, HttpServletResponse var2, @Nullable Object var3, Exception var4); }
public class SysExceptionResolver implements HandlerExceptionResolver { /** * 跳转到具体的错误页面的方法 */ public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler,Exception ex) { ex.printStackTrace(); //解析出异常类型 SysException sysException = null; // 获取到异常对象 if (ex instanceof SysException) { //如果异常类型是系统自定义异常,直接取出异常信息,在错误页面展示 sysException = (SysException) ex; } else { //如果异常类型不是系统自定义异常,则构造一个自定义异常类型 sysException = new SysException("未知错误"); } ModelAndView mv = new ModelAndView(); // 存入错误的提示信息 mv.addObject("errorMsg", sysException.getMessage()); // 跳转的Jsp页面 mv.setViewName("error"); return mv; } }
springmvc.xml 中配置
<bean id="sysExceptionResolver" class="cn.ideal.exception.SysExceptionResolver"/>
@Controller @RequestMapping("/user") public class UserController { @RequestMapping("testException") public String testException() throws SysException { System.out.println("testException被执行了"); try { int a = 100 / 0; } catch (Exception e) { e.printStackTrace(); throw new SysException("测试这个方法出错了"); } return "success"; } }
<h3>主页面</h3> <a href="user/testException">测试一下</a>
<%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %> <html> <head> <title>Title</title> </head> <body> ${errorMsg} </body> </html>
拦截器,用来干嘛呢,就比如你需要检测用户的权限,或者把请求信息记录到日志等等,也就是说需要在用户请求前后执行的一些行为
首先在 Spring MVC 中是有两种机制,第一种就是实现 HandlerInterceptor接口,还有一种就是实现 Spring 的 WebRequestInterceptor 接口,不过我们就简单说一下第一种
自定义一个类简单看一下,实现三个方法
public class MyInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("preHandle方法执行了"); return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("postHandle方法执行了"); } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("afterCompletion方法执行了"); } }
① preHandle方法:controller 方法执行前拦截的方法
② postHandle:controller 方法执行后执行的方法,在 JSP 视图执行前
③ postHandle方法:在JSP执行后执行
注:不要拦截用这个标签 <mvc:exclude-mapping path=""/>
注: /user/*
代表所有访问带有 /user/的路径都会被拦截,例如 /user/test
<!--配置拦截器--> <mvc:interceptors> <!--配置拦截器--> <mvc:interceptor> <!--要拦截的具体的方法--> <mvc:mapping path="/user/*"/> <!--配置拦截器对象--> <bean class="cn.ideal.interceptor.MyInterceptor" /> </mvc:interceptor> </mvc:interceptors>
随便写一个方法测试一下
@RequestMapping("/testInterceptor") public String testInterceptor() { System.out.println("testInterceptor被执行了"); return "success"; }
执行结果
preHandle方法执行了 testInterceptor被执行了 postHandle方法执行了 afterCompletion方法执行了
写着写着又是1w字了,之前就想着写这篇文章,也一直没什么空,对于这一篇文章,我认为对于入门来说还是比较有好的,前面给了几个大点的基本知识讲解,然后从开发环境以及一个入门程序开始,再到请求以及如何响应,以及一些常用的注解,再到其他的,文件上传,异常处理,拦截器等知识,基本来说,达到了一个 工具书 + 入门讲解的效果,不过要说的点太多了,即使1w字的文章,实际上也只够简单提及,再加个小案例,就例如拦截器,或者文件上传的讲解,只能说讲了最基本的,对于已经有一定基础的朋友,自然没什么进阶的帮助,不过我的初心,也是想巩固一下自己的知识,然后能将文章带给刚接触 Spring MVC 的朋友,我也不是什么大牛,不过希望能给大家一点帮助,我们可以一起交流,一起进步哈!
感谢大家的支持!!!
如果文章中有什么不足,欢迎大家留言交流,感谢朋友们的支持!
如果能帮到你的话,那就来关注我吧!如果您更喜欢微信文章的阅读方式,可以关注我的公众号
一个坚持推送原创开发技术文章的公众号:理想二旬不止