zoukankan      html  css  js  c++  java
  • 深度长文回顾web基础组件

    什么是Serlvet ?

    全称 server applet 运行在服务端的小程序:

    首先来说,这个servlet是java语言编写的出来的应用程序,换句话说servlet拥有java语言全部的优点,比如跨越平台,一次编译到处运行

    其次: 相对于CGI(common gateway interface)规范而言,CGI是针对每一个用户的请求创建一个进程处理,而servlet所在的服务器会对每一个请求创建一个线程来处理,虽然线程数量有上限,但是相对于创建进程来说,后者对系统资源的开销更小

    然后就是: 现在盛行javaWeb服务器Tomcat也是java语言编写的,毕竟Tomcat有Serlvet容器支持,所以servlet和web服务器之间无缝连接

    Servlet其实一个接口,一套规范,不同的厂家对它有不同的实现,tomcat也是如此,
    web服务器会把解析http协议信息的逻辑封装进他们的Servlet中,比如将用户发送的请求(request) HttpRequestServlet,
    把响应给用户http报文的逻辑封装进HttpResponseServlet中, 然后web服务器负责不同组件,不同servlet之间的调度关系,
    什么是调度呢? 比如说: 通过某个URL找到指定的Servlet,回调Servlet的service()方法处理请求

    Servlet的体系结构

    servlet接口的实现类

    servlet接口的实现类如上图

    Servlet在java中是一个接口,封装了被浏览器访问到服务器(tomcat)的规则

    添加serlvet

    通过web.xml

    <?xml version="1.0" encoding="ISO-8859-1"?>
    <web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
    
      <display-name>Camel Routes</display-name>
    
        <!-- Camel servlet -->
        <servlet>
            <servlet-name>app1</servlet-name>
            <servlet-class>com.changwu.web.MyServlet</servlet-class>
            <load-on-startup>1</load-on-startup>
        </servlet>
    
        <!-- Camel servlet mapping -->
        <servlet-mapping>
            <servlet-name>app1</servlet-name>
            <url-pattern>/app1</url-pattern>
        </servlet-mapping>
    
    </web-app>
    
    

    通过注解

    舍弃web.xml是serlet3.0添加全注解技术, 这个注解的属性和需要在xml中配置的对应的

    需要Tomcat7及以上才支持

    @Target({ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface WebServlet {
        
        /**
         * servlet-name
         */
        String name() default "";
        
        /**
         * The URL patterns of the servlet
         */
        String[] value() default {};
    
        /**
         * servlet的资源路径, 可以为一个servlet配置多个访问路径
         */
        String[] urlPatterns() default {};
        
        /**
         * 启动级别默认是-1,同样意味着依然是第一次访问时初始化
         */
        int loadOnStartup() default -1;
        
        /**
         * The init parameters of the servlet
         */
        WebInitParam [] initParams() default {};
        
        /**
         * Declares whether the servlet supports asynchronous operation mode.
         *
         * @see javax.servlet.ServletRequest#startAsync
         * @see javax.servlet.ServletRequest#startAsync(ServletRequest,
         * ServletResponse)
         */
        boolean asyncSupported() default false;
        
        /**
         * The small-icon of the servlet
         */
        String smallIcon() default "";
    
         /**
          * The large-icon of the servlet
          */
        String largeIcon() default "";
    
        /**
         * The description of the servlet
         */
        String description() default "";
    
        /**
         * The display name of the servlet
         */
        String displayName() default "";
    
    }
    

    servlet的路径定义规则

    • /xxx
    @WebServlet(urlPatterns = {"/app1","/app2"})
    
    • /xxx/yyy
    @WebServlet(urlPatterns = {"/app1/app2"})
    
    • /xxx/*
    @WebServlet(urlPatterns = {"/app1/*"})
    
    • *.do
    @WebServlet(urlPatterns = {"*.do"})
    

    执行原理:

    1. tomcat读取xml配置文件中配置servlet,根据用户配置的加载时机,通过反射技术创建出对象实例
    2. 用户的请求报文经过tomcat的解析,分发到的Servlet下面,进行不同的回调处理

    Servlet接口的方法

    • 初始化方法, 创建servlet时执行一次
    • 什么时候被创建: 默认情况下 第一次访问时被创建
      • 一般我们都在web.xml配置,让Servlet在启动时完成加载 <load-on-startup>1</load-on-startup>默认这个值是-1, 表示第一次访问时被创建, 整数表示启动时初始化
    • 此外: Servlet的init()方法仅仅被执行一次,说明serlet是单例的,那么在并发的情况的就可能出现线程安全问题 , 解决: 尽量不要在serlvet中定义成员变量,我们最好去成员方法中定义变量,即使定义了, 不要提供set()方法,仅仅提供的get()
        @Override
        public void init(ServletConfig servletConfig) throws ServletException {
            System.out.println("init.....");
        }
    
    • 获取serlvet config 配置对象
        //
        @Override
        public ServletConfig getServletConfig() {
            return null;
        }
    
    • 提供服务的方法, 每次serlvet被访问都会执行一次
        @Override
        public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
            System.out.println("--------------------service------------");
        }
    
    • 获取serlvet 的信息, 版本等
        @Override
        public String getServletInfo() {
            return null;
        }
    
    • 服务器正常关闭前, 销毁servlet时 回调
    • 服务器非正常关闭,不会执行
        @Override
        public void destroy() {
            System.out.println("destroy");
        }
    

    Servlet3.0新特性

    Servlet3.0中的重大升级是ServletContainerInitializer,通过这个技术使我们可以为现有的组件写出可插拔的组件,与之相对应的是Servlet的新规范如下:

    在执行的路径下面创建指定的文件

    /classpath:   
        --META-INF    (目录)
           --services   (目录) 
              --javax.servlet.ServletContainerInitializer (文件)
    

    我们可以在上面的文件中配置一个类的全类名,这个类是谁无所谓,但是只要它实现了这个ServletContainnerInitializer接口,并重写它的onStart()方法,于是当容器(tomcat)启动的时候就会调用这个类的 onStart()方法

    这个规范带来的革命决定是历史性的,有了它我们的代码就有了可插拔的能力,不信可以回想一下传统的配置文件,如果想给项目进行升级,还不想改动xml文件,那是不可能的,但是现在不同了,只要让我们的类实现这个ServletContainnerInitializer,重写它的方法,它的onStart()就会被回调,而其他的功能不受响应,去掉这个类,项目整体也不受响应

    示例:

    容器启动的时候,会把容器中,被@HandlerTypes(value={Test.class}) 中指定的所有Test.class实现类(子类,子接口)的实例传递进下面的set集合

    @HandlesTypes(Test.class)
    public class ChangWuInitializer implements ServletContainerInitializer {
    
        @Override
        public void onStartup(Set<Class<?>> set, ServletContext servletContext) throws ServletException {
            System.out.println(set);
        }
    }
    

    通过上面onstart()方法可以看到,第二个参数位置上是 ServletContext, 这个对象是什么?有啥用? 在下文中单独开一个模块说

    ServletContext

    tomcat会为每一个web项目创建一个全局唯一的ServeltContext,这个对象里面封装着整个应用的信息,常用的当作域对象,所有的servlet之间共享数据,同时他还可以获取出web.xml文件中的数据

    功能:

    • 获取MIME类型
      • MIME类型是互联网通信中定义的文件数据类型
      • 格式: 大类型/小类型 如: test/html

    在tomcat的配置文件目录中存在web.xml ,里面的存在大量的MEMI类型的数据,都可以从ServletContext中获取出来

    String getMimeType(String file)
    
    • 域对象(共享数据)

    范围: 类似于Session,通过ServletContext对象我们也可以实现数据共享,但值得注意的是,Session是只能在一个客户端中共享数据,而ServletContext中的数据是在所有客户端中都可以实现数据共享的。

    方法:

    setAttribute(String name,Onject obj);
    getAttribute(String name);
    removeAttribute(String name);
    
    • 获取文件真实的文件路径
      方法
    this.getServletContext().getRealPath("/");  // 现在访问的目录是tomcat中和WEB-INF同级目录
    
    • 实现请求转发
    // 方式1:
    request.getRequestDispatcher("/url").forward(req,res);
    
    // 方式2:
    this.getServletContext().getRequestDispatcher("/url").forward(req,res);
    
    • 获取web应用的初始化参数

    我们可以用标签为servlet配置初始化参数,然后使用ServletConfig对象获取这些参数,假如有如下的MyServlet,它的配置为:

    <servlet>  
        <servlet-name>MyServlet</servlet-name>  
        <servlet-class>com.gavin.servlet.MyServlet</servlet-class>  
        <init-param>  
            <param-name>encoding</param-name>
            <param-value>utf-8</param-value>
        </init-param>  
    </servlet> 
    

    获取:

    
    String encoding = this.getServletConfig().getInitParameter("encoding");
    

    如何获取:

    ServletContext在web应用上下文中以单例的形式存在,下面两种获取方式得到的ServletContext是同一个对象

    ServletContext servlet1 = request.getServletContext();
    ServletContext servlet2 = this.getServletContext();
    this.getServletConfig().getServletContext();
    

    生命周期

    服务器一启动就创建,服务器关闭时才销毁

    注册三大web组件(servlet filter listener)

    • Servlet
    addServlet、createServlet、getServletRegistration、getServletRegistrations
    
    • Filter
    addFilter、createFilter、getFilterRegistration、getFilterRegistrations
    
    • 监听器
    addListener、createListener
    

    Spring-web对Servlet3.0的应用

    spring-web-Init体系图

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

    @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的安全性更好一点

    继承图如下:

    Request继承图

    上图中我们最常使用的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的请求转发

    forward

    当用户的某一个请求需要通过多个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);
    

    Reponse

    响应信息格式如下:

    HTTP/1.1 200 OK  //响应行
    
    ---------------------------------------
    
    响应头
     // 服务器的类型
    Server: server-name 
    //服务端告诉客户端,自己推送给它的数据的编码格式(浏览器根据指定的类型进行解码)
    Content-Type: text/html;charset=utf-8  
    // 响应内容的长度
    Content-Length: XXX 
    // 响应日期
    Date: XXX  
    //服务器告诉浏览器用什么格式打开响应体数据, 默认是in-line 表示在当前页面中打开, attachment(文件下载)
    Content-disposition: in-line
    
    ----------------------------------------
    
    (响应空行)
    
    ----------------------------------------
    
    XXX   // 响应体
    

    常用方法

    Response对象就是用来设置响应消息的对象

    • 设置响应行
    // 格式: HTTP/1.1 200 ok
    setStatus(int status);
    
    • 设置响应头
    setHeader(String name, String value);
    
    • 设置响应体
    // 0 在往客户端写中文前先设置编码
    response.setContentType("text/html;charset=utf-8");
    // 1. 获取到输出流(字节流/字符流)
    
    // 2. 往客户端写
    response.getWriter().write("XXXXXXX");
    
    • 重定向

    redirect

    // 实现,从 AServlet 重定向到BServlet
    
    // 两步实现: 重定向
    // 设置状态码 / 响应头
    reponse.setStatus(302);
    response.setHeader("location","/项目路径/serlet-url-pattern");
    
    // 单行代理实现重定向
    response.sendRedirect("/项目路径/serlet-url-pattern");
    

    特点:

    • 重定向: 浏览器地址栏路径改变了
    • 重定向: 可以请求其他服务器
    • 重定向: 实际上发起了两次请求 (不能使用request域共享数据)

    因为我们让浏览器发送了两次请求, 因此重定向的路径中包含 项目路径

    常用api

    客户端会话技术,将数据保存在浏览器本地, 下一次访问时会携带着cookie

    • 创建cookie,绑定数据
    new Cookie(String name,String value)
    
    • 发送cookie
    response.addCookie(Cookie cookie);
    
    • 获取解析cookie
    Cookie [] cookies = request.getCookies();
    
    • 一次发送多个cookie
    Cookie c1 = new Cookie(String name,String value)
    Cookie c2 =new Cookie(String name,String value)
    reponse.addCookie(c1);
    reponse.addCookie(c2);
    
    • cookie的生命周期

    默认cookie存储在浏览器内存中,一旦浏览器关闭,cookie销毁

    设置cookie的生命周期

    setMaxAge(int seconds);
    seconds 为存活时间, 
    正数: 表示以文件的形式进行持久化,默认单位 s
    负数: 表示仅仅存在于内存中
    零:   表示让浏览器删除cookie
    
    • cookie存储中文

    tomcat8之前,cookie不支持中文,Tomcat8之后cookie支持中文

    tomcat8之前需要进行转码,一般采用URL编码

    • cookie获取的范围

    默认情况下:在一个tomcat中的部署的多个web项目之间cookie是不能共享的

    但是可以通过下面的方法设置更大路径,实现想要的效果

    setPath(String path); // 默认是当前的虚拟目录
    
    // 可以设置成下面这样
    setPath("/"); // 默认是当前的虚拟目录
    

    跨域tomcat之间cookie共享使用-- 根据域名划分

    setDomain(String path); // 只要一级域名相同,则多个服务器之间共享 cookie
    

    特点:

    • 存储在浏览器,不安全
    • 浏览器对单个cookie大小(一般都在4kb),对同一个域名下的cookie总数也有限制(一般20个以内)

    小场景:

    服务器,浏览器协作的流程: 比如登录: 用户通过浏览器往服务端发送登录请求,服务端验证用户名密码,通过后往客户端的发送cookie, 其实是设置了响应头set-cookie=XXX, 浏览器碰到这种响应头,就把这个响应体缓存在本地,再次请求这个网站时,会自动携带这个请求头cookie=XXX , 后端通过解析用户发送过来的cookie,可以判断当前请求的用户是否是合法的

    Session

    服务端会话技术,在一次会话中多次请求共享数据,将数据保存在服务端的对象--HttpSession

    session也是域对象

    常用API

    • 获取session
    HttpSession session = request.getSession();
    
    • 使用session
    setAttribute(String name,Object obj);
    
    getAttribute(String name);
    
    romoveAttribute(String name);
    

    如何确保多次会话中,多次获取到的session是同一个

    1. 用户通过浏览器向服务端发送请求
    2. 服务端验证用户的信息,通过验证后,如果没有当前的session就为用户创建session(每个session都有唯一的id),创建cookie,给客户端返回相应 set-coolkie:JSESSIONID=shdfiuhduifha
    3. 用户再次访问服务端,浏览器会自动携带上请求头cookie:JSESSIONID=shdfiuhduifha
    4. 服务器解析cookie携带的JSESSIONID=shdfiuhduifha便可以找出唯一的session

    细节

    • 客户端关闭,服务端不关闭,两次获取到的session一样吗?
      • session是依赖cookie的,客户端关闭,cookie被干掉了,也就是说本次会话也就结束了,后端的session将被干掉
      • 但是如果我们发送给浏览器一个可以存活很长时间的cookie,再次打开浏览器访问后端,session还是同一个
    • 客户端不关闭,服务端关闭,两次获取到的session一样吗?
      • 服务器都没了,session肯定被干掉了,session肯定不一样
      • 补救:钝化 tomcat在服务器正常关闭前,将session序列化持久化到磁盘上
      • 补救:活化 tomcat在服务器启动时,将session重新加载进内存

    idea中可以成功完成钝化,但是不能完成活化

    • session失效时间
      • 服务器关闭
      • 使用api,自动关闭 invalidate()
      • session默认的失效时间30分钟

    过期时间可以在Tomcat中的配置文件目录下的web.xml中配置

    <session-config>
        <session-timeout>30</session-timeout>
    </session-config>
    

    特点:

    • session用于存储一次会话的多次请求数据,存储在服务端
    • session可以存储任意类型的数据没有大小限制
  • 相关阅读:
    JAVA学习25天
    Java学习第24天
    Java学习第23天
    Java学习22天
    第3周
    Java21
    day23作业
    day23
    Typecho使用技巧
    搭建Typecho博客
  • 原文地址:https://www.cnblogs.com/ZhuChangwu/p/11712899.html
Copyright © 2011-2022 走看看