Spring Boot 入门(三)拦截器、过滤器、Rest 模板


代码在 https://github.com/betterGa/SpringBootDemo

一、Spring Boot 拦截器

    在 Spring Boot 中使用拦截器(“拦截器” 是 AOP 的思想的体现🤗),可在以下情况下执行操作 :

  • preHandle() :在将 请求 发送到 控制器 之前 —— 在 controller 之前。
  • postHandle() :在将 响应 发送给 客户端 之前 —— 在 controller return 之前。
  • afterCompletion():在 全部请求 和 响应 完成之后 —— 在前端请求完全完毕之后。
        例如:使用拦截器在将请求发送到控制器之前 添加请求标头,并在将响应发送到客户端之前 添加响应标头。
    在这里插入图片描述
        要使用拦截器,需要创建支持它的 @Component 类,它应该实现 HandlerInterceptor 接口。

💎 简单逻辑的拦截器:

// 拦截器
@Component
public class UserAccessInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("pre hander method is calling ");

        // 返回 true 才能继续进入 Controller
        return true;
    }

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

        System.out.println("post hander method is calling");
    }

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

注册拦截器:

@Configuration
// 注册拦截器
public class InterceptorConfig implements WebMvcConfigurer {
    @Autowired
    private UserAccessInterceptor userAccessInterceptor;
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(userAccessInterceptor)
                // 拦截的路径
        .addPathPatterns("/addPerson")
                // 不拦截的路径
                .excludePathPatterns("/user/loggin");
    }
}

(这里说不拦截的路径应该是指排除访问路径,尤其是静态页面。之前 Spring Boot 2.x 依赖的 spring WebMVC 5.x版本,相对于 Spring Boot 1.5.x 依赖的 spring WebMVC 4.3.x 版本而言,针对资源的拦截器初始化时有区别,前者会拦截静态资源,所以需要对仍要访问的静态资源通过 excludePathPatterns 方法 进行排除。) (但是我发现现在这个版本,是不会对静态资源进行拦截的,是否就不需要排除了呢?)

然后在 之前 PersonController 中添加一行代码来验证 postHandle() 方法的执行:

	@GetMapping("/addPerson")
    public ResponseResult addPerson(@RequestBody Person person){
        int ret= personService.addPerson(person);
        
        // 在将 响应 发送给 客户端 之前,即 在 controller return 之前。
        System.out.println("addPerson Method is calling.");
        
       return ret == 1 ? ResponseResult.success(null) : ResponseResult.error("新增用户失败");
    }
}

运行结果:
在这里插入图片描述
如果修改拦截路径为不存在的:.addPathPatterns("/addPersons"),再去访问 /addPerson,运行结果:在这里插入图片描述
    可以看到,那三个方法并不会执行,连 pre… 都不执行,相当于拦截器失效啦。 (那可不是嘛,你访问的路径没有被拦截,拦截器可不就不起作用了。)
    

二、Spring Boot Servlet 过滤器

    过滤器是用于拦截应用程序的 HTTP 请求 和 响应 的对象,拦截的是 servlet
在这里插入图片描述

  • 使用场景:为 shiro 权限过滤器,编码过滤器,微信接口过滤器,上传文件过滤器等。
    💎 过滤器:
// 过滤器
@WebServlet
public class MyFilter implements Filter {

    @Override
    // 在系统启动时就会执行
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("init Method start...");
    }


    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        // doFilter 方法之前的代码在 Serlet 执行之前先执行
        System.out.println("doFilter Method start...");

        filterChain.doFilter(servletRequest, servletResponse);

        // doFilter 方法之后的代码在 Serlet 执行之前先执行
        System.out.println("doFilter Method end...");
    }

    @Override
    public void destroy() {
        System.out.println("destory Method start...");
    }
}

提供一个 Servlet:

public class MyServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
       System.out.println("Get Method start--------------");
        doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("Post Method start--------------");
        resp.getWriter().print("<h1>return post method start</h1>");
    }
}

为 Servlet 配置路径,在 App.java 中添加方法:

	//@Bean
    public ServletRegistrationBean myServlet() {
        return new ServletRegistrationBean(new MyServlet(), "/dofilter");
    }

运行结果:
在这里插入图片描述
再写一个过滤器 Filter1:

@Order(2)
public class MyFilter1 implements Filter {...

运行结果:(不是请求一次就终止了,可以多次请求的,效果像是循环。)
在这里插入图片描述

👀 例子:过滤非法字符,先获取参数 name 的值,如果是 “赌博”,就进行过滤,再不往下执行,在 过滤器1 中修改 doFilter 方法即可:

	@Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        // doFilter 方法之前的代码在 Serlet 执行之前先执行
        System.out.println("doFilter1 Method start...");

        // 获取 name 值
        String name = servletRequest.getParameter("name");
        System.out.println("name=====" + name);

        if (name != null && name.equals("赌博")) {
            // 直接返回
            return;
        }

        filterChain.doFilter(servletRequest, servletResponse);

        // doFilter 方法之后的代码在 Serlet 执行之前先执行
        System.out.println("doFilter1 Method end...");
    }

依次传入参数 name=嘉、赌博、名字,在传入 “赌博” 时,访问页面是空白的,控制台输出如下:
在这里插入图片描述
    可以看到,传入“赌博”时,过滤器1 doFilter 没有执行,也就是说,这时并没有进到 Servlet 中,以及 doFilter 之后的方法也没再执行了,所以这里没有打印 “doFilter1 Method end…” ,而 另一个过滤器都执行着呢。
    接下来给刚开始的过滤器(也就是外层那个过滤器)中添加过滤 “赌博” 的逻辑,过滤器1 只是打印字符串,第一次就传入参数 name=赌博,页面自然是空白的,运行结果:
在这里插入图片描述

    可以看到,在最外层就 return 了,不会进入过滤器链。

🐾 踩坑记录:设置过滤路径

    再有一个问题,后来运行程序时,我发现这两个过滤器对所有的 Servlet 都进行了过滤,于是我用 @WebFilter(urlPatterns = {"/dofilter"}) 注解指定过滤路径,但是并不起作用,这里的问题就是,同时使用 @WebFilter 和 @Component,Spring Boot将会自动注册过滤器,不管写成什么拦截地址,过滤器注册的地址都是 “/*”。 (真的好坑 !😑)
    解决方法:
    使用注册 Bean 的方式,在 自己写的过滤类 Myfilter 上使用 @Component 注解,让 Spring 容器能识别到过滤器组件,然后 通过 FilterRegistrationBean 对象的 addUrlPatterns 方法来指定过滤器的过滤地址:

@Configuration
public class Config {

    @Autowired
    Myfilter myfilter;

    @Bean
    public FilterRegistrationBean registrationProjectFilter() {
        FilterRegistrationBean registration = new FilterRegistrationBean();
        registration.setFilter(myfilter);
        registration.addUrlPatterns("/dofilter");
        return registration;
    }
}

    

三、Spring Boot Rest 模板

    像 jdbctemplate、redistemplate 、RestTemple 都是设计模式的一种体现。
    一个应用,可能有 APP 端、PC 端、小程序端,这些 前端(有给用户展示的页面的 笼统地成为 “前端”) 调用微服务的接口就是 RESTFUL 接口,走的都是 HTTP 通信。
    RestTemple 是 Spring 提供的 用于访问 Http 请求的客户端,RestTemple 提供了多种简洁的远程访问服务的方法,省去了很多无用的代码。
    相信大家之前都用过 apache 的 HTTPClient 类,逻辑繁琐,代码复杂,还要自己编写使用类 HttpClientUtil,封装对应的 post,get,delete 等方法。
     RestTemplate 的行为可以通过 callback 回调方法 和 配置HttpMessageConverter 来定制,用来把对象封装到 HTTP 请求体,将响应信息放到一个对象中。RestTemplate 提供更高等级的符合 HTTP 的 六种主要方法,可以很简单的调用 RESTful 服务。
    Rest 模板用于创建 使用 RESTful Web 服务的应用程序。它的底层其实就是 HttpClient,封装成了模板。
    
💎 在 App.java 中创建 RestTemplate 对象:

 @Bean
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }

    默认情况下 RestTemplate 帮我们自动注册了一组 HttpMessageConverter 用来处理一些不同的 contentType 的请求。如果 现有的转换器 不能满足你的需求,还可以实现 org.springframework.http.converter.HttpMessageConverter 接口自己写转换器。详情参考官方api https://docs.spring.io/spring-framework/docs/4.3.7.RELEASE/javadoc-api/org/springframework/http/converter/package-summary.html
    对于一个请求来说,超期时间,请求连接时间等都是必不可少的参数,为了更好的适应业务需求,可以自己修改 restTemplate 的配置。

(1)GET

    通过使用 RestTemplate 的 postForObject() 或者 exchange() / execute() 方法来使用 GET API。
    先给项目打个包,端口号是 8080,提供个方法,对于 name = jia1、jia2 或者 jia3 的 person 对象会加到 ret 列表中,返回值是 ret 列表:

   static ArrayList<Person> list = new ArrayList<>();
    static {
        list.add(new Person("1", "jia1"));
        list.add(new Person("2", "jia2"));
        list.add(new Person("3", "jia3"));
    }

@RequestMapping(value = "/user1", method = RequestMethod.GET)
    public ArrayList<Person> queryUser(@RequestParam(name = "name", required = false) String name) {
        if(name == null){
            return list;}
        ArrayList<Person> ret=new ArrayList<>();
       for(Person person:list){
           if(name.equals(person.getName())){
               ret.add(person);
           }
       }
       return ret;
    }

稍后用 jar 包形式启动。
在这里插入图片描述
    再在 IDEA (端口号是 9999)里调用接口:

  • 使用 getForObject() 方法
    在这里插入图片描述
 @RequestMapping(value = "/user1", method = RequestMethod.GET)
    public List<Person> queryUser(@RequestParam(name = "name", required = false) String name) {
        // {}表示占位符,1 表示一个参数
       List list=restTemplate.getForObject("http://localhost:8080/user1?name={1}",list.class,name);
    return list;
    }

使用 postman 访问:
在这里插入图片描述
    可以看到,9999 端口里的方法调用了 8080 端口的方法。这种方式模拟的就是 在微服务中,不同的服务部署在不同的虚拟机上,一个服务需要访问另一台机器上的服务的场景。
    

  • 使用 getForObject() 方法,传入 HashMap 类型参数
    在这里插入图片描述
        🙌 注意,这里的 Map 泛型参数是 <String, ?> ,比如传入的是 <Object, Object>,会被识别为 Object 类型,就会调用上一个 getForObject() 方法喔。
 	@RequestMapping(value = "/user2", method = RequestMethod.GET)
    public List<Person> queryUser1(@RequestParam(name = "name", required = false) String name) {
        // 方法二
        HashMap<String, Object> map = new HashMap<>();
        map.put("name", name);
        List list = restTemplate.getForObject("http://localhost:8080/user1?name={1}", List.class, map);
        return list;
    }

运行结果:

在这里插入图片描述
    

  • 使用 exchage() 方法
	@RequestMapping(value = "/user2", method = RequestMethod.GET)
    public List<Person> queryUser1(@RequestParam(name = "name", required = false) String name) {
        // 方法三
        HttpHeaders headers=new HttpHeaders();

        // 接受 Json 数据
        headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));

        HttpEntity httpEntity = new HttpEntity(headers);

        List Retlist = restTemplate.exchange("http://localhost:8080/user1?name={1}", HttpMethod.GET,httpEntity,List.class,name)
        .getBody();

        return Retlist;
    }
  • 使用 exchage() 方法,传入 HashMap 类型参数
// 方法四
        HttpHeaders headers=new HttpHeaders();
        // 接受 Json 数据
        headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));
        HttpEntity httpEntity = new HttpEntity(headers);
        HashMap<String,Object> map = new HashMap<>();
        map.put("name",name);
        List Retlist = restTemplate.exchange("http://localhost:8080/user1?name={name}", HttpMethod.GET,httpEntity,List.class,map)
                .getBody();
        return Retlist;

    

(2)POST

演示第一种方法 getForObject() 方法 的:

 @PostMapping(value = "/user1")
    public ArrayList<Person> createUser(@RequestBody Person person) {
        list.add(person);
        return list;
    }

    // POST 方法
    @RequestMapping(value = "/user2", method = RequestMethod.POST)
    public List<Person> createUser1(@RequestBody Person person) {
        list = (ArrayList<Person>) restTemplate.postForObject("http://localhost:8080/user1", person, List.class);
        return list;
    }

运行结果:
在这里插入图片描述
演示第二种方法 exchage() 方法 的:

		// 方法二
        HttpHeaders headers = new HttpHeaders();
        // 接受 Json 数据
        headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));
        HttpEntity<Object> httpEntity = new HttpEntity(person,headers);

        return restTemplate.exchange("http://localhost:8080/user1",HttpMethod.POST,httpEntity,List.class)
        .getBody();

    

(3)PUT

演示第一种方法 getForObject() 方法 的,是没有返回值的:

 // put 方法
    @PutMapping(value = "/user1/{id}")
    public ArrayList<Person> updateUser(@PathVariable("id") String id, @RequestBody Person person) {
        System.out.println("id from user ====" + id);
        System.out.println("person====" + person);
        for (Person person1 : list) {
            if (id.equals(person1.getId())) {
                list.set(list.indexOf(person1), person);
            }
        }
        return list;
    }
    
    @PutMapping(value = "/user2/{id}")
    public void updateUser1(@PathVariable("id") String id, @RequestBody Person person) {
        // 方法一 没有返回值
        restTemplate.put("http://localhost:8080/user1/{1}", person, id);
    }

运行结果:
在这里插入图片描述
    可以看到,因为 “/user2/{id}” 路径对应的方法返回类型为 void,所以响应为空白,但是在 /user1 端 可以看到是更新成功了的:
在这里插入图片描述
演示第二种方法 exchage() 方法 的,是有返回值的 :

// 方法二
    @PutMapping(value = "/user2/{id}")
    public List<Person> updateUser1(@PathVariable("id") String id, @RequestBody Person person) {
        HttpHeaders headers = new HttpHeaders();
        // 接受 Json 数据
        headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));
        HttpEntity<Object> httpEntity = new HttpEntity(person, headers);
        return restTemplate.exchange("http://localhost:8080/user1/{1}", HttpMethod.PUT, httpEntity, List.class, id)
                .getBody();

运行结果:
在这里插入图片描述

(4)DELETE

     注意,删除不能只是遍历数组,然后调用 remove(Object o) 方法,比如,写成这样子:

@DeleteMapping(value = "/user1/{id}")
    public ArrayList<Person> deleteUser(@PathVariable("id") String id) {
        for (Person person1 : list) {
            if (id != null && person1.getId().equals(id)) {
                list.remove(person1);
            }
        }
        System.out.println("List" + list);
        return list;
    }

    因为 remove 里涉及 size,如果 remove 了,size 就会减一,那如果有重复的 id,按照 id 去查询,就会出现漏删的情况;而且每次 remove 前都需要调用 next 方法的,详情见这篇博客 https://blog.csdn.net/weixin_41750142/article/details/109518530源码如下:
在这里插入图片描述
应该写成这样:

 @DeleteMapping(value = "/user1/{id}")
    public ArrayList<Person> deleteUser(@PathVariable("id") String id) {
        for (int i=0;i<list.size();i++) {
            if (id != null && list.get(i).getId().equals(id)) {
                list.remove(i);
                i--;
            }
        }
        System.out.println("List" + list);
        return list;
    }

     比如说,第一次删除了下标为 1 的元素,这时候 i–,再经历 i++,i = 1 ,开始新的循环,这时遍历到下标为 1 的元素,这样的情况是合理的,因为刚刚执行了 remove,原先下标为 2 的元素就会往前挪一位的,要是直接 i++,就跳过了往前挪的这个元素。
    
演示第一种方法 getForObject() 方法 的,是没有返回值的:

    @DeleteMapping(value = "/user2/{id}")
    public void deleteUser1(@PathVariable("id") String id) {
        restTemplate.delete("http://localhost:8080/user1/{1}", id);
    }

运行结果:
1
因为返回值是 void,所以显示空白,来看 /user1 端 :
在这里插入图片描述
演示第二种方法 exchage() 方法 的,是有返回值的 :

@DeleteMapping(value = "/user2/{id}")
    public List<Person> deleteUser1(@PathVariable("id") String id) {
        HttpHeaders headers = new HttpHeaders();
        // 接受 Json 数据
        headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));
        HttpEntity<Object> httpEntity = new HttpEntity(headers);
        return restTemplate.exchange("http://localhost:8080/user1/{1}", HttpMethod.DELETE, httpEntity, List.class, id)
                .getBody();
    }

运行结果:
在这里插入图片描述

(其实这几个方法熟悉一个了就会其他的了,照猫画虎… 照葫芦画瓢…😏)
    

四、Spring 文件处理

1、文件上传
@RestController
public class FileController {

    @RequestMapping(value = "/upload", method = RequestMethod.POST, consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
    public String fileUpload(@RequestParam("file") MultipartFile file) throws IOException {
        File file1 = new File("d:/" + file.getOriginalFilename());
        file1.createNewFile();
        FileOutputStream fileOutputStream = new FileOutputStream(file1);
        fileOutputStream.write(file.getBytes());
        fileOutputStream.close();
        return "file is upload success.";
    }

}

使用 postman 访问:
在这里插入图片描述

2、文件下载
 @RequestMapping(value = "/download", method = RequestMethod.GET)
    // 这里演示把文件在页面上展示出来,不是真正的下载
    public ResponseEntity<Object> downFile(HttpServletResponse response) throws IOException {
        String fileName = "d:/Ready to study.txt";
        File file = new File(fileName);
        FileSystemResource fileSystemResource = new FileSystemResource(file);

        HttpHeaders headers = new HttpHeaders();
        headers.add("Content-Disposition", "attachment; " +
                "filename=" + file.getName());
        headers.add("Cache-Control", "no-cache, no-store, must-revalidate");
        headers.add("Pragma", "no-cache");
        headers.add("Expires", "0");
        return ResponseEntity.ok().
                headers(headers).
                contentLength(fileSystemResource.contentLength()).
                contentType(MediaType.parseMediaType("application/octet-stream")).
                body(new InputStreamResource(fileSystemResource.getInputStream()));
    }

    

五、总结

(1)拦截器需要实现 HandlerInterceptor 接口,使用 @Component 注解,根据需要 实现 preHandle方法—— 在 Controller 之前(返回 true 才能进入 Controller)、postHandle 方法——在 Controller return返回前、afterCompletion 方法 前端请求完毕后,然后使用 @Configuration 注解 注册拦截器,配置拦截和不拦截的路径。
    
(2)过滤器需要实现 Filter 接口,使用 @WebServlet 注解,doFilter 方法之前的代码在 Serlet 执行之前先执行,doFilter 方法之后的代码在 Serlet 执行之后先执行。
    
(3)RestTemplate 适用于 在微服务中,不同的服务部署在不同的虚拟机上,一个服务需要访问另一台机器上的服务的场景 。比如本文中的例子本地 9090 调用 8080 的 方法,当然,调用的方法肯定是要相同的,使用相应的 GET、POST、PUT、DELETE API 即可,主要是 getForObject 方法(注意传入 HashMap 类型参数时,需要注意泛型参数需要是 <String,?>,否则会被识别为 Object 类型)、exchage 方法。
    

🎉 补充:Java Web 中 拦截器和过滤器

    二者都是 AOP 编程思想的体现,都能实现权限检查、日志记录等。
    拦截器是 Spring 框架中通过反射实现的,是 Spring 的组件,因此能使用 Spring 中的任何资源、对象,比如 Service 对象 通过 IOC 注入到拦截器即可,而过滤器不能;而 过滤器是 Servlet 的规范。
    拦截器拦的是 Controller 层,而 过滤器拦截的是 Servlet,实际上 Controller 是在 Servlet 之后,也就是说,过滤器比拦截器早执行。
    过滤器只在 Servlet 前后起作用,而拦截器能够深入到方法前后、异常抛出前后等,在 Spring 框架的程序中,优先考虑用拦截器。
    

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值