14_SpringMVC

文章目录

  • MVC
  • SpringMVC与JavaEE对比
  • SpringMVC
    • SpringMVC的核心流程
    • SpringMVC入门案例
    • `@RequestMapping`注解的使用
    • Handler方法的返回值
    • Handler方法的形参
      • `key=value`形式的请求参数
      • Json请求参数
    • RESTful风格接口
    • 静态资源处理
    • Filter
    • HandlerInterceptor
    • 异常处理
    • SpringMVC核心流程
      • 流程图

MVC

  • MVC设计模式的任务是将包含业务数据的模块与显示模块的视图解耦
  • SpringMVC是在Spring框架的基础上做的

SpringMVC与JavaEE对比

在这里插入图片描述

  • SpringMVC是通过一个Servlet(DispatcherServlet)来接收全部请求,然后分发到不同的方法上

SpringMVC

SpringMVC的核心流程

在这里插入图片描述


SpringMVC入门案例

  1. 导入依赖
<dependencies>
    <!--spring的核心包 + spring-web 、 spring-webmvc-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
        <version>5.2.15.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>javax.servlet-api</artifactId>
        <version>3.1.0</version>
        <scope>provided</scope>
    </dependency>
</dependencies>
  1. 启动项配置
    • AACDSI → 抽象类 AbstractAnnotationConfigDispatcherServletInitializer
      • 类似于SpringMVC的启动类
      • onStartup方法,里面做了两次ac = new AnnotationConfigWebApplicationContext
    • 抽象类的特点是:如果里面有抽象方法我们需要是实现其抽象方法
    • 在抽象类的子类中去写这两个抽象方法的实现 → 提供自定义的配置类
    • 示意图如下:

在这里插入图片描述

eg:

  • config目录下
    • ApplicationInitialization.java
// AACDSI == AbstractAnnotationConfigDispatcherServletInitializer

/**
 * 启动类的配置
 */
public class ApplicationInitialization
        extends AbstractAnnotationConfigDispatcherServletInitializer {

    /**
     * AnnotationConfigWebApplicationContext 对比 ApplicationContext 多了对web应用的支持
     *
     * 创建ApplicationContext的时候提供配置类
     * ApplicationContext ac1 = new AnnotationConfigWebApplicationContext(clazz);
     *
     * 创建ApplicationContext的时候提供配置类
     * ApplicationContext ac2 = new AnnotationConfigWebApplicationContext(clazz);
     *
     * 应用程序中有两个ApplicationContext?
     * 两个ac之间存在着包含关系
     *
     */

    /**
     * 内部的ApplicationContext的配置类
     * @return
     */
    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class[]{RootConfiguration.class};
    }

    /**
     * 外部的ApplicationContext的配置类
     * @return
     */
    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class[]{WebConfiguration.class};
    }

    /**
     * 配置DispatcherServlet的ServletMapping = /
     * @return
     */
    @Override
    protected String[] getServletMappings() {
        return new String[]{"/"};
    }

}
  • RootConfiguration.java
/**
 * 在容器中排除掉controller组件
 */

/**
 * 这里可以整合Mybatis、增加事务等
 */
@Configuration
@ComponentScan(value = "com.coo1heisenberg.demo1",
        // excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, value = Controller.class))
        // excludeFilters = @ComponentScan.Filter(value = Controller.class))
        excludeFilters = @ComponentScan.Filter(value =
                {Controller.class, EnableWebMvc.class}))
public class RootConfiguration {
}
  • WebConfiguration.java
// @Configuration

@EnableWebMvc // 功能有:@Configuration + 使用SpringMVC的相关配置
@ComponentScan("com.coo1heisenberg.demo1.controller")
// WebMvcConfigurer接口:后面还会做一些和SpringMVC相关的配置 
// 需要实现接口 WebMvcConfigurer,如果实现接口里的方法就是提供一些额外的配置信息
public class WebConfiguration implements WebMvcConfigurer {
}
  1. SpringMVC的使用
    • Controller目录下

eg:

@Controller
public class UserController {
    public UserController() {
        System.out.println("UserController 组件的初始化");
    }

    @SneakyThrows
    @RequestMapping("/hello")
    public void hello(HttpServletRequest request, HttpServletResponse response) {
        BaseRespVo vo = BaseRespVo.ok("hello world");
        ObjectMapper objectMapper = new ObjectMapper();
        String jsonStr = objectMapper.writeValueAsString(vo);
        response.getWriter().println(jsonStr);
    }

    // Handler -> HandlerMethod -> Handler方法
    @RequestMapping("hello2")
    @ResponseBody // SpringMVC帮我们使用Jackson,把我们的返回值转换成JSON字符串进行响应
    public BaseRespVo hello2() {
        return BaseRespVo.ok("hello world2");
    }
}


@RequestMapping注解的使用

  • 通过@RequestMapping注解的不同属性,实现不同功能上的限定
    • 请求URL限定
    • 窄化请求
      • 窄化请求:将@RequestMapping写在类上
      • 该Controller组件中的方法映射的URL:类上的@RequestMapping的value属性值 + 方法上的@RequestMapping的value属性值
    • 请求方法限定
    • 请求参数限定
    • 请求头限定
  1. 注解的定义
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping
public @interface RequestMapping {
    String name() default ""; // 没啥用

    @AliasFor("path")
    String[] value() default {};
    @AliasFor("value")
    String[] path() default {}; // value属性:映射URL是什么,可以映射多个值,还可以写通配符
    /**
    eg:@RequestMapping("hello/*", "hello*");
    */

    RequestMethod[] method() default {}; // 请求方法限定 GET/POST

    String[] params() default {}; // 请求参数限定 ?后面的

    String[] headers() default {}; // 请求体限定 携带的token等等

    String[] consumes() default {}; // 和请求头有关,对应的content-type
    // content-type指请求体里正文的类型

    String[] produces() default {}; // 和请求头有关,对应的accept
    // accept指服务端响应报文响应体里正文的格式
}

  1. value属性的使用
/**
 * value属性的使用
 */
@Controller
@RequestMapping("user")
public class UserController {
    /**
     * localhost:8080/use/hello/111
     * localhost:8080/use/helloXXX
     * @return
     */
    @RequestMapping(value = {"hello/*", "hello*"})
    @ResponseBody
    public BaseRespVo hello() {
        return BaseRespVo.ok("hello aaaaa");
    }
}

  1. 请求方法限定
    • 引申注解:@GetMapping@PostMapping
      • 这两个注解就是限定了请求方法的@RequestMapping
@RequestMapping(
    method = {RequestMethod.GET}
)
public @interface GetMapping {}


@RequestMapping(
    method = {RequestMethod.POST}
)
public @interface PostMapping {}

eg:

/**
 * method属性:请求方法限定
 */
@Controller
@RequestMapping("method")
public class MethodLimitController {

    /**
     * localhost:8080/demo2/method/get
     *
     * @return
     */

    @ResponseBody
    // @RequestMapping(value = "get", method = RequestMethod.GET)
    @GetMapping("get") // 与上面等同
    public BaseRespVo methodGet() {
        return BaseRespVo.ok();
    }

    @ResponseBody
    //@RequestMapping(value = "post", method = RequestMethod.POST)
    @PostMapping("post") // 与上面等同
    public BaseRespVo methodPost() {
        return BaseRespVo.ok();
    }

    /**
     * 多个值之间的关系是or的关系
     * @return
     */
    @ResponseBody
    @RequestMapping(value = "getorpost", method = {RequestMethod.GET, RequestMethod.POST})
    public BaseRespVo methodGetOrPost() {
        return BaseRespVo.ok();
    }
}
  1. 请求参数限定
    • 可以写多个值,写多个值的情况,多个值之间的关系是and(或着说是:且)
    • 错误码:400 → 是请求参数封装有问题

eg:

@Controller
@RequestMapping("parameter")
public class ParameterLimitController {

    /**
     * localhost:8080/demo2/parameter/limit?username=zs&password=123456
     * @return
     */
    @ResponseBody
    // params = {"username","password"} 含义:既要携带username这个请求参数,也要携带password
    @RequestMapping(value = "limit", params = {"username", "password"}) // params里面是且的关系
    public BaseRespVo limit() {
        return BaseRespVo.ok();
    }

}
  1. 请求头限定
    • 是字符串数组,多个值之间的关系也是and(或者说是且)

eg:

@Controller
@RequestMapping("header")
public class HeaderLimitController {

    /**
     * localhost:8080/demo2/header/limit
     * @return
     */
    @ResponseBody
    // headers也是且的关系
    @RequestMapping(value = "limit", headers = {"x-coo1heisenberg-token", "language"})
    public BaseRespVo limit() {
        return BaseRespVo.ok();
    }
}
  1. Content-Type请求头值限定consumes属性
  • 限定的是Content-Type这个请求头的值 → 这个请求头的含义:请求携带的 正文的类型(请求体)
    • 比如一个jpg文件:image/jpeg
    • 比如文本:text/html
    • 比如json:application/json
  • 语法的格式:xxx/xxx
  1. Accept请求头值限定produces属性
  • 限定的是Accept这个请求头的值 → 这个请求的含义 → 客户端希望接收到的服务端响应的正文类型
  • 语法的格式:xxx/xxx

eg:

@Controller
@RequestMapping("header")
public class HeaderLimitController {

    /**
     * localhost:8080/demo2/header/consumes
     * localhost:8080/demo2/header/produces
     *
     * @return
     */

    @ResponseBody
    @RequestMapping(value = "consumes", consumes = "abc/def")
    // 限定content-type:abc/def
    public BaseRespVo consumes() {
        return BaseRespVo.ok();
    }

    @ResponseBody
    @RequestMapping(value = "produces", produces = "application/json")
    // 限定accept:application/json
    public BaseRespVo produces() {
        return BaseRespVo.ok();
    }
}

  • 解决中文乱码问题
    • JavaEE阶段解决响应中文乱码问题:响应头中Content-Type → 响应体的内容的字符集
    • SpringMVC阶段解决中文乱码问题:请求头中的Accept → 响应体的内容的字符集

eg:

@Controller
@RequestMapping("chinese")
public class ChineseResolveController {
    @RequestMapping(value = "resolve", produces = "application/json;charset=utf-8")
    @ResponseBody
    public String chinese() {
        return "你好中文";
    }
}

Handler方法的返回值

DispatcherServlet获取 → 判断类型

  • 如果是ModelAndView → 转发到jsp页面,在jsp页面上渲染model提供的数据 → 已经不是主流了
  • 如果是Json需要的形式 → response.getWriter.write(jsonStr)

  1. ModelAndView
    • ModelAndView主要是为单体应用服务的,单体应用的话,前端和后端都整合在一个应用程序中
    • ModelAndView主要是给访问前端的视图和数据的,当服务器的这个方法返回了一个ModelAndView的话,会访问到前端的内容
      • Tomcat对jsp处理本身就是特殊的处理 → jsp文件会被编译Servlet
    • 返回值写为了ModelAndView,在方法上并没有增加@ResponseBody

eg:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
    <h1>hello ${name}</h1>
</body>
</html>
@Controller
@RequestMapping("mav")
public class ModelAndViewController {

    /**
     * localhost:8080/demo3/mav/hello?name=zs
     * localhost:8080/demo3/mav/hello2?name=zs
     */

    /**
     * 访问到hello.jsp,里面显示的也是hello xxx
     * @return
     */
//    @SneakyThrows
//    @RequestMapping("hello")
//    public ModelAndView hello(HttpServletRequest request, HttpServletResponse response) {
//        request.setAttribute("name", request.getParameter("name"));

    /**
     * 请求转发:
     * 将请求从一个Servlet转发到另一个Servlet或JSP页面,但URL不会改变。
     * 在转发过程中,请求对象(HttpServletRequest)和响应对象(HttpServletResponse)在Servlet之间共享
     * @param request
     * @return
     */
//        request.getRequestDispatcher("/WEB-INF/hello.jsp").forward(request, response);
//
//    }

    @RequestMapping("hello")
    public ModelAndView hello(HttpServletRequest request) {
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.setViewName("/WEB-INF/hello.jsp");
        modelAndView.addObject("name", request.getParameter("name"));
        return modelAndView;
    }

    // 如果返回值是视图名,不能加@RequestBody注解
    @RequestMapping("/hello2")
    public String hello2(Model model, HttpServletRequest request) {
        model.addAttribute("name", request.getParameter("name"));
        return "/WEB-INF/hello.jsp"; // ModelAndView中的视图名
        // 等价于:modelAndView.setViewName("/WEB-INF/hello.jsp");
    }
}

  1. Json
    • 准备事务
      • jackson相关依赖(jackson-databind)
      • @EnableWebMvc
      • 方法上(或类上)增加注解@ResponseBody
    • 注意事项:如果要响应的是引用类型的对象,提供构造方法和getter/setter方法
    • @ResponseBody注解:
    @Target({ElementType.TYPE, ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface ResponseBody {
    }
    
    • 引申注解:@RestController = @Controller + @ResponseBody
  • 有两种方式:
    • String并不建议继续使用了
      • 后续我们并不直接返回String了,因为这样子写比较繁琐,大家还需要处理字符集问题,返回值为String的话,它的字符集默认是iso-8859-1
    • Object你写啥类型都行
      • 直接返回集合或引用类型对象就可以了
      • 直接返回对象,Jackson会自动的帮我们进行转换, 并且也不需要设置字符集,返回值的中文没有乱码

eg:

@RequestMapping("json")
//@Controller
//@ResponseBody // 意味着所有的方法上都加上了@ResponseBody
@RestController // 复合注解 等同于:@Controller + @ResponseBody
public class JsonController {
    /**
     * 1. 直接响应字符串
     * 2. 直接响应Object对象,然后Jackson会将该对象转换成Json字符串响应出去
     */

    @RequestMapping("hello1")
    // @ResponseBody
    public String hello1() {
        return "hello world";
    }

    @RequestMapping("hello2")
    // @ResponseBody
    public BaseRespVo hello2() {
        return BaseRespVo.ok("hello2");
    }
}

Handler方法的形参

方法的形参:主要做的事情是接收请求参数 以及一些其他的信息

  • key=value形式的请求参数
  • Json形式的请求参数
  • 其他的信息

key=value形式的请求参数

  1. String、基本类型以及对应的包装类
    • 直接接收:请求参数名和方法的形参名一致
    • 可以使用基本类型或其包装类接收的时候,建议使用包装类来接收 → 健壮性

eg:

/**
 * key = value形式的请求参数的接收
 */
@RequestMapping("user")
@RestController
public class UserController {
    /**
     * localhost:8080/demo4/user/register?username=zs&password=nihaoya&age=30
     */
    @RequestMapping("/register")
    public BaseRespVo register(String username, String password, Integer age) {

        return BaseRespVo.ok();
    }
}
  • vo的封装是开发过程中非常常规的使用

eg:

@Data
public class BaseRespVo<T> {
    T data;
    String errmsg;
    int errno;

    public static <T> BaseRespVo ok(T data) {
        BaseRespVo baseRespVo = new BaseRespVo();
        baseRespVo.setErrmsg("成功");
        baseRespVo.setData(data);
        return baseRespVo;
    }
    public static <T> BaseRespVo ok() {
        BaseRespVo baseRespVo = new BaseRespVo();
        baseRespVo.setErrmsg("成功");
        return baseRespVo;
    }

    public static <T> BaseRespVo invalidData(String msg) {
        BaseRespVo baseRespVo = new BaseRespVo();
        baseRespVo.setErrno(504);
        baseRespVo.setErrmsg(msg);
        return baseRespVo;
    }
    public static <T> BaseRespVo invalidData() {
        BaseRespVo baseRespVo = new BaseRespVo();
        baseRespVo.setErrno(504);
        baseRespVo.setErrmsg("更新数据已失效");
        return baseRespVo;
    }
    public static <T> BaseRespVo invalidParameter(String msg) {
        BaseRespVo baseRespVo = new BaseRespVo();
        baseRespVo.setErrno(400);
        baseRespVo.setErrmsg(msg);
        return baseRespVo;
    }
    public static <T> BaseRespVo unAuthc() {
        BaseRespVo baseRespVo = new BaseRespVo();
        baseRespVo.setErrno(502);
        baseRespVo.setErrmsg("认证失败");
        return baseRespVo;
    }
    public static <T> BaseRespVo expired() {
        BaseRespVo baseRespVo = new BaseRespVo();
        baseRespVo.setErrno(502);
        baseRespVo.setErrmsg("认证信息过期,请重新登录");
        return baseRespVo;
    }


    public static BaseRespVo badArgument() {

        return fail(401, "参数不对");
    }

    public static BaseRespVo fail(int errno, String errmsg) {
        BaseRespVo baseRespVo = new BaseRespVo();
        baseRespVo.setErrno(errno);
        baseRespVo.setErrmsg(errmsg);
        return baseRespVo;
    }
    public static BaseRespVo fail(String errmsg) {
        BaseRespVo baseRespVo = new BaseRespVo();
        baseRespVo.setErrno(500);
        baseRespVo.setErrmsg(errmsg);
        return baseRespVo;
    }
}
  1. Date形式
    • SpringMVC提供的String → Date的转换器,执行转换的过程中也是需要pattern信息的
      • 第一种是:采用默认的pattern信息 → yyyy/MM/dd
      • 第二种是:使用@DateTimeFormat注解的pattern属性指定
    • 注意:请求参数名和方法的形参名一致

eg:

@RequestMapping("register3")
public BaseRespVo register3(String username, String password, Integer age, Date birthday) {
    /**
     * 期望使用Date来接收birthday,能否接收取决于SpringMVC是否有提供 String -> Date的Converter
     *
     * → 可以使用Date来接收 → SpringMVC提供了转换器 → 有DateFormat的要求 yyyy/MM/dd
     *
     * eg:localhost:8080/demo4/user/register3?username=ls&password=nihao&age=30&birthday=2022/06/21
     */
    return BaseRespVo.ok();
}


@RequestMapping("register4")
public BaseRespVo register4(String username, String password, Integer age,
                            @DateTimeFormat(pattern = "yyyy-MM-dd") Date birthday) {
    // 也可以提供自定义的DateFormat
    /**
     * eg:localhost:8080/demo4/user/register4?username=ww&password=byebye&age=30&birthday=2022-06-21
     */
    return BaseRespVo.ok();
}
  1. 数组
    • 可以直接来形参中使用数组来接收
    • 前面能够接收到的这个类型的值,也可以使用对应类型的数组来接收

eg:

@RequestMapping("register5")
public BaseRespVo register5(String username, String password, Integer age,
                            String[] hobbies, Integer[] ids) {
    // 之前得使用这个方法
    // String[] hobbies = request.getParametersValues("hobbies");
    
    /**
     *  localhost:8080/user/register5?username=zs&password=nihaoya&age=30
     *  &hobbies=sing&hobbies=dance&hobbies=rap
     *  &ids=1&ids=2&ids=3
     */
    return BaseRespVo.ok();
}
  1. 文件 MultipartFile
  • 导入commons-iocommons-fileupload依赖
<!--引入commons-fileupload时候也会将commons-io引入-->
<dependency>
    <groupId>commons-fileupload</groupId>
    <artifactId>commons-fileupload</artifactId>
    <version>1.4</version>
</dependency>
  • 先向容器中注册一个组件

eg:

@EnableWebMvc // 功能有:@Configuration + 使用SpringMVC的相关配置
@ComponentScan("com.coo1heisenberg.demo4.controller")
// WebMvcConfigurer接口:MVC相关的配置要用到这个接口提供的方法和参数
public class WebConfiguration implements WebMvcConfigurer {

    /**
     * 如果要使用SpringMVC的文件接收,接收MultipartFile的话,
     * 需要向容器中注册MultipartResolver组件
     * 处理MultipartFile封装过程中,需要使用这个组件
     * 而这个组件 → ac.getBean(beanName)
     * beanName=multipartResolver → 也就意味着组件名称只能叫multipartResolver
     * 
     * @return
     */
    @Bean
    public CommonsMultipartResolver multipartResolver() {
        return new CommonsMultipartResolver();
    }
}
  • MultipartFile类中的内容
public interface MultipartFile extends InputStreamSource {
    String getName();

    @Nullable
    String getOriginalFilename();

    @Nullable
    String getContentType();

    boolean isEmpty();

    long getSize();

    byte[] getBytes() throws IOException;

    InputStream getInputStream() throws IOException;

    default Resource getResource() {
        return new MultipartFileResource(this);
    }

    void transferTo(File var1) throws IOException, IllegalStateException;

    default void transferTo(Path dest) throws IOException, IllegalStateException {
        FileCopyUtils.copy(this.getInputStream(), Files.newOutputStream(dest));
    }
}
  • MultipartFile提供的方法
方法名描述返回值
getOriginFileName()获得上传时的文件名String
getContentType()获得上传的文件的正文类型,比如上传banner.png,正文类型就是image/pngString
getName()获得是请求参数名(没啥用)String
getSize()获得文件大小long
transferTo(File)提供的参数是File类型的值,File提供的保存位置及文件名,就可以保存这个文件void
  • 向指定路径上传一个或者多个文件
@RestController
@RequestMapping("upload")
public class uploadController {

    /**
     * localhost:8080/demo4/upload/file
     *
     * 请求参数名和handler方法的形参名一致
     * @return
     */

    // 上传单个文件
    @SneakyThrows
    @RequestMapping("file")
    public BaseRespVo fileUpload(MultipartFile myfile /*, HttpServletRequest request */) {

        /**

         // 之前对参数信息的封装
        Part part = request.getPart("1.jpg");
        InputStream inputStream = part.getInputStream();
        long size = part.getSize();
        String originFileName = part.getSubmittedFileName();
        String name = part.getName();
        String contentType = part.getContentType();

         */

        InputStream inputStream = myfile.getInputStream();
        long size = myfile.getSize();
        String originFileName = myfile.getOriginalFilename();
        String name = myfile.getName();
        String contentType = myfile.getContentType();

        /*
        FileOutputStream fileOutputStream = new FileOutputStream(new File("D:\\tmp", originFileName));
        byte[] bytes = new byte[1024];
        int length = 0;
        while ((length = inputStream.read(bytes)) != -1) {
            fileOutputStream.write(bytes,0,length);
        }
        inputStream.close();
        fileOutputStream.close();

         */
        // 上面可以优化
        File file = new File("d:/tmp", originFileName);
        myfile.transferTo(file);

        return BaseRespVo.ok();
    }

    // 上传多个文件
    @RequestMapping("files")
    public BaseRespVo uploadFiles(MultipartFile[] myfiles) throws IOException {
        File fatherPath = new File("d:/tmp");
        for (MultipartFile myfile : myfiles) {
            File file = new File(fatherPath, myfile.getOriginalFilename());
            myfile.transferTo(file);
        }
        return BaseRespVo.ok();
    }

}

  1. 使用引用类型接收
    • 使用引用类型的话,将接收到的形参,封装为一个引用类型对象,这个引用类型的对象的成员变量封装的就是这些形参的值
    • 请求参数名和引用类型的成员变量名一致

eg:

/**
 * localhost:8080/user/register6?username=songge&password=niupi&age=30
 *  &hobbies=sing&hobbies=dance&hobbies=rap&ids=1&ids=2&ids=3
 *  &birthday=2022-06-21
 * @return
 */
@RequestMapping("register6")
public BaseRespVo register6(String username, String password, Integer age,
                            String[] hobbies, Integer[] ids,
                            @DateTimeFormat(pattern = "yyyy-MM-dd") Date birthday) {
    /**
     * User user = new User();
     * user.setUsername(username);
     * user.setPassword(password);
     * user.setAge(age);
     * user.setHobbies(hobbies);
     */
    return BaseRespVo.ok();
  • 将这个形参变为一个实体类的成员变量,形参这里写这个实体类的实例

eg:

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    String username;
    /**
     * request.getParameter("username")
     * user.setUsername(username)
     */
    String password;
    Integer age;
    /**
     * request.getParameter("age")
     * Integer.parseInt(ageStr)
     * user.setAge()
     */
    String[] hobbies;
    Integer[] ids;
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    Date birthday;
}
@RequestMapping("register7")
public BaseRespVo register7 (User user) {
    return BaseRespVo.ok();
}

总结

  • 使用形参直接接收:如果参数比较少,直接使用形参
  • 使用引用类型的对象来接收:参数比较多;在多个请求中都使用了相同的请求参数
  • 可以都用:有些参数就用一次或者用的次数比较少(直接接收),有的参数用了多次(封装为实体类)

Json请求参数

  • 如果说一个请求携带Json请求参数,这个请求的特点是:
    • 请求方法:POST
    • 正文类型Content-Type:application/json
    • 数据是Json字符串
  • Json请求参数仍然是在形参中进行接收,可以使用以下几种类型来接收
    • String
    • 引用类型对象
    • Map
  • 形参前需要增加一个注解@RequestBody

eg:

    /**
     * String类型
     * {"username":"root","password":"123456","birthday":"2022-07-12"}
     */
//    @RequestMapping("login1")
//    public BaseRespVo login1(HttpServletRequest request) {
//        String jsonStr = request.getReader().readLine();
//        return BaseRespVo.ok();
//    }

    @RequestMapping("login1")
    public BaseRespVo login1(@RequestBody String jsonStr) {
        return BaseRespVo.ok();
    }

    /**
     * 接收到这个json字符串,将字符串转换成map映射
     * @param map
     * @return
     */
    @RequestMapping("login2")
    public BaseRespVo login2(@RequestBody Map map) {
        // jsonStr -> map
        return BaseRespVo.ok();
    }

    @RequestMapping("login3")
    public BaseRespVo login3(@RequestBody UserVo userVo) {
        return BaseRespVo.ok();
    }
    
    @ResponseBody
    @RequestMapping("create")
    public BaseRespVo create(@RequestBody GoodVo goodVo) {
        return goodsService.create(goodVo);
    }
  • @RequestBody@ResponseBody这两个注解都是和Json打交道的时候使用的注解
    • 接收的时候用的是@RequestBody
    • 响应的时候用的是@ResponseBody

RESTful风格接口

  • REST → 是单词的首字母缩写 → 表述性状态传递 → Representational State Transfer
  • 通过请求(报文)能够提供一些对应的信息,提供给服务器,也就是通过一些方法来获得对应的信息
  • 其实就是对Request的封装
  1. 当前定义接口的URL,通过URL的不同区分不同的操作:
    • /user/list 查询
    • /user/remove 删除
    • /user/create 新增
    • /user/modify 修改
  • 过去使用RESTful风格接口的时候,构建一个场景,做user的增删改查,请求URL都是/user
    • /user 查询 GET
    • /user 删除 DELETE
    • /user 新增 PUT
    • /user 修改 POST

eg:

@RestController
@RequestMapping("user")
public class UserController {
    // User的新增
    // @RequestMapping(value = "user", method = RequestMethod.PUT)
    @RequestMapping("create")
    public BaseRespVo create() {
        return BaseRespVo.ok();
    }

    // User的删除
    // @RequestMapping(value = "user", method = RequestMethod.DELETE)
    @RequestMapping("delete")
    public BaseRespVo delete() {
        return BaseRespVo.ok();
    }

    // User的修改
    // @RequestMapping(value = "user", method = RequestMethod.POST)
    @RequestMapping("modify")
    public BaseRespVo modify() {
        return BaseRespVo.ok();
    }

    // User的查询
    // @RequestMapping(value = "user", method = RequestMethod.GET)
    @RequestMapping("list")
    public BaseRespVo list() {
        return BaseRespVo.ok();
    }

}
  1. 我们还可以获得一些其他的信息:
    • 请求URL的信息 → @PathVariable
    • 请求参数信息 → @RequestParam
    • 请求头信息 → @RequestHeader
    • Cookie信息 → @CookieValue
    • Session信息 → @SessionAttribute
  • 之前JavaEE阶段的方法:
@RestController
public class ArticleController {

    // @RequestMapping("cscscs/article/details/12321241")
    @RequestMapping("*/article/details/*")
    public BaseRespVo articleDetails(HttpServletRequest request) {
        /**
         * localhost:8080/demo5/cscscs/article/details/12321241
         */
        String requestURI = request.getRequestURI();
        String contextPath = request.getContextPath();// /demo5
        String suffix = requestURI.replaceAll(contextPath + "/", "");
        // → cscscs/article/details/12321241
        String username = suffix.substring(0, suffix.indexOf("/"));
        String id = suffix.substring(suffix.lastIndexOf("/") + 1);

        return BaseRespVo.ok();
    }
}
  • @PathVariable注解 → URI
    • @PathVariable → 获得请求URL的一部分值 → 在@RequestMapping的value属性写占位符{}
    • 获得指定占位符位置的值给到所对应的形参 → 形参通过注解接收指定占位符位置的值
    • 通过这个注解,就可以把一部分请求参数写到URL中

eg:

@RestController
public class ArticleController {
    @RequestMapping("{username}/article/details/{id}")
    public BaseRespVo articleDetails(@PathVariable("username") String username,
                                     @PathVariable("id") Integer id) {
        return BaseRespVo.ok();
    }
}
  • @RequestParam → 请求参数
    • 形参通过这个注解获得指定请求参数,如果使用了这个注解,就一定要携带对应的请求参数

eg:

/**
 * localhost:8080/demo5/param?username=zs&password=123456
 */
@RequestMapping("param")
public BaseRespVo param(@RequestParam("username") String usernamex,
                        @RequestParam("password") String passwordy) {
    return BaseRespVo.ok();
}
  • @RequestHeader → 请求头
    • 形参通过这个注解获得指定请求头的值

eg:

   /**
     * localhost:8080/demo5/header
     * <p>
     * 构造请求体 X-token:a1b2c3
     */
//    @RequestMapping("header")
//    public BaseRespVo header(HttpServletRequest request) {
//        request.getHeader("X-token");
//        return BaseRespVo.ok();
//    }
    @RequestMapping("header")
    public BaseRespVo header(@RequestHeader("X-token") String tokenValue) {
        return BaseRespVo.ok();
    }

    /**
     * 也可以使用String[] 来接收,如果使用数组来接收,将字符串根据逗号进行分割,分割为数组
     */
    @RequestMapping("header1")
    public BaseRespVo header1(@RequestHeader("X-token") String tokenValue,
                              @RequestHeader("Accept") String[] acceptArray) {
        return BaseRespVo.ok();
    }
  • @CookieValue → Cookie
    • 形参通过这个注解获得指定Cookie的值,根据key获得value

eg:

    /**
     * localhost:8080/demo5/cookie
     * 构造请求头 Cookie: username=zs
     */
    /**
     * 之前JavaEE的方法
     * @param request
     * @return
     */
    @RequestMapping("cookie")
    public BaseRespVo cookie(HttpServletRequest request) {
        String value = null;
        Cookie[] cookies = request.getCookies();
        if(cookies != null && cookies.length > 0) {
            for (Cookie cookie : cookies) {
                if("username".equals(cookie.getName())) {
                    value = cookie.getValue();
                }
            }
        }
        return BaseRespVo.ok();
    }
    @RequestMapping("cookie1")
    public BaseRespVo cookie1(@CookieValue("username") String value) {
        return BaseRespVo.ok();
    }
  • 之前JavaEE的方法:
    /**
     * localhost:8080/demo5/session/put
     */
    /**
     * 向session里存入数据
     * localhost:8080/demo5/session/put?username=zs
     */
    @RequestMapping("session/put")
    public BaseRespVo putSession(HttpSession httpSession, String username) {
        httpSession.setAttribute("username", username);
        return BaseRespVo.ok();
    }

    /**
     * 向session里取出数据
     * localhost:8080/demo5/session/get
     */
    @RequestMapping("session/get")
    public BaseRespVo getSession(HttpSession httpSession) {
        String value = (String) httpSession.getAttribute("username");
        return BaseRespVo.ok();
    }
  • @SessionAttribute → Session
    • 形参通过这个注解获得指定Session的值,根据key获得value
    /**
     * localhost:8080/demo5/session/put
     */
    /**
     * 向session里存入数据
     * localhost:8080/demo5/session/put?username=zs
     */
    @RequestMapping("session/put")
    public BaseRespVo putSession(HttpSession httpSession, String username) {
        httpSession.setAttribute("username", username);
        return BaseRespVo.ok();
    }

    /**
     * 向session里取出数据
     * localhost:8080/demo5/session/get
     */
    @RequestMapping("session/get")
    public BaseRespVo getSession(@SessionAttribute("username") String username) {
        return BaseRespVo.ok();
    }

静态资源处理

  • JavaEE阶段如果将图片放在webapp目录,它会编译到web资源根目录,图片能访问到
  • 应用程序整合SpringMVC之后,放在web资源根目录的图片访问不到了,原因是:
    • JavaEE阶段,缺省的servlet是default → 这个缺省的servlet做的事情就是根据请求找目录下的静态资源
    • SpringMVC这里,缺省的Servlet是DispatcherServlet → localhost:8080/1.jpg找的是DispatcherServlet而不是default

在这里插入图片描述

  • 解决办法:
    • SpringMVC有提供对应的类,提供的是ResourceHandler ,需要我们自己来配置
      • 处理静态资源
      • 配置其映射范围
  • 之前的做法:
controller目录下:

@RestController
@RequestMapping("pic/storage")
public class PictureStorageController {

    @Autowired
    StorageService storageService;
    @RequestMapping("fetch/{filename}")
    public void fetchImage(HttpServletResponse response,
                           @PathVariable("filename")String filename) {
        storageService.show(response, filename);
    }
}
service目录下:

public interface StorageService {
    void show(HttpServletResponse response, String filename);

    public StorageData create(MultipartFile file) throws IOException;
}

@Service
public class StorageServiceImpl implements StorageService {
    @Value("d:/tmp")
    String path;

    @Override
    public StorageData create(MultipartFile file) throws IOException {

        // 随机获取一个 UUID,有极小的概率会重复
        String uuid = UUID.randomUUID().toString();
        String savedFileName = uuid + ".jpeg";
        file.transferTo(new File(path, savedFileName));

        // http://localhost:8083/wx/storage/fetch/yq5f2kgrno9q1eoyhz1u.jpeg
        String urlPrefix = "http://localhost:8080/demo6/wx/storage/fetch/";
        String url = urlPrefix + savedFileName;
        StorageData storageData = new StorageData(null, savedFileName, file.getOriginalFilename(),
                file.getContentType(), file.getSize(), url, new Date(), new Date());
        //storageMapper.insert(storageData); // 略,插入到数据库中,并且获得自增的主键封装给id
        return storageData;
    }

    @Override
    @SneakyThrows
    public void show(HttpServletResponse response, String filename) {
        File file = new File(path, filename);
        ServletOutputStream outputStream = response.getOutputStream();
        FileInputStream fileInputStream = new FileInputStream(file);

        int length = 0;
        byte[] bytes = new byte[1024];

        while ((length = fileInputStream.read()) != -1) {
            outputStream.write(bytes, 0, length);
        }
        outputStream.close();
        fileInputStream.close();
    }
}

  • SpringMVC中的方法
    • 注册ResourceHandler使用ResourceHandlerRegistry
      • addResourceHandler配置ResourceHandler的映射范围
      • addResourceLocations告知当前ResourceHandler你的资源文件处于什么位置
      • ** 代表多级任意url
      • location写的时候注意:最后的位置/不要漏掉
    • 建议使用文件路径
      • 过去应用程序打包的时候打包为war包,后面我们使用SpringBoot应用 → 应用程序打包的时候是jar

eg:

@EnableWebMvc // 功能有:@Configuration + 使用SpringMVC的相关配置
@ComponentScan("com.coo1heisenberg.demo6.controller")
// WebMvcConfigurer接口:MVC相关的配置要用到这个接口提供的方法和参数
public class WebConfiguration implements WebMvcConfigurer {

    /**
     * 如果要使用SpringMVC的文件接收,接收MultipartFile的话,
     * 需要向容器中注册MultipartResolver组件
     * 处理MultipartFile封装过程中,需要使用这个组件
     * 而这个组件 → ac.getBean(beanName)
     * beanName=multipartResolver → 也就意味着组件名称只能叫multipartResolver
     *
     * @return
     */
    @Bean
    public CommonsMultipartResolver multipartResolver() {
        return new CommonsMultipartResolver();
    }

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        /**
         * addResourceHandler():配置的是path
         * addResourceLocations():配置的是location
         */
        /**
         * localhost:8080/demo6/pic/1.jpg
         *
         * localhost:8080/demo6/pic/2.jpg
         */
        /**
         * pic/** : 匹配多级任意值
         */
        /**
         * 如果指定文件路径,需要加上 file: 这样的前缀
         *
         * location最后要加上一个 /
         */

        registry.addResourceHandler("/pic/*").addResourceLocations("file:d:/tmp/");

        /**
         * 如果指定类加载路径(target/artifactid-version/WEB-INF/classes),要增加classpath:这样的前
         * 缀
         */
         // 得先进行编译
        registry.addResourceHandler("/pic2/**").addResourceLocations("classpath:/");

        /**
         * 如果指定web资源路径(target/artifactid-version),就不要增加前缀,如果要指定根路径,只写 / 就行
         */
    }
}

Filter

  • Filter和SpringMVC之间的关系:本质上就是Filter和Servlet之间的关系,执行SpringMVC的核心DispatcherServletdoDispatch方法之前先去执行的是Filter的doFilter方法
  • SpringMVC给我们提供了一个抽象类OncePerRequestFilter → 实现了Filter接口 → 里面包含了doFilter方法的实现(非抽象方法)
    • OncePerRequestFilter能够保证doFilterInternal只执行一次
    • 相当于之前在JavaEE阶段的doFilter方法
    • 避免因为JavaEE容器的原因导致Filter会执行多次

eg:

  • 配置ApplicationInitialization
    @Override
    protected Filter[] getServletFilters() {
        // 提供Filter的配置 → 告知web应用,你有哪个或者哪些Filter
        return new Filter[]{new CustomFilter()};
    }
  • 配置filter目录下的CustomFilter
public class CustomFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain filterChain) 
                                    throws ServletException, IOException {
        // 相当于之前在JavaEE阶段的doFilter方法
        // 避免因为JavaEE容器的原因导致Filter会执行多次
        // OncePerRequestFilter能够保证doFilterInternal只执行一次
        request.setCharacterEncoding("utf-8");
        System.out.println("执行了CustomFilter");
        filterChain.doFilter(request, response);
    }
}
  • SpringMVC有提供一个处理字符集的Filter → CharacterEncodingFilter也是继承了OncePerRequestFilter

eg:

  • 配置ApplicationInitialization
@Override
protected Filter[] getServletFilters() {
    // 提供Filter的配置 → 告知web应用,你有哪个或者哪些Filter
    // return new Filter[]{new CustomFilter()};
    CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter();
    
    // 强制设置utf-8
    characterEncodingFilter.setEncoding("utf-8");
    characterEncodingFilter.setForceEncoding(true);
    return new Filter[]{characterEncodingFilter};
}

HandlerInterceptor

  • Handler的拦截器,在Handler执行之前做的拦截,是SpringMVC提供的拦截器
    在这里插入图片描述
    在这里插入图片描述
  • HandlerMapping起作用 → HandlerExecutionChain实例
  • 每次发送请求:都会生成新的HandlerExecutionChain的实例
    • 封装了Handler
    • 封装了多个HandlerInterceptor
public class HandlerExecutionChain {
  // Handler
  private final Object handler;
  
  //多个HandlerInterceptor
  @Nullable
  private HandlerInterceptor[] interceptors;
  @Nullable
  private List<HandlerInterceptor> interceptorList;
  
  // 标记(记号)
  private int interceptorIndex;
  /**
  interceptorIndex就是一个标记,标记HandlerInterceptorList或数组中哪一些preHandle返回值为true,
  提供的是下标(序号)
  */
}
public interface HandlerInterceptor {

    default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        return true;
    }

    default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {
    }

    default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
    }
}

在这里插入图片描述

  • 配置HandlerInterceptor
    • 实现HandlerInterceptor接口
      • preHandle在Handler执行之前执行的,返回值为boolean
        • 如果返回值为true则继续流程
        • 如果返回值为false则中断流程;会去执行返回值为true的部分的afterCompletion
        • 如果有多个HandlerInterceptor,preHandle方法是正序遍历
      • Handler → 通常就是Controller组件中的Handler方法
      • postHandle在Handler之后执行的
        • 如果执行不到Handler(前面的preHandle返回值为false),那么一个postHandle都执行不到
        • 如果能够执行到Handler就能够执行到全部的postHandle
        • 如果有多个HandlerInterceptor,postHandle方法是倒序遍历
      • afterCompletion执行完postHandle、preHandle返回值为false
        • 执行完postHandle之后执行afterCompletion,能够执行到全部的afterCompletion
        • preHandle返回值为false的时候执行afterCompletion的话,执行的是preHandle返回值为true的部分afterCompletion
        • 如果有多个HandlerInterceptor,afterCompletion方法是倒序遍历
  • 配置HandlerInterceptor以及其作用范围
    • WebMvcConfigurer接口中的方法:getInterceptors

eg:

  • WebConfiguration中配置HandlerInterceptor
    在这里插入图片描述
@EnableWebMvc // 功能有:@Configuration + 使用SpringMVC的相关配置
@ComponentScan("com.coo1heisenberg.demo7.controller")
// WebMvcConfigurer接口:MVC相关的配置要用到这个接口提供的方法和参数
public class WebConfiguration implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        /**
         * 默认的作用范围是 /**
         */
        registry.addInterceptor(new HandlerInterceptor1()).addPathPatterns("/hello");
        registry.addInterceptor(new HandlerInterceptor2()).addPathPatterns("/hello/**");
        registry.addInterceptor(new HandlerInterceptor3()).addPathPatterns("/goodbye");
        registry.addInterceptor(new HandlerInterceptor4()).addPathPatterns("/goodbye/**");
        //registry.addInterceptor(new HandlerInterceptor5()) //.addPathPatterns("/**");
    }
}
  • interceptor目录下的handlerInterceptor1/2/3/4
@Component
public class HandlerInterceptor1 implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("preHandle1");
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("postHandle1");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("afterCompletion1");
    }
}
@Component
public class HandlerInterceptor2 implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("preHandle2");
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("postHandle2");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("afterCompletion2");
    }
}
@Component
public class HandlerInterceptor3 implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("preHandle3");
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("postHandle3");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("afterCompletion3");
    }
}
@Component
public class HandlerInterceptor4 implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("preHandle4");
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("postHandle4");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("afterCompletion4");
    }
}

Filter与HandlerInterceptor之间的对比

  • 实现的功能都是类似的:
    • Filter是在Servlet之前、之后都可以执行一些业务
    • HandlerInterceptor是在Handler之前和之后可以执行一些业务
  • 如果访问不到对应的Handler,Filter可以执行到;如果没有对应的Handler处理对应的请求,HandlerExecutionChain也为空
  • HandlerInterceptor的使用和Filter不一样 → 容器 → 注册进去,可以维护和其他组件之间的依赖关系(成员变量注入容器中的其他组件) → 取出来的时候其成员变量就已经赋值了

异常处理

  • 在Handler中制造异常
  • 如果不做异常处理:不友好、有可能泄露信息
  1. HandlerExceptionResolver
    • 处理全局的全部异常
    • 返回值为ModelAndView

eg:

@Component
public class CustomHandlerExceptionResolver implements HandlerExceptionResolver {
    @Override
    public ModelAndView resolveException(HttpServletRequest httpServletRequest,
                                         HttpServletResponse httpServletResponse,
                                         Object handler, // 抛出异常的handler
                                         Exception e) { // handler抛出的异常
        ModelAndView modelAndView = new ModelAndView("/exception.jsp");
        if (e instanceof ArithmeticException) {
            // 算数异常 做特定的处理
        }
        if (e instanceof  NullPointerException) {
            // 空指针异常 做特定的处理
        }
        // 能否响应Json字符串
        // httpServletResponse.getWriter().println(jsonStr);
        return modelAndView;
    }
}
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>

<h1>应用程序正在升级维护,请稍后</h1>

</body>
</html>
  1. @ExceptionHandler → 建议
    • 处理的特定类型的异常
    • 返回值可以为ModelAndView,也可以为String或Json字符串
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ExceptionHandler {
    Class<? extends Throwable>[] value() default {};
}

eg:

@ControllerAdvice
@ResponseBody
@RestControllerAdvice // @RestControllerAdvice == @ResponseBody + @ControllerAdvice
public class ExceptionControllerAdvice {
    /**
     * 下面两个都是响应ModelAndView
     * @return
     */
    @ExceptionHandler(value = ArithmeticException.class)
    public ModelAndView arithmeticExceptionResolveMethod() {
        return new ModelAndView("/exception.jsp");
    }

    @ExceptionHandler(value = ArithmeticException.class)
    public String arithmeticExceptionResolveMethod() {
        return "/exception.jsp"; // //返回值作为ModelAndView中的视图名
    }

    /**
     * 响应Json字符串
     */
    @ExceptionHandler(ArithmeticException.class)
    @ResponseBody
    public BaseRespVo arithmeticExceptionResolveMethod() {
        return BaseRespVo.fail("除零异常");
    }
}

SpringMVC核心流程

  • DispatcherServlet处理我们(几乎)全部请求
  • Controller组件中的方法处理请求,这也是我们主要开发的内容

流程图

在这里插入图片描述
DispatcherServlet和ApplicationContext的关系

  • 方法存在于Controller组件中
  • Controller组件存储在容器中 ,容器就是ApplicationContext
  • 当我们发送请求的时候,会执行到这些方法;当我们发送请求的时候,DispatcherServlet处理我们全部的请求, DispatcherServlet → 方法
  • Handler处理器,method1、method2、method3其实就是处理器,处理我们的请求,也称之为HandlerMethod(Handler方法)
  • DispatcherServlet如果能够找到ApplicationContext(容器),就可以执行到容器中的Controller组件中Handler(方法)

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

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

相关文章

界面设计【1】-项目的UI设计css

引言&#xff1a; 本篇博客对简单的css html界面设计做了简要介绍 这篇博客主要就是介绍了做横向项目中&#xff0c;CSS界面设计与优化。 界面设计【1】-项目的UI设计css 1. 什么是css?2. css编程demo3. 可视化效果 1. 什么是css? CSS是层叠样式表&#xff08;Cascading S…

一篇写给前端的精选面试题,中大厂面试重复率高到爆!!!

写在前面 针对前端环境恶劣&#xff0c;很多人在前端面试的时候都直接去找相关公司的面经&#xff0c;或者没有真正新一点各个厂里常用面试题&#xff0c;现在小编给大家整理好了&#xff0c;前端面试无非就是那些&#xff0c;面试题更别谈新旧&#xff0c;只不过很多公司常用…

L2-024. 部落-PAT团体程序设计天梯赛GPLT(tarjan缩点)

题解&#xff1a; 可能有人在多个圈子&#xff0c;那么这几个圈子合并为一个部落&#xff0c;一个做法就是将圈子转化为有向图&#xff0c;最后求出的缩点就是部落个数。再查询是否在一个缩点当中。 #include<bits/stdc.h> #pragma GCC optimize("Ofast") #d…

BackTrader 中文文档(十二)

原文&#xff1a;www.backtrader.com/ Visual Chart 原文&#xff1a;www.backtrader.com/docu/live/vc/vc/ 与 Visual Chart 的集成支持两者&#xff1a; 实时数据提供 实时交易 Visual Chart是完整的交易解决方案&#xff1a; 在单个平台上集成图表、数据源和经纪功能 更多…

【在线OJ系统】自定义注解实现自增ID的无感插入

实现思路 首先自定义参数注解&#xff0c;然后根据AOP思想&#xff0c;找到该注解作用的切点&#xff0c;也就是mapper层对于mapper层的接口在执行前都会执行该aop操作&#xff1a;获取到对于的方法对象&#xff0c;根据方法对象获取参数列表&#xff0c;根据参数列表判断某个…

时序分解 | Matlab实现WOA-VMD鲸鱼算法WOA优化VMD变分模态分解

时序分解 | Matlab实现WOA-VMD鲸鱼算法WOA优化VMD变分模态分解 目录 时序分解 | Matlab实现WOA-VMD鲸鱼算法WOA优化VMD变分模态分解效果一览基本介绍程序设计参考资料 效果一览 基本介绍 Matlab实现WOA-VMD鲸鱼算法WOA优化VMD变分模态分解&#xff08;完整源码和数据) 1.利用鲸…

Semaphore信号量源码解读与使用

&#x1f3f7;️个人主页&#xff1a;牵着猫散步的鼠鼠 &#x1f3f7;️系列专栏&#xff1a;Java全栈-专栏 &#x1f3f7;️个人学习笔记&#xff0c;若有缺误&#xff0c;欢迎评论区指正 目录 1. 前言 2. 什么是Semaphore&#xff1f; 3. Semaphore源码解读 3.1 acquire…

Linux系统的引导过程与服务控制

目录 一、Linux操作系统引导过程 二、Linux系统服务控制 系统初始化进程 三、运行级别切换 *运行级别及切换 Linux系统的运行级别 四、优化开机自动加载服务 五、修复MBR扇区故障 一、Linux操作系统引导过程 主要步骤 开机自检&#xff1a; 检测硬件设备&#…

C++从入门到精通——const与取地址重载

const与取地址重载 前言一、const正常用法const成员函数问题const对象可以调用非const成员函数吗非const对象可以调用const成员函数吗const成员函数内可以调用其它的非const成员函数吗非const成员函数内可以调用其它的const成员函数吗总结 二、取地址及const取地址操作符重载概…

小米汽车SU7隐藏款曝光!新配色和透明车身亮了 coreldraw教程入门零基础 coreldraw下载 coreldraw2024

刘强东说&#xff0c;论营销&#xff0c;没有任何人能比得过小米。 小米SU7发布会24小时&#xff0c;下定量就超过了蔚来汽车2023年四季度的交付量。 ▲雷军发布的小米SU7 24小时订单量 小米SU7发布会后五天&#xff0c;雷军在北京亦庄工厂亲自交付了第一批创世版本小米SU7&a…

黑马点评(四) -- 分布式锁

1 . 分布式锁基本原理和实现方式对比 分布式锁&#xff1a;满足分布式系统或集群模式下多进程可见并且互斥的锁。 分布式锁的核心思想就是让大家都使用同一把锁&#xff0c;只要大家使用的是同一把锁&#xff0c;那么我们就能锁住线程&#xff0c;不让线程进行&#xff0c;让…

gpt4.0人工智能网页版

在最新的AI基准测试中&#xff0c;OpenAI几天前刚刚发布的GPT-4-Turbo-2024-04-09版本&#xff0c;大幅超越了Claude3 Opus&#xff0c;重新夺回了全球第一的AI王座。 GPT-4-Turbo-2024-04-09版本是目前国内外最强的大模型&#xff0c;官网需要20美元每月才能使用&#xff0c;…

【UE5.1】使用MySQL and MariaDB Integration插件——(3)表格形式显示数据

在上一篇&#xff08;【UE5.1】使用MySQL and MariaDB Integration插件——&#xff08;2&#xff09;查询&#xff09;基础上继续实现以表格形式显示查询到的数据的功能 效果 步骤 1. 在“WBP_Query”中将多行文本框替换未网格面板控件&#xff0c;该控件可以用表格形式布局…

Pytest测试用例中的mark用法(包含代码示例与使用场景详解)

在软件开发中&#xff0c;测试是确保代码质量和功能稳定性的重要环节。Python作为一门流行的编程语言&#xff0c;拥有丰富的测试工具和框架&#xff0c;其中pytest是其中之一。pytest提供了丰富的功能来简化测试用例的编写&#xff0c;其中的mark功能允许我们对测试用例进行标…

理解思维链Chain of Thought(CoT)

Chain of Thought&#xff08;CoT&#xff09;&#xff0c;即“思维链”&#xff0c;是人工智能领域中的一个概念&#xff0c;特别是在自然语言处理和推理任务中。它指的是一种推理过程&#xff0c;其中模型在生成最终答案之前&#xff0c;先逐步推导出一系列的中间步骤或子目标…

【日常记录】【CSS】SASS循环的使用

文章目录 1、引言2、安装3、举例4、参考链接 1、引言 目前在任何项目框架中&#xff0c;都会有css 预处理器&#xff0c;目前一般使用 sass、less 这俩其中之一&#xff0c;它可以简化css的书写 Sass 是一款强化 CSS 的辅助工具&#xff0c;它在 CSS 语法的基础上增加了变量 (v…

推动企业档案数字化转型的措施

推动企业档案数字化转型的措施有以下几点&#xff1a; 1. 制定数字化转型战略&#xff1a;企业应该制定明确的数字化转型战略&#xff0c;明确企业数字化转型的目标、步骤和时间表&#xff0c;并将档案数字化转型作为其中的重要内容。 2. 投资数字化技术&#xff1a;企业应该投…

代码随想录:二叉树5(层序遍历全解)

目录 102.二叉树的层序遍历 107.二叉树的层序遍历II 199.二叉树的右视图 637.二叉树的层平均值 429.N叉树的层序遍历 501.在每个树行中找最大值 116.填充每个节点的下一个右侧节点指针 117.填充每个节点的下一个右侧节点指针II 104.二叉树的最大深度 111.二叉树的最大…

UE5 HLSL 详细学习笔记

这里的POSITION是变量Position的语义&#xff0c;告诉寄存器&#xff0c;此变量的保存位置&#xff0c;通常语义用于着色器的输入和输出&#xff0c;以冒号“&#xff1a;”的方式进一步说明此变量&#xff0c;COLOR也类似 还有什么语义呢&#xff1f; HLSL核心函数&#xff1a…

CAN的底层驱动

框架图 拆解链路模型 CAN子系统 can_controller Core 包含协议控制器和接收/发送移位寄存器。它可处理所有 ISO 11898-1: 2015 协议功能,并支持 11 位和 29 位标识符。
最新文章