Spring笔记(三)(Spring整合web环境)

01、Spring整合web环境

1.1 Javaweb三大组件及环境特点

在Java语言范畴内,web层框架都是基于Javaweb基础组件完成的,所以有必要复习一下Javaweb组件的特点

组件作用特点
Servlet服务端小程序,负责接收客户端请求并作出响应的单例对象,默认第一次访问创建,可以通过配置指定服务器启动就创建,Servlet创建完毕会执行初始化init方法。每个Servlet有一个service方法,每次访问都会执行service方法,但是缺点是一个业务功能就需要配置一个Servlet
Filter过滤器,负责对客户端请求进行过滤操作的单例对象,服务器启动时就创建,对象创建完毕执行init方法,对客户端的请求进行过滤,符合要求的放行,不符合要求的直接响应客户端,执行过滤的核心方法doFilter
Listener监听器,负责对域对象的创建和属性变化进行监听的根据类型和作用不同,又可分为监听域对象创建销毁和域对象属性内容变化的, 根据监听的域不同,又可以分为监听Request域的,监听Session域的,监听ServletContext域的

1.2 Spring整合web环境的思路及实现

在进行Java开发时要遵循三层架构+MVC,Spring操作最核心的就是Spring容器,web层需要注入Service, service层需要注入Dao(Mapper),web层使用Servlet技术充当的话,需要在Servlet中获得Spring容器
具体操作如下:

  1. 在第二节笔记中的转账业务模块,修改pom.xml 文件的打包方式及导包:
<packaging>war</packaging>
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>4.0.1</version>
</dependency>
  1. 创建web层,servlet
@WebServlet(urlPatterns = "/accountServlet")
public class AccountServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // web层调用service层,获得accountService,现在accountService在Spring容器中
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AccountConfig.class);
        AccountService accountService = (AccountService) applicationContext.getBean("accountService");
        accountService.transferMoney("tom","lucy",500);
        System.out.println("转账操作成功");
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

    }
}
  1. 配置tomcat,部署项目,访问链接http://localhost:8080/accountServlet

测试事务

  • 模拟有异常

访问前:数据库tom:5000, lucy:5000
访问后:数据库tom:5000, lucy:5000
image.png

  • 无异常

访问前:数据库tom:5000, lucy:5000
访问后:数据库tom:4500, lucy:5500
image.png

上述方案的问题:web层代码如果都去编写创建AnnotationConfigApplicationContext的代码,
那么配置类重复被加载了, Spring容器也重复被创建了,不能每次想从容器中获得一个Bean都得先创建一次容器,这样肯定是不允许。
所以,我们现在的诉求很简单,如下:

  • ApplicationContext创建一次,配置类加载一次;
  • 最好web服务器启动时,就执行第1步操作,后续直接从容器中获取Bean使用即可;
  • ApplicationContext的引用需要在web层任何位置都可以获取到。

针对以上诉求我们给出解决思路,如下:

  • 在ServletContextListener的contextInitialized方法中执行ApplicationContext的创建。或在Servlet的init 方法中执行ApplicationContext的创建,并给Servlet的load-on-startup属性一个数字值,确保服务器启动Servlet就创建;
  • 将创建好的ApplicationContext存储到ServletContext域中,这样整个web层任何位置就都可以获取到了

实现:

  1. 在main目录下,新增webapp/WEB-INF/web.xml目录及文件
<?xml version="1.0" encoding="UTF8"?>
<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_3_1.xsd"
  version="3.1">
</web-app>
  1. 新建一个监听器ContextLoaderListener,用于创建applicationContext,并存入ServletContext域中
public class ContextLoaderListener implements ServletContextListener {
    @Override
    // 服务器启动,ServletContext创建,该方法被执行1次
    public void contextInitialized(ServletContextEvent sce) {
        System.out.println("ContextLoaderListener init ....");
        // 1. 创建spring容器
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
        // 2. 将容器存储到servletContext域中
        sce.getServletContext().setAttribute("applicationContext",applicationContext);
    }
}
  1. 修改AccountServlet类中applicationContext的获取方式,改为从ServletContext域中获取
package com.mem.web;

import com.mem.config.AccountConfig;
import com.mem.service.AccountService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.*;
import java.io.IOException;

@WebServlet(urlPatterns = "/accountServlet")
public class AccountServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // web层调用service层
        // 从ServletContext域中获取applicationContext对象
        ApplicationContext applicationContext =
        (ApplicationContext) request.getServletContext().getAttribute("applicationContext");
        AccountService accountService = (AccountService) applicationContext.getBean("accountService");
        accountService.transferMoney("tom","lucy",500);
        System.out.println("转账操作成功####");
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

    }
}
  1. 将新建的监听器配置到web.xml中
<!--配置Listener-->
<listener>
    <listener-class>com.mem.listener.ContextLoaderListener</listener-class>
</listener>
  1. 测试,结果同上

上述方案问题描述:
在ContextLoaderListener类中,配置文件的名称和容器的名称都写死了,不太友好;
解决方法:
利用常量进行解耦

改进:

  1. 在web.xml文件中,新增全局参数
<!--定义全局参数-->
<context-param>
  <param-name>contextConfigLocation</param-name>
  <param-value>classpath:applicationContext.xml</param-value>
</context-param>
  1. 修改ContextLoaderListener中的代码
public class ContextLoaderListener implements ServletContextListener {
    private String CONTEXT_CONFIG_LOCATION = "contextConfigLocation";
    @Override
    // 服务器启动,ServletContext创建,该方法被执行1次
    public void contextInitialized(ServletContextEvent sce) {
        System.out.println("ContextLoaderListener init ....");
        ServletContext servletContext = sce.getServletContext();
        // 0. 获取contextConfigLocation配置文件的名称
        String contextConfigLocation = servletContext.getInitParameter(CONTEXT_CONFIG_LOCATION);
        contextConfigLocation = contextConfigLocation.substring("classpath:".length());
        // 1. 创建spring容器
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext(contextConfigLocation);
        // 2. 将容器存储到servletContext域中
        servletContext.setAttribute("applicationContext",applicationContext);
    }
}
  1. 新建一个工具类WebApplicationContextUtils获取applicationContext对象
public class WebApplicationContextUtils {
    public static ApplicationContext getWebApplicationContext(ServletContext servletContext){
        ApplicationContext applicationContext = (ApplicationContext) servletContext.getAttribute("applicationContext");
        return applicationContext;
    }
}
  1. 修改AccountServlet类中applicationContext的获取方式
@WebServlet(urlPatterns = "/accountServlet")
public class AccountServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // web层调用service层,从getWebApplicationContext()方法中获取applicationContext对象
        ApplicationContext applicationContext = WebApplicationContextUtils.getWebApplicationContext(request.getServletContext());
        AccountService accountService = (AccountService) applicationContext.getBean("accountService");
        accountService.transferMoney("tom","lucy",500);
        System.out.println("转账操作成功####");
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

    }
}

1.3 Spring的web开发组件spring-web

到此,就将一开始的诉求都解决了,当然我们能想到的Spring 框架自然也会想到,Spring其实已经为我们定义 好了一个ContextLoaderListener,使用方式跟我们上面自己定义的大体一样,但是功能要比我们强百倍,所以 ,遵循Spring “拿来主义” 的精神,我们直接使用Spring提供的就可以了,开发如下:

  1. 先导入Spring-web的坐标:
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-web</artifactId>
  <version>5.3.9</version>
</dependency>
  1. 在web.xml中去配置ContextLoaderListener,并指定配置文件的位置
<!--自定义的Listener,用于创建applicationContext容器并存入到servletContext域中-->
<!--    <listener>-->
<!--        <listener-class>com.mem.listener.ContextLoaderListener</listener-class>-->
<!--    </listener>-->
<!--官方提供的-->
<listener>
  <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
  1. 在Servlet中直接使用,改成官方的WebApplicationContextUtils
import org.springframework.web.context.support.WebApplicationContextUtils;

@WebServlet(urlPatterns = "/accountServlet")
public class AccountServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // web层调用service层
        ApplicationContext applicationContext = WebApplicationContextUtils.getWebApplicationContext(request.getServletContext());
        AccountService accountService = (AccountService) applicationContext.getBean("accountService");
        accountService.transferMoney("tom","lucy",500);
        System.out.println("转账操作成功####");
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

    }
}
  1. 测试,结果同上,容器名:XmlWebApplicationContext

如果核心配置类使用的是注解形式的,那么Spring容器是AnnotationConfigWebApplicationContext,如下配 置方式

    <!--配置类的方式(注解)-->
    <context-param>
        <param-name>contextClass</param-name>
        <param-value>com.mem.web.MyAnnotationConfigWebApplicationContext</param-value>
    </context-param>

    <!--配置文件的方式-->
<!--    <context-param>-->
<!--        <param-name>contextConfigLocation</param-name>-->
<!--        <param-value>classpath:applicationContext.xml</param-value>-->
<!--    </context-param>-->

两个配置方式都打开,按配置类的方式创建容器,容器名是 MyAnnotationConfigWebApplicationContext
原因:在下面源码分析中,
determineContextClass方法里面,根据是否含有contextClass变量而决定的初始化哪个容器。
String contextClassName = servletContext.getInitParameter(“contextClass”);

public class MyAnnotationConfigWebApplicationContext extends AnnotationConfigWebApplicationContext {
    public MyAnnotationConfigWebApplicationContext(){
        super();
        // 注册配置类到容器
        this.register(AccountConfig.class);
    }
}

测试:结果同上,容器名为:MyAnnotationConfigWebApplicationContext

源码分析:
目的:如何决定是xml配置文件方式还是用配置类方式创建applicationContext
入口:org.springframework.web.context.ContextLoaderListener类的contextInitialized方法

public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
    public ContextLoaderListener() {
    }

    public ContextLoaderListener(WebApplicationContext context) {
        super(context);
    }

    public void contextInitialized(ServletContextEvent event) {
        // 调用父类中的initWebApplicationContext方法,所以查看ContextLoader类
        this.initWebApplicationContext(event.getServletContext());
    }

    public void contextDestroyed(ServletContextEvent event) {
        this.closeWebApplicationContext(event.getServletContext());
        ContextCleanupListener.cleanupAttributes(event.getServletContext());
    }
}
public class ContextLoader {
	public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
        // 调用本类的createWebApplicationContext方法
    	this.context = this.createWebApplicationContext(servletContext);
    }
    protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
        // 调用本类的determineContextClass方法,获取字节码对象,
        Class<?> contextClass = this.determineContextClass(sc);
        if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
            throw new ApplicationContextException("Custom context class [" + contextClass.getName() + "] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
        } else {
            return (ConfigurableWebApplicationContext)BeanUtils.instantiateClass(contextClass);
        }
    }
    protected Class<?> determineContextClass(ServletContext servletContext) {
        // 判断 web.xml 文件中有没有contextClass参数,
        // 如果有,则按配置类的方式(注解) 容器名:AnnotationConfigWebApplicationContext
        // 如果没有,则按配置文件的方式 容器名:XmlWebApplicationContext
        String contextClassName = servletContext.getInitParameter("contextClass");
        if (contextClassName != null) {
            return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());
        } else {
            contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());
            return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());
            
        }
    }
}

查看WebApplicationContext的实现类:
image.png

02、web层MVC框架思想与设计思路

Java程序员在开发一般都是MVC+三层架构,MVC是web开发模式,传统的Javaweb技术栈实现的MVC如下
image.png
原始Javaweb开发中,Servlet充当Controller的角色,Jsp充当View角色,JavaBean充当模型角色,后期Ajax异 步流行后,在加上现在前后端分离开发模式成熟后,View就被原始Html+Vue替代。原始Javaweb开发中, Service充当Controller有很多弊端,显而易见的有如下几个:
image.png

负责共有行为的Servlet称之为前端控制器,负责业务行为的JavaBean称之为控制器Controller
image.png
分析前端控制器基本功能如下:

  1. 具备可以映射到业务Bean的能力
  2. 具备可以解析请求参数、封装实体 等共有功能
  3. 具备响应视图及响应其他数据的功能

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/121106.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

详解FreeRTOS:系统时钟节拍(进阶篇—8)

在详解FreeRTOS:FreeRTOS时间片调度(进阶篇—7)中我们讲到了时钟节拍这个概念,本篇博文将详细讲解FreeRTOS系统时钟节拍相关知识。 不管是什么系统,运行都需要有个系统时钟节拍,xTickCount 就是FreeRTOS 的系统时钟节拍计数器。每个滴答定时器中断中 xTickCount 就会加一…

Go - 用户服务和Web服务

文章目录 go日志库之zap1. 安装和基本使用2. 写入日志文件 go的配置文件管理-viper1. 介绍2. yaml教程3. 安装4. 将配置文件映射成struct5. 使用 自定义验证器1. 手机号码正则表达式2. 自定义翻译的问题 JWT1. json web token是什么&#xff1f;2. 什么时候你应该用JSON Web To…

虚幻引擎:如何在工程里面添加插件

1.在自己的项目中安装插件 在content目录下创建一个Plugins的文件,将插件文件放进去即可 2.在软件上安装,这样所有创建的项目都会带有此插件 将插件放在自己软件的这个目录下就好了

mediapipe流水线分析 二

目标检测 Graph 一 流水线上游输入处理 1 TfLiteConverterCalculator 将输入的数据转换成tensorflow api 支持的Tensor TfLiteTensor 并初始化相关输入输出节点 &#xff0c;该类的业务主要通过 interpreter std::unique_ptrtflite::Interpreter interpreter_ nullptr; 实现…

【漏洞复现】Apache_Shiro_1.2.4_反序列化漏洞(CVE-2016-4437)

感谢互联网提供分享知识与智慧&#xff0c;在法治的社会里&#xff0c;请遵守有关法律法规 文章目录 1.1、漏洞描述1.2、漏洞等级1.3、影响版本1.4、漏洞复现1、基础环境2、漏洞分析3、漏洞验证 说明内容漏洞编号CVE-2016-4437漏洞名称Apache_Shiro_1.2.4_反序列化漏洞漏洞评级…

Xmake v2.8.5 发布,支持链接排序和单元测试

Xmake 是一个基于 Lua 的轻量级跨平台构建工具。 它非常的轻量&#xff0c;没有任何依赖&#xff0c;因为它内置了 Lua 运行时。 它使用 xmake.lua 维护项目构建&#xff0c;相比 makefile/CMakeLists.txt&#xff0c;配置语法更加简洁直观&#xff0c;对新手非常友好&#x…

linux下IO模及其特点及select

ftp实现 模拟FTP核心原理&#xff1a;客户端连接服务器后&#xff0c;向服务器发送一个文件。文件名可以通过参数指定&#xff0c;服务器端接收客户端传来的文件&#xff08;文件名随意&#xff09;&#xff0c;如果文件不存在自动创建文件&#xff0c;如果文件存在&#xff0c…

Discourse 如何在 header 上添加 HTML

虽然现在大部分网站都开始支持使用 CDN 的网站校验了。 但还有些网站在你需要他们提供服务的时候要求使用 header 的 meta 数据校验。 Discourse 是可以轻松的实现上面的功能的。 添加方法 选择你的 Discourse 网站下的自定义。 然后在左侧选择你需要添加的主题。 为了方便…

AD9371 官方例程 NO-OS 主函数 headless 梳理(一)

AD9371 系列快速入口 AD9371ZCU102 移植到 ZCU106 &#xff1a; AD9371 官方例程构建及单音信号收发 ad9371_tx_jesd -->util_ad9371_xcvr接口映射&#xff1a; AD9371 官方例程之 tx_jesd 与 xcvr接口映射 AD9371 官方例程 时钟间的关系与生成 &#xff1a; AD9371 官方…

【Java 进阶篇】JSTL 详解

Java JSTL&#xff08;JavaServer Pages Standard Tag Library&#xff09;是用于简化在 JSP 页面上的开发工作的 Java 标签库。它提供了在 JSP 页面上使用的标准标签&#xff0c;可以帮助开发人员更轻松地访问和操作数据&#xff0c;而无需编写大量的 Java 代码。Java JSTL 是…

工业相机基本知识理解:工业相机IO接口,功耗和供电方式

I-input 相机接收外部信号&#xff0c;可用于触发相机&#xff08;硬触发&#xff09;&#xff0c;也可用于定制不同的 功能&#xff0c;例如使用不同信号宽度来改变相机的曝光时间。主要用于现场设 备控制相机使用&#xff0c;常常配合各种传感器使用 O-output 相机输出信号&a…

antd-vue + vue3 实现a-table动态增减行,通过a-from实现a-table行内输入验证

一、效果图 图一&#xff1a;校验效果 二、主要代码 注意&#xff1a; 1、form 与 table 绑定的是同一个数据 tableSource 并且是一个数据&#xff08;ElementUI 需要 对象包数组&#xff09; 2、form用的是 name 绑定 -> :name"[index, vlan_id]" 3、form-i…

【分布式事务】深入探索 Seata 的四种分布式事务解决方案的原理,优缺点以及在微服务中的实现

文章目录 前言一、XA 模式1.1 XA 模式原理1.2 XA 模式的优缺点及应用场景1.3 Seata XA 模式在微服务中的实现 二、AT 模式2.1 Seata AT 模式原理2.2 AT 模式的脏写问题和写隔离3.3 AT 模式的优缺点3.4 Seata AT 模式在微服务中的实现 三、TCC 模式3.1 TCC 模式原理3.2 Seata 的…

内窥镜项目

★ 手持pad内窥镜项目 项目描述&#xff1a;3D电子内窥镜软件项目是一个基于BS&#xff08;浏览器服务器&#xff09;架构的项目&#xff0c;旨在实现对内窥镜设备的远程控制和高级功能操作。该项目允许操作员使用平板电脑手动触摸屏上的按钮、外部按键或脚踏板 来控制内窥镜设…

初阶JavaEE(15)(Cookie 和 Session、理解会话机制 (Session)、实现用户登录网页、上传文件网页、常用的代码片段)

接上次博客&#xff1a;初阶JavaEE&#xff08;14&#xff09;表白墙程序-CSDN博客 Cookie 和 Session 你还记得我们之前提到的Cookie吗&#xff1f; Cookie是HTTP请求header中的一个属性&#xff0c;是一种用于在浏览器和服务器之间持久存储数据的机制&#xff0c;允许网站…

【C++】类和对象(一):什么是面向对象,访问限定符有哪些,类定义细节,结构体和类的关系。

&#x1f490; &#x1f338; &#x1f337; &#x1f340; &#x1f339; &#x1f33b; &#x1f33a; &#x1f341; &#x1f343; &#x1f342; &#x1f33f; &#x1f344;&#x1f35d; &#x1f35b; &#x1f364; &#x1f4c3;个人主页 &#xff1a;阿然成长日记 …

机组 指令系统

机器指令 机器指令&#xff1a;每一条机器语言的语句 指令系统&#xff1a;全部机器指令的集合 指令的一般格式 指令由操作码和地址码两部分组成 操作码 作用&#xff1a;指明该指令要完成的操作 位数&#xff1a;反映机器的操作种类&#xff0c;即机器允许的指令条数 …

瞅瞅 Opencv:扫描图像

扫描图像查询表 一、概述二、图像矩阵如何存储在内存中?三、高效的方式四、迭代器(安全)方法五、带引用返回的动态地址计算六、核心功能七、性能差异 一、概述 让我们考虑一种简单的色彩还原方法。通过使用unsigned char C和c类型进行矩阵项存储&#xff0c;一个像素通道可以…

音视频技术开发周刊 | 318

每周一期&#xff0c;纵览音视频技术领域的干货。 新闻投稿&#xff1a;contributelivevideostack.com。 日程揭晓&#xff01;速览深圳站大会专题议程详解 LiveVideoStackCon 2023 音视频技术大会深圳站&#xff0c;保持着往届强大的讲师阵容以及高水准的演讲质量。两天的参会…

git commit规范提交

Git每次提交代码时&#xff0c;都要写Commit Message&#xff08;提交说明&#xff09;&#xff0c;通常情况下&#xff0c;Commit Message应该清晰明了&#xff0c;说明本次提交的目的和具体操作等。然而笔者工作多年来发现&#xff0c;有些公司对Commit Message没有明确的要求…