用cdr做网站设计尺寸要多少湖南网站建设360o

当前位置: 首页 > news >正文

用cdr做网站设计尺寸要多少,湖南网站建设360o,如何做实体店的网站,饮料网站建设我们都知道#xff0c;过滤器是 Servlet 的重要标准之一#xff0c;其在请求和响应的统一处理、访问日志记录、请求权限审核等方面都有着不可替代的作用。在 Spring 编程中#xff0c;我们主要就是配合使用ServletComponentScan 和 WebFilter 这两个注解来构建过滤器。 说起…我们都知道过滤器是 Servlet 的重要标准之一其在请求和响应的统一处理、访问日志记录、请求权限审核等方面都有着不可替代的作用。在 Spring 编程中我们主要就是配合使用ServletComponentScan 和 WebFilter 这两个注解来构建过滤器。 说起来比较简单好像只是标记下这两个注解就一劳永逸了。但是我们还是会遇到各式各样的问题例如工作不起来、顺序不对、执行多次等等都是常见的问题。这些问题的出现大多都是使用简单致使我们掉以轻心只要你加强意识大概率就可以规避了。 那么接下来我们就来学习两个典型的案例并通过分析带你进一步理解过滤器执行的流程和原理 案例 1WebFilter 过滤器无法被自动注入 假设我们要基于 Spring Boot 去开发一个学籍管理系统。为了统计接口耗时可以实现一个过滤器如下 WebFilter Slf4j public class TimeCostFilter implements Filter {public TimeCostFilter(){System.out.println(construct);}Overridepublic void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {log.info(开始计算接口耗时);long start System.currentTimeMillis();chain.doFilter(request, response);long end System.currentTimeMillis();long time end - start;System.out.println(执行时间(ms) time);} } 这个过滤器标记了 WebFilter。所以在启动程序中我们需要加上扫描注解即 ServletComponentScan让其生效启动程序如下 SpringBootApplication ServletComponentScan Slf4j public class Application {public static void main(String[] args) {SpringApplication.run(Application.class, args);log.info(启动成功);} } 然后我们提供了一个 StudentController 接口来供学生注册 Controller Slf4j public class StudentController {PostMapping(/regStudent/{name})ResponseBodypublic String saveUser(String name) throws Exception {System.out.println(用户注册成功);return success;} } 上述程序完成后你会发现一切按预期执行。但是假设有一天我们可能需要把 TimeCostFilter 记录的统计数据输出到专业的度量系统ElasticeSearch/InfluxDB 等里面去我们可能会添加这样一个 Service 类 Service public class MetricsService {Autowiredpublic TimeCostFilter timeCostFilter;//省略其他非关键代码} 完成后你会发现Spring Boot 都无法启动了 *************************** APPLICATION FAILED TO START ***************************  Description: Field timeCostFilter in com.spring.puzzle.web.filter.example1.MetricsService required a bean of type com.spring.puzzle.web.filter.example1.TimeCostFilter that could not be found. 为什么会出现这样的问题既然 TimeCostFilter 生效了看起来也像一个普通的 Bean为什么不能被自动注入 案例解析 这次我们换个方式我先告诉你结论你可以暂停几分钟想想关键点。 本质上过滤器被 WebFilter 修饰后TimeCostFilter 只会被包装为 FilterRegistrationBean而 TimeCostFilter 自身只会作为一个 InnerBean 被实例化这意味着 TimeCostFilter 实例并不会作为 Bean 注册到 Spring 容器。 所以当我们想自动注入 TimeCostFilter 时就会失败了。知道这个结论后我们可以带着两个问题去理清一些关键的逻辑 1.FilterRegistrationBean 是什么它是如何被定义的 2.TimeCostFilter 是怎么实例化并和 FilterRegistrationBean 关联起来的 我们先来看第一个问题FilterRegistrationBean 是什么它是如何定义的 实际上WebFilter 的全名是 javax.servlet.annotation.WebFilter很明显它并不属于 Spring而是 Servlet 的规范。当 Spring Boot 项目中使用它时Spring Boot 使用了 org.springframework.boot.web.servlet.FilterRegistrationBean 来包装 WebFilter 标记的实例。从实现上来说即 FilterRegistrationBean#Filter 属性就是 WebFilter 标记的实例。这点我们可以从之前给出的截图中看出端倪。 另外当我们定义一个 Filter 类时我们可能想的是我们会自动生成它的实例然后以 Filter 的名称作为 Bean 的名字来指向它。但是调试下你会发现在 Spring Boot 中Bean 名字确实是对的只是 Bean 实例其实是 FilterRegistrationBean。 那么这个 FilterRegistrationBean 最早是如何获取的呢这还得追溯到 WebFilter 这个注解是如何被处理的。在具体解析之前我们先看下 WebFilter 是如何工作起来的。使用 WebFilter 时Filter 被加载有两个条件 声明了 WebFilter在能被 ServletComponentScan 扫到的路径之下。 这里我们直接检索对 WebFilter 的使用可以发现 WebFilterHandler 类使用了它直接在 doHandle() 中加入断点开始调试执行调用栈如下 从堆栈上我们可以看出对 WebFilter 的处理是在 Spring Boot 启动时而处理的触发点是 ServletComponentRegisteringPostProcessor 这个类。它继承了 BeanFactoryPostProcessor 接口实现对 WebFilter、WebListener、WebServlet 的扫描和处理其中对于 WebFilter 的处理使用的就是上文中提到的 WebFilterHandler。这个逻辑可以参考下面的关键代码 class ServletComponentRegisteringPostProcessor implements BeanFactoryPostProcessor, ApplicationContextAware {private static final ListServletComponentHandler HANDLERS;static {ListServletComponentHandler servletComponentHandlers new ArrayList();servletComponentHandlers.add(new WebServletHandler());servletComponentHandlers.add(new WebFilterHandler());servletComponentHandlers.add(new WebListenerHandler());HANDLERS Collections.unmodifiableList(servletComponentHandlers);}// 省略非关键代码Overridepublic void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {if (isRunningInEmbeddedWebServer()) {ClassPathScanningCandidateComponentProvider componentProvider createComponentProvider();for (String packageToScan : this.packagesToScan) {scanPackage(componentProvider, packageToScan);}}}private void scanPackage(ClassPathScanningCandidateComponentProvider componentProvider, String packageToScan) {// 扫描注解for (BeanDefinition candidate : componentProvider.findCandidateComponents(packageToScan)) {if (candidate instanceof AnnotatedBeanDefinition) {// 使用 WebFilterHandler 等进行处理for (ServletComponentHandler handler : HANDLERS) {handler.handle(((AnnotatedBeanDefinition) candidate),(BeanDefinitionRegistry) this.applicationContext);}}}} 最终WebServletHandler 通过父类 ServletComponentHandler 的模版方法模式处理了所有被 WebFilter 注解的类关键代码如下 public void doHandle(MapString, Object attributes, AnnotatedBeanDefinition beanDefinition,BeanDefinitionRegistry registry) {BeanDefinitionBuilder builder BeanDefinitionBuilder.rootBeanDefinition(FilterRegistrationBean.class);builder.addPropertyValue(asyncSupported, attributes.get(asyncSupported));builder.addPropertyValue(dispatcherTypes, extractDispatcherTypes(attributes));builder.addPropertyValue(filter, beanDefinition);//省略其他非关键代码builder.addPropertyValue(urlPatterns, extractUrlPatterns(attributes));registry.registerBeanDefinition(name, builder.getBeanDefinition()); } 从这里我们第一次看到了 FilterRegistrationBean。通过调试上述代码的最后一行可以看到最终我们注册的 FilterRegistrationBean其名字就是我们定义的 WebFilter 的名字 后续这个 Bean 的具体创建过程这里不再赘述 现在我们接着看第二个问题TimeCostFilter 何时被实例化 此时我们想要的 Bean 被“张冠李戴”成 FilterRegistrationBean但是 TimeCostFilter 是何时实例化的呢为什么它没有成为一个普通的 Bean? 关于这点我们可以在 TimeCostFilter 的构造器中加个断点然后使用调试的方式快速定位到它的初始化时机这里我直接给出了调试截图 在上述的关键调用栈中结合源码你可以找出一些关键信息 1. Tomcat 等容器启动时才会创建 FilterRegistrationBean 2. FilterRegistrationBean 在被创建时createBean会创建 TimeCostFilter 来装配自身TimeCostFilter 是通过 ResolveInnerBean 来创建的 3. TimeCostFilter 实例最终是一种 InnerBean我们可以通过下面的调试视图看到它的一些关键信息 通过上述分析你可以看出最终 TimeCostFilter 实例是一种 InnerBean所以自动注入不到也就非常合理了。 问题修正 找到了问题的根源解决就变得简单了。 从上述的解析中我们可以了解到当使用 WebFilter 修饰过滤器时TimeCostFilter 类型的 Bean 并没有注册到 Spring 容器中真正注册的是 FilterRegistrationBean。这里考虑到可能存在多个 Filter所以我们可以这样修改下案例代码 Controller Slf4j public class StudentController {AutowiredQualifier(com.spring.puzzle.filter.TimeCostFilter)​FilterRegistrationBean timeCostFilter;} 这里的关键点在于 注入的类型是 FilterRegistrationBean 类型而不是 TimeCostFilter 类型注入的名称是包含包名的长名称, 即 com.spring.puzzle.filter.TimeCostFilter不能用 TimeCostFilter以便于存在多个过滤器时进行精确匹配。 经过上述修改后代码成功运行无任何报错符合我们的预期。 案例 2Filter 中不小心多次执行 doFilter() 在之前的案例中我们主要都讨论了使用 ServletComponentScan WebFilter 构建过滤器过程中的一些常见问题。 而在实际生产过程中如果我们需要构建的过滤器是针对全局路径有效且没有任何特殊需求主要是指对 Servlet 3.0 的一些异步特性支持那么你完全可以直接使用 Filter 接口或者继承 Spring 对 Filter 接口的包装类 OncePerRequestFilter并使用 Component 将其包装为 Spring 中的普通 Bean也是可以达到预期的需求。 不过不管你使用哪一种方式你都可能会遇到一个共同的问题业务代码重复执行多次。 考虑到上一个案例用的是 ServletComponentScan WebFilter这里我们不妨再以 Component Filter 接口的实现方式来呈现下我们的案例也好让你对 Filter 的使用能了解到更多。 首先还是需要通过 Spring Boot 创建一个 Web 项目不过已经不需要 ServletComponentScan SpringBootApplication() public class LearningApplication {public static void main(String[] args) {SpringApplication.run(LearningApplication.class, args);System.out.println(启动成功);} } StudentController 保持功能不变所以你可以直接参考之前的代码。另外我们定义一个 DemoFilter 用来模拟问题这个 Filter 标记了 Component 且实现了 Filter 接口已经不同于我们上一个案例的方式 Component public class DemoFilter implements Filter {public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {try {//模拟异常System.out.println(Filter 处理中时发生异常);throw new RuntimeException();} catch (Exception e) {chain.doFilter(request, response);}chain.doFilter(request, response);} } 全部代码实现完毕执行后结果如下 Filter 处理中时发生异常 ……用户注册成功 ……用户注册成功 这里我们可以看出业务代码被执行了两次这并不符合我们的预期。 我们本来的设计目标是希望 Filter 的业务执行不会影响到核心业务的执行所以当抛出异常时我们还是会调用 chain.doFilter。不过往往有时候我们会忘记及时返回而误入其他的 chain.doFilter最终导致我们的 Filter 执行多次。 而检查代码时我们往往不能立马看出问题。所以说这是一个典型的错误虽然原因很简单吧。不过借着这个案例我们可以分析下为什么会执行两次以深入了解 Filter 的执行。 案例解析 在解析之前我先给你讲下 Filter 背后的机制即责任链模式。 以 Tomcat 为例我们先来看下它的 Filter 实现中最重要的类 ApplicationFilterChain。它采用的是责任职责链设计模式在形式上很像一种递归调用。 但区别在于递归调用是同一个对象把子任务交给同一个方法本身去完成而职责链则是一个对象把子任务交给其他对象的同名方法去完成。其核心在于上下文 FilterChain 在不同对象 Filter 间的传递与状态的改变通过这种链式串联我们就可以对同一种对象资源实现不同业务场景的处理达到业务解耦。整个 FilterChain 的结构就像这张图一样 这里我们不妨还是带着两个问题去理解 FilterChain 1.FilterChain 在何处被创建又是在何处进行初始化调用从而激活责任链开始链式调用 2.FilterChain 为什么能够被链式调用其内在的调用细节是什么 接下来我们直接查看负责请求处理的 StandardWrapperValve#invoke()快速解决第一个问题 public final void invoke(Request request, Response response)throws IOException, ServletException {// 省略非关键代码// 创建filterChain ApplicationFilterChain filterChain ApplicationFilterFactory.createFilterChain(request, wrapper, servlet); // 省略非关键代码 try {if ((servlet ! null) (filterChain ! null)) {// Swallow output if neededif (context.getSwallowOutput()) {// 省略非关键代码 //执行filterChainfilterChain.doFilter(request.getRequest(),response.getResponse());// 省略非关键代码 } // 省略非关键代码 } 通过代码可以看出Spring 通过 ApplicationFilterFactory.createFilterChain() 创建 FilterChain然后调用其 doFilter() 执行责任链。而这些步骤的起始点正是 StandardWrapperValve#invoke()。 接下来我们来一起研究第二个问题即 FilterChain 能够被链式调用的原因和内部细节。 首先查看 ApplicationFilterFactory.createFilterChain()来看下 FilterChain 如何被创建如下所示 public static ApplicationFilterChain createFilterChain(ServletRequest request,Wrapper wrapper, Servlet servlet) {// 省略非关键代码ApplicationFilterChain filterChain null;if (request instanceof Request) {// 省略非关键代码// 创建Chain filterChain new ApplicationFilterChain();// 省略非关键代码}// 省略非关键代码// Add the relevant path-mapped filters to this filter chainfor (int i 0; i filterMaps.length; i) {// 省略非关键代码ApplicationFilterConfig filterConfig (ApplicationFilterConfig)context.findFilterConfig(filterMaps[i].getFilterName());if (filterConfig null) {continue;}// 增加filterConfig到ChainfilterChain.addFilter(filterConfig);}// 省略非关键代码return filterChain; } 它创建 FilterChain并将所有 Filter 逐一添加到 FilterChain 中。然后我们继续查看 ApplicationFilterChain 类及其 addFilter() // 省略非关键代码 private ApplicationFilterConfig[] filters new ApplicationFilterConfig[0]; private int pos 0; private int n 0 // 省略非关键代码 void addFilter(ApplicationFilterConfig filterConfig) {for(ApplicationFilterConfig filter:filters)if(filterfilterConfig)return;if (n filters.length) {ApplicationFilterConfig[] newFilters new ApplicationFilterConfig[n INCREMENT];System.arraycopy(filters, 0, newFilters, 0, n);filters newFilters;}filters[n] filterConfig; } 在 ApplicationFilterChain 里声明了 3 个变量类型为 ApplicationFilterConfig 的数组 Filters、过滤器总数计数器 n以及标识运行过程中被执行过的过滤器个数 pos。 每个被初始化的 Filter 都会通过 filterChain.addFilter()加入到类型为 ApplicationFilterConfig 的类成员数组 Filters 中并同时更新 Filter 总数计数器 n使其等于 Filters 数组的长度。到这Spring 就完成了 FilterChain 的创建准备工作。 接下来我们继续看 FilterChain 的执行细节即 ApplicationFilterChain 的 doFilter() public void doFilter(ServletRequest request, ServletResponse response)throws IOException, ServletException {if( Globals.IS_SECURITY_ENABLED ) {//省略非关键代码internalDoFilter(request,response);//省略非关键代码} else {internalDoFilter(request,response);} } 这里逻辑被委派到了当前类的私有方法 internalDoFilter具体实现如下 private void internalDoFilter(ServletRequest request,ServletResponse response){if (pos n) {// pos会递增ApplicationFilterConfig filterConfig filters[pos];try {Filter filter filterConfig.getFilter();// 省略非关键代码// 执行filterfilter.doFilter(request, response, this);// 省略非关键代码} // 省略非关键代码return;}// 执行真正实际业务servlet.service(request, response);} // 省略非关键代码 } 我们可以归纳下核心知识点 ApplicationFilterChain 的 internalDoFilter() 是过滤器逻辑的核心ApplicationFilterChain 的成员变量 Filters 维护了所有用户定义的过滤器ApplicationFilterChain 的类成员变量 n 为过滤器总数变量 pos 是运行过程中已经执行的过滤器个数internalDoFilter() 每被调用一次pos 变量值自增 1即从类成员变量 Filters 中取下一个 Filterfilter.doFilter(request, response, this) 会调用过滤器实现的 doFilter()注意第三个参数值为 this即为当前 ApplicationFilterChain 实例 这意味着用户需要在过滤器中显式调用一次 javax.servlet.FilterChain#doFilter才能完成整个链路pos n 意味着执行完所有的过滤器才能通过 servlet.service(request, response) 去执行真正的业务。 执行完所有的过滤器后代码调用了 servlet.service(request, response) 方法。从下面这张调用栈的截图中可以看到经历了一个很长的看似循环的调用栈我们终于从 internalDoFilter() 执行到了 Controller 层的 saveUser()。这个过程就不再一一细讲了。 分析了这么多最后我们再来思考一下这个问题案例。 DemoFilter 代码中的 doFilter() 在捕获异常的部分执行了一次随后在 try 外面又执行了一次因而当抛出异常的时候doFilter() 明显会被执行两次相对应的 servlet.service(request, response) 方法以及对应的 Controller 处理方法也被执行了两次。 你不妨回过头再次查看上文中的过滤器执行流程图相信你会有更多的收获。 问题修正 现在就剩下解决这个问题了。其实只需要删掉重复的 filterChain.doFilter(request, response) 就可以了于是代码就变成了这样 Component public class DemoFilter implements Filter {public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {try {//模拟异常System.out.println(Filter 处理中时发生异常);throw new RuntimeException();} catch (Exception e) {//去掉下面这行调用//chain.doFilter(request, response);}chain.doFilter(request, response);} } 重新运行程序和测试结果符合预期业务只执行了一次。回顾这个问题我想你应该有所警示在使用过滤器的时候一定要注意不管怎么调用不能多次调用 FilterChain#doFilter()。 重点回顾 通过这节课的学习相信你对过滤器已经有了一个较为深入的了解这里我们不妨再次梳理下关键知识点 1.WebFilter 这种方式构建的 Filter 是无法直接根据过滤器定义类型来自动注入的因为这种 Filter 本身是以内部 Bean 来呈现的它最终是通过 FilterRegistrationBean 来呈现给 Spring 的。所以我们可以通过自动注入 FilterRegistrationBean 类型来完成装配工作示例如下 AutowiredQualifier(com.spring.puzzle.filter.TimeCostFilter)​FilterRegistrationBean timeCostFilter; 2.我们在过滤器的执行中一定要注意避免不要多次调用 doFilter()否则可能会出现业务代码执行多次的问题。这个问题出现的根源往往在于“不小心”但是要理解这个问题呈现的现象就必须对过滤器的流程有所了解。可以看过滤器执行的核心流程图 结合这个流程图我们还可以进一步细化出以下关键步骤 当一个请求来临时会执行到 StandardWrapperValve 的 invoke()这个方法会创建 ApplicationFilterChain并通过 ApplicationFilterChain#doFilter() 触发过滤器执行ApplicationFilterChain 的 doFilter() 会执行其私有方法 internalDoFilter在 internalDoFilter 方法中获取下一个 Filter并使用 request、response、this当前 ApplicationFilterChain 实例作为参数来调用 doFilter()在 Filter 类的 doFilter() 中执行 Filter 定义的动作并继续传递获取第三个参数 ApplicationFilterChain并执行其 doFilter()此时会循环执行进入第 2 步、第 3 步、第 4 步直到第 3 步中所有的 Filter 类都被执行完毕为止所有的 Filter 过滤器都被执行完毕后会执行 servlet.service(request, response) 方法最终调用对应的 Controller 层方法 。