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节点
    <servlet>
        <servlet-name>default</servlet-name> <!--定义了defualt的servlet,可以到配置文件中查找对应的映射Servlet-->
        <servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class>
        <init-param>
            <param-name>debug</param-name>  <!--是否开启debug-->
            <param-value>0</param-value>
        </init-param>
        <init-param>
            <param-name>listings</param-name> <!--是否展示菜单列表-->
            <param-value>false</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

 
  例子:在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配置文件
      <welcome-file-list>
    <welcome-file>index.html</welcome-file>
    <welcome-file>index.htm</welcome-file>
    <welcome-file>index.jsp</welcome-file>
      </welcome-file-list>
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配置中:
    <!-- A "Connector" represents an endpoint by which requests are received
         and responses are returned. Documentation at :
         Java HTTP Connector: /docs/config/http.html
         Java AJP  Connector: /docs/config/ajp.html
         APR (HTTP/AJP) Connector: /docs/apr.html
         Define a non-SSL/TLS HTTP/1.1 Connector on port 8080
    -->
    <Connector connectionTimeout="20000" port="8080" protocol="HTTP/1.1" redirectPort="8443"/>

端口(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

    <Resource name="jdbc/UserDatabase"   目录的名称
          auth="Container"   认证方式:容器内部
              type="org.apache.catalina.UserDatabase"  接口名称
          <!--type="javax.sql.DataSource"  -->
          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
<?xml version="1.0" encoding="UTF-8"?>
<!--
  Licensed to the Apache Software Foundation (ASF) under one or more
  contributor license agreements.  See the NOTICE file distributed with
  this work for additional information regarding copyright ownership.
  The ASF licenses this file to You under the Apache License, Version 2.0
  (the "License"); you may not use this file except in compliance with
  the License.  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
--><!-- The contents of this file will be loaded for each web application --><Context>

    <!-- Default set of monitored resources. If one of these changes, the    -->
    <!-- web application will be reloaded.                                   -->
    <WatchedResource>WEB-INF/web.xml</WatchedResource>
    <WatchedResource>${catalina.base}/conf/web.xml</WatchedResource>

    <!-- Uncomment this to disable session persistence across Tomcat restarts -->
    <!--
    <Manager pathname="" />
    -->
    <Resource name="jdbc/TestDB" auth="Container" type="javax.sql.DataSource"
        username="root" password="root" driverClassName="com.mysql.jdbc.Driver"
        url="jdbc:mysql://localhost:3306/"
    />
</Context>

7.3)配置web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         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">
    <listener>
        <listener-class>com.segmentfault.lesson6.ServletContextListenerImpl</listener-class>
    </listener>
    
    <resource-ref>
        <res-ref-name>jdbc/testdemo</res-ref-name>
        <res-type>javax.sql.DataSource</res-type>
        <res-auth>Container</res-auth>
    </resource-ref>
      
    <env-entry>
    <env-entry-name>Bean</env-entry-name>
    <env-entry-type>java.lang.String</env-entry-type>
    <env-entry-value>hello</env-entry-value>
    </env-entry>
</web-app>

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<TomcatConnectorCustomizer> tomcatConnectorCustomizers = new ArrayList<TomcatConnectorCustomizer>();
并将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协议





同样的,我们也可以手写修改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是怎么搭配集群的?
因为强调的是无状态,所以集群容易搭建,类似于克隆
无状态,每个机器上不存储用户信息
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值