《Spring实战》-第五章:Web中的Spring(SpringMVC注解实现)

慢慢来比较快,虚心学技术

前言:前面我们学习了关于Spring核心的IOC和AOP知识,除此之外,以此为基础,Spring的MVC框架还经常被用于Web开发(SpringMVC)

一、什么是SpringMVC框架?

在了解SpringMVC之前,我们先回顾一下Spring基础架构:

Spring MVC 是Spring的一部分,基于模型 - 视图 - 控制器( Model-View-Controller , MVC )模式实现,它能够帮你构建像 Spring 框架那样灵活和松耦合的 Web 应用程序。在实际开发中,接收浏览器的请求响应,对数据进行处理,然后返回页面进行显示。

二、SpringMVC组成以及运行原理

Ⅰ、SpringMVC的组成

  1. DispatcherServlet:前端控制器 (SpringMVC的核心)-----相当于MVC中的C,作为中心调用其他组件,降低其他组件之间的耦合性
  2. HandlerMapping:处理器映射器 ----------------------------------根据用户请求找到对应路径的处理器(相当于处理器的名单)
  3. HandlAdapter:处理器适配器 --------------------------------------调用执行处理器方法(适配器模式的应用)
  4. Handler:处理器 --------------------------------------------------------处理用户请求的类,相当于传统意义上的Servlet
  5. ViewResolver:视图解析器 ------------------------------------------处理返回结果,将处理器适配器返回的数据模型转换成具体视图,并进行渲染输出(实际上就是将处理器返回的名称补充成具体的路径也就是一个视图,同时从数据模型中提取数据进行填充)
  6. View:视图 ---------------------------------------------------------------视图是数据最终需要展现给客户的地方,Spring支持多种类型的视图:jstlVies,freemarkerView等,最常用的是JSP和使用模板实现的html等

Ⅱ、SpringMVC请求响应流程

用户发起请求,携带请求信息到前端控制器进行调度

前端控制器(DispatcherServlet)调用处理器映射器,根据请求信息从处理器映射器中找到访问路径的目标处理器

前端控制器(DispatcherServlet)根据得到的目标处理器映射,调用处理器适配器方法(处理器适配器将处理器方法包装成适配器模式)

处理器适配器(HandlerAdapter)调用处理器(Handler)相应功能方法,并将结果返回给前端控制器

前端控制器(DispatcherServlet)根据得到的数据结果和目标视图名称,调用视图解析器(ViewResolver)返回目标视图完整路径

前端控制器(DispatcherServlet)根据得到的视图路径,对目标视图(view)进行渲染(数据填充等),得到目标视图

前端控制器(DispatcherServlet)将目标视图展现给用户

从上述流程可以看到,SpringMVC的功能流转是围绕前端控制器(DispatcherServlet)实现的,这样的好处是使得各个组件之间的耦合性大大降低,各个组件只做自己应该做的事情。其实这是大部分框架想要实现的目标。

分析DispatcherServlet,从Spring官网查看到的结构图如下:

从结构图可以看到,DispatcherServlet包含了两个Web应用上下文,用于独立控制,其中:

  • Servlet WebApplicationContext:管理用于网络请求的处理器适配器,视图解析器,处理器映射器和处理器
  • Root WebApplicationContext:管理基本的数据库操作类,业务逻辑类等Bean

通过两个应用上下文管理基本Bean和网络Bean,互不干扰,但是其中管理的Bean之间可以互相使用

三、SpringMVC的简单使用(注解方式)

首先我们应该先引入Spring对WebMvc的支持,maven引入如下:

<!--引入网络Servlet支持-->
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>3.1.0</version>
    <scope>provided</scope>
</dependency>
<!--引入SpringMVC-->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>${org.springframework.version}</version>
</dependency>

①创建DispatcherServlet类,继承并重载AbstractAnnotationConfigDispatcherServletInitializer类的三个方法:

//定义DispatcherServlet类名为WebAppInitializer
public class WebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    /**
     * 指定DispatcherServlet的基本Bean应用上下文配置类
     * @return
     */
    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class[]{RootConfig.class};
    }

    /**
     * 指定DispatcherServlet的网络类应用上下文配置类
     * @return
     */
    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class[]{WebConfig.class};
    }

    /**
     * 将DispatcherServlet映射到“/”,即应用内所有访问都会经过DispatcherServlet的处理
     * @return
     */
    @Override
    protected String[] getServletMappings() {
        logger.debug("DispatcherServlet获取匹配的前端控制器。。。。。。");
        return new String[]{"/"};
    }
}

②创建上述代码的两个配置类:RootConfig.javaWebConfig.java,其中,RootConfig只扫描除了WebConfig扫描范围外的基本类,而WebConfig只扫描基本的网络类,同时配置视图解析器和处理器映射器,并开启mvc配置

//定义WebConfig配置类
@Configuration
@ComponentScan(basePackages = {"com.my.spring.controller"})//WebConfig扫描包的范围
@EnableWebMvc /*<mvc:annotation-driven> 开启mvc配置*/
public class WebConfig extends WebMvcConfigurationSupport {

    /**
     * 定义一个视图解析器
     *
     * @return org.springframework.web.servlet.ViewResolver
     *
     * @author xxx 2019/3/5
     * @version 1.0
     **/
    @Bean
    public ViewResolver viewResolver(){
        //基本的视图解析器
        InternalResourceViewResolver resourceViewResolver = new InternalResourceViewResolver();
        //视图前缀,指向WEB-INF目录下的view目录,意思是所有的视图名称进入视图解析器的时候都会被加上前缀
        resourceViewResolver.setPrefix("/WEB-INF/view/");
         //视图后缀,此处指定后缀为jsp,意思是所有的视图名称进入视图解析器的时候都会被加上后缀,前缀+view名+后缀得到完整路径
        resourceViewResolver.setSuffix(".jsp");
         //可以在JSP页面中通过${}访问beans
        resourceViewResolver.setExposeContextBeansAsAttributes(true);
        return resourceViewResolver;
    }

    /**
     * 配置一个默认的处理器,实现父类接口,自动处理静态资源的映射
     * @param configurer
     */
    @Override
    protected void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
    }
}

//定义RootConfig配置类
@Configuration
//指定扫描范围,排除过滤掉使用了@EnableWebMvc注解扫描范围的bean,不进行扫描
@ComponentScan(basePackages ={"com.my.spring"},excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,value = {EnableWebMvc.class})})
public class RootConfig {
}

③编写基本的Bean类

@Data//lombok的注解,编译添加setter和getter方法
public class BaseBean {

    private Integer id;

    private String name;

}

④编写逻辑操作类(暂时没有用到数据库,所以只是模拟)

//定义基本Dao接口
public interface BaseRepository {
    /**
     * 根据id获取BaseBean
     * @param id 目标id
     * @return
     */
    BaseBean findOne(Integer id);
}

//定义基本Dao实现类,@Repository注解使用了@Component,可以被当作组件装配
@Repository
public class BaseRepositoryImpl implements BaseRepository {

    @Override
    public BaseBean findOne(Integer id) {
        if(id!=0){
            return null;
        }
        BaseBean baseBean = new BaseBean();
        baseBean.setId(0);
        baseBean.setName("测试bean");
        return baseBean;
    }
}

//定义基本Service接口
public interface BaseService {
     /**
     * 根据id获取BaseBean
     * @param id 目标id
     * @return
     */
    BaseBean findBean(Integer id);
}

//定义基本操作实现类,使用@Service注解,标明该类是一个service,该注解使用了@Comonnet注解,所以可被作为组件进行装配
@Service
public class BaseServiceImpl implements BaseService {
    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    //注入基本操作dao
    @Autowired
    private BaseRepository baseRepository;

    @Override
    public BaseBean findBean(Integer id) {
        return this.baseRepository.findOne(id);
    }
}

⑤创建处理类HomeController,使用@Controller注解标明当前类为一个处理类,同样使用@Component注解,可被装配

@Controller
public class HomeController {

    @Autowired
    private BaseService baseService;

    /**
     *使用@RequestMapping注解,将当前方法作为可访问路径,value值指定了访问路径,而method值指定了访问方式
     */
    @RequestMapping(method = RequestMethod.GET,value = "/home")
    public String home(){
        //返回试图名为home的视图
        return "home";
    }
}

看到上述代码,我们首先做最简单的测试,通过访问/home路径,访问具体的静态资源:

根据WebConfig中的视图解析器,我们在相应路径下创建:/WEB-INF/view/home.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Home Page</title>
</head>
<body>
    <h1>Hello World</h1>
</body>
</html>

路径如下:

启动项目,页面访问如下:本项目名为SpringAction05

从访问结果可以看到,访问被转向了home.jsp,而我们在controller方法中只是return了一个home,也就是说,视图解析器为我们补充了完整的路径并将视图返回给浏览器

注:

AbstractAnnotationConfigDispatcherServletInitializer 会同时创建 DispatcherServletContextLoaderListener

GetServlet-ConfigClasses() 方法返回的带有 @Configuration 注解的类将会用来定义 DispatcherServlet 应用上下文中的 bean 。

getRootConfigClasses() 方法返回的带有 @Configuration 注解的类将会用来配置 ContextLoaderListener 创建的应用上下文中的 bean 。

四、信息交互

Ⅰ、传递模型数据到视图中

有时候我们并不只是需要对访问进行转发,同时可能需要携带一些信息给浏览器端,SpringMVC提供了Model类对返回信息进行封装返回:

下面我们在上述controller方法home()返回之前封装信息返回给视图,并从视图中获取到对应的数据:

@Controller
public class HomeController {

    @Autowired
    private BaseService baseService;

    @RequestMapping(method = RequestMethod.GET,value = "/home")
    public String home(Model model){

        //往model中放置信息(key,value)
        model.addAttribute("Message","I am HomePage!!!");

        //返回试图名为home的视图
        return "home";
    }
}

修改home.jsp获取目标数据,此处我们使用JSTL标签库获取,所以需要先引入JSTL标签库的jar包:

<!-- jstl -->
<dependency>
    <groupId>jstl</groupId>
    <artifactId>jstl</artifactId>
    <version>1.2</version>
</dependency>

home.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %><!--引入JSTL标签库,并以c为标签前缀-->
<%@ page isELIgnored="false" %><!--禁用tomcat自带的EL表达式,否则无法获取对应的数据-->
<html>
<head>
    <title>Home Page</title>
</head>
<body>
    <h1>Hello World</h1>
    <!--通过EL表达式获取key为Message的属性值-->
    <c:out value="${Message}"></c:out>
</body>
</html>

测试结果如下:

Ⅱ、接受请求的输入

Spring MVC 允许以多种方式将客户端中的数据传送到控制器的处理器方法中,包括:

  • 查询参数( Query Parameter )。
  • 路径变量( Path Variable )
  • 表单参数( Form Parameter )。

①查询参数形式

我们在controller中编写一个getBean方法,要求接收一个参数,参数名为id,通过拿到的id进行查询,将查询到的基本bean封装到数据模型并返回给视图

@RequestMapping(method = RequestMethod.GET,value = "/getBean")
public String getBeanByParam(@RequestParam("id")Integer beanId, Model model){

    BaseBean bean = this.baseService.findBean(beanId);
    //以bean为key将目标对象封装到数据模型
    model.addAttribute("bean",bean);

    //返回试图名为home的视图
    return "showMessage";
}

然后创建一个showMessage.jsp作为目标视图

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ page isELIgnored="false" %>
<html>
<head>
    <title>信息主页</title>
</head>
<body>
    <!--判空-->
    <c:if test="${bean==null}">
        <li>bean不存在</li>
    </c:if>
    <c:out value="${bean.id}"></c:out><!--提取目标对象的信息-->
    <c:out value="${bean.name}"></c:out>
</body>
</html>

浏览器访问路径如下:http://locahost:8080/SpringAction05/getBean?id=0

注:其实getBeanByParam(@RequestParam("id")Integer id, Model model)方法中的@RequestParam("id")可以省略不写,如果不写的话,那么访问参数必须与参数位的名称一致,即:http://locahost:8080/SpringAction05/getBean?beanId=0

②表单参数形式

将查询参数通过form表单提交到前端控制器,此时,Spring允许通过对象接收查询参数,要求form表单提交的字段与对象属性字段对应

首先创建一个form表单,表单提交到/getBean

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<body>
<h2>Hello World!</h2>
<form action="./getBean" method="post">
    提交id:<input name="id" type="text"/><br>
    <button type="submit">提交</button>
</form>
</body>
</html>

Contorller接收参数(以对象形式):

@RequestMapping(method = RequestMethod.POST,value = "/getBean")
public String getBeanByForm(BaseBean baseBean, Model model){

    BaseBean bean = this.baseService.findBean(baseBean.getId());

    model.addAttribute("bean",bean);

    //返回试图名为home的视图
    return "showMessage";
}

浏览器访问结果如下:


③路径变量方式

SpringMVC可以通过@PathVariable从路径中提取参数变量:

@RequestMapping(method = RequestMethod.GET,value = "/getBean/{id}")//指定getBean/后的参数位id参数占位
public String getBeanByPath(@PathVariable("id") Integer id, Model model){//通过@PathVariable注解提取变量
    BaseBean bean = this.baseService.findBean(id);

    model.addAttribute("bean",bean);

    //返回试图名为home的视图
    return "showMessage";
}

访问结果如下:

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,590评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,808评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,151评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,779评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,773评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,656评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,022评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,678评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,038评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,756评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,411评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,005评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,973评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,053评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,495评论 2 343

推荐阅读更多精彩内容