Spring Boot学习5:spring-boot web容器

1传统Servlet容器
1.1Eclipse Jetty:是一个嵌入式的容器,最新版本jetty9.0。支持的功能如下:
    异步http server
    标准的servlet容器
    websocket
    http/2 server
    asynchronous Client(http/1.1, http/2, websocket)  Java7开始才有AIO
    OSGI,JNDI,JMX,JASPI,AJP support


1.2 Apache Tomcat:
1.2.1 标准实现:
    Servlet
    JSP
    Expression Language
    WebSocket

1.2.2 Apache Tomcat
1)核心组件Components
  Engine
    
 

  Host
  管理主机

  Context:是tomcat运维中重要的一块
  和Application同等级别,类似于ServletContext

  我们可以查看Tomcat的配置文件server.xml
  注意:Engin中有Host,Host中有Context,Tomcat8.5中Host并没有配置Context,后面的版本建议在Host中配置Context(见Context.xml)

2)静态资源处理
  查看web.xml配置文件servlet节点
   
        default
        org.apache.catalina.servlets.DefaultServlet
       
            debug 
            0
       

       
            listings
            false
       

        1
   


 
  例子:在Idea中创建项目web application

  启动项目,访问:http://localhost:8080/tomcat-test/
  页面默认返回index.jsp页面

  在webapp中创建index.html静态文件,重启访问:http://localhost:8080/tomcat-test/index.html
  页面返回index.html内容,其实任何一个请求都会走ServletContext

3)欢迎页面
  查看web.xml配置文件
     
    index.html
    index.htm
    index.jsp
     

4)JSP处理

 
5)类加载
例子:
package com.segmentfault.lesson6;

public class Demo {
    public static void main(String[] args) {

        //查看加载本类的所有的ClassLoader
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        System.out.println(classLoader.getClass().getName());
        while(true){
            classLoader = classLoader.getParent();
            if(classLoader!=null){
                System.out.println(classLoader.getClass().getName());
            }
            else{
                break;
            }
        }

        //获取当前程序的SystemClassLoader
        ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
        System.out.println(systemClassLoader.getClass().getName());


    }
}

打印结果:
sun.misc.Launcher$AppClassLoader
sun.misc.Launcher$ExtClassLoader
sun.misc.Launcher$AppClassLoader



例子:创建一个Listener的Servlet类
查看ServletContext的classloader过程
package com.segmentfault.lesson6;

import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;

public class ServletContextListenerImpl implements ServletContextListener {
    @Override
    public void contextInitialized(ServletContextEvent servletContextEvent) {
        ServletContext sc = servletContextEvent.getServletContext();
        ClassLoader classLoader = sc.getClassLoader();
        System.out.println(classLoader.getClass().getName());
        while(true){
            classLoader = classLoader.getParent();
            if(classLoader!=null){
                System.out.println(classLoader.getClass().getName());
            }else{
                break;
            }
        }
    }

    @Override
    public void contextDestroyed(ServletContextEvent servletContextEvent) {

    }
}


控制台打印结果:
org.apache.catalina.loader.ParallelWebappClassLoader
java.net.URLClassLoader
sun.misc.Launcher$AppClassLoader
sun.misc.Launcher$ExtClassLoader

6)连接器
在tomcat的server.xml配置中:
   
   

端口(port)



协议(Protocol)

连接池(Thread Pool)

超时时间(Timeout)


等等。。。



我们可以查看Connector类的源码(导入tomcat的源码)
可以看到里面的port,
   /**
     * Coyote Protocol handler class name.
     * Defaults to the Coyote HTTP/1.1 protocolHandler.
     */
    protected String protocolHandlerClassName =
        "org.apache.coyote.http11.Http11NioProtocol";



    /**
     * @return the Coyote protocol handler in use.
     */
    public String getProtocol() {
        if (("org.apache.coyote.http11.Http11NioProtocol".equals(getProtocolHandlerClassName()) &&
                    (!AprLifecycleListener.isAprAvailable() || !AprLifecycleListener.getUseAprConnector())) ||
                "org.apache.coyote.http11.Http11AprProtocol".equals(getProtocolHandlerClassName()) &&
                    AprLifecycleListener.getUseAprConnector()) {
            return "HTTP/1.1";
        } else if (("org.apache.coyote.ajp.AjpNioProtocol".equals(getProtocolHandlerClassName()) &&
                    (!AprLifecycleListener.isAprAvailable() || !AprLifecycleListener.getUseAprConnector())) ||
                "org.apache.coyote.ajp.AjpAprProtocol".equals(getProtocolHandlerClassName()) &&
                    AprLifecycleListener.getUseAprConnector()) {
            return "AJP/1.3";
        }
        return getProtocolHandlerClassName();
    }

从这里也可以看出,我们的很多属性都是可以设置的,运行的时候显示的信息: Starting ProtocolHandler ["http-nio-8888"],其中的nio就是协议,所以任何的显示都是有根据的。


 
7)JDBC数据源
8)JNDI(Java Naming and Directory Interface

              auth="Container"   认证方式:容器内部
              type="org.apache.catalina.UserDatabase"  接口名称
         
          maxTotal="100" 最大连接数
          maxIdle="30"  所谓的Idle就是不活动连接数
          maxWaitMillis="10000"   最大等待时间10000毫秒
          username="javauser" password="javadude"   账号密码
          driverClassName="com.mysql.jdbc.Driver"    驱动名称
          url="jdbc:mysql://localhost:3306/javatest"   数据库连接
              description="User database that can be updated and saved"
              factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
              pathname="conf/tomcat-users.xml" />



使用Eclipse工具
例子:配置tomcat中的Context.xml文件,配置数据库信息
7.1)首先创建数据库testdemo,并创建user表
create database testdemo;
mysql> create table user(id int primary key auto_increment,
    -> name varchar(100));

7.2)修改Server中的Context.xml



   
   
    WEB-INF/web.xml
    ${catalina.base}/conf/web.xml

   
   
            username="root" password="root" driverClassName="com.mysql.jdbc.Driver"
        url="jdbc:mysql://localhost:3306/"
    />


7.3)配置web.xml

         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
         version="3.1">
   
        com.segmentfault.lesson6.ServletContextListenerImpl
   

    
   
        jdbc/testdemo
        javax.sql.DataSource
        Container
   

      
   
    Bean
    java.lang.String
    hello
   



7.4)编写java测试代码
package com.spring.servlet;

import java.io.IOException;
import java.io.PrintWriter;
import java.io.Writer;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.sql.DataSource;


public class JdbcServlet extends HttpServlet{
    private DataSource dataSource;
    @Override
    public void init() throws ServletException {
        try {
            Context context = new InitialContext();
            Context envContext = (Context) context.lookup("java:comp/env");
            dataSource = (DataSource) envContext.lookup("jdbc/TestDB");
            String bean = (String) envContext.lookup("Bean");
            System.out.println(bean);
        } catch (NamingException e) {
            e.printStackTrace();
        }
        
    }
    
    @Override
    protected void service(HttpServletRequest arg0, HttpServletResponse resp) throws ServletException, IOException {
        Writer writer = resp.getWriter();
        try {
            Connection connection = dataSource.getConnection();
            Statement statement = connection.createStatement();
            ResultSet resultSet = statement.executeQuery("show databases;");
            while(resultSet.next()) {
                writer.write(resultSet.getString(1));
                System.out.println(resultSet.getString(1));
            }
        } catch (SQLException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}


运行后:
控制台打印:
hello
information_schema
mysql
performance_schema
test
testdemo

这个其实就是依赖反转的原理和鼻祖

运行:报错
Caused by: java.sql.SQLException: No suitable driver
显然,没有驱动,将驱动包复制到项目中,并引用进来

另外,如果出现权限安全问题
在url后加上?useSSL=false



 

 




2 Spring Boot嵌入式Web容器

1)使用IDEA软件
打开applicationContext.xml配置server.port,并查找源码的地方
org.springframework.boot.autoconfigure.web.ServerProperties
对应的配置
    {
      "sourceType": "org.springframework.boot.autoconfigure.web.ServerProperties",
      "name": "server.port",
      "description": "Server HTTP port.",
      "type": "java.lang.Integer"
    },


然后我们可以找到getPort的地方:
这个customize方法中,设置了port,address,contextPath, SSL,timeout等等
@Override
    public void customize(ConfigurableEmbeddedServletContainer container) {
        if (getPort() != null) {
            container.setPort(getPort());
        }
        if (getAddress() != null) {
            container.setAddress(getAddress());
        }
        if (getContextPath() != null) {
            container.setContextPath(getContextPath());
        }
        if (getDisplayName() != null) {
            container.setDisplayName(getDisplayName());
        }
        if (getSession().getTimeout() != null) {
            container.setSessionTimeout(getSession().getTimeout());
        }
        container.setPersistSession(getSession().isPersistent());
        container.setSessionStoreDir(getSession().getStoreDir());
        if (getSsl() != null) {
            container.setSsl(getSsl());
        }
        if (getJspServlet() != null) {
            container.setJspServlet(getJspServlet());
        }
        if (getCompression() != null) {
            container.setCompression(getCompression());
        }
        container.setServerHeader(getServerHeader());
        if (container instanceof TomcatEmbeddedServletContainerFactory) {
            getTomcat().customizeTomcat(this,
                    (TomcatEmbeddedServletContainerFactory) container);
        }
        if (container instanceof JettyEmbeddedServletContainerFactory) {
            getJetty().customizeJetty(this,
                    (JettyEmbeddedServletContainerFactory) container);
        }

        if (container instanceof UndertowEmbeddedServletContainerFactory) {
            getUndertow().customizeUndertow(this,
                    (UndertowEmbeddedServletContainerFactory) container);
        }
        container.addInitializers(new SessionConfiguringInitializer(this.session));
        container.addInitializers(new InitParameterConfiguringServletContextInitializer(
                getContextParameters()));
    }


其中,方法customize是接口EmbeddedServletContainerCustomizer的方法,这个是嵌入式servlet容器引擎
public interface EmbeddedServletContainerCustomizer {

    /**
     * Customize the specified {@link ConfigurableEmbeddedServletContainer}.
     * @param container the container to customize
     */
    void customize(ConfigurableEmbeddedServletContainer container);

}


我们来看一下嵌入式引擎的connector
查找到Connector,查看源码中的setProtocol代码
    @Deprecated
    public void setProtocol(String protocol) {

        boolean aprConnector = AprLifecycleListener.isAprAvailable() &&
                AprLifecycleListener.getUseAprConnector();

        if ("HTTP/1.1".equals(protocol) || protocol == null) {
            if (aprConnector) {
                setProtocolHandlerClassName("org.apache.coyote.http11.Http11AprProtocol");
            } else {
                setProtocolHandlerClassName("org.apache.coyote.http11.Http11NioProtocol");
            }
        } else if ("AJP/1.3".equals(protocol)) {
            if (aprConnector) {
                setProtocolHandlerClassName("org.apache.coyote.ajp.AjpAprProtocol");
            } else {
                setProtocolHandlerClassName("org.apache.coyote.ajp.AjpNioProtocol");
            }
        } else {
            setProtocolHandlerClassName(protocol);
        }
    }


可以看到,这个setProtocol已经是过时的方法了,建议使用构造器的方法进行初始化


    public Connector(String protocol) {
        setProtocol(protocol);
        // Instantiate protocol handler
        ProtocolHandler p = null;
        try {
            Class clazz = Class.forName(protocolHandlerClassName);
            p = (ProtocolHandler) clazz.getConstructor().newInstance();
        } catch (Exception e) {
            log.error(sm.getString(
                    "coyoteConnector.protocolHandlerInstantiationFailed"), e);
        } finally {
            this.protocolHandler = p;
        }

        if (Globals.STRICT_SERVLET_COMPLIANCE) {
            uriCharset = StandardCharsets.ISO_8859_1;
        } else {
            uriCharset = StandardCharsets.UTF_8;
        }
    }


默认的Protocol是
    /**
     * The class name of default protocol used.
     */
    public static final String DEFAULT_PROTOCOL = "org.apache.coyote.http11.Http11NioProtocol";


那么,我们可以在哪里修改这个protocol呢?

重写:EmbeddedServletContainerCustomizer的customize方法

注入:
    private List tomcatConnectorCustomizers = new ArrayList();
并将connector设置对应的port和protocol,并加入到tomcat嵌入式servlet工厂中

代码实现:
package com.segmentfault.springbootlesson6;

import org.apache.catalina.Context;
import org.apache.catalina.connector.Connector;
import org.apache.coyote.http11.Http11Nio2Protocol;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.embedded.ConfigurableEmbeddedServletContainer;
import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizer;
import org.springframework.boot.context.embedded.tomcat.TomcatConnectorCustomizer;
import org.springframework.boot.context.embedded.tomcat.TomcatContextCustomizer;
import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@SpringBootApplication
@RestController
public class SpringBootLesson6Application {

    public static void main(String[] args) {
        SpringApplication.run(SpringBootLesson6Application.class, args);
    }

    @GetMapping("/hello")
    public String index(){
        return "hello,world";
    }

    @Bean
    public EmbeddedServletContainerCustomizer embeddedServletContainerCustomizer(){
        return new EmbeddedServletContainerCustomizer(){
            @Override
            public void customize(ConfigurableEmbeddedServletContainer container) {
                if(container instanceof TomcatEmbeddedServletContainerFactory){
                    TomcatEmbeddedServletContainerFactory factory = TomcatEmbeddedServletContainerFactory.class.cast(container);
                    factory.addContextCustomizers(new TomcatContextCustomizer() {
                        @Override
                        public void customize(Context context) {
                            context.setPath("/spring-boot");
                        }
                    });

                    factory.addConnectorCustomizers(new TomcatConnectorCustomizer() {
                        @Override
                        public void customize(Connector connector) {
                            connector.setPort(8888);
                            //这个方法已经过时的
                            connector.setProtocol(Http11Nio2Protocol.class.getName());
                        }
                    });
                }
            }
        };
    }
}



其中测试:可以看到控制台中打印

2018-02-12 10:05:44.971  INFO 17768 --- [           main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8888 (http)

然后打开jconsole,查看该java进程中的http协议
Spring Boot学习5:spring-boot web容器_第1张图片




同样的,我们也可以手写修改context

factory.addContextCustomizers(new TomcatContextCustomizer() {
    @Override
    public void customize(Context context) {
        context.setPath("/spring-boot");
    }
});

并创建一个接口:

    @GetMapping("/hello")
    public String index(){
        return "hello,world";
    }

访问:http://localhost:8888/spring-boot/hello

返回hello,world





3 Q&A

1)jndi在实际开发中有什么用?
jndi告訴我們,不要直接使用new的方式生成對象,而是用get的方法从容器中取。
如果设置了密码,我们可以使用jndi的方式,到指定的路径取密码,而不用直接配置密码

jndi对应spring的上下文,和classloader不同。是以虚拟路径的方式如 jdbc/testDB等
资源以树形结构的方式存储资源



2)微服务和j2ee
微服务强调的是无状态

j2ee强调有状态


3)嵌入式的tomcat是怎么搭配集群的?
因为强调的是无状态,所以集群容易搭建,类似于克隆
无状态,每个机器上不存储用户信息

你可能感兴趣的:(Spring,Boot,Spring,Boot)