配置嵌入式servlet容器(JavaWeb三大组件(Servlet、Filter、Listener))

如何定制和修改Servlet容器的相关配置
1,修改和server有关的配置
1)配置文件的形式修改

server.port=8081
server.context-path=/crud

server.tomcat.uri-encoding=UTF-8

//通用的Servlet容器设置
server.xxx
//Tomcat的设置
server.tomcat.xxx

2)编写一个EmbeddedServletContainerCustomizer ,2.0以后改为WebServerFactoryCustomizer:嵌入式的Servlet容器的定制器;来修改Servlet容器的配置.

    @Bean
    public WebServerFactoryCustomizer webServerFactoryCustomizer() {
        return new WebServerFactoryCustomizer<ConfigurableServletWebServerFactory>() {
            @Override
            public void customize(ConfigurableServletWebServerFactory factory) {
                factory.setPort(8081);
            }
        };
    }

在这里插入图片描述
注意:

代码方式的配置会覆盖配置文件的配置
如果使用的是360极速浏览器就不要用8082端口了

注册Servlet三大组件
由于SpringBoot默认是以jar包的方式启动嵌入式的Servlet容器来启动SpringBoot的web应用,没有web.xml文件。
这块怎么理解呢???
spring boot 之所以没有那么多配置文件,其实就是把配置文件的形式改为了配置类的形式。
第一大组件: Servlet

自己配置的servlet

public class MyServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req,resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
       resp.getWriter().write("hello myservlet");
    }
}

把组件注入

 @Bean
    public ServletRegistrationBean myservlet(){
        ServletRegistrationBean servletRegistrationBean=new ServletRegistrationBean(new MyServlet(),"/myServlet");
        return servletRegistrationBean;
    }

第二大组件: 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 {
        System.out.println("进自己配置的Filter了");
        filterChain.doFilter(servletRequest,servletResponse);
    }

    @Override
    public void destroy() {

    }
    }

注册:

  @Bean
    public FilterRegistrationBean myfilter(){
        FilterRegistrationBean filterFilterRegistrationBean = new FilterRegistrationBean();
        filterFilterRegistrationBean.setFilter(new MyFilter());
        filterFilterRegistrationBean.setUrlPatterns(Arrays.asList("/hello","/aaa"));
        return filterFilterRegistrationBean;
    }

第二大组件: Listener

public class MyListener implements ServletContextListener {
    @Override
    public void contextInitialized(ServletContextEvent sce) {

        System.out.println("web 启动");
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        System.out.println("web 销毁");
    }
}

注册:

@Bean
    public ServletListenerRegistrationBean mylistener(){
        ServletListenerRegistrationBean<EventListener> eventListenerServletListenerRegistrationBean = new ServletListenerRegistrationBean<>(new MyListener());
        return eventListenerServletListenerRegistrationBean;
    }

JavaWeb三大组件(Servlet、Filter、Listener)
Servlet
是用来处理客户端请求的动态资源,也就是当我们在浏览器中键入一个地址回车跳转后,请求就会被发送到对应的Servlet上进行处理。

  • 接收请求数据:我们都知道客户端请求会被封装成HttpServletRequest对象,里面包含了请求头、参数等各种信息。
  • 处理请求:通常我们会在service、doPost或者doGet方法进行接收参数,并且调用业务层(service)的方法来处理请求。
  • 完成响应:处理完请求后,我们一般会转发(forward)或者重定向(redirect)到某个页面,转发是HttpServletRequest中的方法,重定向是HttpServletResponse中的方法,两者是有很大区别的。
    Servlet生命周期
    Servlet运行在Servlet容器中,其生命周期由容器来管理。Servlet的生命周期通过Servlet接口中的init()、service()和destory()方法来表示。
    在这里插入图片描述
    注: 如果需要让Servlet容器在启动时自动加载Servlet,可以在web.xml文件中配置。
    单实例多线程的Servlet模型
    在这里插入图片描述
    Servlet规范中定义, 默认情况下(Servlet不是在分布式的环境中部署),Servlet容器对声明的每一个Servlet,只创建一个实例。如果有多个客户端请求同时访问这个Servlet,Servlet容器如何处理多个请求呢?答案是采用多线程,Servlet容器维护一个线程池来服务请求。当容器接收到一个访问Servlet的请求,调度者线程从线程池中选取一个工作线程,将请求传递给该线程,然后由这个线程执行Servlet的service()方法。

线程安全的Servlet
变量的线程安全
因为Servlet是单实例多线程模型,多个线程共享一个Servlet实例,因此对于实例变量的访问是非线程安全的。
建议:在Servlet中尽可能的使用局部变量,应该只使用只读的实例变量和静态变量。如果非得使用共享的实例变量或静态变量,在修改共享变量时应该注意线程同步。
属性的线程安全
在Servlet中,可以访问保存在ServletContext、HttpSession和ServletRequest对象中的属性。那么这三种不同范围的对象,属性访问是否是线程安全的呢?

ServletContext:该对象被Web应用程序的所有Servlet共享,多线程环境下肯定是非线程安全的。

HttpSession:HttpSession对象只能在同属于一个Session的请求线程中共享。对于同一个Session,我们可能会认为在同一时刻只有一个用户请求,因此,Session对象的属性访问是线程安全的。但是,如果用户打开多个同属于一个进程的浏览器窗口,在这些窗口中的访问请求同属于一个Session,对于多个线程的并发修改显然不是线程安全的。

ServletRequest:因为Servlet容器对它所接收到的每一个请求,都创建一个新的ServletRequest对象,所以ServletRequest对象只在一个线程中被访问,因此对ServletRequest的属性访问是线程安全的。但是,如果在Servlet中创建了自己的线程,那么对ServletRequest的属性访问的线程安全性就得自己去保证。此外,如果作死的将当前请求的Servlet通过HttpSession或者ServletContext共享,那当然也是非线程安全的。
Filter (过滤器)
filter是对客户端访问资源的过滤,符合条件放行,不符合条件不放行,并且可以对目 标资源访问前后进行逻辑处理。

public interface Filter {  
        //用于完成Filter的初始化  
    public void init(FilterConfig filterConfig) throws ServletException;  
    //实现过滤功能  
        public void doFilter ( ServletRequest request, ServletResponse response,   
FilterChain chain ) throws IOException, ServletException;  
        //用于销毁Filter前,完成某些资源的回收  
    public void destroy();  
}  

Filter生命周期
web.xml 中声明的每个 filter 在每个虚拟机中仅仅只有一个实例。
(1) 加载和实例化
Web 容器启动时,即会根据 web.xml 中声明的 filter 顺序依次实例化这些 filter。
(2) 初始化
Web 容器调用 init(FilterConfig) 来初始化过滤器。容器在调用该方法时,向过滤器传递 FilterConfig 对象,FilterConfig 的用法和 ServletConfig 类似。利用 FilterConfig 对象可以得到 ServletContext 对象,以及在 web.xml 中配置的过滤器的初始化参数。在这个方法中,可以抛出 ServletException 异常,通知容器该过滤器不能正常工作。此时的 Web 容器启动失败,整个应用程序不能够被访问。实例化和初始化的操作只会在容器启动时执行,而且只会执行一次。
(3) doFilter
doFilter 方法类似于 Servlet 接口的 service 方法。当客户端请求目标资源的时候,容器会筛选出符合 filter-mapping 中的 url-pattern 的 filter,并按照声明 filter-mapping 的顺序依次调用这些 filter 的 doFilter 方法。在这个链式调用过程中,可以调用 chain.doFilter(ServletRequest, ServletResponse) 将请求传给下一个过滤器(或目标资源),也可以直接向客户端返回响应信息,或者利用 RequestDispatcher 的 forward 和 include 方法,以及 HttpServletResponse 的 sendRedirect 方法将请求转向到其它资源。需要注意的是,这个方法的请求和响应参数的类型是 ServletRequest 和 ServletResponse,也就是说,过滤器的使用并不依赖于具体的协议。
(4) 销毁
Web 容器调用 destroy 方法指示过滤器的生命周期结束。在这个方法中,可以释放过滤器使用的资源。
Listener
当Web应用在Web容器中运行时,Web应用内部会不断地发生各种事件:如Web应用的启动和停止、用户Session的开始和结束等,通常这些Web事件对开发者是透明的。Listener(监听器)是观察者模式的应用,通过方法回调来实现。
Listener生命周期
Listener在当web容器启动的时候,去读取每个web应用的web.xml配置文件,当配置文件中配有filter和listener时,web容器实例化listener,listener是当某个事件发生时,调用它特定方法,如HttpSessionListener,当创建一个session时会调用它的sessionCreated()方法,当servlet容器关闭或者重新加载web应用时lister对象被销毁。

Listener分类
不同功能的Listener 需要实现不同的 Listener 接口,一个Listener也可以实现多个接口,这样就可以多种功能的监听器一起工作。常用监听器:
Listener应用
1、利用HttpSessionLister,统计当前在线人数。

public class OnLineCountListener implements HttpSessionListener {  
  
    @Override  
    public void sessionCreated(HttpSessionEvent se) {  
        ServletContext context = se.getSession().getServletContext();  
        Integer onLineCount = (Integer) context.getAttribute("onLineCount");  
        if (onLineCount == null) {  
            context.setAttribute("onLineCount", 1);  
        } else {  
            onLineCount++;  
            context.setAttribute("onLineCount", onLineCount);  
        }  
    }  
  
    @Override  
    public void sessionDestroyed(HttpSessionEvent se) {  
        ServletContext context = se.getSession().getServletContext();  
        Integer onLineCount = (Integer) context.getAttribute("onLineCount");  
        if (onLineCount == null) {  
            context.setAttribute("onLineCount", 1);  
        } else {  
            onLineCount--;  
            context.setAttribute("onLineCount", onLineCount);  
        }  
    }  
}

2、自定义Session扫描器
当一个Web应用创建的Session很多时,为了避免Session占用太多的内存,我们可以选择手动将这些内存中的session销毁,那么此时也可以借助监听器技术来实现。

package me.gacl.web.listener;
 
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Timer;
import java.util.TimerTask;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
 
/**
* @ClassName: SessionScanerListener
* @Description: 自定义session扫描器
* @author: 孤傲苍狼
* @date: 2014-9-10 下午10:16:42
* 
*/ 
public class SessionScanerListener implements HttpSessionListener,ServletContextListener {
 
    /**
    * @Field: list
    *          定义一个集合存储服务器创建的HttpSession
    *        LinkedList不是一个线程安全的集合
    */ 
    /**
     * private List<HttpSession> list = new LinkedList<HttpSession>();
     * 这样写涉及到线程安全问题,SessionScanerListener对象在内存中只有一个
     * sessionCreated可能会被多个人同时调用,
     * 当有多个人并发访问站点时,服务器同时为这些并发访问的人创建session
     * 那么sessionCreated方法在某一时刻内会被几个线程同时调用,几个线程并发调用sessionCreated方法
     * sessionCreated方法的内部处理是往一个集合中添加创建好的session,那么在加session的时候就会
     * 涉及到几个Session同时抢夺集合中一个位置的情况,所以往集合中添加session时,一定要保证集合是线程安全的才行
     * 如何把一个集合做成线程安全的集合呢?
     * 可以使用使用 Collections.synchronizedList(List<T> list)方法将不是线程安全的list集合包装线程安全的list集合
     */
    //使用 Collections.synchronizedList(List<T> list)方法将LinkedList包装成一个线程安全的集合
    private List<HttpSession> list = Collections.synchronizedList(new LinkedList<HttpSession>());
    //定义一个对象,让这个对象充当一把锁,用这把锁来保证往list集合添加的新的session和遍历list集合中的session这两个操作达到同步
    private Object lock = new Object();
            
    @Override
    public void sessionCreated(HttpSessionEvent se) {
        System.out.println("session被创建了!!");
        HttpSession session = se.getSession();
        
        synchronized (lock){
            /**
             *将该操作加锁进行锁定,当有一个thread-1(线程1)在调用这段代码时,会先拿到lock这把锁,然后往集合中添加session,
             *在添加session的这个过程中假设有另外一个thread-2(线程2)来访问了,thread-2可能是执行定时器任务的,
             *当thread-2要调用run方法遍历list集合中的session时,结果发现遍历list集合中的session的那段代码被锁住了,
             *而这把锁正在被往集合中添加session的那个thread-1占用着,因此thread-2只能等待thread-1操作完成之后才能够进行操作
             *当thread-1添加完session之后,就把lock放开了,此时thread-2拿到lock,就可以执行遍历list集合中的session的那段代码了
             *通过这把锁就保证了往集合中添加session和变量集合中的session这两步操作不能同时进行,必须按照先来后到的顺序来进行。
             */
            list.add(session);
        }
    }
 
    @Override
    public void sessionDestroyed(HttpSessionEvent se) {
        System.out.println("session被销毁了了!!");
    }
 
    /* Web应用启动时触发这个事件
     * @see javax.servlet.ServletContextListener#contextInitialized(javax.servlet.ServletContextEvent)
     */
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        System.out.println("web应用初始化");
        //创建定时器
        Timer timer = new Timer();
        //每隔30秒就定时执行任务
        timer.schedule(new MyTask(list,lock), 0, 1000*30);
    }
 
    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        System.out.println("web应用关闭");
    }
}
 
/**
* @ClassName: MyTask
* @Description:定时器要定时执行的任务
* @author: 孤傲苍狼
* @date: 2014-9-11 上午12:02:36
*
*/ 
class MyTask extends TimerTask {
        
    //存储HttpSession的list集合
    private List<HttpSession> list;
    //存储传递过来的锁
    private Object lock;
    public MyTask(List<HttpSession> list,Object lock){
        this.list = list;
        this.lock = lock;
    }
    /* run方法指明了任务要做的事情
     * @see java.util.TimerTask#run()
     */
    @Override
    public void run() {
            //将该操作加锁进行锁定
        synchronized (lock) {
            System.out.println("定时器执行!!");
            ListIterator<HttpSession> it = list.listIterator();
            /**
             * 迭代list集合中的session,在迭代list集合中的session的过程中可能有别的用户来访问,
             * 用户一访问,服务器就会为该用户创建一个session,此时就会调用sessionCreated往list集合中添加新的session,
             * 然而定时器在定时执行扫描遍历list集合中的session时是无法知道正在遍历的list集合又添加的新的session进来了,
             * 这样就导致了往list集合添加的新的session和遍历list集合中的session这两个操作无法达到同步
             * 那么解决的办法就是把"list.add(session)和while(it.hasNext()){//迭代list集合}"这两段代码做成同步,
             * 保证当有一个线程在访问"list.add(session)"这段代码时,另一个线程就不能访问"while(it.hasNext()){//迭代list集合}"这段代码
             * 为了能够将这两段不相干的代码做成同步,只能定义一把锁(Object lock),然后给这两步操作加上同一把锁,
             * 用这把锁来保证往list集合添加的新的session和遍历list集合中的session这两个操作达到同步
             * 当在执行往list集合添加的新的session操作时,就必须等添加完成之后才能够对list集合进行迭代操作,
             * 当在执行对list集合进行迭代操作时,那么必须等到迭代操作结束之后才能够往往list集合添加的新的session
             */
            while(it.hasNext()){
                HttpSession session = (HttpSession) it.next();
                /**
                 * 如果当前时间-session的最后访问时间>1000*15(15秒)
                 * session.getLastAccessedTime()获取session的最后访问时间
                 */
                if(System.currentTimeMillis()-session.getLastAccessedTime()>1000*30){
                    //手动销毁session
                    session.invalidate();
                    //移除集合中已经被销毁的session
                    it.remove();
                }
            }
        }
    }
}

替换为其他嵌入式web服务器
如果要换成其他的就把Tomcat的依赖排除掉,然后引入其他嵌入式Servlet容器的以来,如Jetty,Undertow
1,SpringBoot默认使用的是Tomcat

     <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
      </dependency>
      //自动帮你引入依赖

2,jetty

        <dependency>
            <artifactId>spring-boot-starter-jetty</artifactId>
            <groupId>org.springframework.boot</groupId>
        </dependency>

3,undertow

        <dependency>
            <artifactId>spring-boot-starter-undertow</artifactId>
            <groupId>org.springframework.boot</groupId>
        </dependency>

可以使用其他的嵌入式web服务器原理
查看web容器自动配置类
ServletWebServerFactoryAutoConfiguration:嵌入式的web服务器自动配置

  • SpringBoot根据导入的依赖情况,给容器中添加相应的XXXServletWebServerFactory
  • 容器中某个组件要创建对象就会惊动后置处理器 webServerFactoryCustomizerBeanPostProcessor
    只要是嵌入式的是Servlet容器工厂,后置处理器就会工作;
  • 后置处理器,从容器中获取所有的WebServerFactoryCustomizer,调用定制器的定制方法给工厂添加配置

如何使用外置的Servlet容器????
怎么理解呢,就是内置的tomcat是9.1的,但是我电脑本身安装了9.3版本的,我们使用9.3版本的tomcat.而不用spring boot的内置tomcat.
内置的tomcat用的是jar包.
外置的tomcat用的是war包.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值