认识SSM框架----SpringMVC

SpringMVC 简介

Spring MVC 和 Servlet技术相同,都是做Web层(表现层)开发的
Spring MVC 比 Servlet更简便,可以用更少的代码完成功能

SpringMVC是一种基于java实现MVC模型的轻量级Web框架

如何快速搭建一个极简的SpringMVC项目:

  1. 从原型创建一个maven项目,在项目路径的main文件夹下创建一个java源码文件夹用于写项目代码
    在这里插入图片描述 在这里插入图片描述

  2. 在pom.xml中导入spring-webmvc和Servlet的坐标

    <dependencies>
      <!--   1. 导入坐标spring-webmvc和Servlet-->
      <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
        <version>5.2.10.RELEASE</version>
      </dependency>
      <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>javax.servlet-api</artifactId>
        <version>3.1.0</version>
        <scope>provided</scope>    <!-- 这个和Tomcat插件会有冲突,因此要配成provided -->
      </dependency>
    </dependencies>
    
  3. 定义C层的controller。首先使用@Controller注解定义bean,然后写业务方法,用@RequestMapping注解设置当前业务方法的访问路径,并用@ResponseBody设置当前方法的返回值类型

    // 2. 定义controller
    @Controller  // 2.1 使用@Controller定义bean
    public class UserController {
        @RequestMapping("/save")  // 2.2 写操作,并用@RequestMapping设置访问路径
        @ResponseBody   // 2.3 设置当前操作的返回值类型。意思是将返回的东西整体作为响应的内容给到外面 
        public String save(){
            System.out.println("user save ...");
            return "{'module':'springmvc'}";
        }
    }
    
  4. 创建SpringMVC的配置文件(本质上还是Spring的配置文件),加载controller对应的bean

    // 3. 创建SpringMVC的配置文件,加载controller对应的bean
    @Configuration
    @ComponentScan("com.itheima.controller")
    public class SpringMvcConfig {
    }
    
  5. 定义一个Servlet容器启动初始化的配置类,用于加载spring的配置

    // 4. 定义一个Servlet容器启动的配置类,在里面加载spring的配置
    public class ServletContainerInitConfig extends AbstractDispatcherServletInitializer {
        // 加载SpringMVC容器的配置,Tomcat启动时会自动加载
        @Override
        protected WebApplicationContext createServletApplicationContext() {
            AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
            ctx.register(SpringMvcConfig.class);
            return ctx;
        }
        // 设置哪些请求归属SpringMVC处理
        @Override
        protected String[] getServletMappings() {
            return new String[]{"/"};    // 所有请求归SpringMVC处理
        }
        // 加载Spring容器的配置
        @Override
        protected WebApplicationContext createRootApplicationContext() {
            return null;
        }
    }
    
  6. 在pom.xml中配置Tomcat插件,并设置以Tomcat启动,然后运行就可以访问了

      <!-- 配置Tomcat插件 -->
      <build>
        <plugins>
          <plugin>
            <groupId>org.apache.tomcat.maven</groupId>
            <artifactId>tomcat7-maven-plugin</artifactId>
            <version>2.1</version>
            <configuration>
              <port>80</port>
              <path>/</path>
            </configuration>
          </plugin>
        </plugins>
      </build>
    

    在这里插入图片描述

项目工作流程分析

启动服务器初始化过程
简洁版:将springMVC的配置加载到Tomcat的容器中,同时设置所有请求由SpringMVC处理
详细版:

  • 服务器启动,执行ServletContainersInitConfig类,初始化Web容器
    • 执行createServletApplicationContext方法,创建WebApplicationContext对象ctx
    • 用ctx注册SpringMvcConfig
      • 执行@ComponentScan加载所有的bean
      • 建立Controller Bean中的每个@RequestMapping的名称与方法的映射关系(注册请求路径和对应方法)
    • 执行getServletMappings方法,定义所有的请求都通过SpringMVC
      在这里插入图片描述

单次请求过程

  • 客户端发起请求
  • web容器发现所有请求被设置都要经过 SpringMVC,将请求交给 SpringMVC 处理
  • SpringMVC 解析请求路径,执行对应的方法
  • 检测到方法有@ResponseBody注解,将方法的返回值作为响应体返回给请求方

Bean加载控制

如何加载Bean呢?
用Web容器加载 Spring 和 SpringMVC 配置

public class ServletContainersInitConfig extends AbstractDispatcherServletInitializer {
    @Override
    protected WebApplicationContext createServletApplicationContext() {
        // 加载SpringMVC环境
        AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
        ctx.register(SpringMvcConfig.class);
        return ctx;
    }
    @Override
    protected WebApplicationContext createRootApplicationContext() {
        // 加载Spring环境
        AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
        ctx.register(SpringConfig.class);
        return ctx;
    }
    @Override
    protected String[] getServletMappings() {
        return new String[]{"/"};
    }
}

下面这种事上面的简化开发版本,只需要提供配置类即可
public class ServletContainersInitConfig extends AbstractAnnotationConfigDispatcherServletInitializer {
    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class[]{SpringConfig.class};
    }

    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class[]{SpringMvcConfig.class};
    }

    @Override
    protected String[] getServletMappings() {
        return new String[]{"/"};
    }
}

SpringMVC 要控制表现层的相关Bean(Controller包中的)
Spring 要控制业务层和数据层的相关Bean(Service和Dao包中的)

如何避免Spring错误加载SpringMVC 中的 Bean?
加载 Spring 控制的 Bean 时排除掉 SpringMVC 控制的 Bean
SpringMVC 只加载controller包内的bean

//@Configuration
@ComponentScan("com.itheima.controller")
public class SpringMvcConfig {
}

Spring

  • 设置 SpringConfig 扫描加载所有包,排除掉controller(若要使用排除,必须去掉SpringMvcConfig上的@Configuration,不然Spring会将SpringMvcConfig扫上,SpringConfig就白排除了)
  • 设置 SpringConfig 扫描范围为精准范围,如只扫 Service 和 Dao

或者 不区分Spring和SpringMVC的环境,加载到同一环境中

@Configuration
//@ComponentScan({"com.itheima.service", "com.itheima.dao"})
// 或
@ComponentScan(value = "com.itheima",
    excludeFilters = @ComponentScan.Filter(
        type = FilterType.ANNOTATION,    // 按注解过滤
        classes = Controller.class    // 排除Controller注解的
    )
)
public class SpringConfig {
}

PostMan(接口测试工具)

网页调试 与 发送HTTP请求的Chrome插件,常用于接口测试

请求与响应

请求映射路径

在Controller或其方法上用**@RequestMapping**注解可以设置请求访问路径
为避免方法名冲突,有两种做法

  1. 将每个方法的请求映射路径设置为:模块名 + 方法名

    @Controller
    public class UserController {
        @RequestMapping("/user/save")
        @ResponseBody
        public String save(){
            return "{'moudle':'user save'}";
        }
        @RequestMapping("/user/delete")
        @ResponseBody
        public String delete(){
            return "{'moudle':'user delete'}";
        }
    }
    
  2. 为Controller设置请求路径前缀,然后每个方法的路径依然设置为方法名

    @Controller
    @RequestMapping("/user")
    public class UserController {
        @RequestMapping("/save")
        @ResponseBody
        public String save(){
            return "{'moudle':'user save'}";
        }
        @RequestMapping("/delete")
        @ResponseBody
        public String delete(){
            return "{'moudle':'user delete'}";
        }
    }
    

请求

前端携带数据发起请求,后端接收数据并做处理

Get请求和Post请求

Get和Post的区别:

SpringMVC 如何接参数

在对应方法中用形参接收

请求:http://localhost/commonParam?name=zjh&id=18
// 普通参数
@RequestMapping("/commonparam")
@ResponseBody
public String commonParam(String name, String id){
    System.out.println("普通参数传递 name ===>" + name);
    System.out.println("普通参数传递 id ===>" + id);
    return "{'module':'common param'}";
}

中文乱码怎么办?在Web容器初始化配置中添加过滤器

public class ServletContainersInitConfig extends AbstractAnnotationConfigDispatcherServletInitializer {
    // 乱码处理,为web容器添加一个字符编码过滤器,并指定字符集
    @Override
    protected Filter[] getServletFilters() {
        // 用Spring-web提供的字符过滤器
        CharacterEncodingFilter filter = new CharacterEncodingFilter();
        filter.setEncoding("UTF-8");
        return new Filter[]{filter};
    }
}
一般数据

请求参数类型

  1. 普通参数:URL地址传参
  • 请求参数名 = 形参名:直接接收
  • 请求参数名 ≠ 形参名:用**@RequestParam**注解绑定参数关系
  1. POJO类型参数
  • 请求参数名形参对象属性名 相同,定义POJO类型形参即可接收
  • 若存在POJO嵌套,子类的对应请求参数要用"类名.属性名"作为 请求参数名
  1. 数组类型参数
  • 多个 同名请求参数,用数组类型的同名形参即可接收
  1. 集合类型参数
  • 传参与数组类型相同,区别在于List是集合接口,直接用同名的List类型对象会报错(因为是要把参数放到集合对象里,而不是作为集合对象的某个属性,且接口无法直接实例化一个对象),需要用**@RequestParam**注解修饰List,将参数作为List的元素进行接收

总结:默认请求名和参数名要对应,对不上的用@RequestParam绑定对应关系

@Controller
//@RequestMapping("/user")
public class UserController {
    // 普通参数
    @RequestMapping("/commonParam")
    @ResponseBody
    public String commonParam(String name, String id){
        System.out.println("普通参数传递 name ===>" + name);
        System.out.println("普通参数传递 id ===>" + id);
        return "{'module':'common param'}";
    }
    // 普通参数:请求参数名与形参名不同
    @RequestMapping("/commonParamDifName")
    @ResponseBody
    public String commonParamDifName(@RequestParam("name") String userName, String id){
        System.out.println("普通参数传递 userName ===>" + userName);
        System.out.println("普通参数传递 id ===>" + id);
        return "{'module':'common param'}";
    }
    // POJO参数(可以嵌套传递)
    @RequestMapping("/pojoParam")
    @ResponseBody
    public String commonParamDifName(User user){
        System.out.println("POJO参数传递 user ===>" + user);
        return "{'module':'common param'}";
    }
    // 数组参数
    @RequestMapping("/arrayParam")
    @ResponseBody
    public String arrayParam(String[] likes){
        System.out.println("数组参数传递 likes ===>" + Arrays.toString(likes));
        return "{'module':'common param'}";
    }
    // 集合参数
    @RequestMapping("/listParam")
    @ResponseBody
    public String listParam(@RequestParam List<Address> likes){
        System.out.println("集合参数传递 likes ===>" + likes);
        return "{'module':'common param'}";
    }
}
json格式数据

步骤:
①添加json数据转换的坐标(jackson-databind)
②在SpringMvcConfig上用 @EnableWebMvc 注解开启自动转换json数据的功能,这个不是该注解的全部作用
③发送json数据
④设置接收json数据。用 @RequestBody 修饰方法形参,从请求体中取数据赋值给形参

json数据怎么发:

  1. 普通 json数组
    [“game”,“travel”,“music”]
  2. POJO json数据
    {“name”:“zjh”, “age”:“18”, “address”:{“province”:“gansu”, “city”:“lanzhou”}}
  3. POJO json数组
    [{“name”:“zjh”, “age”:“18”}, {“name”:“zjh”, “age”:“18”}]

@RequestParam和@RequestBody的区别

  • @RequestParam 用于接收 URL地址传参、表单传参
  • @RequestBody 用于接收 json数据
日期类型参数

不同系统中的日期格式也不同,如2088-08-18、2088/08/18、08/18/2088
因此接收参数时,要根据不同的日期格式设置不同的接收方式
可以通过设置 @DateTimeFormat 注解的pattern属性值实现,用该注解修饰对应的形参

该功能是通过converter接口的一个实现类实现的
@EnableWebMvc的功能之一:根据类型匹配对应的类型转换器

// 日期参数
@RequestMapping("/dateParam")
@ResponseBody
public String dateParam(Date date,
                        @DateTimeFormat(pattern = "yyyy-MM-dd") Date date1,
                        @DateTimeFormat(pattern = "yyyy/MM/dd HH:mm:ss") Date date2
                        ){
    System.out.println("参数传递 date ===>" + date);
    System.out.println("参数传递 date (yyyy/MM/dd) ===>" + date1);
    System.out.println("参数传递 date (yyyy/MM/dd HH:mm:ss) ===>" + date2);
    return "{'module':'data param'}";
}

响应

后端将处理结果反馈给前端,前端进行展示

@ResponseBody注解:用于设置将对应方法的返回的对象转换格式(转为JSON)后写入到response的body区,作为响应体
由HttpMessageConverter接口的实现类转,他是专门做web消息类型转换,他依赖于Jackson坐标

响应页面

将页面名称作为字符串return即可,不加@ResponseBody注解

// 响应页面(跳转页面)
@RequestMapping("/toJumpPage")
public String toJumpPage(){
  System.out.println("跳转页面");
  return "index.jsp";
}

响应数据

需要用@ResponseBody注解修饰方法,否则Spring默认方法返回值是一个页面

文本数据:简单的字符串

// 响应文本数据
@RequestMapping("/toText")
@ResponseBody    // 将返回的对象转换格式后写入到response的body区
public String toText(){
    System.out.println("返回纯文本");
    return "response text";
}

JSON数据:键值对

// 响应POJO对象(JSON数据)
// 将方法返回类型设定为POJO对象类型,然后return POJO对象即可
@RequestMapping("/toJsonPOJO")
@ResponseBody
public User toJsonPOJO(){
    User user = new User();
    user.setName("zjh");
    user.setAge(18);
    return user;
}

// 响应POJO对象集合(JSON数据)
// 将方法返回类型设定为POJO对象类型的List,然后return List对象即可
@RequestMapping("/toJsonList")
@ResponseBody
public List<User> toJsonList(){
    User user1 = new User();
    user1.setName("aaa");
    user1.setAge(15);

    User user2 = new User();
    user2.setName("bbb");
    user2.setAge(19);

    List<User> userList = new ArrayList<User>();
    userList.add(user1);
    userList.add(user2);
    return userList;
}

REST风格

REST

(Representational State Transfer):表现形式状态转换

传统风格资源描述:

  • http://localhost/user/getById?id=1
  • http://localhost/user/saveUser

REST风格资源描述:

  • http://localhost/user/1
  • http://localhost/user

优点

  • 书写简化
  • 隐藏资源访问行为,无法通过地址得知操作类型

如何区分操作

按照REST风格访问资源时通过行为动作(请求方式)区分对资源如何操作
路径 + 请求方式 = 具体访问行为
SpringMVC 支持8种请求方式,常用的有4种

资源访问路径功能请求方式
http://localhost/users查询全部用户信息Get(查询)
http://localhost/users/1查询指定用户信息Get(查询)
http://localhost/users添加用户信息POST(新增/保存)
http://localhost/users修改用户信息PUT(修改/更新)
http://localhost/users/1删除用户信息DELETE(删除)

在REST风格中,描述模块的名称通常使用复数,即加s,表示此类资源而非单个资源,如users、books

根据REST风格对资源进行访问称为RESTful

实现方法

  1. 设置请求方式同一个controller内,在@RequestMapping注解中,value设置为相同的(如/users),用method属性设置http的请求方式
  2. 设置请求参数
    • 路径参数:①用@PathVariable修饰对应方法的形参 ②路径处用{形参名}绑定参数和形参
      @RequestMapping(value = "/users/{id}&{name}", method = RequestMethod.DELETE)
      @ResponseBody
      public String delete(@PathVariable Integer id, @PathVarible String name){
         // @PathVariable表示从请求路径中取值,在@RequestMapping中用大括号{形参名}绑定路径中的值和形参
         System.out.println("user delete" + id + name);
         return "{'module':'user delete'}}";
      }
      

快速开发

  • @RestController = @Controller + @ResponseBody
    设置当前控制器类为RESTful风格,将方法级别要写的@ResponseBody提到类级别

  • 用@RequestMapping注解设置对应控制器的根访问路径,简化每个方法上的访问路径设置

  • @请求方式Mapping = @RequestMapping(method = RequestMethod.请求方式)
    若还有路径参数,则@请求方式Mapping(“/{id}”)

@RestController
@RequestMapping("/books2")
public class BookController2 {
    @PostMapping
    public String save(@RequestBody Book book){
        System.out.println("book save" + book);
        return "{'module':'book save'}}";
    }
    @DeleteMapping("/{id}")
    public String delete(@PathVariable Integer id){
        System.out.println("book delete" + id);
        return "{'module':'book delete'}}";
    }
}

基于RESTful的页面数据交互

要求:前端将数据发送给后端,后端接收后将反馈数据返回给前端,其中通过REST风格进行资源访问

重点

设置对静态资源的放行。在Servlet容器初始化时设置了默认对资源的访问都由SpringMVC接管,这会导致webapp下的静态资源(html,css,js等)无法访问。此时需要创建一个配置类(继承WebMvcConfigurationSupport)去设置放行相关资源,在SpringMvcConfig中扫描config路径加载该配置到Servlet容器中

@Configuration
public class SpringMvcSupport extends WebMvcConfigurationSupport {
    @Override
    protected void addResourceHandlers(ResourceHandlerRegistry registry) {
        // 实现资源的普通访问:当访问某些路径时(如/pages/???),走/pages目录下的内容,不经过SpringMVC
        registry.addResourceHandler("/pages/**").addResourceLocations("/pages/");
        registry.addResourceHandler("/js/**").addResourceLocations("/js/");
        registry.addResourceHandler("/css/**").addResourceLocations("/css/");
        registry.addResourceHandler("/plugins/**").addResourceLocations("/plugins/");
        super.addResourceHandlers(registry);
    }
}

拦截器

在这里插入图片描述

拦截器是一种动态拦截方法调用的机制,在SpringMVC中动态拦截控制器方法的执行
可以在指定方法调用前后执行预先设定的代码;或阻止原方法的执行

拦截器与过滤器的区别
归属:Filter属于Servlet技术,Interceptor属于SpringMVC技术
拦截内容:Filter对所有访问进行增强,Interceptor仅针对SpringMVC的访问进行增强

实现

制作拦截器功能类
实现 HandlerInterceptor 接口

@Component
public class ProjectInterceptor implements HandlerInterceptor {
    @Override
    方法执行前
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("pre"); 
        // 这里return false可以终止原始方法的运行
        return true;
    }
    方法完整执行后(即不报错)
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("post");
    }
    拦截完成后执行后
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("after");
    }
}

配置拦截器执行位置
通过 WebMvcConfigurationSupport 接口

@Configuration
public class SpringMvcSupport extends WebMvcConfigurationSupport {
    @Autowired
    private ProjectInterceptor projectInterceptor;
    @Override
    protected void addInterceptors(InterceptorRegistry registry) {
        // addPathPatterns支持可变参数,往后加路径就行,支持任意多个
        registry.addInterceptor(projectInterceptor).addPathPatterns("/books", "/books/*");
}

参数

  • preHandle
    • request:请求对象
    • response:响应对象
    • handler:被调用的方法对象,是反射中的Method方法的再包装。可以通过它拿到原始执行的对象,然后通过反射为所欲为
      HandlerMethod hm = (HandlerMethod) handler;
      System.out.println(hm.getMethod().getParameters());
      
    • 返回值:true执行post和after;false终止原始方法的运行
  • postHandle
    • ModelAndView:封装了SpringMVC页面跳转的一些相关数据,如页面名称等
  • afterHandle
    • Exception:ex是运行过程中抛出的异常,但是Spring有异常处理机制可以替换它

拦截器链

配置

写多个拦截器,在WebMvcConfigurationSupport 接口的addIntercrptors方法中多次配置拦截器

@Configuration
public class SpringMvcSupport extends WebMvcConfigurationSupport {
    @Autowired
    private ProjectInterceptor projectInterceptor;
    @Autowired
    private ProjectInterceptor2 projectInterceptor2;
    @Override
    protected void addInterceptors(InterceptorRegistry registry) {
        // addPathPatterns支持可变参数,往后加路径就行,支持任意多个
        registry.addInterceptor(projectInterceptor).addPathPatterns("/books", "/books/*");
        registry.addInterceptor(projectInterceptor2).addPathPatterns("/books", "/books/*");
    }
}

执行顺序

整体来说,按照拦截器的配置添加顺序依次栈执行
对于preHandle,按添加顺序执行;对于 postHandle和 afterCompletion ,按添加顺序的逆序执行
每层拦截器的pre返回true才会进入下一层拦截器,否则级联跳过下一层拦截器
如:若pre2返回false,不会进入pre3那一层,而是直接执行post1和after1

  • pre1
    • pre2
      • pre3
        • controller
      • post3
      • after3
    • post2
    • after2
  • post1
  • after1

注解速记

请求参数是URL路径**?后**的参数
http://localhost:8888/SpringMVC/main ?name=springmvc
路径变量是在URL项目名后 ?之前 所跟的信息,
http://localhost:8888/SpringMVC/3769?

http://localhost/api/employee/status/1?id=2
其中1为路径变量,需要在后端用@PathVariable注解修饰对应参数,id为请求参数,同名形参即可接收

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值