Spring的几个常用的工具类和小技巧

一、OncePerRequestFilter

1.在Web应用中,我们的一次请求可能需要再同一服务端转发多次,在服务端的每次转发,普通的过滤器就会多执行一次,在某些情况下,我们可能希望确保过滤器逻辑在一次完整的HTTP请求中仅执行一次,即使请求在服务器内部经过了多次转发(forward)或包含多个资源请求。Spring框架的OncePerRequestFilter就是为了满足这一需求而设计的。这对于需要在请求处理之前或之后进行一次性设置或清理资源的场景特别有用。

2.OncePerRequestFilter通过内部使用HttpServletRequest的getAttribute和setAttribute方法,以及一个ThreadLocal变量来跟踪当前请求是否已经被处理过。当请求首次进入过滤器时,OncePerRequestFilter会检查是否已经存在一个与当前请求关联的标记。如果不存在,则执行过滤器的逻辑,并将一个标记与当前请求关联起来;如果已经存在,则直接跳过过滤器的逻辑。

public final void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
			throws ServletException, IOException {

		if (!(request instanceof HttpServletRequest) || !(response instanceof HttpServletResponse)) {
			throw new ServletException("OncePerRequestFilter just supports HTTP requests");
		}
		HttpServletRequest httpRequest = (HttpServletRequest) request;
		HttpServletResponse httpResponse = (HttpServletResponse) response;
        
        // 获取本次请求,该过滤器的逻辑是否执行过了,如果是第一次执行的话为null
		String alreadyFilteredAttributeName = getAlreadyFilteredAttributeName();
		boolean hasAlreadyFilteredAttribute = request.getAttribute(alreadyFilteredAttributeName) != null;

		if (skipDispatch(httpRequest) || shouldNotFilter(httpRequest)) {
			// Proceed without invoking this filter...
			filterChain.doFilter(request, response);
		}else if (hasAlreadyFilteredAttribute) {
            
            // 如果hasAlreadyFilteredAttribute不为空,说明该过滤器的逻辑已经执行过了

            //接下来有两种情况,
            // 1.如果请求有错误,直接处理错误转发
            
			if (DispatcherType.ERROR.equals(request.getDispatcherType())) {
				doFilterNestedErrorDispatch(httpRequest, httpResponse, filterChain);
				return;
			}

            // 2.如果没有错误,直接调用剩下的过滤器,注意这种情况下该过滤器的逻辑没有被执行
			// Proceed without invoking this filter...
			filterChain.doFilter(request, response);
		} else {

            // 如果hasAlreadyFilteredAttribute为空,说明第一次执行该过滤器
            // 1.标记该过滤器已经被执行过了
			// Do invoke this filter...
			request.setAttribute(alreadyFilteredAttributeName, Boolean.TRUE);
			try {
            // 2.开始执行该过滤器的逻辑
				doFilterInternal(httpRequest, httpResponse, filterChain);
			}finally {
                // 这里将该过滤器逻辑已经执行过的标记删除好多人可能不理解
                // 这里需要理解,每次对该过滤器的调用都是对该方法进行一次压栈,所以要在第一次
                // 调用时压的栈帧,弹栈之后移除这个标记。方法调用时先压的栈帧,后弹栈
				// Remove the "already filtered" request attribute for this request.
				request.removeAttribute(alreadyFilteredAttributeName);
			}
		}
	}

3.举例我们做一个跨域拦截的过滤器,正常来说,一次请求无论转发多少次,我们只需要执行一次该逻辑告诉浏览器,允不允许当前的域名跨域请求就行了。

package com.fll.filter;

import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;

public class CorsFilter extends OncePerRequestFilter {

    private final Set<String> allowedDomains = new HashSet<>(Arrays.asList("www.aaa.com" , "www.bbb.com"));

    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
                                    FilterChain filterChain) throws ServletException, IOException {

        String origin = request.getHeader("origin");// 获取源站
        if( origin != null ){
            //跨域写cookie不能使用通配符*
            //还需要判断是否是我们服务容许跨域请求的域名
            if( allowedDomains.contains(origin) ) {
                // 告诉浏览器,这个域名容许跨域请求
                response.setHeader("Access-Control-Allow-Origin", origin);
            }

            // 获取允许的header字段
            String strHeader = request.getHeader("Access-Control-Request-Headers");
            if( strHeader != null ){
                response.setHeader("Access-Control-Allow-Headers", strHeader);
            }

            response.setHeader("Access-Control-Allow-Methods", "POST, GET,OPTIONS");
            //单位秒
            response.setHeader("Access-Control-Max-Age", "86400");
            //支持跨域写cookie
            response.setHeader("Access-Control-Allow-Credentials", "true");
        }

        //如果是跨域请求的 preflight (预请求),就会有这个请求头
        String accessControlRequestMethod = request.getHeader("access-control-request-method");

        // preflight 的请求方式是 OPTIONS
        if("OPTIONS".equals(request.getMethod()) && accessControlRequestMethod != null ){
            // 到这说明是一个 preflight ,不需要继续后面的直接返回给客户端
            return;
        }

        filterChain.doFilter(request, response);

    }

}
package com.fll.filter;

import org.springframework.boot.web.servlet.FilterRegistrationBean;  
import org.springframework.context.annotation.Bean;  
import org.springframework.context.annotation.Configuration;  
  
@Configuration  
public class FilterConfig {  
  
    @Bean  
    public FilterRegistrationBean<CorsFilter> corsFilterRegistration() {  
        FilterRegistrationBean<CorsFilter> registration = new FilterRegistrationBean<>();  
        registration.setFilter(new CorsFilter());
        // 设置过滤器拦截的URL模式  
        registration.addUrlPatterns("/*"); 
        return registration;  
    }  
    
}

二、StopWatch

1.StopWatch 可用于统计一系列操作的不同阶段的耗时,还提供了方便的统计输出,对于定位业务中哪写操作耗时过多,或者定位问题很有帮助

package com.fll;

import org.springframework.util.StopWatch;

public class StopWatchTest {

    public static void main(String[] args) {

        StopWatch stopWatch = new StopWatch();
        stopWatch.start("任务1");

        //
        System.out.println("状态1:" + stopWatch.isRunning());

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        stopWatch.stop();

        System.out.println("状态2:" + stopWatch.isRunning());

        for (StopWatch.TaskInfo taskInfo : stopWatch.getTaskInfo()) {
            System.out.println("已完成的任务1:" + taskInfo.getTaskName());
        }

        stopWatch.start("任务2");

        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        stopWatch.stop();

        for (StopWatch.TaskInfo taskInfo : stopWatch.getTaskInfo()) {
            System.out.println("已完成的任务2:" + taskInfo.getTaskName());
        }


        System.out.println(stopWatch.prettyPrint());

    }

}
状态1:true
状态2:false
已完成的任务1:任务1
已完成的任务2:任务1
已完成的任务2:任务2
StopWatch '': running time = 3009331000 ns
---------------------------------------------
ns         %     Task name
---------------------------------------------
1000184200  033%  任务1
2009146800  067%  任务2

三、UricomponentsBuilder

UricomponentsBuilder 可用于拼接路径

package com.fll;

import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;

import java.net.URI;
import java.nio.charset.Charset;

public class UricomponentsBuilderTest {

    public static void main(String[] args) {

        UriComponents build = UriComponentsBuilder.fromHttpUrl("http://www.baidu.com")
                .queryParam("name", "zhangsan")
                .queryParam("age", "18").build();

        System.out.println(build.toString());

        URI build1 = UriComponentsBuilder.fromHttpUrl("http://www.baidu.com")
                .port(8080)
                .path("user/{id}/{age}")
                .queryParam("name", "zhangsan")
                .queryParam("age", "18")
                .build("112233" , "25");

        System.out.println(build1);


        URI build2 = UriComponentsBuilder.fromHttpUrl("http://www.baidu.com")
                .port(8080)
                .path("user/{id}/{age}")
                .queryParam("name", "张三")
                .queryParam("age", "18")
                .encode(Charset.defaultCharset())
                .build("112233" , "25");

        System.out.println(build2);


        URI build3 = UriComponentsBuilder.fromHttpUrl("http://www.baidu.com")
                .port(8080)
                .path("user/{id}/{age}")
                .queryParam("name", "张三")
                .queryParam("age", "18")
                .fragment("userPage")
                .build("112233" , "25");

        System.out.println(build3);


        URI build4 = UriComponentsBuilder.fromHttpUrl("http://www.baidu.com")
                .port(8080)
                .path("user/{id}/{age}")
                .queryParam("name", "张三")
                .queryParam("age", "18")
                .query("aaaaa")
                .build("112233" , "25");

        System.out.println(build4);

        URI build5 = UriComponentsBuilder.fromHttpUrl("http://www.baidu.com")
                .port(8080)
                .path("user/{id}/{age}")
                .queryParam("name", "张三")
                .queryParam("age", "18")
                .userInfo("zhangsan:123456")
                .build("112233" , "25");

        System.out.println(build5);


        URI build6 = UriComponentsBuilder.fromHttpUrl("http://www.baidu.com")
                .port(8080)
                .path("user/{id}/{age}")
                .queryParam("name", "张三")
                .queryParam("age", "18")
                .replacePath("goods/{id}/{price}")
                .build("112233" , "25");

        System.out.println(build6);

        URI build7 = UriComponentsBuilder.fromHttpUrl("http://www.baidu.com")
                .port(8080)
                .path("user/{id}/{age}")
                .queryParam("name", "zhangsan")
                .queryParam("age", "18")
                .replaceQueryParam("name", "lisi" , "wangwu")
                .build("112233" , "25");

        System.out.println(build7);

    }

}
http://www.baidu.com?name=zhangsan&age=18
http://www.baidu.com:8080/user/112233/25?name=zhangsan&age=18
http://www.baidu.com:8080/user/112233/25?name=%E5%BC%A0%E4%B8%89&age=18
http://www.baidu.com:8080/user/112233/25?name=%E5%BC%A0%E4%B8%89&age=18#userPage
http://www.baidu.com:8080/user/112233/25?name=%E5%BC%A0%E4%B8%89&age=18&aaaaa
http://zhangsan:123456@www.baidu.com:8080/user/112233/25?name=%E5%BC%A0%E4%B8%89&age=18
http://www.baidu.com:8080/goods/112233/25?name=%E5%BC%A0%E4%B8%89&age=18
http://www.baidu.com:8080/user/112233/25?age=18&name=lisi&name=wangwu

四、FileSystemUtils

FileSystemUtils 提供了对文件的递归删除,和递归复制
package com.fll;

import org.springframework.util.FileSystemUtils;

import java.io.File;
import java.io.IOException;

public class FileSystemUtilsTest {

    public static void main(String[] args) throws IOException {

        File srcFile = new File("D:\\test");
        FileSystemUtils.copyRecursively(srcFile, new File("D:\\test1"));

        File rootFile = new File("D:\\test");
        FileSystemUtils.deleteRecursively(rootFile);

    }

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值