• 5_SpringMVC


    一. 什么是MVC框架

    • MVC全名是Model View Controller, 是模型(model), 视图(view), 控制器(controller)的缩写, 一种软件设计典范, 用一种业务逻辑、数据、界面显示分离的方法组织代码, 将业务逻辑聚集到一个部件里面, 在改进和个性化定制界面及用户交互的同时, 不需要重新编写业务逻辑

    二. SpringMVC是什么

    • SpringMVC是Spring框架的一个模块, 是一个基于MVC的框架

    三. SpringMVC的核心

    • DispatcherServlet
      • 前端控制器, 也叫中央控制器, 相关组件都是它来调度

    四. SpringMVC的几个组件

    • DispatcherServlet
      • 前端控制器, 也叫中央控制器, 相关组件都是它来调度
    • HandlerMapping
      • 处理器映射器, 根据URL路径映射到不同的Handler
    • HandlerAdapter
      • 处理器适配器, 按照HandlerAdapter的规则去执行Handler
    • Handler
      • 处理器, 由我们自己根据业务开发
    • ViewResolver
      • 视图解析器, 把逻辑视图解析成具体的视图
    • View
      • 一个接口, 它的实现支持不同的视图类型(freeMaker, JSP等)

    五. SpringMVC的工作流程

    1. 用户请求旅程第一站是DispatcherServlet
    2. 收到请求后, DispatcherServlet 调用HandlerMapping, 获取对应的Handler
    3. 如果有拦截器一并返回
    4. 拿到Handler后, 提到HandlerAdapter, 通过它来访问Handler, 并执行处理器
    5. 执行Handler的逻辑
    6. Handler会返回一个ModelAndView对象给DispatcherServlet
    7. 将获得到的ModelAndView对象返回给DispatcherServlet
    8. 请求ViewResolver解析视图, 根据逻辑视图名解析成真正的View
    9. 返回View给DispatcherServlet
    10. DispatcherServlet 对View进行渲染视图
    11. DispatcherServlet 响应用户

    六. SpringMVC的优点

    1. 具有Spring的特性
    2. 可以支持多种视图(freeMaker, JSP等)
    3. 配置方便
    4. 非侵入
    5. 分层更清晰, 利于团队开发的代码维护, 以及可读性好
    • Tips: JSP目前很少使用了

    七. SpringMVC的工作流程代码

    7.1 /web/WEB-INF/web.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
             version="4.0">
    
        <!--1. 注册DispatcherServlet: 这个是SpringMVC的核心:请求分发器,前端控制器-->
        <servlet>
            <servlet-name>springmvc</servlet-name>
            <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
            <!--关联一个springmvc的配置文件: [servlet-name]-servlet.xml-->
            <init-param>
                <param-name>contextConfigLocation</param-name>
                <param-value>classpath:springmvc-servlet.xml</param-value>
            </init-param>
            <!--启动级别-1-->
            <load-on-startup>1</load-on-startup>
        </servlet>
    
        <!--/ 匹配所有的请求:(不包括.jsp)-->
        <!--/* 匹配所有的请求: (包括.jsp)-->
        <servlet-mapping>
            <servlet-name>springmvc</servlet-name>
            <url-pattern>/</url-pattern>
        </servlet-mapping>
    </web-app>
    

    7.2 /resources/springmvc-servlet.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="
    		http://www.springframework.org/schema/beans
    		http://www.springframework.org/schema/beans/spring-beans.xsd">
    
        <!--2. 处理器映射器 根据URL路径映射到不同的Handler-->
        <bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/>
        <!--4. 处理器适配器 按照HandlerAdapter的规则去执行Handler-->
        <bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"/>
    
        <!--6. 视图解析器 ViewResolver DispatcherServlet把从Handler那里得到的ModelAndView返回给它-->
    
        <!--1. 获取ModelAndView中的数据
            2. 解析ModelAndView的视图名字
            3. 拼接视图名字, 找到对应的视图 /WEB-INF/jsp/hello.jsp
            4. 将数据渲染到这个视图上
        -->
        <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="internalResourceViewResolver">
            <!--前缀-->
            <property name="prefix" value="/WEB-INF/jsp/"/>
            <!--后缀-->
            <property name="suffix" value=".jsp"/>
        </bean>
    
        <!--3. Handler 处理器 由我们自己根据业务开发-->
        <bean name="/hello" class="com.dz.controller.HelloController"/>
    </beans>
    

    7.3 /controller/HelloController.java

    package com.dz.controller;
    
    import org.springframework.web.servlet.ModelAndView;
    import org.springframework.web.servlet.mvc.Controller;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    // 5.
    public class HelloController implements Controller {
        @Override
        public ModelAndView handleRequest(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception {
            //ModelAndView 模型和视图
            ModelAndView modelAndView = new ModelAndView();
            //业务代码
            String result = "Hello SpringMVC!";
            //封装对象, 放在ModelAndView中. Model
            modelAndView.addObject("msg",result);
            //封装要跳转的视图, 放在ModelAndView中
            modelAndView.setViewName("hello");//相当于 /WEB-INF/jsp/hello.jsp
            return modelAndView;
        }
    }
    
    

    7.4 /web/WEb-INF/jsp/hello.jsp

    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <html>
    <head>
        <title>Title</title>
    </head>
    <body>
    ${msg}
    </body>
    </html>
    
    

    八. 创建SpringMVC项目步骤

    8.1 创建Maven项目

    • 先创建好一个Maven项目, 例如SpringMVC, 创建过程中什么也不用勾选
    • 然后把src目录删除, 之后鼠标右键点击SpringMVC新建Module, 例如springmvc-01, 我们把SpringMVC当做一个总项目, 下面可以创建很多模块(子项目)
    • 然后在SpringMVC总项目下的pom.xm中导入依赖和Build(如果所有配置文件都写在resources下就不需要Build), 子项目是共享总项目的pom.xml的, 当然子项目中也可以单独导入自己需要的依赖
    <dependencies>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>servlet-api</artifactId>
            <version>2.5</version>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>jsp-api</artifactId>
            <version>2.0</version>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>jstl</artifactId>
            <version>1.2</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>5.3.5</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <build>
        <resources>
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.properties</include>
                    <include>**/*.xml</include>
                </includes>
                <filtering>false</filtering>
            </resource>
            <resource>
                <directory>src/main/resources</directory>
                <includes>
                    <include>**/*.properties</include>
                    <include>**/*.xml</include>
                </includes>
                <filtering>false</filtering>
            </resource>
        </resources>
    </build>
    

    8.2 配置web.xml

    • 右键子项目springmvc-01 > Add Framework Support 然后勾选 Web Application 注意右侧的Versions是否为4.0(要用现阶段的最新版本), 低版本的会出错
    • File > Project Structure > Artifacts > 选择我们当前的子项目 > 在WEB-INF下创建lib目录 > 点击+ > 选择Library Files > 添加所有的jar包 > OK
    • 配置WEB-INF下的web.xml
      1. 注意web.xml版本,要最新版
      2. 注册DispatcherServlet
      3. 关联SpringMVC的配置文件
      4. 启动级别设置为1
      5. 映射路径为 / [不要用/*, 会404]
    <!--1. 注册DispatcherServlet: 这个是SpringMVC的核心:请求分发器,前端控制器-->
    <servlet>
    	<servlet-name>springmvc</servlet-name>
    	<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    	<!--关联一个springmvc的配置文件: [servlet-name]-servlet.xml-->
    	<init-param>
    		<param-name>contextConfigLocation</param-name>
    		<param-value>classpath:springmvc-servlet.xml</param-value>
    	</init-param>
    	<!--启动级别-1-->
    	<load-on-startup>1</load-on-startup>
    </servlet>
    
    <!--/ 匹配所有的请求:(不包括.jsp)-->
    <!--/* 匹配所有的请求: (包括.jsp)-->
    <servlet-mapping>
    	<servlet-name>springmvc</servlet-name>
    	<url-pattern>/</url-pattern>
    </servlet-mapping>
    

    8.3 配置springmvc-servlet.xml

    • 在resources下新建springmvc-servlet.xml文件进行配置
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:context="http://www.springframework.org/schema/context"
           xmlns:mvc="http://www.springframework.org/schema/mvc"
           xsi:schemaLocation="
    		http://www.springframework.org/schema/beans
    		http://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/context
    		http://www.springframework.org/schema/context/spring-context.xsd
            http://www.springframework.org/schema/mvc
    		http://www.springframework.org/schema/mvc/spring-mvc.xsd">
    
        <!--开启注解支持-->
        <context:annotation-config/>
        <!--自动扫描包, 使指定包下的注解生效, 由IOC容器统一管理-->
        <context:component-scan base-package="com.dz"/>
        <!--使SpringMVC不处理静态资源-->
        <mvc:default-servlet-handler/>
        <!--相当于处理器 处理器映射器 处理器适配器-->
        <mvc:annotation-driven/>
    
        <!--视图解析器-->
        <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"
              id="internalResourceViewResolver">
            <!--前缀-->
            <property name="prefix" value="/WEB-INF/jsp/"/>
            <!--后缀-->
            <property name="suffix" value=".jsp"/>
        </bean>
    
    </beans>
    

    8.4 编写controller

    • src/main/java/com/dz/controller/HelloControler.java
    package com.dz.controller;
    
    import org.springframework.stereotype.Controller;
    import org.springframework.ui.Model;
    import org.springframework.web.bind.annotation.RequestMapping;
    
    @Controller
    public class HelloController {
    
        @RequestMapping("/hello")
        public String hello(Model model) {
            //向模型中添加属性msg和值, 可以在JSP页面中取出并渲染
            model.addAttribute("msg","Hello SpringMVCAnnotation!");
            // /WEB-INF/jsp/hello.jsp
            return "hello";
        }
    }
    
    

    8.5 编写jsp

    • WEB-INF下新建jsp目录, 然后在jsp目录下创建.jsp文件, 例如hello.jsp
    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <html>
    <head>
        <title>Title</title>
    </head>
    <body>
    ${msg}
    </body>
    </html>
    
    

    8.6 测试运行调试

    • 启动Tomcat
    • 注意: 如果我们之前运行过其他子项目时, 别忘了在Deploment中将上一个子项目删除掉, 换上我们本次需要运行的项目
      • Edit > Deployment > 减号去掉之前的项目 > 然后加号选择我们本次运行的项目
    • 任何时候出现1 字节的 UTF-8 序列的字节 1 无效 的错误请在总项目的pom.xml中配置以下编码格式
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
    

    九. 请求与响应

    9.1 RequestMapping

    • @RequestMapping注解用于映射url到控制器类或者一个特定的处理程序方法. 可用于类或方法上, 用于类上, 表示类中所有响应请求的方法都是以该地址作为父路径
    • localhost:8080/admin/t0
    package com.dz.controller;
    
    import org.springframework.stereotype.Controller;
    import org.springframework.ui.Model;
    import org.springframework.web.bind.annotation.RequestMapping;
    
    @Controller
    @RequestMapping("/admin")
    public class ControllerTest3 {
    
        @RequestMapping("/t0")
        public String test3(Model model) {
            String result = "Java 太棒啦!";
            model.addAttribute("msg",result);
    
            return "admin/test0";
        }
    }
    

    9.2 Restful风格

    • 概念

    • Restful就是一个资源定位及资源操作的风格. 不是标准也不是协议, 只是一种风格, 基于这个风格设计的软件可以更简洁, 更有层次, 更易于实现缓存等机制

    • 功能

      • 资源: 互联网所有事物都可以被抽象为资源
      • 资源操作: 使用POST, DELETE, PUT, GET, 使用不同的方法对资源进行操作
      • 分别对应 添加 删除 修改 查询
    • 传统方式操作资源: 通过不同的参数来实现不同的效果! 方法单一,post和get

    • 使用Restful操作资源: 可以通过不同的请求方式来实现不同的效果, 如下: 请求地址一样, 但是功能可以不同

    • 在SpringMVC中可以使用@PathVariable注解, 让方法参数的值对应绑定到一个URL模板变量上

    • @RequestMapping注解能够处理HTTP请求的方法,比如GET, PUT, POST, DELETE 以及 PATCH

    • 所有的地址栏请求默认都会是HTTP GET类型的

    • 方法级别的注解变体有如下几个: 组合注解

    • @GetMapping
      @PostMapping
      @PutMapping
      @DeleteMapping
      @PatchMapping
      
      //下面这两个注解实现了同样的功能
      @RequestMapping(value = "/add/{a}/{b}",method = RequestMethod.GET)
      
      @GetMapping("/add/{a}/{b}")
      
    package com.dz.controller;
    
    import org.springframework.stereotype.Controller;
    import org.springframework.ui.Model;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.PostMapping;
    
    @Controller
    public class RestfulController {
    
        //原来的: http://localhost:8080/add?a=1&b=3
        //Restful: http://localhost:8080/add/a/b
    
        //@RequestMapping(value = "/add/{a}/{b}",method = RequestMethod.GET)
        //@GetMapping("/add/{a}/{b}")
        @PostMapping("/add/{a}/{b}")
        public String test4(@PathVariable int a, @PathVariable String b, Model model) {
            String result = a + b;
            model.addAttribute("msg", "result1=" + result);
    
            return "test";
        }
    
        @GetMapping("/add/{a}/{b}")
        public String test5(@PathVariable int a, @PathVariable String b, Model model) {
            String result = a + b;
            model.addAttribute("msg", "result2=" + result);
    
            return "test";
        }
    
    }
    
    

    9.3 SpringMVC结果跳转方式

    9.3.1 ModelAndView
    • 设置ModelAndView对象, 根据view的名称, 和视图解析器跳到指定的页面
    • 页面: [视图解析器前缀] + viewName + [视图解析器后缀]
    <!--视图解析器-->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"
          id="internalResourceViewResolver">
        <!--前缀-->
        <property name="prefix" value="/WEB-INF/jsp/"/>
        <!--后缀-->
        <property name="suffix" value=".jsp"/>
    </bean>
    
    • 对应的controller类
    //方法一: 实现Controller接口
    // 在springmvc-servlet中配置 <bean name="/test1" class="com.dz.controller.ControllerTest1"/>
    public class ControllerTest1 implements Controller {
        @Override
        public ModelAndView handleRequest(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception {
    
            ModelAndView modelAndView = new ModelAndView();
            String result = "学习SpringMVC!";
            modelAndView.addObject("msg",result);
            modelAndView.setViewName("test");
            return modelAndView;
        }
    }
    
    • 使用注解时的controller
    package com.dz.controller;
    
    import org.springframework.stereotype.Controller;
    import org.springframework.ui.Model;
    import org.springframework.web.bind.annotation.RequestMapping;
    
    @Controller
    public class HelloController {
    
        //真实访问地址: 项目名/HelloController/hello
        @RequestMapping("/hello")
        public String hello(Model model) {
            //向模型中添加属性msg和值, 可以在JSP页面中取出并渲染
            model.addAttribute("msg","Hello SpringMVCAnnotation!");
            // /WEB-INF/jsp/hello.jsp
            return "hello";
        }
    }
    
    
    9.3.2 通过SpringMVC实现转发和重定向
    • 无需视图解析器, 测试前把视图解析器注释掉
    package com.dz.controller;
    
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;
    
    @Controller
    public class ModelTest1 {
    
        @RequestMapping("rsm/t1")
        public String test1() {
            //转发
            return "/index.jsp";
        }
    
        @RequestMapping("rsm/t2")
        public String test2() {
            //转发
            return "forward:/index.jsp";
        }
    
        @RequestMapping("rsm/t3")
        public String test3() {
            //重定向
            return "redirect:/index.jsp";
        }
    }
    
    
    • 实际使用时,

      • 转发:

        • return "test";
        • 或者 return "forward:test";
      • 重定向:

        • return "redirect:test";
    • 注意: test 要在WEB-INF下

    9.4 接收请求参数及数据回显

    • 接收请求参数
      • 接收前端用户传递的参数, 判断参数的名字, 假设名字直接在方法上, 可以直接使用
      • 假设前端传递的是一个对象User, 匹配User对象中的字段名, 如果名字一致就匹配到, 反之为null
      • 请确保前端传递的请求参数和后端方法的参数名一致
        • @RequestParam("username") 代表只能接收前端的请求参数为username
    package com.dz.controller;
    
    import com.dz.poji.User;
    import org.springframework.stereotype.Controller;
    import org.springframework.ui.Model;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestParam;
    
    @Controller
    @RequestMapping("/user")
    public class UserController {
    
        //http://localhost:8080/user/t?username=abc
        @GetMapping("/t")
        public String test(@RequestParam("username") String name, Model model) {
            //1. 接收前端参数
            System.out.println("接收到的参数名是: " + name);
            //2. 将返回的结果传递给前端 Model
            model.addAttribute("msg",name);
            //3. 视图跳转
            return "test";
        }
    
        /*
        1. 接收前端用户传递的参数, 判断参数的名字, 假设名字直接在方法上, 可以直接使用
        2. 假设前端传递的是一个对象User, 匹配User对象中的字段名, 如果名字一致就匹配到, 反之为null
    
         */
        @GetMapping("/t2")
        public String test2(User user) {
            System.out.println(user);
            return "test";
        }
    
        /*
        Model 只有几个方法适合用于存储数据, 简化了新手对Model对象的操作和理解
        ModelMap 继承了LinkedMap 除了实现了自身的一些方法, 同样继承了LinkedMap的方法和特性
        ModelAndView 可以在存储数据的同时, 进行设置返回的逻辑视图,进行控制展示层的跳转
         */
    
    
    }
    
    

    数据回显

    • Model 只有几个方法适合用于存储数据, 简化了新手对Model对象的操作和理解

    • ModelMap 继承了LinkedMap 除了实现了自身的一些方法, 同样继承了LinkedMap的方法和特性

    • ModelAndView 可以在存储数据的同时, 进行设置返回的逻辑视图,进行控制展示层的跳转

    9.5 乱码问题

    9.5.1 自己写一个过滤器
    package com.dz.filter;
    
    import javax.servlet.*;
    import java.io.IOException;
    
    public class EncodingFilter implements Filter {
        public void destroy() {
        }
    
        public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
            req.setCharacterEncoding("UTF-8");
            resp.setContentType("text/html;charset=UTF-8");
            chain.doFilter(req, resp);
        }
    
        public void init(FilterConfig config) throws ServletException {
    
        }
    
    }
    
    • 然后去web.xml里配置
    <filter>
        <filter-name>EncodingFilter</filter-name>
        <filter-class>com.dz.filter.EncodingFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>EncodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    
    9.5.2 使用SpringMVC的过滤器
    • 直接在web.xml里配置以下信息
    <filter>
        <filter-name>encoding</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>utf-8</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>encoding</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    

    9.6 JSON

    9.6.1 概念
    • 前后端分离时代
      • 后端部署后端, 提供接口, 提供数据
      • 前端独立部署, 负责渲染后端的数据
      • 所以前后端统一使用JSON进行数据交换
    • JSON(JavaScript Object Notation, JS对象标记) 是一种轻量级的数据交换格式, 目前使用特别广泛
      • 采用完全独立于编程语言的 文本格式 来存储和表示数据
      • 简洁和清晰的层次结构使得JSON成为理想的数据交换语言
      • 易于人阅读和编写, 同时也易于机器解析和生成, 并有效地提升网络传输效率
    • 在 JavaScript 语言中, 一切都是对象. 因此,任何JavaScript 支持的类型都可以通过 JSON 来表示, 例如字符串, 数字, 对象, 数组等, 其要求的语法格式为:
      • 对象表示为键值对, 数据由逗号分隔
      • 花括号保存对象
      • 方括号保存数组
    • JSON键值对 是用来保存 JavaScript 对象的一种方式, 和 JavaScript 对象的写法也大同小异, 键值对组合中的键名写在前面并用双引号 "" 包裹, 使用冒号: 分隔, 然后紧接着写值
    {"name": "张三"}
    {"age": "18"}
    {"sex": "男"}
    
    9.6.2 JSON和JavaScript
    • JSONJavaScript(以下简称JS) 对象的字符串表示法, 它使用文本表示一个JS对象的信息, 本质是一个字符串
    var obj = {name: "张三", age: 18, sex: "男"};//这是一个对象, 注意键名也是可以使用引号包裹的
    var json = '{"name":"张三","age":18,"sex":"男"}';//这是一个JSON字符串, 本质就是一个字符串
    
    • JSON 和 JS 对象转换
      • JSON字符串转为JS对象, 使用 JSON.parse() 方法
      • JS对象转为 JSON字符串, 使用 JSON.stringify() 方法
    <script type="text/javascript">
        //编写一个JavaScript对象
        var user = {
            name:"张三",
            age:18,
            sex:"男"
            };
        //将JS对象转换为JSON对象
        var json = JSON.stringify(user);
        console.log(json)
    
        console.log("==============")
    
        //将JSON对象转换为 JS对象
        var obj = JSON.parse(json);
        console.log(obj);
    </script>
    
    9.6.3 jackson [重点]
    • Java平台的JSON库, 用来序列化和反序列化 json 的 Java 的开源框架
    • 使用前先导入以下依赖
    <!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
        <version>2.12.3</version>
    </dependency>
    <!-- 顺便导一下lombok -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.16</version>
        <scope>provided</scope>
    </dependency>
    
    • @ResponseBody(用在方法上): 表示此方法不走视图解析器, 会直接返回一个字符串, @ResponseBody+@Controller=@RestController
    • @RestController(用在类上): 表示此类下的所有方法只会返回字符串
    • ObjectMapper: 调用writeValueAsString方法,把对象转为JSON格式
    package com.dz.controller;
    
    import com.dz.pojo.User;
    import com.fasterxml.jackson.core.JsonProcessingException;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    @RestController //表示此类下的所有方法都只会返回字符串
    public class UserController {
    
        //采用此方式也可以解决JSON乱码问题
        //@RequestMapping(value = "/j1", produces = "application/json;charset=utf-8")
        @RequestMapping(value = "/j1")
        //@ResponseBody //表示此方法不走视图解析器, 会直接返回一个字符串
        public String json1() throws JsonProcessingException {
    
            //jackson  ObjectMapper
            ObjectMapper mapper = new ObjectMapper();
            //创建一个对象
            User user = new User("张三",11,true);
    
            String str = mapper.writeValueAsString(user);
            return str;
            //User(name=??, age=11, sex=true) toString
            //{"name":"??","age":11,"sex":true} JSON
        }
    }
    
    • 如果出现乱码我们需要设置它的编码格式为utf-8, 以及返回类型,就是下面这句
    @RequestMapping(value = "/j1", produces = "application/json;charset=utf-8")
    
    9.6.4 统一解决JSON乱码问题 [重点]
    • 在springmvc-servlet.xml中做以下配置
    <!--JSON乱码问题配置-->
    <mvc:annotation-driven>
        <mvc:message-converters>
            <bean class="org.springframework.http.converter.StringHttpMessageConverter">
                <constructor-arg value="UTF-8"/>
            </bean>
            <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
                <property name="objectMapper">
                    <bean class="org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBean">
                        <property name="failOnEmptyBeans" value="false"/>
                    </bean>
                </property>
            </bean>
        </mvc:message-converters>
    </mvc:annotation-driven>
    
    9.6.5 返回前端一个JSON集合
    @RequestMapping("/j2")
    public String json2() throws JsonProcessingException {
    
        ObjectMapper mapper = new ObjectMapper();
    
        List<User> userList = new ArrayList<>();
        User user1 = new User("张三1",11,true);
        User user2 = new User("张三2",11,true);
        User user3 = new User("张三3",11,true);
        User user4 = new User("张三4",11,true);
    
        userList.add(user1);
        userList.add(user2);
        userList.add(user3);
        userList.add(user4);
    
        String str = mapper.writeValueAsString(userList);
    
        return str;//可以直接写成return new ObjectMapper().writeValueAsString(userList);
    }
    
    9.6.6 返回前端日期格式的JSON

    方法一: 因为ObjectMapper解析Date后的默认格式为: Timestamp: 时间戳, 所以我们先使用SimpleDateFormat把日期转为字符串,再被ObjectMapper解析

    @RequestMapping("/j3")
    public String json3() throws JsonProcessingException {
    
        ObjectMapper mapper = new ObjectMapper();
    
        Date date = new Date();
        //自定义日期的格式
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String s = sdf.format(date);
    
        // ObjectMapper解析Date后的默认格式为: Timestamp: 时间戳
        // 我们可以使用SimpleDateFormat把Date转为字符串,然后再被解析
        return mapper.writeValueAsString(s);
    }
    

    方法二 关闭ObjectMapper解析Date后默认的时间戳格式, 改用我们自定义的日期格式

    @RequestMapping("/j4")
    public String json4() throws JsonProcessingException {
    
        ObjectMapper mapper = new ObjectMapper();
        //不使用时间戳的格式
        mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
        //自定义日期的格式
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        mapper.setDateFormat(sdf);
    
        Date date = new Date();
    
        // ObjectMapper解析Date后的默认格式为: Timestamp: 时间戳
        return mapper.writeValueAsString(date);
    }
    
    9.6.7 JSON工具类 [重点]
    package com.dz.utils;
    
    import com.fasterxml.jackson.core.JsonProcessingException;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.fasterxml.jackson.databind.SerializationFeature;
    
    import java.text.SimpleDateFormat;
    
    public class JsonUtils {
    
        public static String getJson(Object object) {
            return getJson(object, "yyyy-MM-dd HH:mm:ss");
        }
    
        public static String getJson(Object object, String dateFormat) {
            ObjectMapper mapper = new ObjectMapper();
            //不使用时间戳的格式
            mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
            //自定义日期的格式
            SimpleDateFormat sdf = new SimpleDateFormat(dateFormat);
            mapper.setDateFormat(sdf);
    
            try {
                return mapper.writeValueAsString(object);
            } catch (JsonProcessingException e) {
                e.printStackTrace();
            }
    
            return null;
        }
    }
    
    
    @RequestMapping("/j5")
    public String json5() throws JsonProcessingException {
        //使用工具类后
        return JsonUtils.getJson(new Date());
    }
    
    • 使用工具类后给前端返回字符串变得就很简单,
      1. 提供想要被解析成JSON字符串的对象
      2. 使用JSON工具类解析并返回JSON字符串
    • 最后也是最重要的一点就是可以使用注解@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss"), 在日期属性上写上注解即可, 十分方便
    9.6.8 fastjson [重点]
    • fastjson是由alibaba开源的一套json处理器。与其他json处理器(如Gson,Jackson等)和其他的Java对象序列化反序列化方式相比,有比较明显的性能优势

    • 使用前先导入以下依赖

    <!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
        <version>1.2.75</version>
    </dependency>
    
    • 导入依赖后直接用就行, JSON.toJSONString(对象)
    @RequestMapping("/j6")
    public String json6() throws JsonProcessingException {
        //使用fastjson
        List<User> userList = new ArrayList<>();
        User user1 = new User("张三1",11,true);
        User user2 = new User("张三2",11,true);
        User user3 = new User("张三3",11,true);
        User user4 = new User("张三4",11,true);
    
        userList.add(user1);
        userList.add(user2);
        userList.add(user3);
        userList.add(user4);
    
        return JSON.toJSONString(userList);
    }
    

    9.7 jQuery

    • jQuery 库可以通过一行简单的标记被添加到网页中
    9.7.1 jQuery库特性
    • jQuery 是一个 JavaScript 函数库

    • jQuery 库包含以下特性:

      • HTML 元素选取
      • HTML 元素操作
      • CSS 操作
      • HTML 事件函数
      • JavaScript 特效和动画
      • HTML DOM 遍历和修改
      • AJAX
      • Utilities
    9.7.2 下载和使用
    • 下载
      • 使用前请先下载jquery-3.5.1.js(版本可自己选择), 然后web下创建/statics/js 放在此目录下(目录名自己定)
      • jquery下载地址: https://www.jq22.com/jquery-info122
    • 使用
    • jQuery 库位于一个 JavaScript 文件中,其中包含了所有的 jQuery 函数, 可以通过下面的标记把 jQuery 添加到网页中:
    <head>
    <script type="text/javascript" src="jquery.js"></script>
    </head>
    
    • 库的替代
      • 如果您不愿意在自己的计算机上存放 jQuery 库,那么可以从 Google 或 Microsoft 加载 CDN jQuery 核心文件。
    • 使用 Google 的CDN
    <head>
    <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs
    /jquery/1.4.0/jquery.min.js"></script>
    </head>
    
    • 使用 Microsoft 的 CDN
    <head>
    <script type="text/javascript" src="http://ajax.microsoft.com/ajax/jquery
    /jquery-1.4.min.js"></script>
    </head>
    

    9.8 AJAX

    9.8.1 简介
    • AJAX = Asynchronous JavaScript And XML(异步 JavaScript 及 XML)
      AJAX 是 Asynchronous JavaScript And XML 的首字母缩写。

    • AJAX 并不是一种新的编程语言,而仅仅是一种新的技术,它可以创建更好、更快且交互性更强的 web 应用程序。

    • AJAX 使用 JavaScript 在 web 浏览器与 web 服务器之间来发送和接收数据。

    • 通过在幕后与 web 服务器交换数据,而不是每当用户作出改变时重载整个 web 页面,AJAX 技术可以使网页更迅速地响应

    • AJAX 基于以下开放的标准:

      • JavaScript
      • XML
      • HTML
      • CSS
    • 在 AJAX 中使用的开放标准被良好地定义,并得到所有主要浏览器的支持。AJAX 应用程序独立于浏览器和平台。(可以说,它是一种跨平台跨浏览器的技术)

    • 使用AJAX技术之前先把JQuery库引用到页面中

    9.8.2 AJAX初体验
    • controller
    @RequestMapping("/a1")
        public void a1(String name, HttpServletResponse response) throws IOException {
            System.out.println("a1:param=>"+name);
            if ("dz".equals(name)) {
                response.getWriter().println("true");
            }else {
                response.getWriter().println("false");
            }
        }
    
    • html
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>iframe测试</title>
    
        <script>
            function go() {
                var url = document.getElementById("url").value;
                document.getElementById("iframe1").src=url;
            }
        </script>
    </head>
    <body>
    
    <div>
        <p>请输入地址: </p>
        <p>
            <input type="text" id="url" value="https://space.bilibili.com">
            <input type="button" value="提交" onclick="go()">
        </p>
    </div>
    
    <div>
        <iframe id="iframe1" style=" 100%;height: 500px"></iframe>
    </div>
    
    </body>
    </html>
    
    9.8.3 AJAX异步加载数据
    • controller
    @RequestMapping("/a2")
    public List<User> a2() {
        List<User> userList = new ArrayList<>();
        //添加数据
        userList.add(new User("dz1啦啦啦",1,"男"));
        userList.add(new User("dz2啦啦啦",1,"女"));
        userList.add(new User("dz3啦啦啦",1,"男"));
    
        return userList;
    }
    
    • jsp
    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <html>
    <head>
        <title>Title</title>
        <script src="${pageContext.request.contextPath}/statics/js/jquery-3.5.1.js"></script>
        <script>
            $(function () {
                $("#btn").click(function () {
                    /*
                    * $.post(url, param[可以省略], success)
                    * */
                    $.post("${pageContext.request.contextPath}/a2",function (data) {
                        //console.log(data)
                        var html="";
                        for (let i = 0; i < data.length; i++) {
                            html += "<tr>" +
                                "<td>" + data[i].name + "</td>" +
                                "<td>" + data[i].age + "</td>" +
                                "<td>" + data[i].sex + "</td>" +
                                "</tr>"
                        }
                        $("#content").html(html);
                    });
                });
            });
        </script>
    </head>
    <body>
    <input type="button" value="加载数据" id="btn">
    <table>
        <tr>
            <td>姓名</td>
            <td>年龄</td>
            <td>性别</td>
        </tr>
        <tbody id="content">
            <%--数据:后台--%>
        </tbody>
    </table>
    </body>
    </html>
    
    
    9.7.3 AJAX验证用户名密码
    • controller
    @RequestMapping("/a3")
    public String a3(String name,String pwd) {
        String msg = "";
        if (name != null) {
            //admin 这些数据应该在数据库中查
            if ("admin".equals(name)) {
                msg = "ok";
            }else {
                msg = "用户名有误";
            }
        }
        if (pwd != null) {
            //123456 这些数据应该在数据库中查
            if ("123456".equals(pwd)) {
                msg = "ok";
            }else {
                msg = "密码有误";
            }
        }
        return msg;
    }
    
    • jsp
    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <html>
    <head>
        <title>登陆</title>
    
        <script src="${pageContext.request.contextPath}/statics/js/jquery-3.5.1.js"></script>
        <script>
            function a1() {
                $.post({
                    url: "${pageContext.request.contextPath}/a3",
                    data: {"name":$("#name").val()},
                    success: function (data) {
                        if (data.toString()==='ok') {
                            $("#userInfo").css("color","green");
                        }else {
                            $("#userInfo").css("color","red");
                        }
                        $("#userInfo").html(data);
                    }
                })
            }
            function a2() {
                $.post({
                    url: "${pageContext.request.contextPath}/a3",
                    data: {"pwd":$("#pwd").val()},
                    success: function (data) {
                        if (data.toString()==='ok') {
                            $("#pwdInfo").css("color","green");
                        }else {
                            $("#pwdInfo").css("color","red");
                        }
                        $("#pwdInfo").html(data);
                    }
                })
            }
        </script>
    </head>
    <body>
    <p>
        用户名: <input type="text" id="name" onblur="a1()">
        <span id="userInfo"></span>
    </p>
    <p>
        密码: <input type="text" id="pwd" onblur="a2()">
        <span id="pwdInfo"></span>
    </p>
        
    </body>
    </html>
    
    
    • 如出现JSON乱码问题, 请看 9.6.4 小节

    9.9 SpringMVC 拦截器

    • SpringMVC的 处理器拦截器 类似于Servlet开发中的过滤器Filter, 用于对处理器进行预处理和后处理, 开发者可以自定义一些拦截器来实现特定的功能
    • 过滤器与拦截器的区别
      • 拦截器是AOP思想的具体应用
    • 过滤器
      • servlet规范中的一部分, 任何java web 工程都可以使用
      • 在 url-pattern 中配置了 /* 之后, 可以对所有要访问的资源进行拦截
    • 拦截器
      • 拦截器是SpringMVC框架自己的, 只有使用了SpringMVC框架的工程才能使用
      • 拦截器只会拦截访问的控制器方法, 如果访问的是jsp/html/css/images/js是不会进行拦截的
    9.9.1 自定义拦截器
    • 必须实现 HandlerInterceptor 接口

    • 步骤

    1. 新建一个Module springmvc-07-interceptor 添加web支持
    2. 配置web.xml 和 springmvc-servlet.xml 文件
    3. 编写一个拦截器
    package com.dz.config;
    
    import org.springframework.web.servlet.HandlerInterceptor;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    public class MyInterceptor implements HandlerInterceptor {
        // return true: 执行下一个拦截器, 放行
        // return false: 不执行下一个拦截器, 拦截
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            System.out.println("======处理前======");
            return true;
        }
    }
    
    • 然后去springmvc-servlet.xml中做以下配置,
      • /** 代表拦截所有访问controller的请求
    <!--拦截器配置-->
    <mvc:interceptors>
        <mvc:interceptor>
            <!--所有访问controller的请求都经过此拦截器-->
            <mvc:mapping path="/**"/>
            <bean class="com.dz.config.MyInterceptor"/>
        </mvc:interceptor>
    </mvc:interceptors>
    
    9.9.2 拦截器实现登陆判断验证
    • springmvc-servlet.xml中做以下配置,
    <mvc:interceptors>
        <mvc:interceptor>
            <!--/uer下的所有请求都经过此拦截器-->
            <mvc:mapping path="/user/**"/>
            <bean class="com.dz.config.LoginInterceptor"/>
        </mvc:interceptor>
    </mvc:interceptors>
    
    • LoginController.java
    package com.dz.controller;
    
    import org.springframework.stereotype.Controller;
    import org.springframework.ui.Model;
    import org.springframework.web.bind.annotation.RequestMapping;
    
    import javax.servlet.http.HttpSession;
    
    @Controller
    @RequestMapping("/user")
    public class LoginController {
    
        //跳到主页面
        @RequestMapping("/toMain")
        public String toMain() {
            return "main";
        }
    
        //跳到登陆页面
        @RequestMapping("/toLogin")
        public String toLogin() {
            return "login";
        }
    
        //登陆功能
        @RequestMapping("/login")
        public String login(HttpSession session, String username, String password, Model model) {
            //把用户的信息存在session中
            session.setAttribute("userLoginInfo",username);
            model.addAttribute("userLoginInfo",username);
            return "main";
        }
    
        //注销功能
        @RequestMapping("/logout")
        public String logout(HttpSession session) {
            //把用户的信息存在session中
            session.removeAttribute("userLoginInfo");
    
            return "redirect:main";
        }
    }
    
    • LoginInterceptor.java
    package com.dz.config;
    
    import org.springframework.web.servlet.HandlerInterceptor;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import javax.servlet.http.HttpSession;
    
    public class LoginInterceptor implements HandlerInterceptor {
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            HttpSession session = request.getSession();
            //放行: 判断什么情况下登陆
    
            //进入登陆页面放行
            if (request.getRequestURI().contains("toLogin")) {
                return true;
            }
            //提交登陆时放行
            if (request.getRequestURI().contains("login")) {
                return true;
            }
            //session不为空放行
            if (session.getAttribute("userLoginInfo") != null){
                return true;
            }
            //否则转发到登陆界面
            request.getRequestDispatcher("/WEB-INF/jsp/login.jsp").forward(request,response);
    
            return false;
        }
    }
    
    • index.jsp
    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <html>
      <head>
        <title>$Title$</title>
      </head>
      <body>
      <h1><a href="${pageContext.request.contextPath}/user/toLogin">登陆页面</a></h1>
    
      <h1><a href="${pageContext.request.contextPath}/user/toMain">首页</a></h1>
      </body>
    </html>
    
    • login.jsp
    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <html>
    <head>
        <title>登陆页面</title>
    </head>
    <body>
    
    <h1>登陆页面</h1>
    <form action="${pageContext.request.contextPath}/user/login" method="post">
        用户名: <input type="text" name="username">
        密码: <input type="text" name="password">
        <input type="submit" value="提交">
    
    </form>
    
    
    </body>
    </html>
    
    • main.jsp
    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <html>
    <head>
        <title>Title</title>
    </head>
    <body>
    <h1>首页</h1>
    
    <span>${userLoginInfo}</span>
    <p>
        <a href="${pageContext.request.contextPath}/user/logout">退出登陆</a>
    </p>
    </body>
    </html>
    

    9.10 SpringMVC文件上传和下载

    • 文件上传是项目开发中最常见的功能之一, SpringMVC上下文中默认没有装配MultipartResolver, 因此默认情况下不能处理文件上传工作, 如果想要使用Spring的文件上传功能, 则需 在上下文中配置MultipartResolver
    • 前端表单要求:: 为了能上传文件, 必须将表单的method设置为post, 并将enctype设置为 multipart/form-data. 只有在这样的情况下, 浏览器才会把用户选择的文件以二进制数据发送给服务器
    9.10.1 导入相关依赖
    <!--commons-fileupload-->
    <dependency>
        <groupId>commons-fileupload</groupId>
        <artifactId>commons-fileupload</artifactId>
        <version>1.4</version>
    </dependency>
    <!--javax.servlet-api 导入高版本的-->
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>javax.servlet-api</artifactId>
        <version>4.0.1</version>
        <scope>provided</scope>
    </dependency>
    
    9.10.2 配置springmvc.xml
    <!--文件上传配置-->
        <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
            <!--请求的编码格式, 必须和jsp的pageEncoding属性一致, 以便正确读取表单内容, 默认是ISO-8859-1-->
            <property name="defaultEncoding" value="utf-8"/>
            <!--上传文件大小上限, 单位为字节 (10485760=10M)-->
            <property name="maxUploadSize" value="10485760"/>
            <property name="maxInMemorySize" value="40960"/>
        </bean>
    
    9.10.3 index.jsp
    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <html>
      <head>
        <title>文件上传</title>
      </head>
      <body>
      <form action="${pageContext.request.contextPath}/upload" enctype="multipart/form-data" method="post">
        <input type="file" name="file">
        <input type="submit" value="upload">
      </form>
    
      <a href="${pageContext.request.contextPath}/download">下载图片</a>
      </body>
    </html>
    
    
    9.10.4 FileController.java
    package com.dz.controller;
    
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestParam;
    import org.springframework.web.bind.annotation.RestController;
    import org.springframework.web.multipart.commons.CommonsMultipartFile;
    
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.*;
    import java.net.URLEncoder;
    
    @RestController
    public class FileController {
        //文件上传
        @RequestMapping("/upload")
        public String fileUpload(@RequestParam("file") CommonsMultipartFile file, HttpServletRequest request) throws IOException, ServletException {
    
            //获取文件名 file.getOriginalFilename()
            String uploadFilename = file.getOriginalFilename();
    
            //如果文件名为空, 直接返回到首页
            if ("".equals(uploadFilename)) {
                return "redirect:/index.jsp";
            }
            System.out.println("上传文件名: " + uploadFilename);
    
            //上传路径保存设置
            String path = request.getServletContext().getRealPath("/WEB-INF/upload");
            //如果路径不存在, 就创建一个
            File realpath = new File(path);
            if (!realpath.exists()) {
                realpath.mkdirs();
            }
            System.out.println("上传文件保存地址: " + realpath);
            //文件输入流
            InputStream is = file.getInputStream();
            //文件输出流
            OutputStream os = new FileOutputStream(new File(realpath, uploadFilename));
    
            //读写操作
            int len = 0;
            byte[] buffer = new byte[1024];
            while ((len=is.read(buffer))!=-1){
                os.write(buffer,0,len);
                os.flush();
            }
            os.close();
            is.close();
    
            return "redirect:index";
    
        }
    
        //文件下载
        @RequestMapping("/download")
        public String fileDownload(HttpServletRequest request, HttpServletResponse response) throws IOException {
            //要下载的图片地址
            String path = request.getServletContext().getRealPath("/WEB-INF/upload");
            String filename = "butterfly.jpg";
    
            //1. 设置response 响应头
            response.reset();//设置页面不缓存,清空buffer
            response.setCharacterEncoding("UTF-8");//字符编码
            response.setContentType("multipart/form-data");//二进制传播数据
            //设置响应头, 告诉浏览器响应的文件类型和如何处理该文件, 附件下载
            response.setHeader("content-disposition", "attachment;filename=" + URLEncoder.encode(filename,"UTF-8"));
    
            //2. 使用流读取
            File file = new File(path, filename);
            //输入流
            InputStream is = new FileInputStream(file);
            //输出流
            OutputStream os = response.getOutputStream();
            byte[] buffer = new byte[1024*1024*100];
            int len = 0;
            while ((len=is.read(buffer)) != -1) {
                os.write(buffer,0,len);
            }
    
            //3. 关闭流
            is.close();
            os.close();
    
            return "redirect";
        }
    }
    
  • 相关阅读:
    操盘之王 摘要
    转贴:Icon Design Guidelines, Android 2.0
    低端用户反文化这个问题真这么简单?
    关于沟通的一些细节(二)主动权
    不仅仅是开发控件《道不远人深入解析ASP.NET 2.0控件开发》作者序
    partition生成规则
    linux send与recv函数详解
    exec函数族实例解析
    在ubuntu 下 编译 在windows 下面使用的adb
    Android属性之build.prop生成过程分析
  • 原文地址:https://www.cnblogs.com/MRASdoubleZ/p/14769695.html
Copyright © 2020-2023  润新知