SpringMVC源码分析------基础知识(二)

项目地址
SpringMVC_02
觉得博主还可以给个Star

1.注解形式代替web.xml
maven引入

<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-webmvc</artifactId>
  <version>5.0.5.RELEASE</version>
</dependency>

我们可以先看到
在这里插入图片描述这一块熟悉吗?我们在上面讲到ServletContainerInitializer的时候目录一模一样,只有类名不同。
里面添加的自写的org.springframework.web.SpringServletContainerInitializer他的作用是和ServletContainerInitializer一样的,加载第三方依赖实现初始化操作。这也是springboot不使用web.xml的关键部分。

现在我们要完全摆脱web.xml就要先了解我们之前在web.xml中接入了什么配置。在以前的文章之中有过配置(跳转)里面有着扫描注解,开启mvc事务,当然还有个很重要的xml文件。以上三个可以使用@ComponentScan、@EnableWebMvc、@Configuration。在Web.xml中还有一个很重要的DispatcherServlet过滤器。这个我们需要怎么去注入呢?
借助WebApplicationInitializer类来实现
具体代码如下:
MyConfig.java:

package com.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.view.InternalResourceViewResolver;

/**
 * @author 龙小虬
 * @date 2021/3/9 17:33
 */
@Configuration
@ComponentScan("com.controller")
@EnableWebMvc
public class MyConfig {
    
    
}

WebInitializer.java:

package com.config;

import org.springframework.web.WebApplicationInitializer;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRegistration;

/**
 * @author 龙小虬
 * @date 2021/3/9 18:06
 */
public class WebInitializer implements WebApplicationInitializer {
    
    
    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
    
    
        // 1.   创建SpringMVC容器
        AnnotationConfigWebApplicationContext app = new AnnotationConfigWebApplicationContext();
        // 2. 注册我们的配置文件
        app.register(MyConfig.class);
        // 注册我们的
        DispatcherServlet dispatcherServlet = new DispatcherServlet(app);
        ServletRegistration.Dynamic dynamic = servletContext.addServlet("dispatcherServlet", dispatcherServlet);
        dynamic.addMapping("/");
        dynamic.setLoadOnStartup(1);// 最优先启动
    }
}

MyController.java:

package com.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author 龙小虬
 * @date 2021/3/9 17:33
 */
@Controller
public class MyController {
    
    

    // produces = "text/html;charset=UTF-8" 解决中乱码
    @RequestMapping(value = "/member",produces = "text/html;charset=UTF-8")
    @ResponseBody
    public String Member(){
    
    
        return "解决掉web.xml";
    }
}

现在我们就正是启动成功了,并且没有web.xml,但是我们会发现他不能转发到*.jsp。所以我们需要进行配置。
MyConfig.java:

package com.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.view.InternalResourceViewResolver;

/**
 * @author 龙小虬
 * @date 2021/3/9 17:33
 */
@Configuration
@ComponentScan("com.controller")
@EnableWebMvc
public class MyConfig {
    
    

    @Bean
    public ViewResolver viewResolver() {
    
    
        InternalResourceViewResolver internalResourceViewResolver = new InternalResourceViewResolver();
        // 前缀
        internalResourceViewResolver.setPrefix("/WEB-INF/view/");
        // 后缀
        internalResourceViewResolver.setSuffix(".jsp");
        return internalResourceViewResolver;
    }

}

加入此代码即可。(切记,默认情况下先走web.xml,所以必须先把web.xml等xml去掉)
代码完成了。可以思考一个问题。
WebApplicationInitializer的实现类我们并没有进行注入但是可以使用,为何?
在之前我们看过一个jar包中的内容,里面有着“org.springframework.web.SpringServletContainerInitializer”,我们进入这个类,可以看到
在这里插入图片描述是不是很熟悉?这就是我们之前使用过的注解?他会将实现了
WebApplicationInitializer类的子类全部注入。

2.@RestController和@Controller区别
点开@RestController可以发现
在这里插入图片描述里面包含了@Controller、@ResponseBody两个注解,也就是说@RestController有着这两个注解的功能,一个是返回JSON数据,一个是bean注入的的注解。

3.拦截器与过滤器

  1. 相同点:都是基于AOP技术,对方法实现增强,都可以拦截请求的方法
  2. 不同点:
    1)过滤器属于servlet自己研发的,拦截器技术属于SpringMVC自己研发的
    2)过滤器属于拦截WEB请求的,而拦截器不仅可以拦截请求还可以拦截普通方法
    3)过滤器先于拦截器执行,拦截器封装的方法比过滤器拦截使用起来更加简单
    应用场景:
    实用开发中大多数情况下使用拦截器
    过滤器应用场景:编码转换、跨域解决;
    拦截器应用场景:权限控制、日志打印、参数验证、会话

4.SpringMVC拦截器的使用
应用场景:自定义token验证(实现HandlerInterceptor 接口)
创建MyHandlerInterceptor.java

package com.interceptor;

import org.springframework.beans.BeanUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * @author 龙小虬
 * @date 2021/3/11 14:42
 */
public class MyHandlerInterceptor implements HandlerInterceptor {
    
    

    /**
     * 目标方法执行之前执行
     * @param request
     * @param response
     * @param handler
     * @return
     * @throws Exception
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    
    
        String token = request.getParameter("token");
        if(StringUtils.isEmpty(token)){
    
    
            response.getWriter().print("not token");
            return false;
        }
        return true;
    }

    /**
     * 目标方法执行之后执行
     * @param request
     * @param response
     * @param handler
     * @param modelAndView
     * @throws Exception
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
    
    
        System.out.println("postHandle");
    }

    /**
     * 目标方法执行之后执行
     * @param request
     * @param response
     * @param handler
     * @param ex
     * @throws Exception
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
    
    
        System.out.println("afterCompletion");
    }
}

现在将MyHandlerInterceptor拦截器注入,在我们之前创建的
MyConfig.java中更改代码。

package com.config;

import com.interceptor.MyHandlerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
import org.springframework.web.servlet.view.InternalResourceViewResolver;

/**
 * @author 龙小虬
 * @date 2021/3/9 17:33
 */
@Configuration
@ComponentScan("com.controller")
//@EnableWebMvc  若要添加拦截器必须将此注解删除,否则拦截器不会生效
public class MyConfig extends WebMvcConfigurationSupport {
    
    

    @Bean
    public ViewResolver viewResolver() {
    
    
        InternalResourceViewResolver internalResourceViewResolver = new InternalResourceViewResolver();
        // 前缀
        internalResourceViewResolver.setPrefix("/WEB-INF/view/");
        // 后缀
        internalResourceViewResolver.setSuffix(".jsp");
        return internalResourceViewResolver;
    }

    public MyHandlerInterceptor myHandlerInterceptor(){
    
    
        return new MyHandlerInterceptor();
    }

    @Override
    protected void addInterceptors(InterceptorRegistry registry) {
    
    
        super.addInterceptors(registry);
        registry.addInterceptor(myHandlerInterceptor()).addPathPatterns("/**");
    }
}

运行并访问 http://localhost:8080/SpringMVC_02_war/abc,可以看到结果
在这里插入图片描述
这样我们就只能让携带token参数的url访问了。
比如:(http://localhost:8080/SpringMVC_02_war/abc?token=1)

5.异步的使用

异步的使用一般用于我们目标方法中有一部分非常占用时间的处理。那么我们就可以利用异步来创建另一个线程来进行这个非常占用时间的处理。

下面我们来尝试一下。
创建AsyncService.java

package com.service;

import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

/**
 * @author 龙小虬
 * @date 2021/3/11 15:01
 */
@Async
@Service
public class AsyncService {
    
    
	// 此方法替代很占用时间的处理
    public String Test(){
    
    
        try {
    
    
            System.out.println("开始休眠:"+Thread.currentThread().getName());
            Thread.sleep(3000);
            System.out.println("结束休眠:"+Thread.currentThread().getName());
            return "success";
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
        return "fail";
    }
}

在MyConfig.java中,增加扫包范围,并且打开异步

@ComponentScan(basePackages = {
    
    "com.controller","com.service"})
@EnableAsync //打开异步

在MyController.java中,注入AsyncService 并增加接口。

	@Autowired
    AsyncService asyncService;
    
	@RequestMapping(value = "/async",produces = "text/html;charset=UTF-8")
    @ResponseBody
    public String async(){
    
    
        System.out.println("方法开始调用:"+Thread.currentThread().getName());
        String res = asyncService.Test();
        System.out.println("方法结束调用:"+Thread.currentThread().getName()+";res:"+res);
        return "AsyncController";
    }

运行并访问 ,我们看一下结果
在这里插入图片描述会发现我们打印的res的值为空,而res的值又是那非常占时的处理。那么我们需要怎么处理呢?(总不可能又把它变回同步吧?)

那么这时候我们就需要使用到Callable,先来了解一下Callable吧。
Callable可调用接口类似于Runnable,但是Callable的任务执行后可返回值。其他的和Runnable差不多。

那么我们来使用一下它吧。
MyController.java添加接口

	@RequestMapping(value = "/callable",produces = "text/html;charset=UTF-8")
    @ResponseBody
    public Callable<String> callable(){
    
    
        System.out.println("方法开始调用:"+Thread.currentThread().getName());
        Callable callable = new Callable<String>(){
    
    
            @Override
            public String call() throws Exception {
    
    
                return asyncService.Test();
            }
        };
        System.out.println("方法结束调用:"+Thread.currentThread().getName()+";res: "+callable.toString());
        return callable;
    }

直接运行,并访问http://localhost:8080/SpringMVC_02_war/callable?token=1会发现
在这里插入图片描述
直接报错了,但是里面有一个很重要的提示==> <async-supported>true</async-supported>
这一段应该很熟悉,在web.xml里面见过,提到web.xml我们应该就能想到之前创建的类WebInitializer.java中我们还配置了dispatcherServlet,dispatcherServlet都可以配置,那么那个重要的提示肯定也能配置,添加代码dynamic.setAsyncSupported(true);即可。
目前我们WebInitializer.java中的代码如下:

package com.config;

import com.interceptor.MyHandlerInterceptor;
import org.springframework.web.SpringServletContainerInitializer;
import org.springframework.web.WebApplicationInitializer;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRegistration;

/**
 * @author 龙小虬
 * @date 2021/3/9 18:06
 */
public class WebInitializer implements WebApplicationInitializer {
    
    
    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
    
    
        // 1.   创建SpringMVC容器
        AnnotationConfigWebApplicationContext app = new AnnotationConfigWebApplicationContext();
        // 2. 注册我们的配置文件
        app.register(MyConfig.class);
        // 注册我们的
        DispatcherServlet dispatcherServlet = new DispatcherServlet(app);
        ServletRegistration.Dynamic dynamic = servletContext.addServlet("dispatcherServlet", dispatcherServlet);
        dynamic.addMapping("/");
        dynamic.setLoadOnStartup(1);// 最优先启动
        dynamic.setAsyncSupported(true);
//        SpringServletContainerInitializer
//        WebMvcConfigurerAdapter
    }
}

我们再次运行并访问http://localhost:8080/SpringMVC_02_war/callable?token=1,会发现没有报错了,可是页面什么都没有。在这里插入图片描述我们之前在写那一段占用时间长的处理上面加了异步的注解,只要去掉异步的注解(@Async)即可,访问则会正常。

留下一个问题,可能有不少人发现了,我们在写token验证拦截器时,postHandl()、afterCompletion()两个方法的注解都是目标方法执行之后执行,为什么需要两个方法呢?我们后面解释。

猜你喜欢

转载自blog.csdn.net/weixin_43911969/article/details/114633548