上次说SpringMVC主要是解决Controller层的相关问题,这节就用配置文件+注解的方式完成一个SpringMVC程序的开发。
一、第一个SpringMVC程序的开发
1、开发版本
1. JDK1.8+
2. Maven3.6.3
3. IDEA2020.3.2
4. SpringFramework 5.1.4
5. Tomcat8.5.42
6. MySQL5.7.26
2、相关依赖
在之前整合Spring+Struts2+Mybatis开发的基础上,去掉struts2依赖,加一个springmvc的依赖即可,参考Spring(8):
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.1.14.RELEASE</version>
</dependency>
既然是用配置文件+注解的方式开发,那么其实是和Struts2时的配置差不多的,也需要在web.xml中指定配置文件,并实现工厂创建,配置文件可以随便命名,这里叫dispatcher.xml,创建Spring配置文件如下,bean标签中缺少引用需要自己补充。
web.xml配置:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<!--创建工厂-->
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!--指定SpringMVC配置文件的路径-->
<!-- <init-param>-->
<!-- <param-name>contextConfigLocation</param-name>-->
<!-- <param-value>classpath:dispatcher.xml</param-value>-->
<!-- </init-param>-->
<!--本Servlet会在tomcat启动的时候就会被创建-->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
DispathcerServlet,创建工厂:
1. DispatcherServlet称为前端控制器(中央控制器)
2. DispatcherServlet的核心作用:
1. 用于创建Spring的工厂(容器)。
ApplicationContext ctx = new ClassPathXmlApplicationContext("dispatcher.xml");
因为DispatcherServlet封装的Spring工厂(容器)只能读取xml,所以无法迁移到纯注解编程
2. 控制SpringMVC内部的运行流程。
dispatcher.xml,添加springmvc核心功能标签和包扫描标签:
<!--设置注解扫描的路径-->
<context:component-scan base-package="com.jin" />
<!--引入SpringMVC的核心功能-->
<mvc:annotation-driven />
mvc:annotation-driven的作用:
# <mvc:annotation-driven/> 这段配置的主要作用:引入SpringMVC的核心功能。
# 主要引入了2个核心类型
# 1.RequestMappingHandlerMapping
# 2.RequestMappingHandlerAdapter
1. RequestMappingHandlerMapping实现了HandlerMapping接口【了解】
它会处理@RequestMapping注解,并将其注册到请求映射表中。
2. RequestMappingHandlerAdapter实现了 HandlerAdapter接口【了解】
它是处理请求的适配器,确定调用某个符合要求的控制器类中具体服务的方法。
简单来说这两个接口实现的作用就是,从哪里来,到哪里去。
context:component-scan的作用:
1. 进行注解扫描
2. DispatcherServlet所创建的工厂需要读取XML的配置文件,不能使用纯注解的开发。所以目前使用Spring配置文件+基础注解的形式,进行开发。
基础注解:@Component,@Service,@Repository,@Controller,@Scope,@Transactional等
高级注解:@Configuration,@Bean,@ComponentScan等
后续SpringMVC高级版的课程,会使用纯注解版开发,与SpringBoot的使用方式高度一致。
一些基础配置,本质还是创建工厂,并将配置文件中的对象交由工厂管理。
3、程序编码
# 基本流程
1. 开发一个类在上面加入@Controller注解 (生成bean对象交由工厂管理)
2. 提供一个控制器方法:参数是HttpServletRequest,HttpServletResponse,返回值是String的,同时加入@RequestMapping注解定义请求路径(从哪里来)
3. 在控制方法中,完成核心开发功能,把对应JSP页面的路径,作为方法的返回值返回。(到哪里去)
package com.jin;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @author jinyunlong
* @date 2022/1/7 16:21
* @profession ICBC锅炉房保安
*/
@Controller
//@Scope("singleton")
//@Scope("prototype")
public class FirstController {
public FirstController(){
System.out.println("FirstController.FirstController");
}
// @RequestMapping("/first")
// @RequestMapping("first")
@RequestMapping(value={"/first","/third"})
public String first(HttpServletRequest request, HttpServletResponse response){
System.out.println("request = " + request);
return "/result.jsp";
}
@RequestMapping(value="/second") //在注解使用时,如果只有一个value属性的话,value属性名,可以省略。
public String second(HttpServletRequest request, HttpServletResponse response) {
//第二个控制器功能
System.out.println("request = " + request);
return "/result.jsp";
}
}
如上代码,和Servlet一个类只能提供一个服务方法不同,SpringMVC作为控制器,一个类可以提供多个服务方法。
注意:SpringMVC我们开发的Controller,也称之为Handler(SpringMVC内部的叫法)
二、第一个SpringMVC程序的细节分析
1、关于类对象的创建次数
1. 回顾:Servlet控制器被创建的次数
一种类型的Servlet,只会被Tomcat创建一次,所以Servlet是单实例的。
2. Servlet是单实例并不是单例设计模式
3. SpringMVC的控制器被Spring创建的次数
可以只创建一次,也可以创建多次,默认是只创建一次。
控制器创建的次数,是由@Scope注解决定的。
4. 默认情况下SpringMVC的控制器只会被创建一次,会存在线程安全的问题。
单实例和单例设计模式的区分:简单理解,单例设计模式就是结扎,结果不可逆,单实例是使用了安全措施,可自己控制,在Spring及SpringMVC中均使用@Scope注解控制。
2、@RequestMapping注解
核心作用:为控制器方法提供外部访问的url路径。相当于Servlet中的urlpartten,使用@RequestMapping注解更加灵活多变。特点:
(1)开头的路径分割符 / 可以省略 (2)在一个控制器方法上映射多个路径 ,可看如上代码↑
(3)Controller类上加入@RequestMapping注解,比如
package com.jin;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
/**
* @author jinyunlong
* @date 2022/1/20 14:02
* @profession ICBC锅炉房保安
*/
@Controller
@RequestMapping("/user")
public class UserController {
@RequestMapping("/addUser")
public String addUser(){
return "/result.jsp";
}
@RequestMapping("/deleteUser")
public String deleteUser(){
return "/index.jsp";
}
}
好处是可以更好的按照功能,进行不同模块的区分,有利于项目的管理。
(4)@RequestMapping限定用户的请求方式 比如:
在注解后加入参数:method = RequestMethod.POST 那么请求方就只能以POST方式发起请求,使用其他方式直接会报405
(5)Http协议中其他的请求方式,比如put、delete等,这些在restful架构风格中使用较多,日常了解就完了,用到再看。
3、控制器方法参数
SpringMVC在控制器方法参数设计的过程中,非常灵活,可以支持多种参数的设置方式,非常强大,它也把这种设计,叫做数据绑定。说白了就是传参。以前Servlet时一般就是接request返response,SpringMVC中也差不多,比如:
package com.jin;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.servlet.http.HttpSession;
/**
* @author jinyunlong
* @date 2022/1/20 16:55
* @profession ICBC锅炉房保安
*/
@Controller
@RequestMapping("/methonParam")
public class MethodParamController {
@RequestMapping("/m1")
public String m1(HttpSession session){
System.out.println("session = " + session);
return "/result.jsp";
}
@RequestMapping("/m2")
public String m2(){
System.out.println("MethodParamController.m2");
return "/result.jsp";
}
}
两个例子,但都不好,一种和ServletAPI有耦合,另一种直接没有参数,以后还会详细记录传参这块。
4、视图解析器(页面跳转)
上述代码的返回值String都是"/result.jsp",很明显存在耦合,假如我不把result.jsp放在默认路径下了,加了一层jsp目录,这样返回页面自然就找不到了,这个时候就需要视图解析器ViewResolver来控制:
只需要加入一个封装好的bean对象即可,然后对文件类型后缀和路径前缀的值进行注入即可:
<!-- <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">-->
<!-- <property name="prefix" value="/"></property>-->
<!-- <property name="suffix" value=".jsp"></property>-->
<!-- </bean>-->
加入如上配置后,在Controller返回String值后他会自己做一个前后缀的拼接,所以只需要返回要返回页面对应的文件名即可。
使用注解开发的话只需要放到@Bean中自定义即可。
package com.jin;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
/**
* @author jinyunlong
* @date 2022/1/20 21:37
* @profession ICBC锅炉房保安
*/
@Configuration
public class AppConfig {
@Bean
public InternalResourceViewResolver viewResolver(){
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
viewResolver.setPrefix("/");
viewResolver.setSuffix(".jsp");
return viewResolver;
}
}
注意:
- AppConfig配置Bean应该放置到context:component-scan/ 扫描的路径下。
- 结合前面所讲,目前因为DispatcherServlet封装的Spring工厂(容器)只能读取xml,所以无法进行纯注解替换。
5、SpringMVC配置文件的默认设置
如果在web.xml中没有设置SpringMVC配置文件的的路径,系统会查找默认配置文件,如下servlet-name命名为dispatcherServlet,如果没设置配置文件路径,系统就会去找默认路径下是否有dispatcherServlet-servlet.xml这个文件,找不到的就会报错提示找不到这个文件。
<!--创建工厂-->
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!--指定SpringMVC配置文件的路径-->
<!-- <init-param>-->
<!-- <param-name>contextConfigLocation</param-name>-->
<!-- <param-value>classpath:dispatcher.xml</param-value>-->
<!-- </init-param>-->
<!--本Servlet会在tomcat启动的时候就会被创建-->
<load-on-startup>1</load-on-startup>
</servlet>
所以 SpringMVC默认配置文件的名字,放置位置是 /WEB-INF/[servlet-name]-servlet.xml