Spring MVC 学习笔记

Spring MVC IDEA 框架搭建:https://www.cnblogs.com/zuti666/p/13987082.html
个人笔记 做个记录

前置知识

具体可以看:https://www.w3cschool.cn/wkspring/xypt1icg.html

Spring 概述

Spring 的优良特性

  • 非侵入式:基于Spring开发的应用中的对象可以不依赖于Spring的API
  • 控制反转:IOC——Inversion of Control,指的是将对象的创建权交给 Spring 去创建。使用 Spring 之前,对象的创建都是由我们自己在代码中new创建。而使用 Spring 之后。对象的创建都是给了 Spring 框架。
  • 依赖注入:DI——Dependency Injection,是指依赖的对象不需要手动调用 setXX 方法去设置,而是通过配置赋值。
  • 面向切面编程:Aspect Oriented Programming——AOP
  • 容器:Spring 是一个容器,因为它包含并且管理应用对象的生命周期
  • 组件化:Spring 实现了使用简单的组件配置组合成一个复杂的应用。在 Spring 中可以使用XML和Java注解组合这些对象。
  • 一站式:在 IOC 和 AOP 的基础上可以整合各种企业应用的开源框架和优秀的第三方类库(实际上 Spring 自身也提供了表现层的 SpringMVC 和持久层的 Spring JDBC)

IOC容器

IOC容器即控制反转。Spring IOC 将类的初始化与配置 和 使用拆分开来,Spring IOC 将组件自动进行初始化,并且都存放在 IOC 容器中,而这些初始化好的就叫做 Bean,当我们需要使用的时候进行注入就可以了。

具体可查看:https://www.liaoxuefeng.com/wiki/1252599548343744/1282381977747489

Bean

被称作 bean 的对象是构成应用程序的支柱也是由 Spring IoC 容器管理的。bean 是一个被实例化,组装,并通过 Spring IoC 容器所管理的对象

可参考树哥:https://www.yuque.com/u92750/bpdenk/tbs0o9

Bean 与容器的关系

image-20211221062051425

AOP

看了看代码,说白了就是动态代理

https://www.liaoxuefeng.com/wiki/1252599548343744/1310052352786466

用到再说吧

IDEA 环境搭建

image-20211219154247105

image-20211219154055586

pom 文件中的 properties 中添加 <spring.version>4.3.6.RELEASE</spring.version>

image-20211219154125979

添加 spring 核心包

 <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context-support</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-expression</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-orm</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
            <version>3.2.13.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>3.2.13.RELEASE</version>
        </dependency>

创建 webapp 文件夹

image-20211219164028610

路径对上就行了

image-20211219170551307

开始

SpringMVC 感觉和 Servlet 差不多,都是要配置 servlet ,不同的是 servlet 中配置都是在 web.xml 中的,SpringMVC都是在springmvc-servlet.xml 里面的

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<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">
    <display-name>springMVC</display-name>
    <welcome-file-list>
        <welcome-file>/index</welcome-file>
    </welcome-file-list>

    <servlet>
        <servlet-name>springmvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>

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

</web-app>

存放在 /WEB-INF/ 下的文件不会被直接请求到,所以要把jsp放在 WEB-INF 下面

通常路径为 webapp/WEB-INF

第一个 SpringMVC

视图解析

视图解析之后controller就可以直接String返回了

    <bean id="viewResolver"
          class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <!--前缀 -->
        <property name="prefix" value="/WEB-INF/jsp/" />
        <!--后缀 -->
        <property name="suffix" value=".jsp" />
    </bean>
/**
 * Controller 层中调用 Services 层中的业务代码来进行处理
 * Controller 主要是流程的控制,具体业务实现细节都在 services
 */
@Controller
@RequestMapping("/user")
public class UserController {

    @Autowired
    private UserServices userServices;

    @RequestMapping("/login")
    public String login(User user, Model model, HttpSession session){
        if (userServices.login(user)) {
            // 登录成功,将用户信息保存到session对象中
            session.setAttribute("user", user);
            // 重定向到主页面的跳转方法
            return "redirect:index";
        }
        System.out.println(2);
        model.addAttribute("msg", "用户名或密码错误,请重新登录! ");
        return "login";
    }

    @RequestMapping("/index")
    public String toIndex(){
        return "index";
    }
}

Spring MVC 执行流程

image-20211220082144545

主要有以下这些组件:

DispatcherServlet :Spring MVC 的所有请求都要经过 DispatcherServlet 来统一分发。DispatcherServlet 相当于一个转发器或中央处理器,控制整个流程的执行,对各个组件进行统一调度,以降低组件之间的耦合性,有利于组件之间的拓展。

HandlerMapping:处理器映射器,其作用是根据请求的 URL 路径,通过注解或者 XML 配置,寻找匹配的处理器(Handler)信息。

HandlerAdapter:处理器适配器,其作用是根据映射器找到的处理器(Handler)信息,按照特定规则执行相关的处理器(Handler)

Handler:处理器,和 Java Servlet 扮演的角色一致。其作用是执行相关的请求处理逻辑,并返回相应的数据和视图信息,将其封装至 ModelAndView 对象中

ViewResolver:将收到的请求解析到对应的视图

单看这张图确实看懂了,但是针对源码中的一些细节其实也可以再看一下,接下来去查看每个模块的源码部分

DispatcherServet

initStrategies 中会进行初始化,请求响应这些都在 ApplicationContext 中

image-20211220084219852

DispatcherServet#doDispatch 为主要函数 ,传入参数为 request、response

image-20211220084824967

HandlerMapping

首先会调用 this.getHandler 来获取 HandlerMapping,通过迭代器的方式来遍历 handlerMappings ,然后根据传入的请求返回对应的 HandlerExecutionChain (Handler 执行链)

private List<HandlerMapping> handlerMappings;

protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
        Iterator i$ = this.handlerMappings.iterator();

        HandlerExecutionChain handler;
        do {
            if (!i$.hasNext()) {
                return null;
            }

            HandlerMapping hm = (HandlerMapping)i$.next();
            if (this.logger.isTraceEnabled()) {
                this.logger.trace("Testing handler map [" + hm + "] in DispatcherServlet with name '" + this.getServletName() + "'");
            }

            handler = hm.getHandler(request);
        } while(handler == null);

        return handler;
    }

HandlerAdapter

然后会通过调用 getHandlerAdapter 来获取 HandlerExecutionChain 中的 HandlerAdapter

    protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
        Iterator i$ = this.handlerAdapters.iterator();

        HandlerAdapter ha;
        do {
            if (!i$.hasNext()) {
                throw new ServletException("No adapter for handler [" + handler + "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
            }

            ha = (HandlerAdapter)i$.next();
            if (this.logger.isTraceEnabled()) {
                this.logger.trace("Testing handler adapter [" + ha + "]");
            }
        } while(!ha.supports(handler));

        return ha;
    }

获取到 HandlerAdapter 之后会调用 HandlerAdapter#handle ,注意 handle 方法的传参类型为 (HttpServletRequest request, HttpServletResponse response, Object handler),最后一个 handler 从 HandlerExecutionChain 中进行获取

来找到对应的 Controller 进行后续处理

mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

    public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        return ((Controller)handler).handleRequest(request, response);
    }

接下来就是视图那块就不跟了...

所以捋一下思路:Spring 中核心调度在 DispatcherServlet,然后 DispatcherServlet#doDispatch 是核心方法,当收到请求之后首先会从 handlermapping 中找到该请求对应的 HandlerExecutionChain(说人话就是返回一条链,其实理解成 handler Plus 比较好),然后 handlerAdapter调用适配器中的 handle 方法来执行处理器 handler

请求 => DispatcherServlet#doDispatch => HandlerExecutionChain(Handler) => handlerAdapter#handle => Handler(Controller)=> 返回视图 .....

注解

Controller

最常用的就是 Controller 注解,来对请求进行处理 ,和 SpringBoot 类似

@Controller
@RequestMapping("/user")
public class UserController {
    @RequestMapping("/info")
    public ModelAndView UserInfo(){
        return new ModelAndView("/WEB-INF/jsp/info.jsp");
    }
}

但是在 MVC 中我们需要在 springmvc-servlet.xml 中添加如下代码,这样就代表 controller 中所有的注解都会被扫描

    <context:component-scan base-package="com.example.mvc01.controller" />

RequestMapping

该注解可以定义路由,即将请求映射到对应的处理器上,同时可以用 params 来指定参数

@Controller
@RequestMapping("/user")
public class UserController {
    @RequestMapping(value = "/info",params = "id")
    public ModelAndView UserInfo(){
        return new ModelAndView("/WEB-INF/jsp/info.jsp");
    }
}

@RequestMapping(value = "/info",params = "id") // 指定参数
@RequestMapping(value = "toUser",headers = "Referer=http://www.xxx.com")  // 制定 referer

当然还有很多注解,用到了再去看就行了

参数传递

参数传递有很多方法,具体可以看:http://c.biancheng.net/spring_mvc/pass-param.html

我就调几种我有可能会常用的来记录一下

PathVariable 注解来获取 path 中的参数值

Url: http://localhost/user/info/admin

@Controller
@RequestMapping("/user")
public class UserController {
    @RequestMapping(value = "/info/{name}")
    public ModelAndView UserInfo(@PathVariable String name){
        if ("admin".equals(name)){
            return new ModelAndView("/WEB-INF/jsp/info.jsp");
        }else {
            return new ModelAndView("/WEB-INF/jsp/login.jsp");
        }
    }
}

RequestParam 注解来获取请求中的参数值

Url:http://localhost:8080/user/info?name=admin

@Controller
@RequestMapping("/user")
public class UserController {
    @RequestMapping(value = "/info")
    public ModelAndView UserInfo(@RequestParam String name){
        if ("admin".equals(name)){
            return new ModelAndView("/WEB-INF/jsp/info.jsp");
        }else {
            return new ModelAndView("/WEB-INF/jsp/login.jsp");
        }
    }
}

利用 java bean 来接受,但是需要确保对一点传递过来的参数和 bean 的属性是一致的

@RequestMapping("/login")
public String login(User user, Model model, HttpSession session){
    if (userServices.login(user)) {  => user.getUsername()/user.getPassword()
        // 登录成功,将用户信息保存到session对象中
        session.setAttribute("user", user);
        // 重定向到主页面的跳转方法
        return "redirect:index";
    }
    System.out.println(2);
    model.addAttribute("msg", "用户名或密码错误,请重新登录! ");
    return "login";
}
// username==username , password==password
public class User {
    private String username;
    private String password;
}

<form  action="${pageContext.request.contextPath}/user/login" method="POST">
    用户名:<input type="text" name="username" /><br>
    密码:<input type="password" name="password" /><br>
    <input type="submit" value="登录" />
</form>

还有就是一种利用类来接受的,这个的话暂时不知道咋写,后面应该会遇到

重定向和转发

转发:

这里转发不是之前那样的视图转发,其实就是 url 302 跳转,所以这里要输入的是路径,就是能正常访问到的路径 而不是 web-inf 那个

return new ModelAndView("redirect:/login"); // 这里就不是资源了 就是url那边请求的路径了

转发到请求方法

return "forward:/index/isLogin";

在 Spring MVC 框架中,不管是重定向或转发,都需要符合视图解析器的配置,如果直接转发到一个不需要 DispatcherServlet 的资源,例如:

return "forward:/html/my.html";

则需要使用 mvc:resources 配置:

Autowired & Service 注解

关于编写代码中层级的问题:https://blog.csdn.net/zdwzzu2006/article/details/6053006

前面了解过 ioc 所以相对清晰点,ioc 帮我们省去了配置和初始化的步骤,所有初始化好的对象都在 ioc 容器里面,那么我们如果某些地方需要引入对象的话 我们就需要利用一些注解来注入进来

注入进来的都是类似数据库连接的 connection 一样

@Autowired:可以对类成员变量、方法及构造函数进行标注,完成自动装配的工作

@Service:会将被标注的类注册到 ioc 容器中

按照教程写了一下代码大致清楚了开发的一个流程:

entity:User

services:UserService(接口)、UserServicesImpl(接口实现类) => 业务代码

controller:UserController => 主要是流程的控制,具体业务逻辑都是 services

Services 层

public interface UserServices {
    /**
     * 注册登录的业务逻辑
     */
    boolean register(User user);
    boolean login(User user);
}
@Service // 记得添加注解,否则spring扫描不到
public class UserServicesImpl implements UserServices{

    @Override
    public boolean register(User user) {
        return "admin".equals(user.getUsername()) && "admin".equals(user.getPassword());
    }

    @Override
    public boolean login(User user) {
        return "admin".equals(user.getUsername()) && "admin".equals(user.getPassword());
    }
}

Entity 层

public class User {
    private String username;
    private String password;

    public String getUsername() {
        return username;
    }

    public String getPassword() {
        return password;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}

Controller 层

@Controller
@RequestMapping("/user")
public class UserController {

    @Autowired
    private UserServices userServices;

    @RequestMapping("/login")
    public String isLogin(Model model){
        User user = new User();
        user.setUsername("admin");
        user.setPassword("admin");
        userServices.login(user);
        model.addAttribute("user", user);  // 添加进去了 所以前端模版可以找到
        return "login";
    }

    @RequestMapping("/register")
    public String isRegister(Model model){
        User user = new User();
        user.setUsername("admin");
        user.setPassword("admin");
        userServices.register(user);
        model.addAttribute("user", user);
        return "register";
    }
}

这样前端就可以通过 ${user.username} 来直接获取到了 ,其实和 pagecontext 那些是一个原理

@Model Attribute

@Controller
public class ModelAttributeController {
    // 方法有返回值
    @ModelAttribute("name")
    public String myModel(@RequestParam(required = false) String name) {
        return name;
    }
    @RequestMapping(value = "/model")
    public String model() {
        return "index";
    }
}
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>编程帮</title>
</head>
<body>
    ${string }
</body>
</html>

类型转换器

配置转换器

<mvc:annotation-driven conversion-service="conversionService" />
<!--注册类型转换器UserConverter -->
<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
    <property name="converters">
        <list>
            <bean class="net.biancheng.converter.UserConverter" />
        </list>
    </property>
</bean>

就是对输入进行转换一下

@Component
public class UserConverter implements Converter<String, User> {
    @Override
    public User convert(String source) {
        // 创建User实例
        User user = new User();
        // 以“,”分隔
        String stringvalues[] = source.split(",");
        if (stringvalues != null && stringvalues.length == 3) {
            // 为user实例赋值
            user.setName(stringvalues[0]);
            user.setAge(Integer.parseInt(stringvalues[1]));
            user.setHeight(Double.parseDouble(stringvalues[2]));
            return user;
        } else {
            throw new IllegalArgumentException(String.format("类型转换失败, 需要格式'编程帮, 18,1.85',但格式是[% s ] ", source));
        }
    }
}

image-20211220152247623

格式化

标签化

Json交互

http://c.biancheng.net/spring_mvc/json.html (用库)

拦截器 Interceptor

拦截器都要实现 HandlerInterceptor 接口,其中主要方法都写在 preHandle 中

public class LoginInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {
        // 主要写在这里
        String url = httpServletRequest.getRequestURI();
        if (url.contains("/user/login")){ // /user/login/..;/index  /..;/ 相当于bypass字符串检测然后跳到前面
            return true;
        }
        HttpSession httpSession = httpServletRequest.getSession();
        Object obj = httpSession.getAttribute("user");
        if (obj != null){
            return true;
        }

        httpServletRequest.setAttribute("msg", "还没登录,请先登录!");
        httpServletRequest.getRequestDispatcher("/WEB-INF/jsp/login.jsp").forward(httpServletRequest,httpServletResponse);
        return false;
    }

    @Override
    public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {

    }

    @Override
    public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {

    }
}

这样 if (url.contains("/user/login")) 写有漏洞,会导致权限绕过 /..;/,/..;/..;/ 就是跳两层

image-20211220163055663

控制器需要在 springmvc-servlet.xml 进行注册

<mvc:interceptors>
    <mvc:interceptor>
        <!-- 配置拦截器作用的路径 -->
        <mvc:mapping path="/**" />
        <bean class="com.example.mvc01.interceptor.LoginInterceptor" />
    </mvc:interceptor>
</mvc:interceptors>

数据校验

其实就是一些注解,然后能在上面加一点正则

public class User {
    @NotNull(message = "用户id不能为空")
    private Integer id;
    @NotNull
    @Length(min = 2, max = 8, message = "用户名不能少于2位大于8位")
    private String name;
    @Email(regexp = "[a-zA-Z0-9]+@[a-zA-Z0-9]+\\.[a-zA-Z0-9]", message = "邮箱格式不正确")
    private String email;
    /** 省略setter和getter方法*/
}

大致如下:

名称 说明
@Null 被标注的元素必须为 null
@NotNull 被标注的元素必须不为 null
@AssertTrue 被标注的元素必须为 true
@AssertFalse 被标注的元素必须为 false
@Min(value) 被标注的元素必须是一个数字,其值必须大于等于指定的最小值
@Max(value) 被标注的元素必须是一个数字,其值必须小于等于指定的最大值
@DecimalMax(value) 被标注的元素必须是一个数字,其值必须大于等于指定的最大值
@DecimalMin(value) 被标注的元素必须是一个数字,其值必须小于等于指定的最小值
@size 被标注的元素的大小必须在指定的范围内
@Digits(integer,fraction) 被标注的元素必须是一个数字,其值必须在可接受的范围内;integer 指定整数精度,fraction 指定小数精度
@Past 被标注的元素必须是一个过去的日期
@Future 被标注的元素必须是一个将来的日期
@Pattern(value) 被标注的元素必须符合指定的正则表达式

异常处理

@ExceptionHandler

局部异常处理仅能处理指定 Controller 中的异常。

示例 1:下面使用 @ExceptionHandler 注解实现。定义一个处理过程中可能会存在异常情况的 testExceptionHandle 方法。

@RequestMapping("/testExceptionHandle")
public String testExceptionHandle(@RequestParam("i") Integer i) {
    System.out.println(10 / i);
    return "success";
}

显然,当 i=0 时会产生算术运算异常。

下面在同一个类中定义处理异常的方法。

@ExceptionHandler({ ArithmeticException.class })
public String testArithmeticException(Exception e) {
    System.out.println("打印错误信息 ===> ArithmeticException:" + e);
    // 跳转到指定页面
    return "error";
}

REST 风格

之前看过 restful 这块,其实就是遵循了规范的api接口,也没啥好说的

@Controller
public class UserController {
    @RequestMapping("/torest")
    public String torest() {
        return "rest";
    }
    @RequestMapping(value = "/user/{id}", method = RequestMethod.GET)
    public String hello(@PathVariable Integer id) {
        System.out.println("test rest get:" + id);
        return "success";
    }
    @RequestMapping(value = "/user/{id}", method = RequestMethod.POST)
    public String hello() {
        System.out.println("test POST:");
        return "success";
    }
    @RequestMapping(value = "/user/{id}", method = RequestMethod.DELETE)
    public String helloDelete(@PathVariable Integer id) {
        System.out.println("test rest delete:" + id);
        return "success";
    }
    @RequestMapping(value = "/user/{id}", method = RequestMethod.PUT)
    public String helloPUt(@PathVariable Integer id) {
        System.out.println("test rest put:" + id);
        return "success";
    }
}

参考c语言中文网教程学习

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇