Springboot之强大的Servlet「三」

举报
赵KK日常技术记录 发表于 2023/06/29 21:20:03 2023/06/29
【摘要】 接上篇文章,上篇文中讲到了Springboot中请求与响应的细节,接下来详细介绍下Servlet与Tomcat在Springboot中的应用。传统的Servlet容器 Apache Tomcat这里只记录了部分重要场景包含核心组件静态资源处理类加载连接器JDBC数据源HttpServletResponsejavax.servlet.http.HttpServletResponse123456...


接上篇文章,上篇文中讲到了Springboot中请求与响应的细节,接下来详细介绍下Servlet与Tomcat在Springboot中的应用。

传统的Servlet容器 Apache Tomcat

这里只记录了部分重要场景
包含核心组件
静态资源处理
类加载
连接器
JDBC数据源

HttpServletResponse

javax.servlet.http.HttpServletResponse

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public static void main(String[] args) {
    ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
    while (true) {

        System.out.println(contextClassLoader.getClass().getName());
        ClassLoader parent = contextClassLoader.getParent();
        if (parent == null) {
            break;
        }

    }

    ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
    System.out.println(systemClassLoader.getClass().getName());
}

除了其中的状态码之外,结合最近的测试其中的实现可具体参考
addHeader方法,getHeader方法等等
BootStrap–system—common—webapp

静态资源处理类org.apache.catalina.servlets.DefaultServlet

注意下包名

大多数情况下我们关注的更多是server.xml中Tomcat的配置,而在web.xml中除了路径映射等配置外

1
2
3
4
5
6
7
8
9
10
11
12
<!-- The mapping for the default servlet -->
   <servlet-mapping>
       <servlet-name>default</servlet-name>
       <url-pattern>/</url-pattern>
   </servlet-mapping>

   <!-- The mappings for the JSP servlet -->
   <servlet-mapping>
       <servlet-name>jsp</servlet-name>
       <url-pattern>*.jsp</url-pattern>
       <url-pattern>*.jspx</url-pattern>
   </servlet-mapping>

关于是否是开发模式

1
2
3
4
<!--   development         Is Jasper used in development mode? If true,   -->
 <!--                       the frequency at which JSPs are checked for    -->
 <!--                       modification may be specified via the          -->
 <!--                       modificationTestInterval parameter. [true]     -->

由于DefaultServlet是HttpServlet的子类,所以在此不展开讨论
而在server.xml中标签与后台接口是一一绑定的

1
<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" />

而在JDBC中的大多数类中也遵循此规则,那么就上面这段分析标签Connector则对应

org.apache.catalina.connector.Connector,验证一下标签中对应protocol,connectionTimeout,redirectPort

其中标签对应部分

1
2
3
4
5
6
/**
    * Defaults to using HTTP/1.1 NIO implementation.
    */
   public Connector() {
       this("org.apache.coyote.http11.Http11NioProtocol");
   }

而在tomcat8.0+中getProtocol对应protocol
redirectPort对应属性默认值

1
2
3
4
/**
     * The redirect port for non-SSL to SSL redirects.
     */
    protected int redirectPort = 443;

关于标签中connector中这个Http11NioProtocol则在tomcat官方文档中可见其中一句话

1
When using HTTP connectors (based on APR or NIO/NIO2), Tomcat supports using sendfile to send large static files. These writes, as soon as the system load increases, will be performed asynchronously in the most efficient way. Instead of sending a large response using blocking writes, it is possible to write content to a static file, and write it using a sendfile code. A caching valve could take advantage of this to cache the response data in a file rather than store it in memory. Sendfile support is available if the request attribute org.apache.tomcat.sendfile.support is set to Boolean.TRUE

也可在server.xml中搜索

1
2
3
4
5
6
7
<!-- Define an AJP 1.3 Connector on port 8009 -->
    <!--
    <Connector protocol="AJP/1.3"
               address="::1"
               port="8009"
               redirectPort="8443" />
    -->

server.port在文件中的位置

重点来了 ServerProperties包含了tomcat,Jetty,Undertow,而在Springboot2.2.6中则存在Netty

那么理所当然,在tomcat中的一些配置也存在于此

1
2
3
4
5
6
7
8
9
/**
 * Maximum amount of worker threads.
 */
private int maxThreads = 200;

/**
 * Minimum amount of worker threads.
 */
private int minSpareThreads = 10;

那么为什么Tomcat被称之为嵌入式容器呢?

在启动时无需自启动容器,在Bootstrap中调用tomcat,另外tomcat中TomcatEmbeddedContext,Embedded即直译为嵌入式



这里记忆有些混乱了,有点找不过来哪里是入口了,但先从
TomcatServletWebServerFactoryCustomizer的customize()方法调用找,

1
2
3
4
5
6
7
8
9
10
11
12
private Stream<Wrapper> getLoadOnStartupWrappers(Container[] children) {
        Map<Integer, List<Wrapper>> grouped = new TreeMap<>();
        for (Container child : children) {
            Wrapper wrapper = (Wrapper) child;
            int order = wrapper.getLoadOnStartup();
            if (order >= 0) {
                grouped.computeIfAbsent(order, ArrayList::new);
                grouped.get(order).add(wrapper);
            }
        }
        return grouped.values().stream().flatMap(List::stream);
    }

因为要看下Netty,所以还是重新看下server.properties
我将Spring Boot AutoConfigure升级到了2.6.2,内置的Tomcat就升级到9.0了
为了方便查看才升级的,之前的2.1.x就不截图了
server.properties的位置在configuration的下面的json文件

spring-configuration-metadata.json

1
2
3
4
5
{
      "name": "server",
      "type": "org.springframework.boot.autoconfigure.web.ServerProperties",
      "sourceType": "org.springframework.boot.autoconfigure.web.ServerProperties"
}

那既然为了看Netty在这个json文件中同样存在

1
2
3
4
5
6
{
      "name": "server.netty",
      "type": "org.springframework.boot.autoconfigure.web.ServerProperties$Netty",
      "sourceType": "org.springframework.boot.autoconfigure.web.ServerProperties",
      "sourceMethod": "getNetty()"
}

既然在json中存在getNetty等方法,猜测那么对应的ServerProperties也存在对应的方法,
因为存在实例么,tomcat搭配server.xml和web.xml简单看了一下,当然Tomcat还是主要和Servlet的关联关系更为重要,
本身tomcat知识点也最够庞大的,包含类加载器,双拼委派,打破双亲委派、jvm调优等等,可以顺带看下这里的专题

当一次请求发起都发生了什么?

用户通过浏览器进行了一个操作,这个操作可以是输入url地址并回车,或者是点击超链接,或者是在搜索框中输入关键字进行搜索,接着浏览器就捕获到了这个事件
由于 HTTP 协议底层具体的数据传输使用的是 TCP/IP 协议,因此浏览器需要向服务端发出 TCP 连接请求
服务器接受浏览器的连接请求,并经过 TCP 三次握手建立连接
浏览器将请求数据打包成一个 HTTP 协议格式的数据包
浏览器将打包好的数据包推入网络,经过网络传输最终到达服务器指定程序
服务端程序拿到数据包后,根据 HTTP 协议格式进行解包,获取到客户端的意图
得知客户端意图后进行处理,比如提供静态文件或者调用服务端程序获得动态结果
服务器将响应结果按照 HTTP 协议格式打包
服务器将响应数据包推入网络,数据包经过网络传输最终达到到浏览器
浏览器拿到数据包后,按照 HTTP 协议的格式解包,然后对数据进行解析
浏览器将解析后的静态数据(如html、图片)展示给用户

Tomcat 作为一个 HTTP 服务器,主要需要完成的功能是接受连接、解析请求数据、处理请求和发送响应这几个步骤。
作者:若兮缘
链接:
https://www.jianshu.com/p/7c9401b85704

来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
关于tomcat的架构就取自这篇文章,图文都很喜欢~

导入过程Running With JRE 7 Or Later

启动tomcat所需环境

1
2
3
4
5
6
7
8
else
   eval $_NOHUP "\"$_RUNJAVA\"" "\"$CATALINA_LOGGING_CONFIG\"" $LOGGING_MANAGER "$JAVA_OPTS" "$CATALINA_OPTS" \
     -D$ENDORSED_PROP="\"$JAVA_ENDORSED_DIRS\"" \
     -classpath "\"$CLASSPATH\"" \
     -Dcatalina.base="\"$CATALINA_BASE\"" \
     -Dcatalina.home="\"$CATALINA_HOME\"" \
     -Djava.io.tmpdir="\"$CATALINA_TMPDIR\"" \
     org.apache.catalina.startup.Bootstrap "$@" start \

后续不再赘述。重点在Server.properties中版本区别是否包含Netty的这个类,
本来我是想跟着dei一下bug的,实际是我没起来,版本又不兼容,中间穿插了需求,就不dei了skr~

一方库、二方库、三方库说明

有些二方库为apache所需类库,当然定义也尽相同,以统一标准为准吧~就像嵌入式这个单词,
如果学习的时候根据服务的命名,猜测其作用,然后再去证实的话,可能我早就认识这个单词了

一方库:本工程中的各模块的相互依赖
二方库:公司内部的依赖库,一般指公司内部的其他项目发布的jar包
三方库:公司之外的开源库, 比如apache、ibm、google等发布的依赖
为什么写这句话就是因为javax是指扩展我的java,因为原生的二方库是不允许被覆盖的。提到的

1
2
3
4
5
6
7
8
9
10
11
12
13
14

private Stream<Wrapper> getLoadOnStartupWrappers(Container[] children) {
        Map<Integer, List<Wrapper>> grouped = new TreeMap<>();
        for (Container child : children) {
            Wrapper wrapper = (Wrapper) child;
            int order = wrapper.getLoadOnStartup();
            if (order >= 0) {
                grouped.computeIfAbsent(order, ArrayList::new);
                grouped.get(order).add(wrapper);
            }
        }
        return grouped.values().stream().flatMap(List::stream);
    }

再比如这里面的grouped.computeIfAbsent(order, ArrayList::new);其中Absent译为缺席,入参是key=order,以及函数方法,在key!=null的情况下赋值为newAraayList并返回去。
and this flatMap VS map,其他人举的例子很明朗,我就不摘抄了,
https://www.cnblogs.com/yucy/p/10260014.html

JDBC中的servlet

数据库三大范式:
1.第一范式(确保每列保持原子性)
2.第二范式(确保表中的每列都和主键相关)
3.第三范式(确保每列都和主键列直接相关,而不是间接相关)
1、DML:Data Manipulation Language 操作语句
2、DDL:data define Language、
3、存储过程执行后
4、查询中也是有事务的:select查询后结果集关闭后
事务并发可能的影响:
1、脏读(读取未提交数据)
A事务读取B事务尚未提交的数据,此时如果B事务发生错误并执行回滚操作,那么A事务读取到的数据就是脏数据。
就好像原本的数据比较干净、纯粹,此时由于B事务更改了它,这个数据变得不再纯粹。这个时候A事务立即读取了这个脏数据,
但事务B良心发现,又用回滚把数据恢复成原来干净、纯粹的样子,而事务A却什么都不知道,最终结果就是事务A读取了此次的脏数据,称为脏读。
2、不可重复读(前后多次读取,数据内容不一致)
事务A在执行读取操作,由整个事务A比较大,前后读取同一条数据需要经历很长的时间 。而在事务A第一次读取数据,
比如此时读取了小明的年龄为20岁,事务B执行更改操作,将小明的年龄更改为30岁,此时事务A第二次读取到小明的年龄时,
发现其年龄是30岁,和之前的数据不一样了,也就是数据不重复了,系统不可以读取到重复的数据,成为不可重复读
3、幻读(前后多次读取,数据总量不一致)
事务A在执行读取操作,需要两次统计数据的总量,前一次查询数据总量后,此时事务B执行了新增数据的操作并提交后,
这个时候事务A读取的数据总量和之前统计的不一样,就像产生了幻觉一样,平白无故的多了几条数据,成为幻读
幻读产生的根本原因是采用的行级锁,所以只针对脏读和重复读有用


Drivermanager–>getconnection—>connection–>createStatement–>ResultSet executeQuery(String sql) throws SQLException;



重载connection方法可实现在各个数据库中切换,基本不需要太多的代码,JDBC中用到的设计模式?—-桥接模式
不知道为啥都在强调jdbc的设计模式,所以引用下《重学设计模式–小博哥》中的案例分析



代码实现登陆:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
public class PayController {
    private Logger logger = LoggerFactory.getLogger(PayController.class);

    public boolean doPay(String uId, String tradeId, BigDecimal amount,
                         int channelType, int modeType) {
        // 微信⽀付
        if (1 == channelType) {
            logger.info("模拟微信渠道⽀付划账开始。uId:{} tradeId:{} amount:
            {
            }
            ", uId, tradeId, amount);
            if (1 == modeType) {
                logger.info("密码⽀付,⻛控校验环境安全");
            } else if (2 == modeType) {
                logger.info("⼈脸⽀付,⻛控校验脸部识别");
            } else if (3 == modeType) {
                logger.info("指纹⽀付,⻛控校验指纹信息");
                123456789
                10
                11
                12
                13
                14
                上⾯的类提供了⼀个⽀付服务功能,通过提供的必要字段; ⽤户ID 、交易ID 、 ⾦额 、渠道 、模 式 ,来控制⽀付⽅式。
                以上的 ifelse 应该是最差的⼀种写法,即使写 ifelse 也是可以优化的⽅式去写的。
                3. 测试验证
                3.1 编写测试类
                以上分别测试了两种不同的⽀付类型和⽀付模式;微信⼈脸⽀付、⽀付宝指纹⽀付
                3.2 测试结果
            }
        }
        // ⽀付宝⽀付
        else if (2 == channelType) {
            logger.info("模拟⽀付宝渠道⽀付划账开始。uId:{} tradeId:{}
                    amount: {
            }
            ", uId, tradeId, amount);
            if (1 == modeType) {
                logger.info("密码⽀付,⻛控校验环境安全");
            } else if (2 == modeType) {
                logger.info("⼈脸⽀付,⻛控校验脸部识别");
            } else if (3 == modeType) {
                logger.info("指纹⽀付,⻛控校验指纹信息");
            }
        }
        return true;
    }
}

其实面对这种情况一般我是看到大多数是应用策略+模板的,桥接真的很少听

1
2
3
4
5
6
7
8
9
public abstract class Pay {
 protected Logger logger = LoggerFactory.getLogger(Pay.class);
 protected IPayMode payMode;
 public Pay(IPayMode payMode) {
 this.payMode = payMode;
 }
 public abstract String transfer(String uId, String tradeId, BigDecimal
amount);
}

在这个类中定义了⽀付⽅式的需要实现的划账接⼝: transfer ,以及桥接接⼝; IPayMode ,并
在构造函数中⽤户⽅⾃⾏选择⽀付⽅式。
如果没有接触过此类实现,可以᯿点关注 IPayMode payMode ,这部分是桥接的核⼼

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class WxPay extends Pay {
 public WxPay(IPayMode payMode) {
 super(payMode);
 }
 public String transfer(String uId, String tradeId, BigDecimal amount) {
 logger.info("模拟微信渠道⽀付划账开始。uId:{} tradeId:{} amount:{}",
uId, tradeId, amount);
 boolean security = payMode.security(uId);
 logger.info("模拟微信渠道⽀付⻛控校验。uId:{} tradeId:{} security:
{}", uId, tradeId, security);
 if (!security) {
 logger.info("模拟微信渠道⽀付划账拦截。uId:{} tradeId:{} amount:
{}", uId, tradeId, amount);
 return "0001";
 }
 logger.info("模拟微信渠道⽀付划账成功。uId:{} tradeId:{} amount:{}",
uId, tradeId, amount);
 return "0000";
 }
} 

支付宝支付

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class ZfbPay extends Pay {
 public ZfbPay(IPayMode payMode) {
 super(payMode);
 }
 public String transfer(String uId, String tradeId, BigDecimal amount) {
 logger.info("模拟⽀付宝渠道⽀付划账开始。uId:{} tradeId:{} amount:
{}", uId, tradeId, amount);
 boolean security = payMode.security(uId);
 logger.info("模拟⽀付宝渠道⽀付⻛控校验。uId:{} tradeId:{} security:
{}", uId, tradeId, security);
 if (!security) {
 logger.info("模拟⽀付宝渠道⽀付划账拦截。uId:{} tradeId:{}
amount:{}", uId, tradeId, amount);
 return "0001";
 }
 logger.info("模拟⽀付宝渠道⽀付划账成功。uId:{} tradeId:{} amount:
{}", uId, tradeId, amount);
 return "0000";
 }
}



桥接模式接口

1
2
3
4

public interface IPayMode {
 boolean security(String uId);
}

刷脸

1
2
3
4
5
6
7
8
public class PayFaceMode implements IPayMode{
 protected Logger logger = LoggerFactory.getLogger(PayCypher.class);
 public boolean security(String uId) {
 logger.info("⼈脸⽀付,⻛控校验脸部识别");
 return true;
 }
}
其他同上

测试类编写

1
2
3
4
5
6
7
8
9
10
@Test
public void test_pay() {
 System.out.println("\r\n模拟测试场景;微信⽀付、⼈脸⽅式。");
 Pay wxPay = new WxPay(new PayFaceMode());
 wxPay.transfer("weixin_1092033111", "100000109893", new
BigDecimal(100));
 System.out.println("\r\n模拟测试场景;⽀付宝⽀付、指纹⽅式。");
 Pay zfbPay = new ZfbPay(new PayFingerprintMode());
 zfbPay.transfer("jlu19dlxo111","100000109894",new BigDecimal(100));
} 
【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。