深度长文回顾web基础组件 (二)

简介: 深度长文回顾web基础组件 (二)

Spring-web对Servlet3.0的应用#



先上一张继承体系图,下面围绕这张图片展开


@HandlesTypes({WebApplicationInitializer.class})
public class SpringServletContainerInitializer implements ServletContainerInitializer {
    public SpringServletContainerInitializer() {
    }
    public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext) throws ServletException {
        List<WebApplicationInitializer> initializers = new LinkedList();
        Iterator var4;
        if (webAppInitializerClasses != null) {
            var4 = webAppInitializerClasses.iterator();
            while(var4.hasNext()) {
                Class<?> waiClass = (Class)var4.next();
                if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) && WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
                    try {
                        initializers.add((WebApplicationInitializer)ReflectionUtils.accessibleConstructor(waiClass, new Class[0]).newInstance());
                    } catch (Throwable var7) {
                        throw new ServletException("Failed to instantiate WebApplicationInitializer class", var7);
                    }
                }
            }
        }
...
}


可以看到,Spring应用一启动就会加载WebApplicationInitializer接口下的所有组件,并且,只要这些组件不是接口,不是抽象类,Spring就为它们创建实例

更进一步看一下上下文中WebApplicationInitializer接口的实现类


AbstractContextLoaderInitializer#


看他对onstart()方法的重写, 主要干了什么呢? 注册了一个上下文的监听器(借助这个监听器读取SpringMvc的配置文件),初始化应用的上下文


public abstract class AbstractContextLoaderInitializer implements WebApplicationInitializer {
    protected final Log logger = LogFactory.getLog(this.getClass());
    public AbstractContextLoaderInitializer() {
    }
    public void onStartup(ServletContext servletContext) throws ServletException {
        this.registerContextLoaderListener(servletContext);
    }
    protected void registerContextLoaderListener(ServletContext servletContext) {
        WebApplicationContext rootAppContext = this.createRootApplicationContext();
        if (rootAppContext != null) {
            ContextLoaderListener listener = new ContextLoaderListener(rootAppContext);
            listener.setContextInitializers(this.getRootApplicationContextInitializers());
            servletContext.addListener(listener);
        } else {
            this.logger.debug("No ContextLoaderListener registered, as createRootApplicationContext() did not return an application context");
        }
    }


AbstractDispatcherServletInitializer#


见名知意,他是DispatcherServlet的初始化器,他主要做了什么事呢?

  • 上面看了,它的父类初始化上下文,于是它调用父类的构造,往上传递web环境的上下文
  • 紧接着添加DispatcherServlet


public abstract class AbstractDispatcherServletInitializer extends AbstractContextLoaderInitializer {
public static final String DEFAULT_SERVLET_NAME = "dispatcher";
public AbstractDispatcherServletInitializer() {
}
public void onStartup(ServletContext servletContext) throws ServletException {
    super.onStartup(servletContext);
    this.registerDispatcherServlet(servletContext);
}
protected void registerDispatcherServlet(ServletContext servletContext) {
    String servletName = this.getServletName();
    Assert.hasLength(servletName, "getServletName() must not return null or empty");
    // 可以看一下,它创建的是web的容器 
    WebApplicationContext servletAppContext = this.createServletApplicationContext();
    Assert.notNull(servletAppContext, "createServletApplicationContext() must not return null");
    // 创建负责调度的 DispatcherServlet
    FrameworkServlet dispatcherServlet = this.createDispatcherServlet(servletAppContext);
    Assert.notNull(dispatcherServlet, "createDispatcherServlet(WebApplicationContext) must not return null");
    dispatcherServlet.setContextInitializers(this.getServletApplicationContextInitializers());
    // 添加Servlet 
    Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet);
    if (registration == null) {
        throw new IllegalStateException("Failed to register servlet with name '" + servletName + "'. Check if there is another servlet registered under the same name.");
    } else {
        // 添加servlet的mapping信息
        registration.setLoadOnStartup(1);
        registration.addMapping(this.getServletMappings());
        registration.setAsyncSupported(this.isAsyncSupported());
        Filter[] filters = this.getServletFilters();
        if (!ObjectUtils.isEmpty(filters)) {
            Filter[] var7 = filters;
            int var8 = filters.length;
            for(int var9 = 0; var9 < var8; ++var9) {
                Filter filter = var7[var9];
                this.registerServletFilter(servletContext, filter);
            }
        }
        this.customizeRegistration(registration);
    }
  ...
  }


AbstractAnnotationConfigDispatcherServletInitializer#


createRootApplicationContext重写了父类的创建上下文的方法,我觉得这算是一个高潮吧, 因为啥呢,AnnotationConfigWebApplicationContext是SpringMvc使用的应用的上下文,怎么创建的源码在下面,其实我有在Spring源码阅读中写过这个方面的笔记,下面仅仅是将配置类传递给Spring的bean工厂,并没有对配置类进行其他方面的解析,或者是扫描包啥的


createServletApplicationContext()重写了它父类的创建serlvet上下文的方法,

有个点,大家有没有发现,SpringMvc的上下文和Servlet的上下文是同一个对象,都是AnnotationConfigWebApplicationContext,不同点就是添加了if-else分支判断,防止重复创建


public abstract class AbstractAnnotationConfigDispatcherServletInitializer extends AbstractDispatcherServletInitializer {
    public AbstractAnnotationConfigDispatcherServletInitializer() {
    }
    @Nullable
    protected WebApplicationContext createRootApplicationContext() {
        Class<?>[] configClasses = this.getRootConfigClasses();
        if (!ObjectUtils.isEmpty(configClasses)) {
            AnnotationConfigWebApplicationContext context = new 有没有大神了解这个情况, SpringMvc的应用上下文和Servlet应用上下文竟然是同一个();
            context.register(configClasses);
            return context;
        } else {
            return null;
        }
    }
    protected WebApplicationContext createServletApplicationContext() {
        AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
        Class<?>[] configClasses = this.getServletConfigClasses();
        if (!ObjectUtils.isEmpty(configClasses)) {
            context.register(configClasses);
        }
        return context;
    }


对比官网推荐的启动案例:#


下面的是Spring官网推荐是通过注解的配置方法,仔细看看,其实和上面的Spring-Web模块的做法是一样的


public class MyWebApplicationInitializer implements WebApplicationInitializer {
    @Override
    public void onStartup(ServletContext servletCxt) {
        // Load Spring web application configuration
        AnnotationConfigWebApplicationContext ac = new AnnotationConfigWebApplicationContext();
        ac.register(AppConfig.class);
        ac.refresh();
        // Create and register the DispatcherServlet
        DispatcherServlet servlet = new DispatcherServlet(ac);
        ServletRegistration.Dynamic registration = servletCxt.addServlet("app", servlet);
        registration.setLoadOnStartup(1);
        registration.addMapping("/app/*");
    }
}


基于Servlet3.0全注解方式整合SpringMvc#


经过前面的分析,第一个结论是:服务器一启动,经过自上而下的继承体系AbstractAnnotationConfigDispacherServletInitializer会被加载执行,所以,当我们想使用全注解方式完成继承SpringMVC时,继承AbstractAnnotationConfigDispacherServletInitializer就好


public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
    // 获取Spring容器的配置类
    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class[]{RootConfig.class};
    }
    // 获取web容器的配置类
    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class[]{WebConfig.class};
    }
    /**
     * 获取DispatcherSerlvet的映射信息
     * / : 表示拦截所有请求(包含静态资源 XXX.js  XXX.jpg) 但是不包含 XXX.jsp
     * /* : 表示拦截所有请求(包含静态资源 XXX.js  XXX.jpg) 包含 XXX.jsp
     * @return
     */
    @Override
    protected String[] getServletMappings() {
        return new String[]{"/"};
    }
}


下面的两个配置类, 按照他的意思,分成了两个配置类,一个是web上下文中的配置类,另一个是Spring原生环境的配置类


但是吧,看看下面的配置真的是特别麻烦,一个得排除@Controller,完事另一个得包含@Controller,其实Spring原生上下文都认识这些通用注解,倒不如直接就一个配置类,还省事


@ComponentScan(value = "com.changwu",includeFilters = {
        @ComponentScan.Filter(type = FilterType.ANNOTATION,classes = {Controller.class})
},useDefaultFilters = false)
public class WebConfig {
}
@ComponentScan(value = "com.changwu",excludeFilters = {
        @ComponentScan.Filter(type = FilterType.ANNOTATION,classes = {Controller.class})
})
public class RootConfig {
}


Servlet3.0 异步请求处理器#


早前的前后端请求响应的模型是怎样的呢? 用户发送的请求经过网络传输到Tomcat,Tomcat中存在一个线程池,这时Tomcat会从线程池中取出一条线程专门处理这个请求,一直到处理完毕,给了用户响应之后才将此线程回收到线程池,但是线程池中的线程终究是有限的,一旦同时好几百的连接进来,Tomcat的压力骤然上升,难免会出现阻塞的现象


Serlet3.0引入的异步处理,让主线程拥有非阻塞的特性,这样tomcat接收请求访问的吞吐量就会增加


示例:


// 启动异步
@WebServlet(value = "/async",asyncSupported = true)
public class MyAsyncServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doGet(req,resp);
    }
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 开启异步处理
        AsyncContext asyncContext = req.startAsync();
        asyncContext.start(new Runnable() {
            @Override
            public void run() {
                // do other things
                // 结束
                asyncContext.complete();
                // 响应
                ServletResponse response = asyncContext.getResponse();
                try {
                    response.getWriter().write("123");
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        });
    }
}


SpringMvc的异步任务#


针对Servlet3.0的异步特性,SpringMvc相关的支持是提供了异步线程池


DeferredResult#


我觉得这个异步的实现方式简直是无与伦比!!!无法言表!!!


@GetMapping("/quotes")
@ResponseBody
public DeferredResult<String> quotes() {
    DeferredResult<String> deferredResult = new DeferredResult<String>();
    // Save the deferredResult somewhere..
    return deferredResult;
}
// From some other thread...
deferredResult.setResult(result);


Callable#


  • 方法的最后将Callable返回
  • call()方法中做写需要异步处理器的逻辑


执行流程:

  • SpringMvc会将这个Callable放到一个叫TaskExcutor中执行
  • DispatcherSerlvet和所有的Filter退出web容器,但是Response保持打开状态
  • SpringMvc会将Callable的返回结果重写派发给setlvet恢复之前的处理
  • 根据Callable返回的结果SpringMvc进行渲染


@PostMapping
public Callable<String> processUpload(final MultipartFile file) {
    return new Callable<String>() {
        public String call() throws Exception {
            // ...
            return "someView";
        }
    };
}


Request#


请求方式与很多种,get post head trace options 还有put delete

其中get post put delete 是RestfulAPI中推荐,也是现在盛行使用的四种请求方法

get: 最为简单的请求方式,一般数据添加在url后面一般这样写username?张三&password?123123, 由于URL的长度有限制,故能传输的数据一般在1M左右, 而且数据明文传输,像上面那样,村咋存在安全隐患

post的数据存放在请求体中,一般没有大小限制,相对于get而言,post的安全性更好一点


继承图如下:#



上图中我们最常使用的HttpServletRequest竟然是个接口,当时一开始学web的时候确实觉得很奇怪,但是现在想想其实也还好了,因为Tomcat提供了实现类org.apache.catalina.connector.RequestFacade


获取请求行数据-GET#


点击查看文档


请求行: GET /test/app?name=zhangsan http/1.1


  • 获取请求方法: GET


String getMethod()


  • 获取虚拟路径(项目路径): /test


String getContextPath()


  • 获取Servlet路径; /app


String  getServletPath()


  • 获取get请求的请求参数: name=zhangsan


String getQueryString()


  • 获取URI : /test/app


String getRequestURI()


  • 获取URL : http:localhost/test/app


String getRequestURL()


  • 获取协议版本


String getProtocol()


  • 获取远程主机地址


String getRemoteAddr()


获取请求头数据#


  • 根据名称获取请求头


String getHeader(String name);


  • 获取所有的请求头


Enumertion<String> getHeaderNames(); 获取所有请求头的名称


获取请求体数据#


仅仅有post方式,才会有请求体:使用它分成两步:

  • 从request中获取流对象


BufferReader getReader(); // 获取字符输入流
ServletInputStream getInputStrream(); // 获取字节输入流


  • 从流对象中获取到需要的数据


通用的方法#


  • 根据参数名获取参数值


String getParamter(String name); 根据参数名获取参数值


  • 根据参数名,获取参数值数组


String [] getParameterValues(String name)


  • 获取所有请求的参数名称


Enumeration<String> getParamerterNames()


  • 获取所有参数键值对形式的map集合


Map<String,String[]) getParamerterMap()


有了通用的方法特性之后,我们就不跟针对doGet,doPost两种方式写两份代码, 只要在doGet()或者doPost()中调用另外一个就ok,因为方法针对两者通用


解决中文乱码#


首先: Tomcat8自身解决了中文乱码问题

Post方式提交数据依然存在乱码问题,像下面这样先设置编码再使用 req


request.setCharacterEncoding("utf-8")


request的请求转发#



当用户的某一个请求需要通过多个Servlet协作完成时,请求在Servlet之间跳转,这种资源跳转的方式称为请求转发


使用方法:通过当前的request获取出RequestDispacher对象,通过这个对象的forward(req,res)进行转发的动作


RequestDispatcher getRequestDispacher(String path) // path是另一个Servlet的url-pattern
forward(currentReq,currentRes);


特点:

  • 浏览器地址栏路径没有发生变化
  • 服务器内部官网的资源跳转,不能跳往别的站点
  • 一次转发,对浏览器来说,仅仅发送了一次请求

因为我们没让浏览器发送两次请求,在服务端完成了请求转发,所以上面的path仅仅是servlet-url-pattern,而不包含项目路径


域对象-共享数据#


域对象: request域, 既然是域对象,他就有自己的作用范围,request的作用范围是什么呢? 就是一次请求,每次请求都是一个域, 换句话说,如果说客户端的一次请求经过了AServlet,然后AServlet将请求转发到了BServlet,name AServlet BServlet就在一个域中,也就可以共享彼此的数据


怎么玩?


在AServlet
setAttribute(String name,Object obj);
请求转发到BServlet
在BServlet
getAttribute(String name);
移除
removeAttribute(String name);


相关文章
|
5月前
|
JavaScript 前端开发 开发者
哇塞!Vue.js 与 Web Components 携手,掀起前端组件复用风暴,震撼你的开发世界!
【8月更文挑战第30天】这段内容介绍了Vue.js和Web Components在前端开发中的优势及二者结合的可能性。Vue.js提供高效简洁的组件化开发,单个组件包含模板、脚本和样式,方便构建复杂用户界面。Web Components作为新兴技术标准,利用自定义元素、Shadow DOM等技术创建封装性强的自定义HTML元素,实现跨框架复用。结合二者,不仅增强了Web Components的逻辑和交互功能,还实现了Vue.js组件在不同框架中的复用,提高了开发效率和可维护性。未来前端开发中,这种结合将大有可为。
196 0
|
5月前
|
Web App开发 JavaScript 前端开发
[译] 用 Web Worker 改善 Vue 组件性能
[译] 用 Web Worker 改善 Vue 组件性能
|
3月前
|
前端开发 JavaScript 开发者
Web组件:一种新的前端开发范式
【10月更文挑战第9天】Web组件:一种新的前端开发范式
95 2
|
3月前
|
前端开发 JavaScript Go
前端开发趋势:从响应式设计到Web组件的探索
【10月更文挑战第1天】前端开发趋势:从响应式设计到Web组件的探索
47 3
|
2月前
|
消息中间件 监控 Kafka
Apache Kafka 成为处理实时数据流的关键组件。Kafka Manager 提供了一个简洁的 Web 界面
随着大数据技术的发展,Apache Kafka 成为处理实时数据流的关键组件。Kafka Manager 提供了一个简洁的 Web 界面,方便管理和监控 Kafka 集群。本文详细介绍了 Kafka Manager 的部署步骤和基本使用方法,包括配置文件的修改、启动命令、API 示例代码等,帮助你快速上手并有效管理 Kafka 集群。
57 0
|
6月前
|
Web App开发 前端开发 安全
2024年新一代WebOffice内嵌网页组件,Web网页在线编辑Word/Excel/PPT
WebOffice控件面临兼容性、用户体验和维护难题。随着浏览器更新,依赖插件的技术不再适用,如Chrome不再支持NPAPI和PPAPI。产品普遍不支持多版本Office并存,定制能力弱,升级复杂。猿大师办公助手提供了解决方案,它兼容多种浏览器,包括最新版和国产浏览器,不依赖插件,支持文档对比,具有丰富的功能和接口,兼容多种Office版本,允许源码级定制,提供终身技术支持,并实现静默在线升级。适用于多种行业和操作系统。
364 14
|
5月前
|
数据可视化 数据挖掘 持续交付
Axure Web端元件库:从Quick UI到500+组件的飞跃
在快速变化的数字世界中,产品设计不仅仅是功能的堆砌,更是用户体验的精心雕琢。原型设计作为产品开发过程中的关键环节,其重要性不言而喻。Axure,作为业界领先的原型设计工具,凭借其强大的交互设计和丰富的功能,赢得了全球设计师和开发者的信赖。而Axure Web端元件库,则是这一平台上的一颗璀璨明珠,它以超过500个精心设计的组件为基础,为设计师们打开了一扇通往高效、高质量原型设计的大门。
219 0
|
6月前
|
设计模式 JavaScript 前端开发
Web Components详解-组件通信
Web Components详解-组件通信
117 6
|
8月前
|
域名解析 缓存 网络协议
JavaEE精选-Web组件
JavaEE精选-Web组件
54 1
|
8月前
|
JavaScript 前端开发 API
Vue中的组件:构建现代Web应用的基石
Vue中的组件:构建现代Web应用的基石