Spring资源映射,拦截器,过滤器

一、使用Spring Boot 默认配置访问静态资源

  1.默认配置的 /** 映射到项目中src/main/resources/目录下的文件夹 /static、/public、/resources、/META-INF/resources,application.properties文件默认配置:

#静态资源访问路径,默认为/**,即拦截所有的请求,看看其访问的是不是静态资源
#spring.mvc.static-path-pattern=/**
#这里可以定义只把static开头的请求作为静态资源进行拦截
spring.mvc.static-path-pattern=/static/**

#静态资源映射路径
#默认[classpath:/META-INF/resources/,classpath:/resources/,classpath:/static/,classpath:/public/]
#这里可以自定义所有静态资源都在static目录中,一旦自定义,上面默认的就失效了(可以定义多个,写到数组里面)
spring.resources.static-locations=classpath:/static/

  2.目录优先级:/META-INF/resources > /resources > /static > /public

以下是不写配置文件,使用默认配置得到的结果:




可以看到,通过localhost/文件名,就可以直接访问到,这是因为/** 默认拦截了所有的请求,然后静态资源解析器默认就去上面四个文件夹进行查找对应文件名的文件,如果存在就返回。

但是如果静态资源不直接放在上面所示的那几个文件夹下,就需要添加上相对于上面那几个文件夹的路径才能访问,如下所示,这里图片是放在resources文件夹下的img文件夹下:

这里直接访问就会出错:


这里需要加上img,其实这里的查找过程是,请求过来,然后被默认的/**拦截到,然后就在上面那四个目录下进行查找,有没有 localhost/img/3.jpg,这里会在resources文件夹下找到。

探究:

既然默认静态资源的拦截请求配置为/**,那么如果controller中也有同样的路径,那么是访问静态资源还是访问相应的controller接口呢? 这里我们说,请求是优先访问controller的,如果controller找不到,则会从静态资源中查找,如果静态资源也找不到,就会报404错误。如下图:


这里我们controller 的访问请求也为1.jpg,此时我们访问 localhost/1.jpg,会得到如下结果:

可以发现这里走的是controller请求。

结论:

1. 如果我们使用默认的静态资源路径,并且在html模板文件中要访问我们的静态资源,则直接写相对于上面四个静态资源位置的相对路径就行了,如下所示:


这里引入css,js 的路径是相对于上面四个目录中的static目录的相对路径,可以被加载到。
2. 如果我们重新定义了静态资源的请求路径以及请求映射的访问位置,那么上面的访问方式就会出错,对应案例如下:

2.1 :这里,我们只重新定义静态资源的请求路径,让其拦截以syw开头的请求作为静态资源


此时,我们无法像上面一样通过localhost/2.jpg直接访问,因为我们重新定义了拦截方式,不再是默认的/**了,而是/syw/** ,虽然默认的映射位置还是上面的四个,但是我们请求的拦截变了,我们认为只有以syw开头的请求才是静态资源,所以我们此时需要加上syw,新的请求方式为localhost/syw/2.jpg。这样做的好处就是我们在拦截器中进行静态资源放行的时候可以通过 /syw/**,放行静态资源。 如下图所示:


2.2 : 这里我们只重新定义静态资源的映射位置,让所有的静态资源都在static(当然也可以自定义为其他目录)中寻找。

server.port=80
#静态资源映射的位置
spring.resources.static-locations=classpath:/static/

这里虽然我们没有重新定义拦截的请求,依然是拦截/** ,但是由于我们自定义了静态资源的映射位置,所以上面默认的四个位置就失效了,这里只有我们重新定义的 static目录下的静态资源才能被访问到,如下所示:


2.jpg在public目录中,所以就无法访问到。


4.jpg在static目录中,所以能直接访问。

2.3 :如果我们同时自定义拦截的静态资源请求路径和静态资源映射位置,如下所示:

#拦截以static开头的请求作为静态资源
spring.mvc.static-path-pattern=/static/**
#静态资源映射的位置,拦截到静态资源请求之后,去static目录下进行寻找
spring.resources.static-locations=classpath:/static/

这里由于我们重新定义了请求路径为static开头,所以我们在访问时需要加上static,如下所示,便能正常访问到静态资源了:


第二种方式:上面同时自定义拦截的静态资源请求路径和静态资源映射位置的方式相当于新建一个配置类,实现WebMvcConfigurer并重写相应的方法,如下所示:

@Configuration
public class MyWebConfig implements WebMvcConfigurer {
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        //拦截所有的以static开头的请求,映射到resource下的static
        registry.addResourceHandler("/static/**").addResourceLocations("classpath:/static/");
    }
}

该方式在日常的项目中使用较多,因为实现WebMvcConfigurer还可以对拦截器进行管理。

二、Spring Boot 拦截器

拦截器是为了系统的安全考虑的,例如:一般用户在没有登录后台系统之前是不允许用户访问后台的除了登录注册之外的其他页面的,这时候就需要用到拦截器了:

第一步:创建自定义的拦截器

新建一个类,实现HandlerInterceptor接口,并重写其中的方法,这里主要是preHandle方法。

//创建自定义的拦截器
public class MyInterceptor implements HandlerInterceptor {
    //在拦截请求之前进行调用(Controller方法调用之前)
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //后台默认除了不拦截登录注册,其余的都进行拦截,只有session中存在用户信息之后才放行
        //所以在没有登录之前是访问不了其他后台页面的。
        System.out.println("执行了preHandle");
        User user = (User)request.getSession().getAttribute("user");
        if(user!=null){
            return true;
        }
       //如果session中不存在相应的用户登录信息,就跳转到登录页面,“/login/sys”为登录页面的地址
        response.sendRedirect(request.getContextPath()+"/login/sys");
        return false;
    }
    //请求处理之后进行调用,但是在视图被渲染之前(Controller方法调用之后)
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("执行了postHandle");
    }
    //在整个请求结束之后被调用,也就是在DispatcherServlet 渲染了对应的视图之后执行(主要是用于进行资源清理工作)
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("执行了afterCompletion");
    }

    //自定义不进行拦截的路径,请求这些路径则不用经过上面三个方法
    public List<String> getUrl(){
        List<String> url = new LinkedList<>();
        //注意,拦截器和静态资源映射无关,如果这里不配置静态资源放行,则拦截器会把静态资源拦截。
        url.add("/static/**");  //不拦截静态资源,由于我们配置的静态资源请求为static开头,所以这里这样写
        url.add("/login/**");   //不拦截登录页面
        url.add("/reg/**");     //不拦截注册页面
        url.add("/user/**");    //不拦截前台页面
        return url;
    }
}

拦截器中方法的执行顺序是 preHandle -> Controller -> postHandle -> afterCompletion
只有preHandle返回true,才会执行后面的方法

第二步:配置拦截器

和静态资源映射的配置一样,也是实现WebMvcConfigurer接口,这里是重写addInterceptors方法


@Configuration
public class MyWebConfig implements WebMvcConfigurer {
    //配置资源映射器,与拦截器无关
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/static/**").addResourceLocations("classpath:/static/");
    }
    //配置拦截器
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        MyInterceptor myInterceptor = new MyInterceptor();
        registry.addInterceptor(myInterceptor).addPathPatterns("/**")  //拦截所有的请求
                .excludePathPatterns(myInterceptor.getUrl());  //不拦截这些
    }
}

测试结果如下:


可以看到,这里由于放行了以 login开头的请求,所以这里的请求都能够直接访问,不经过拦截器

如果我们请求的是没有放行的路径,此时会执行preHandle,检测是否满足要求,这里就是检测session中是否存在用户信息,如果不存在,则会跳转到登录页面。

这里我们模拟登录成功之后,把用户的信息保存到session中。

@RequestMapping("/success")
    @ResponseBody
    public String loginSuccess(HttpSession session){
        session.setAttribute("user",new User()); //这里我们把user存到session中
        return "模拟登录成功";
    }

此时由于session中已经存在用户信息,再次访问 localhost/others 则会跳到相应页面:


经过测试可以发现,该localhost/others 请求,先后通过了以上三个方法:

三、Mybatis 的mapper文件

第一种:xml文件和java文件不在同一个文件夹

我们知道,mybatis 的xml文件如果和java文件不在同一个文件夹,也就是说把xml文件放到resources文件夹下面,则拦截器以及资源映射会不会影响到xml文件?


经过探索可知,不会影响xml文件的使用。只需要在配置文件中指出mybatis 的xml文件所在位置,以及别名的包路径:

#mapper分离的位置
mybatis.mapper-locations=classpath:/mapper/*.xml
#mybatis别名搜索的位置,例如参数中有cn.henu.pojo.User,每次写这么长很麻烦,我们配置了别名,可以直接使用User来代替上面的详细路径
mybatis.type-aliases-package=cn.henu.pojo

然后我们需要在启动类别中开启mybatis的包扫描,也即是添加@MapperScan 注解:

@SpringBootApplication
@MapperScan("cn.henu.dao") //配置mybatis扫描mapper
public class JsrApplication {
    public static void main(String[] args) {
        SpringApplication.run(JsrApplication.class, args);
    }
}

注意:使用逆向工程自动生成的dao层java接口往往不带@Repository注解,但是我们最好在dao层的接口中添加 @Repository 注解,如果不加入可能不影响使用,但是在service层中使用@Autowired 自动注入的时候会报红(使用正常,只是会报红)。

第二种:xml文件和java文件在同一个文件夹

文件目录如下:


为了解决maven项目无法读取 src/main/java 目录下的xml文件,我们需要在pom文件中引入如下内容,让项目启动的时候能够去读取到src/main/java下面的xml文件。

<build>  
    <resources>  
        <resource>  
            <directory>src/main/java</directory>  
            <includes>  
                <include>**/*.xml</include>  
            </includes>  
        </resource>  
    </resources>  
</build>

注意:这种方式不用配置上面的mapper文件位置。

四、过滤器

1、过滤器和拦截器触发时机不一样,过滤器是在请求进入容器后,但请求进入servlet之前进行预处理的。请求结束返回也是,是在servlet处理完后,返回给前端之前。
如下图:


2、拦截器可以获取IOC容器中的各个bean,而过滤器就不行,因为拦截器是spring提供并管理的,spring的功能可以被拦截器使用,在拦截器里注入一个service,可以调用业务逻辑。而过滤器是JavaEE标准,只需依赖servlet api ,不需要依赖spring。 过滤器拦截器运行先后步骤:


其中第2步,SpringMVC的机制是由DispaterServlet来分发请求给不同的Controller,其实这一步是在Servlet的service()方法中执行的.

3、过滤器的实现基于回调函数。而拦截器(代理模式)的实现基于反射,代理分静态代理和动态代理,动态代理是拦截器的简单实现。

何时使用拦截器?何时使用过滤器?

  • 如果是非spring项目,那么拦截器不能用,只能使用过滤器。
  • 如果是处理controller前后,既可以使用拦截器也可以使用过滤器。
  • 如果是处理dispaterServlet前后,只能使用过滤器。

spring boot 使用过滤器

两种方式:
1、使用spring boot提供的FilterRegistrationBean注册Filter
2、使用原生servlet注解定义Filter
两种方式的本质都是一样的,都是去FilterRegistrationBean注册自定义Filter

方式一:
①、先定义Filter:

public class MyFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        // do something 处理request 或response
        System.out.println("filter1");
        // 调用filter链中的下一个filter
        filterChain.doFilter(servletRequest,servletResponse);
    }
    @Override
    public void destroy() {

    }
}

②、注册自定义Filter

@Configuration
public class FilterConfig {
    @Bean
    public FilterRegistrationBean registrationBean() {
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(new MyFilter());
        filterRegistrationBean.addUrlPatterns("/*"); //表示拦截所有的请求 ,例如/api/* 则表示拦截以api开头的请求。
        return filterRegistrationBean;
    }
}

方式二:

// 注入spring容器
@Component
// 定义filterName 和过滤的url
@WebFilter(filterName = "my2Filter" ,urlPatterns = "/*")
public class My2Filter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("filter2");
    }
    @Override
    public void destroy() {

    }
}

过滤器与拦截器的区别:

过滤器和拦截器非常相似,但是它们有很大的区别
最简单明了的区别就是过滤器可以修改request,而拦截器不能
过滤器需要在servlet容器中实现,拦截器可以适用于javaEE,javaSE等各种环境
拦截器可以调用IOC容器中的各种依赖,而过滤器不能
过滤器只能在请求的前后使用,而拦截器可以详细到每个方法

总的来说
过滤器就是筛选出你要的东西,比如requeset中你要的那部分
拦截器在做安全方面用的比较多,比如终止一些流程

全部评论