Code war of Angel


  • Home

  • Archives

JavaScript中并行语言特性

Posted on 2019-10-10

本文整理周爱民在Qcon中《在JavaScript中的并行语言特性》演讲。

JavaScritp中的执行栈

执行栈,也就是在其它编程语言中所说的“调用栈”,是一种拥有 LIFO(后进先出)数据结构的栈,被用来存储代码运行时创建的所有执行上下文。
引擎会执行那些执行上下文位于栈顶的函数。当该函数执行结束时,执行上下文从栈中弹出,控制流程到达当前栈中的下一个上下文。


Event Loop:当执行栈为空时,从任务队列(RUN JOB)取一个任务执行。再次为空时,再从队列取一个任务,如此循环往复。
当一个函数上下文被从执行栈移走时,相当于函数被挂起了。

Promise是如何处理的

Promise的数据结构:

先看一个hello world例子

1
2
3
// 数据就绪,然后做点什么
// Hello world字符串就绪后,然后执行console.log
Promise.resolve("Hello world").then(console.log);

当then函数,如:

1
aPromise.then(func1,func2)

func1、func2 会传入上图 Reaction列表里,两个fun产生的结果值将会产生新的Promise对象,

1
bPromise = aPromise.then(func1,func2)

bPromise与func1 func2无关,只与它们产生的结果有关系。
然后Promise.resolve(x) 约等于

1
2
3
4
5
var p = new Promise();
// 创建新的置值器
var [resolve, reject] = ... of p;
resolve(x); // 相当于 p[[result]] = x;
return p;

如果x不是一个简单的值,而是一个promise本身,假设 p2 = Promise.resolve(p)

1
2
3
4
var p2 = new Promise()
var [resolve, reject] = ... of p2;
p.then(resolve, reject); // resolve(p);
return p2;

上面提到,当函数被走执行栈移走时,函数会被挂起,Promise就是其中一个例子,会导致函数被挂起。Promise在执行栈被初始化后,被扔会任务队列,所以Promise时并行执行的。当数据就绪后,执行栈再次从任务队列调用回来,Promise就被唤起了。

await是如何处理的

1
2
3
4
5
async function foo(){
var x = await p;
...
}
p2 = foo();

await与Promise相似,也会导致函数被挂起。

  1. 创建一个新的Promise px, 其resolve,reject函数对将用作参数调用p.then(),即Promise.resolve(p)
  2. 为px创建一对onFullfilled, onRejected响应函数,使指向当前栈顶上下文。
  3. 将响应函数onXXX作为参数调用px.then(),使onXXX函数添加到任务队列。
  4. 将当前执行上下文从栈顶移除。
    至于为什么要创建px,我觉得是为了在onFullfilled函数绑定上下文,参考这里

语言特性与架构

在顶层第一行代码使用await会导致主线程被挂起,在ECMA2017创建了工作线程。NodeJs中也有工作线程的概念。主线程与工作线程通过有消息通讯,通过sab共享内存

这种解决方案与分布式环境下并发解决问题是一致的。集群中消息通讯、任务调度、资源管理模型也是语言中底层线程设计思想、解决的问题也是一样的。

在eventlet的猴子补丁下sslsocket链接失败的问题

Posted on 2019-09-24

某项目有一个发邮件的需求,由于项目搭建的Web Server使用了eventlet的monkey patch为了多进程下使用websocket。
发现在建立ssl socket时会报错

1
wrap_socket() got an unexpected keyword argument '_context'

在github查找eventlet的issue。发现python 3.7 - wrap_socket() got an unexpected keyword argument ‘_context’这个bug还是open的。

解决:
起了新进程,用消息队列通讯,实现发送邮件需求。

编码器信号类型

Posted on 2019-09-03

最近在做一些跟嵌入式相关的工作,涉及增量编码器跟PLC的通讯。

增量编码器的方波脉冲数字信号

增量编码器的方波脉冲数字信号。增量方波脉冲数字信号也许很简单,但是还是有很多人难以定义并区分清楚,在这么个简单的问题上犯的错误却比比皆是。它实际上决定了编码器信号接收能否很好匹配,并高质量地传输与读取以及信号抗干扰能力。

增量脉冲信号的方波,以电压的高低(开关)电平脉冲式变化,与正余弦模拟量信号不同,方波脉冲信号是数字式开关逻辑信号。在高电平的时候逻辑为1,低电平的时候逻辑为0,这种编码方式称为编码的正逻辑。反之以高电平为“0”低电平为“1”的编码方式为负逻辑。绝大部分编码器默认正逻辑,部分日系编码器(NPN)为负逻辑。

编码器信号类型

  1. TTL(transistor transistor logic),TTL信号是数字信号的基础,通常我们采用二进制来表示数据。TTL电平信号规定,+5V等价于逻辑“1”,0V等价于逻辑“0”。这样的数据通信及电平规定方式,被称做TTL(晶体管-晶体管逻辑电平)信号系统。这是计算机处理器控制的设备内部各部分之间通信的标准技术。TTL更多的是用于电路设计,各种芯片单片机的输入输出是TTL信号,它是相对于外部电缆传输的较高电平HTL信号的低电平(5V),定义的数据1(5V)和0(0V)的逻辑电平信号。

  2. 5V差分信号:差分是以两个信号之间的电压差经数学比较处理的概念,在增量脉冲信号中,它表明有每两个信号一组,各自为反相(180度相位差)。5V差分信号是TTL信号每两个信号一组,例如A+对A-,当A+在5V=1的时候,A-在0V=0,逻辑等价与“1”;当180度反相时A+在0V的时候,A-在5V,逻辑等价与“0”。
    仅仅是在差分信号的定义上,两个信号是平等的可以互换的,互换后逻辑反相。例如编码器的5V差分信号A+与A-是可以互换接线的,互换后相位反180度,也即是信号增量方向与编码器旋转方向反向了,可以用这个方法改变编码器输出方向。
    差分信号的目的是接收端可以通过差分信号处理消除传输线上的共模干扰。
    差分信号在双绞线上的传输,抗干扰能力较强。差分的两个信号交替高低电平信号变化,在一对双绞线上配对传输,对外界的电磁场贡献平均为无变化的,外界干扰变化的电磁场对其作用也就达到最小。双绞的“绞”起来的作用,一是同时对于信号电流流向所产生的旋转磁场的反旋,以抵消因电流流向的法拉底原理产生的磁场;另一个是在双绞线配对的两个信号之间的电磁场平衡,防止这两个信号之间串音,尤其是信号频率较高的时候的串音。因此双绞绞起来的节据与设计的需要传输信号的主要频段有关。
    差分信号以及配对的使用双绞屏蔽线传输,是信号输出与传递的较佳的具有电磁兼容性EMI和抗干扰特性。
    差分信号的形式不仅仅是5V,不仅仅是方波信号。例如也可以是较高电平的5—30V的HTL信号的含反相差分,HTL-6;或者也可以是正余弦模拟量SINCOS信号,也是差分式的。
    在5V差分信号的定义上,比TTL多了一点内容,就是两个互为反相信号一组的TTL信号。

  3. 长线驱动信号(line driver):是指发送端与接收端有一对配对的长线驱动器,它们各自有正负两个管脚,当发送端正管脚为高电平往外推送信号电流时,接收端的正管脚为低电平往里拉信号电流(推拉式)。此时电流的方向与信号流的方向一致,视为逻辑1。
    当发送端正管脚为低电平时,接收端的正管脚为高电平,发送端相当于往里拉电流,电流方向与信号流方向是反的,此时视为逻辑0。在发送端前面送入5VTTL信号,在接收端经过差分后再送出5VTTL的信号。
    长线驱动信号是有发送端与接收端各有一个长线驱动器配对,在逻辑1和逻辑0时都有信号放大推拉,并也是差分式的。信号走输入单极性TTL~长线驱动(配对的推拉驱动)~接收端差分~单极性TTL输出(进计数器等)。由于有接收端配对的推拉驱动,长线驱动信号传输距离更“长线”。
    典型的长线驱动器26LS31与26LS32配成一对。为5V 的差分式的。
    长线驱动是基于一对配对的长线驱动器的偏向于对电子器件的描述,这种传输方式抗干扰强,驱动传输距离远,一般对于编码器的5V差分长线驱动的描述可以传400米(用专业的编码器双绞屏蔽电缆)。
    长线驱动信号的定义往往取决于配对的长线驱动器件。不局限于5V。
    长线驱动与5V差分的不同:5V差分有两种可能性:三线制 ,电流回路对0V;长线驱动只有第二种二线制的,电流回路不对0V。

  4. RS422信号:Electronic Industries Association (EIA ) 国际电工协会(EIA) 定义的一个更广泛的信号标准。RS-422标准全称是“平衡电压数字接口电路的电气特性”。接收器采用高输入阻抗和发送驱动器采用差模传输,双绞线。
    RS422与TTL区别:不一定是5V,可以是5到24V;RS422定义了A+与A-的差模传输方式。
    RS422与5V差分的区别:信号电压范围更广,对于差分的数学与物理界面、传输的电缆与接口接插头等都有定义。在三线制模式,A+或A-即使在低电平,也可以大于0V。RS422信号的这种基点电压大于0V,可以在传输线上允许有因阻抗而有电压衰减,但差模后仍然能保持大于等于5V的差模电压,因此传输距离可能比长线驱动的更远。其传输距离长度与信号频率有关,在较低的信号频率下最远可传输1000米。

增量编码器中输出电路

  1. 集电极开路输出是以输出电路的晶体管发射极作为公共端,并且集电极悬空的输出电路。一般分为NPN集电极开路输出和PNP集电极开路输出
  2. 电压输出是在集电极开路输出的电路基础上,在电源间和集电极之间接了一个上拉电阻,使得集电极和电源之间能有一个稳定的电压状态
  3. 互补输出是输出上具备NPN和PNP两种输出晶体管的输出电路。根据输出信号的[H]、[L],2个输出晶体管交互进行[ON]、[OFF]动作,比集电极开路输出的电路传输距离能稍远,也可与集电极开路输入机器(NPN、PNP)连接。
  4. 线性驱动输出是采用RS-422标准,用AM26LS31芯片应用于高速、长距离数据传输的输出模式。信号以差分形式输出,因此抗干扰能力更强。输出信号需专门能接收线性驱动输出的设备才能接收。

欧系、日系编码器在描述上的不一致不统一

由于历史原因,先有欧系编码器的TTL信号,然后再有双极性的5V差分,以及基于长线驱动器件配对的长线驱动信号。欧系编码器厂家为原有老客户仍然保持了用TTL信号来表述5V差分信号(长线驱动)。需要注意的是,欧系的TTL目前大部分是默认为5V差分的长线驱动的,但是欧系的TTL也有少部分是5V差分是可以对0V的三线的,也可以是两线的长线驱动。如果是三线的也可以是单独接A+,而可以不接A-(A-悬空),作为单极性的TTL使用。一些简单设计的设备中,5VTTL电平的ABZ即为这种三线制差分的简化接线方式(只接AB信号),例如我参加的对欧洲塔式积热式太阳能跟踪反射板上供应的增量编码器,就是这种5VTTL的只接AB两根线的,设计要求一样要达到较远距离的传输与抗干扰能力。
如果是二线制的,不仅仅必须A+A-都要接上,而且需查产品手册对应寻找到匹配的接收单元与之配对。

欧系编码器的TTL描述有三种可能:

  1. 双极性的5V TTL,接收端封闭配对,接收端单元选型需与发送端查找配对使用(查产品手册),A+A-都必须接。这是最多可能性,或者目前编码器类以TTL表示的几乎默认的模式,目前主要为设备配套提供,较多的是运动控制器与伺服电机编码器匹配。
  2. 单极性的5VTTL,,直接的电路板计算机处理器接口,可以只接AB信号。主要为设备电路设计者提供,可直接进入计算机处理器CPU或者计数器芯片。
  3. 双极性的5V TTL,三线式对0V的5V电平,接收端开放;它也可以是只接AB单极***,直接连接计算机处理器而省去信号接收芯片,与上述2兼容使用。但是原有差分输出的模式在双绞线上传输一样用差分信号配对使用双绞,同样有部分双绞传输抗干扰的作用。
  4. 目前5V差分(欧系仍以TTL表示)大部分用于运动控制器伺服电机编码器,而自动化PLC上用更高电平的HTL信号(非差分式)。在变频器的信号选用上,较佳的应该是HTL-6,即含反相的HTL信号,较高电平的差分信号具有更好的抗变频器干扰特性。

日系编码器通常直接以5V line driver描述,需查手册与发送端芯片配对使用。

日系编码器更有NPN单极性反逻辑的信号输出,接收端也必须是NPN极性的,它的信号电压公共端在电源的高电平上,信号流开关是在0V上的“有”或“无”的“漏性”电流,信号的“1”和“0”是反的,在逻辑处理时需反向。不建议用上拉电阻临时取电压的不规范接法。

HTL含反相信号-(High Threshold Logic的缩写)是“高阈值逻辑电路”,它的电压阈值9–30V,大于5V TTL,目前较多的是以一对NPN+PNP三极管做成推挽式开关放大电路,兼容集电极开路放大器NPN和PNP。其中PNP接法为正逻辑,以电源0V为公共端;NPN接法为负逻辑,以电源高电平为公共端。HTL信号更多地用于PLC接口,尤其是欧系PLC为编码器标准接口。

HTL信号可以用三线制差分模式(电流回路对0V,取电压差比较差分接收)。同样可以有HTL-6的含反相通道做成差分式接收(A+A-B+B-Z+Z-6通道HTL)。HTL-6可用在变频器接收,因其电平阈值高、差分式可消除变频器及电机的共模干扰,用于变频器接收上抗干扰能力更强。

编码器信号不匹配可能引起的错误:

编码器信号不仅仅是有电压差对应,A+A-反相差分也有多种不同,所以并不是看见电压是对的,或者看到是A+A-B+B-含反相的,还是只有ABZ没有反相,就可以判断是不是匹配的可以连接上去了。电压对了ABZ接上去了,哪怕有信号能够读取到,并不代表就是有很好匹配的可以用了,这其中还有多种不匹配引起不良结果的可能性:

  1. 对0V的关系不同,三线的差分信号电流回路对0V,取电压差比较;二线的差分信号电流回路与0V无关,仅为两个互为差分信号自己构成电流回路的正反电流回路。当设备启动时0V会有波动,如果信号不匹配极易被干扰甚至烧器件。对于有较大型电机在现场,或者有大型设备中的电磁线圈,在启动瞬间的三相绕线不平衡,很有可能会在0V瞬间的波动而发生这种不预期状况。
  2. 阻抗匹配的不同,针对于有封闭性配对要求的接收端,编码器脉冲信号的阻抗与信号频率有关,发送与接收阻抗匹配是有预先定义的,尤其是在高频段阻抗的不匹配,将导致在编码器脉冲频率高时的信号丢失的“丢脉”,例如高分辨率编码器或者高速旋转中编码器的频率较高。针对长线驱动有接收端匹配要求的,需查手册配对接收单元。
  3. 信号流与电流方向的一致性,如果不匹配将导致读不到信号,甚至烧了器件。编码器如果没有反极性保护和短路保护,这种上去就烧器件的事情常有发生。
  4. 抗干扰性能的不同,只有匹配的信号,以及使用双绞屏蔽电缆线,具有更好的抗干扰特性,并信号传输距离能达到标称的长度。双绞电缆仅对差分的匹配的信号有利。单极性的信号或者不匹配的信号没有形成配对,双绞线失去了其设计使用的目的。
  5. 日系编码器还有NPN集电极开路式的信号,这类信号接收端也必须是NPN,而且“1”和“0”的逻辑是反的,而不是用上拉电阻临时救急用上去。我不推荐使用这类信号。对于编码器信号输出类别中,NPN信号可以被淘汰了。
  6. 目前国内最典型的编码器信号接口不匹配,是欧系PLC(例如西门子PLC)连日系编码器(例如欧姆龙编码器),看似电压与ABZ都对,连上去也能读取信号,但实际上是不匹配的,在频率较高时抗干扰差,容易丢脉冲,甚至容易上电烧器件,应避免这样的连接。其次,是变频器的信号接收应选用差分式含反相的信号,HTL-6含反相6通道因为有更高的电压阈值而更适合在变频器中使用。而目前国内变频器接收的信号很多并不匹配,尤其是选用NPN集电极开路输出信号,因其公共端不在0V,而电机接地是0V的,NPN接法是冲突的不匹配的。

参考:
增量编码器各种输出信号详解(TTL电平、5V差分、长线驱动、RS422等)
回答10个问题,轻松做旋转编码器专家!
谈谈增量型编码器的信号电路输出方式
常见编码器输出信号比较

Spring boot集成Websocket

Posted on 2019-08-07 | In JAVA
  1. pom.xml配置依赖

    1
    2
    3
    4
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-websocket</artifactId>
    </dependency>
  2. 添加Websocket配置类

    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
    import org.springframework.context.annotation.Configuration;
    import org.springframework.messaging.simp.config.MessageBrokerRegistry;
    import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
    import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
    import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;

    @Configuration
    @EnableWebSocketMessageBroker
    public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

    @Override
    public void configureMessageBroker(MessageBrokerRegistry config) {
    /**
    * 配置消息代理
    * 启动简单Broker,消息的发送的地址符合配置的前缀来的消息才发送到这个broker
    */
    config.enableSimpleBroker("/topic");
    config.setApplicationDestinationPrefixes("/app");
    }

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
    /**
    * 注册 Stomp的端点
    * addEndpoint:添加STOMP协议的端点。这个HTTP URL是供WebSocket或SockJS客户端访问的地址
    * withSockJS:指定端点使用SockJS协议
    */
    registry.addEndpoint("/websocket-demo")
    .setAllowedOrigins("*")
    .withSockJS();
    }

    }
  3. 添加Controller

    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
    import com.example.demo.service.RequestMessage;
    import com.example.demo.service.ResponseMessage;
    import org.springframework.messaging.handler.annotation.MessageMapping;
    import org.springframework.messaging.handler.annotation.SendTo;
    import org.springframework.stereotype.Controller;
    import java.util.concurrent.atomic.AtomicInteger;

    @Controller
    public class BroadcastController {
    // 收到消息记数
    private AtomicInteger count = new AtomicInteger(0);

    /**
    * @MessageMapping 指定要接收消息的地址,类似@RequestMapping。除了注解到方法上,也可以注解到类上
    * @SendTo默认 消息将被发送到与传入消息相同的目的地
    * 消息的返回值是通过{@link org.springframework.messaging.converter.MessageConverter}进行转换
    * @param requestMessage
    * @return
    */
    @MessageMapping("/receive")
    @SendTo("/topic/getResponse")
    public ResponseMessage broadcast(RequestMessage requestMessage){
    ResponseMessage responseMessage = new ResponseMessage();
    try {
    Thread.sleep(1000);
    }
    catch(InterruptedException e) {
    System.out.println("got interrupted!");
    }
    responseMessage.setResponseMessage("Server receive [" + count.incrementAndGet() + "] records");
    return responseMessage;
    }
    }
  4. 前端

    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
    <!-- jquery  -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.js"></script>
    <!-- stomp协议的客户端脚本 -->
    <script src="http://cdn.bootcss.com/stomp.js/2.3.3/stomp.min.js"></script>
    <!-- SockJS的客户端脚本 -->
    <script src="http://cdn.jsdelivr.net/sockjs/1.0.1/sockjs.min.js"></script>
    <script type="text/javascript">
    var stompClient = null;

    function connect() {
    // websocket的连接地址,此值等于WebSocketMessageBrokerConfigurer中registry.addEndpoint("/websocket-simple").withSockJS()配置的地址
    var socket = new SockJS('http://127.0.0.1:8080/websocket-demo');
    stompClient = Stomp.over(socket);
    stompClient.connect({}, function(frame) {
    setConnected(true);
    console.log('Connected: ' + frame);

    // 客户端订阅消息的目的地址:此值BroadcastCtl中被@SendTo("/topic/getResponse")注解的里配置的值
    stompClient.subscribe('/topic/getResponse', function(respnose){
    showResponse(JSON.parse(respnose.body).responseMessage);
    });
    });
    }

    function disconnect() {
    if (stompClient != null) {
    stompClient.disconnect();
    }
    setConnected(false);
    console.log("Disconnected");
    }

    function sendName() {
    var name = $('#name').val();
    // 客户端消息发送的目的:服务端使用BroadcastCtl中@MessageMapping("/receive")注解的方法来处理发送过来的消息
    stompClient.send("/app/receive", {}, JSON.stringify({ 'name': name }));
    }

    function showResponse(message) {
    var response = $("#response");
    response.html(message + "\r\n" + response.html());
    }
    </script>

参考:
Using WebSocket to build an interactive web application

Spring boot 集成Kafka

Posted on 2019-08-05 | In JAVA
  1. pom.xml添加依赖

    1
    2
    3
    4
    5
    <dependency>
    <groupId>org.springframework.kafka</groupId>
    <artifactId>spring-kafka</artifactId>
    <version>2.2.7.RELEASE</version>
    </dependency>
  2. application.properties添加配置

    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
    #============== kafka ===================
    # 指定kafka 代理地址,可以多个
    spring.kafka.bootstrap-servers=127.0.0.1:9092

    #=============== provider =======================

    spring.kafka.producer.retries=0
    # 每次批量发送消息的数量
    spring.kafka.producer.batch-size=16384
    spring.kafka.producer.buffer-memory=33554432

    # 指定消息key和消息体的编解码方式
    spring.kafka.producer.key-serializer=org.apache.kafka.common.serialization.StringSerializer
    spring.kafka.producer.value-serializer=org.apache.kafka.common.serialization.StringSerializer

    #=============== consumer =======================
    # 指定默认消费者group id
    spring.kafka.consumer.group-id=test-consumer-group
    #
    spring.kafka.consumer.auto-offset-reset=earliest
    spring.kafka.consumer.enable-auto-commit=true
    spring.kafka.consumer.auto-commit-interval=100

    # 指定消息key和消息体的编解码方式
    spring.kafka.consumer.key-deserializer=org.apache.kafka.common.serialization.StringDeserializer
    spring.kafka.consumer.value-deserializer=org.apache.kafka.common.serialization.StringDeserializer
  3. 生产者

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    import com.google.gson.Gson;
    import com.google.gson.GsonBuilder;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.kafka.core.KafkaTemplate;
    import java.util.*;

    @Configuration
    public class MessageSender {

    @Autowired
    private KafkaTemplate<String,String> kafkaTemplate;

    private Gson gson = new GsonBuilder().create();

    public void send(String msg){
    Message message = new Message();
    message.setId(System.currentTimeMillis());
    message.setMsg(msg);
    message.setTime(new Date());
    kafkaTemplate.send("test_topic", gson.toJson(message));
    }
    }

Message类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import java.util.*;
public class Message {
private Long id;
private String msg;
private Date time;

public void setId(Long id) {
this.id = id;
}
public void setMsg(String msg){
this.msg = msg;
}
public void setTime(Date time){
this.time = time;
}
}

  1. 消费者
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    import org.apache.kafka.clients.consumer.ConsumerRecord;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.kafka.annotation.KafkaListener;

    import java.util.Optional;

    @Configuration
    public class MessageReceiver {

    @KafkaListener(topics = {"test_tipic"})
    public void listen(ConsumerRecord<?, ?> record) {

    Optional<?> kafkaMessage = Optional.ofNullable(record.value());

    if (kafkaMessage.isPresent()) {

    Object message = kafkaMessage.get();
    System.out.println(message);
    }

    }
    }

参考:
Spring Boot系列文章(一):SpringBoot Kafka 整合使用

Spring boot集成Mysql与Redis

Posted on 2019-08-02 | In JAVA

Mysql

pom.xml添加依赖

1
2
3
4
5
6
7
8
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>

application.properties 添加mysql连接配置

1
2
3
4
5
# Mysql配置
spring.jpa.hibernate.ddl-auto=update
spring.datasource.url=jdbc:mysql://localhost:3306/dbname
spring.datasource.username=username
spring.datasource.password=pwd

添加一个model

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
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Entity // This tells Hibernate to make a table out of this class
public class Users {
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
private Integer id;
private String username;
private String password;

public Integer getId() {
return id;
}

public void setId(Integer id) {
this.id = id;
}

public String getUsername() {
return username;
}

public void setUsername(String name) {
this.username = name;
}

public String getPassword() {
return password;
}

public void setPassword(String pwd) {
this.password = pwd;
}

}

使用Spring boot JPA,集成CRUD接口

1
2
3
4
5
6
7
8
9
10
11
12
import com.example.demo.model.Users;
import org.springframework.data.repository.CrudRepository;
import java.util.*;

// This will be AUTO IMPLEMENTED by Spring into a Bean called userRepository
// CRUD refers Create, Read, Update, Delete

public interface UsersRepository extends CrudRepository<Users, Integer> {

List<Users> findByUsername(String username);

}

在controller中使用

1
2
3
4
5
@RequestMapping("/find-by-username/{username}")
public List findByUsername(@PathVariable("username") String username) {
List<Users> userList = usersRepository.findByUsername(username);
return userList;
}

Redis

pom.xml中添加依赖

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

application.properties中添加Redis连接配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# Redis数据库索引(默认为0)
spring.redis.database=0
# Redis服务器地址
spring.redis.host=192.168.33.10
# Redis服务器连接端口
spring.redis.port=6379
# Redis服务器连接密码(默认为空)
spring.redis.password=
# 连接池最大连接数(使用负值表示没有限制)
spring.redis.jedis.pool.max-active=8
# 连接池最大阻塞等待时间(使用负值表示没有限制)
spring.redis.jedis.pool.max-wait=-1
# 连接池中的最大空闲连接
spring.redis.jedis.pool.max-idle=8
# 连接池中的最小空闲连接
spring.redis.jedis.pool.min-idle=0
# 连接超时时间(毫秒)
spring.redis.timeout=3000

controller中使用

1
2
3
4
5
6
7
8
9
@Autowired
private RedisTemplate<String, String> redisTemplate;

@RequestMapping("/setredis")
public String setRedis(){
redisTemplate.opsForValue().set("javaset","1");
String ans = redisTemplate.opsForValue().get("javaset");
return ans;
}

Spring入门

Posted on 2019-08-01

概念

Spring IOC

Spring 容器是 Spring 框架的核心。容器将创建对象,把它们连接在一起,配置它们,并管理他们的整个生命周期从创建到销毁。Spring 容器使用依赖注入(DI)来管理组成一个应用程序的组件。这些对象被称为 Spring Beans。
IOC 容器具有依赖注入功能的容器,它可以创建对象,IOC 容器负责实例化、定位、配置应用程序中的对象及建立这些对象间的依赖。通常new一个实例,控制权由程序员控制,而”控制反转”是指new实例工作不由程序员来做而是交给Spring容器来做。
Spring的容器:

  1. Spring BeanFactory 容器
  2. Spring ApplicationContext 容器

BeanFactory

这个容器从一个 XML 文件中读取配置元数据,由这些元数据来生成一个被配置化的系统或者应用。

1
2
3
4
5
6
7
8
9
10
11
12
13
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionReader;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
// 注册
BeanFactory factory=new DefaultListableBeanFactory();
BeanDefinitionReader reader=new XmlBeanDefinitionReader((BeanDefinitionRegistry) factory);
reader.loadBeanDefinitions(new ClassPathResource("beans.xml"));
// getbean
ServiceBean service = (ServiceBean)factory.getBean("service");

ApplicationContext

Application Context 是BeanFactory的子接口,也被成为Spring上下文。与BeanFactory类似,但是它增加了企业所需要的功能,比如,从属性文件中解析文本信息和将事件传递给所指定的监听器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;

ApplicationContext context = new FileSystemXmlApplicationContext("Beans.xml");
HelloWorld obj = (HelloWorld) context.getBean("helloWorld");
obj.getMessage();

....
public class HelloWorld {
private String message;
public void setMessage(String message){
this.message = message;
}
public void getMessage(){
System.out.println("Your Message : " + message);
}
}

Bean

被称作 bean 的对象是构成应用程序的支柱也是由 Spring IoC 容器管理的。bean 是一个被实例化,组装,并通过 Spring IoC 容器所管理的对象。
bean的配置需要元数据信息。包括

  1. 作用域:
    • singleton:在spring IoC容器仅存在一个Bean实例,Bean以单例方式存在,默认值
    • prototype:每次从容器中调用Bean时,都返回一个新的实例,即每次调用getBean()时,相当于执行newXxxBean()
    • request:每次HTTP请求都会创建一个新的Bean,该作用域仅适用于WebApplicationContext环境
    • session:同一个HTTP Session共享一个Bean,不同Session使用不同的Bean,仅适用于WebApplicationContext环境
    • global session:一般用于Portlet应用环境,该运用域仅适用于WebApplicationContext环境
  2. 生命周期

    1. 初始化回调

      1
      2
      3
      4
      5
      6
      7
      8
      import org.springframework.beans.factory.InitializingBean
      public class ExampleBean implements InitializingBean {
      public void afterPropertiesSet() {
      // do some initialization work
      }
      }
      // 或者在xml定义
      <bean id="exampleBean" class="examples.ExampleBean" init-method="afterPropertiesSet"/>
    2. 销毁回调

      1
      2
      3
      4
      5
      6
      7
      8
      import org.springframework.beans.factory.DisposableBean
      public class ExampleBean implements DisposableBean {
      public void destroy() {
      // do some destruction work
      }
      }
      // 或者在xml定义
      <bean id="exampleBean" class="examples.ExampleBean" destroy-method="destroy"/>
  3. 后置处理器:
    BeanPostProcessor

  4. 继承
    Spring Bean 定义的继承与 Java 类的继承无关,但是继承的概念是一样的。你可以定义一个父 bean 的定义作为模板和其他子 bean 就可以从父 bean 中继承所需的配置。
    1
    <bean id="helloIndia" class="com.tutorialspoint.HelloIndia" parent="helloWorld">

Spring 依赖注入

基于构造函数的依赖注入

当容器调用带有一组参数的类构造函数时,基于构造函数的 DI 就完成了,其中每个参数代表一个对其他类的依赖。

1
2
3
4
5
6
package x.y;
public class Foo {
public Foo(Bar bar, Baz baz) {
// ...
}
}

beans.xml

1
2
3
4
5
6
7
8
9
<beans>
<bean id="foo" class="x.y.Foo">
<constructor-arg ref="bar"/>
<constructor-arg ref="baz"/>
</bean>

<bean id="bar" class="x.y.Bar"/>
<bean id="baz" class="x.y.Baz"/>
</beans>

main.java

1
2
3
4
5
6
7
public class MainApp {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
Foo te = (Foo) context.getBean("foo");
te.xxxx();
}
}

基于设值函数的依赖注入

当容器调用一个无参的构造函数或一个无参的静态 factory 方法来初始化你的 bean 后,通过容器在你的 bean 上调用设值函数,基于设值函数的 DI 就完成了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.tutorialspoint;
public class TextEditor {
private SpellChecker spellChecker;

public void setSpellChecker(SpellChecker spellChecker) {
System.out.println("Inside setSpellChecker." );
this.spellChecker = spellChecker;
}

public SpellChecker getSpellChecker() {
return spellChecker;
}
public void spellCheck() {
spellChecker.checkSpelling();
}
}

beans.xml

1
2
3
4
5
<bean id="textEditor" class="com.tutorialspoint.TextEditor">
<property name="spellChecker" ref="spellChecker"/>
</bean>

<bean id="spellChecker" class="com.tutorialspoint.SpellChecker"></bean>

main.java

1
2
3
4
5
6
7
public class MainApp {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
TextEditor te = (TextEditor) context.getBean("textEditor");
te.spellCheck();
}
}

内部bean

Java 内部类是在其他类的范围内被定义的,同理,inner beans 是在其他 bean 的范围内定义的 bean。

1
2
3
4
5
<bean id="outerBean" class="...">
<property name="target">
<bean id="innerBean" class="..."/>
</property>
</bean>

注入集合

包括 Java Collection 类型 List、Set、Map 和 Properties

1
2
3
4
5
6
7
8
9
10
11
 <bean id="javaCollection" class="com.tutorialspoint.JavaCollection">
<!-- results in a setAddressList(java.util.List) call -->
<property name="addressList">
<list>
<value>INDIA</value>
<value>Pakistan</value>
<value>USA</value>
<value>USA</value>
</list>
</property>
</bean>

Spring Bean 的自动装配

在上面说明了通过\和\元素来注入 。
也可以使用\元素的 autowire 属性为一个 bean 定义指定自动装配模式以减少配置代码。

模式:

  1. no: 这是默认的设置,它意味着没有自动装配,你应该使用显式的bean引用来连线。
  2. byName: 由属性名自动装配。Spring 容器看到在 XML 配置文件中 bean 的自动装配的属性设置为 byName。

    1
    2
    3
    4
    5
    6
    <bean id="textEditor" class="com.tutorialspoint.TextEditor" autowire="byName">
    <property name="name" value="Generic Text Editor" />
    </bean>

    <bean id="spellChecker" class="com.tutorialspoint.SpellChecker">
    </bean>
  3. byType: 由属性数据类型自动装配。Spring 容器看到在 XML 配置文件中 bean 的自动装配的属性设置为 byType。

  4. constructor: 类似于 byType,但该类型适用于构造函数参数类型。
  5. autodetect: Spring首先尝试通过 constructor 使用自动装配来连接,如果它不执行,Spring 尝试通过 byType 来自动装配。

基于注解的配置

从 Spring 2.5 开始就可以使用注解来配置依赖注入。而不是采用 XML 来描述一个 bean 连线,你可以使用相关类,方法或字段声明的注解,将 bean 配置移动到组件类本身。
打开注解配置需要在bean.xml中配置

1
<context:annotation-config/>

@Required

@Required 注释应用于 bean 属性的 setter 方法,它表明受影响的 bean 属性在配置时必须放在 XML 配置文件中,否则容器就会抛出一个 BeanInitializationException 异常。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import org.springframework.beans.factory.annotation.Required;
public class Student {
private Integer age;
private String name;
@Required
public void setAge(Integer age) {
this.age = age;
}
public Integer getAge() {
return age;
}
@Required
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
}

beans.xml

1
2
3
4
<bean id="student" class="com.tutorialspoint.Student">
<property name="name" value="Zara" />
<property name="age" value="11"/> // 如果没有写这个属性会报错
</bean>

@Autowired

@Autowired 注释对在哪里和如何完成自动连接提供了更多的细微的控制。

可以在beans.xml文件中配置autowired。当Spring遇到一个在 setter方法中使用的 @Autowired 注释,它会在方法中视图执行 byType 自动连接。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import org.springframework.beans.factory.annotation.Autowired;
public class TextEditor {
@Autowired
private SpellChecker spellChecker;
public TextEditor() {
System.out.println("Inside TextEditor constructor." );
}
public SpellChecker getSpellChecker( ){
return spellChecker;
}
public void spellCheck(){
spellChecker.checkSpelling();
}
}

beans.xml

1
2
3
4
5
<bean id="textEditor" class="com.tutorialspoint.TextEditor">
</bean>

<bean id="spellChecker" class="com.tutorialspoint.SpellChecker">
</bean>

@Qualifier 注释

当你创建多个具有相同类型的 bean 时,并且想要用一个属性只为它们其中的一个进行装配,在这种情况下,你可以使用 @Qualifier 注释和 @Autowired 注释通过指定哪一个真正的 bean 将会被装配来消除混乱。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
public class Profile {
@Autowired
@Qualifier("student1")
private Student student;
public Profile(){
System.out.println("Inside Profile constructor." );
}
public void printAge() {
System.out.println("Age : " + student.getAge() );
}
public void printName() {
System.out.println("Name : " + student.getName() );
}
}

beans.xml

1
2
3
4
5
6
7
8
9
10
11
12
 <bean id="profile" class="com.tutorialspoint.Profile">
</bean>

<bean id="student1" class="com.tutorialspoint.Student">
<property name="name" value="Zara" />
<property name="age" value="11"/>
</bean>

<bean id="student2" class="com.tutorialspoint.Student">
<property name="name" value="Nuha" />
<property name="age" value="2"/>
</bean>

JSR-250 注释

  1. @PostConstruct: 初始化回调函数的一个替代
  2. @PreDestroy: 销毁回调函数的一个替代
  3. @Resource: 使用一个 ‘name’ 属性,该属性以一个 bean 名称的形式被注入。你可以说,它遵循 by-name 自动连接语义
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public class TextEditor {
    private SpellChecker spellChecker;
    @Resource(name= "spellChecker")
    public void setSpellChecker( SpellChecker spellChecker ){
    this.spellChecker = spellChecker;
    }
    public SpellChecker getSpellChecker(){
    return spellChecker;
    }
    public void spellCheck(){
    spellChecker.checkSpelling();
    }
    }

基于JAVA的配置

  1. @Configuration 和 @Bean 注解

    • @Configuration 的注解类表示这个类可以使用 Spring IoC 容器作为 bean 定义的来源。
    • @Bean 注解告诉 Spring,一个带有 @Bean 的注解方法将返回一个对象,该对象应该被注册为在 Spring 应用程序上下文中的 bean。

      一个例子:

      1
      2
      3
      4
      5
      6
      7
      8
      import org.springframework.context.annotation.*;
      @Configuration
      public class HelloWorldConfig {
      @Bean
      public HelloWorld helloWorld(){
      return new HelloWorld();
      }
      }

      等同于xml中

      1
      2
      3
      <beans>
      <bean id="helloWorld" class="com.tutorialspoint.HelloWorld" />
      </beans>

      带有 @Bean 注解的方法名称作为 bean 的 ID,它创建并返回实际的 bean。你的配置类可以声明多个 @Bean。

  2. @import 注解允许从另一个配置类中加载 @Bean 定义。

生命周期

Spring 的核心是 ApplicationContext,它负责管理 beans 的完整生命周期。当加载 beans 时,ApplicationContext 发布某些类型的事件。

由于 Spring 的事件处理是单线程的,所以如果一个事件被发布,直至并且除非所有的接收者得到的该消息,该进程被阻塞并且流程将不会继续。

Spring AOP

Spring 框架的一个关键组件是面向方面的编程(AOP)框架。

AOP中的术语

Spring JDBC

Spring JDBC 框架负责所有数据库的低层细节,从开始打开连接,准备和执行 SQL 语句,处理异常,处理事务,到最后关闭连接。

Spring 事务管理

Spring 事务抽象的关键是由 org.springframework.transaction.PlatformTransactionManager 接口定义。

Spring MVC

参考:
W3C School

Spring Boot初探

Posted on 2019-07-30

概念

  1. Spring是一个生态体系(也可以说是技术体系),是集大成者,它包含了Spring Framework、Spring Boot、Spring Cloud等(还包括Spring Cloud data flow、spring data、spring integration、spring batch、spring security、spring hateoas)。
  2. Spring Framework是整个Spring生态的基石。Spring官方对Spring Framework简短描述:为依赖注入、事务管理、WEB应用、数据访问等提供了核心的支持。
  3. SpringMVC是基于Spring的一个MVC框架,用以替代初期的SSH框架。 Spring Framework本身没有Web功能,Spring MVC使用了WebApplicationContext类扩展ApplicationContext,使得拥有Web功能。
  4. Spring Boot是基于Spring全家桶的一套快速开发整合包。以前的Java Web开发模式:Tomcat + WAR包。WEB项目基于Spring framework,项目目录一定要是标准的WEB-INF + classes + lib,而且大量的xml配置。Spring Boot为快速启动且最小化配置的spring应用而设计,并且它具有用于构建生产级别应用的一套固化的视图(约定)。
  5. Spring Cloud事实上是一整套基于Spring Boot的微服务解决方案。它为开发者提供了很多工具,用于快速构建分布式系统的一些通用模式,例如:配置管理、注册中心、服务发现、限流、网关、链路追踪等。

跟着文档开始

基于Maven进行依赖包管理

  1. 创建一个pom.xml,用于管理项目的依赖
    spring-boot-starter-parent是一个特殊的启动器,提供有用的Maven默认值。
    使用 mvn dependency:tree 可以打印项目的依赖项树形结构

    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
    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example</groupId>
    <artifactId>myproject</artifactId>
    <version>0.0.1-SNAPSHOT</version>

    <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.1.1.RELEASE</version>
    </parent>
    // 配置依赖性
    <dependencies>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    </dependencies>
    // 打包配置
    <build>
    <plugins>
    <plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    </plugin>
    </plugins>
    </build>
    </project>
  2. 来一个Hello World

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    import org.springframework.boot.*;
    import org.springframework.boot.autoconfigure.*;
    import org.springframework.web.bind.annotation.*;

    @RestController
    @EnableAutoConfiguration
    public class Example {

    @RequestMapping("/")
    String home() {
    return "Hello World!";
    }

    public static void main(String[] args) throws Exception {
    SpringApplication.run(Example.class, args);
    }
    }
  • @RestController 是一个构造性注释,代表这是一个Controller类
  • @RequestMapping 是一个路由注释
  • 上面这两个注释都是基于Spring MVC
  • @EnableAutoConfiguration 注释告诉Spring Boot根据你添加的jar依赖关系“猜测”你想要如何配置Spring。由于spring-boot-starter-web添加了Tomcat和Spring MVC,因此自动配置假定您正在开发Web应用程序并相应地设置Spring。
  1. 运行
    利用IDE 的 “RUN”, 活着 mvn spring-boot:run,可以看到启动了嵌入了Tomcat服务器在8080端口。
  2. 打包
    在pom.xml配置了build配置项后,运行mvn package会在target目录打包出一个可执行文件myproject-0.0.1-SNAPSHOT.jar跟一个myproject-0.0.1-SNAPSHOT.jar.original小文件。通过以下命令可以直接运行这个jar包。
    1
    java -jar target/myproject-0.0.1-SNAPSHOT.jar

Spring Boot的使用

  1. Starters是一组方便的依赖描述符,您可以在应用程序中包含这些描述符。您可以获得所需的所有Spring和相关技术的一站式服务。通常以 spring-boot-starter-* 命名。Starters列表可以参考文档。
  2. 构建代码

    • package命名
    • @SpringBootApplication 放在主类
  3. 使用@SpringBootApplication Annotation

    • @EnableAutoConfiguration:启用Spring Boot的自动配置机制
    • @ComponentScan:对应用程序所在的软件包启用@Component扫描
    • @Configuration:允许在上下文中注册额外的beans或导入其他配置类
  4. @SpringBootApplication注释等效于使用@Configuration、@EnableAutoConfiguration、@ComponentScan及其默认属性。

Spring Boot注解

  1. @Configuration 等同于spring的XML配置文件;使用Java代码可以检查类型安全。
  2. @EnableAutoConfiguration 自动配置。
  3. @ComponentScan 组件扫描,可自动发现和装配一些Bean。
  4. @Component可配合CommandLineRunner使用,在程序启动后执行一些基础任务。
  5. @RestController注解是@Controller和@ResponseBody的合集,表示这是个控制器bean,并且是将函数的返回值直 接填入HTTP响应体中,是REST风格的控制器。
  6. @Autowired自动导入。
  7. @PathVariable获取参数。
  8. @JsonBackReference解决嵌套外链问题。
  9. @RepositoryRestResourcepublic配合spring-boot-starter-data-rest使用。
  10. @Import:用来导入其他配置类。
  11. @ImportResource:用来加载xml配置文件。
  12. @Service:一般用于修饰service层的组件。
  13. @Repository:使用@Repository注解可以确保DAO或者repositories提供异常转译,这个注解修饰的DAO或者repositories类会被ComponetScan发现并配置,同时也不需要为它们提供XML配置项。
  14. @Bean:用@Bean标注方法等价于XML中配置的bean。
  15. @Value:注入Spring boot application.properties配置的属性的值
  16. @Inject:等价于默认的@Autowired,只是没有required属性;
  17. @Qualifier:当有多个同一类型的Bean时,可以用@Qualifier(“name”)来指定。与@Autowired配合使用。@Qualifier限定描述符除了能根据名字进行注入,但能进行更细粒度的控制如何选择候选者。
  18. @Resource(name=”name”,type=”type”):没有括号内内容的话,默认byName。与@Autowired干类似的事。

JPA注解

  1. @Entity:@Table(name=”“):表明这是一个实体类。一般用于jpa这两个注解一般一块使用,但是如果表名和实体类名相同的话,@Table可以省略
  2. @MappedSuperClass:用在确定是父类的entity上。父类的属性子类可以继承。
  3. @NoRepositoryBean:一般用作父类的repository,有这个注解,spring不会去实例化该repository。
  4. @Column:如果字段名与列名相同,则可以省略。
  5. @Id:表示该属性为主键。
  6. @GeneratedValue(strategy = GenerationType.SEQUENCE,generator = “repair_seq”):表示主键生成策略是sequence(可以为Auto、IDENTITY、native等,Auto表示可在多个数据库间切换),指定sequence的名字是repair_seq。
  7. @SequenceGeneretor(name = “repair_seq”, sequenceName = “seq_repair”, allocationSize = 1):name为sequence的名称,以便使用,sequenceName为数据库的sequence名称,两个名称可以一致。
  8. @Transient:表示该属性并非一个到数据库表的字段的映射,ORM框架将忽略该属性。如果一个属性并非数据库表的字段映射,就务必将其标示为@Transient,否则,ORM框架默认其注解为@Basic。@Basic(fetch=FetchType.LAZY):标记可以指定实体属性的加载方式
  9. @JsonIgnore:作用是json序列化时将Java bean中的一些属性忽略掉,序列化和反序列化都受影响。
  10. @JoinColumn(name=”loginId”):一对一:本表中指向另一个表的外键。一对多:另一个表指向本表的外键。
  11. @OneToOne、@OneToMany、@ManyToOne:对应hibernate配置文件中的一对一,一对多,多对一。

SpringMVC注解

  1. @RequestMapping:@RequestMapping(“/path”)表示该控制器处理所有“/path”的UR L请求。RequestMapping是一个用来处理请求地址映射的注解,可用于类或方法上。
  2. @RequestParam:用在方法的参数前面。

    1
    2
    @RequestParam 
    String a =request.getParameter(“a”)。
  3. @PathVariable:路径变量。

异常注解

  1. @ControllerAdvice:包含@Component。可以被扫描到。统一处理异常。
  2. @ExceptionHandler(Exception.class):用在方法上面表示遇到这个异常就执行以下方法。

参考:
Spring Boot 中文文档
面试官问我:spring、springboot、springcloud的区别,我笑了

Flask源码学习

Posted on 2019-07-25

WSGI规范

WSGI(Web Server Gateway Interface):proposes a simple and universal interface between web servers and web applications or frameworks
PEP_333有这个标准的详细说明
规定:

  1. 每个Application必须是个可调用对象,接收两个参数environ(包含了环境信息的字典)、start_response(开始响应请求的函数),并且返回 iterable。

    1
    2
    3
    4
    5
    # environ 和 start_response 由 HTTP Server 提供并实现
    def application(environ, start_response):
    # Application 内部在返回前调用 start_response
    start_response('200 OK', [('Content-Type', 'text/html')])
    return '<h1>Hello, web!</h1>'
  2. Server需要准备environ参数、定义start_response函数、调用Application的可调用对象。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    import os, sys
    def run_with_cgi(application):
    environ = dict(os.environ.items())
    environ['wsgi.input'] = sys.stdin
    environ['wsgi.errors'] = sys.stderr
    environ['wsgi.version'] = (1, 0)
    environ['wsgi.multithread'] = False
    environ['wsgi.multiprocess'] = True
    environ['wsgi.run_once'] = True
    environ['wsgi.url_scheme'] = 'http'

    headers_set = []
    headers_sent = []

    def write(data):
    sys.stdout.write(data)
    sys.stdout.flush()

    def start_response(status, response_headers, exc_info=None):
    headers_set[:] = [status, response_headers]
    return write

    result = application(environ, start_response)
  3. Middleware: Middleware 对服务器程序和应用是透明的,它像一个代理/管道一样,把接收到的请求进行一些处理,然后往后传递,一直传递到客户端程序,最后把程序的客户端处理的结果再返回。比如:

    • 根据 url 把请求给到不同的客户端程序(url routing)
    • 允许多个客户端程序/web 框架同时运行,就是把接到的同一个请求传递给多个程序。
    • 负载均衡和远程处理:把请求在网络上传输
    • 应答的过滤处理

从Flask run 开始

在 app.py 中

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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
def run(self, host=None, port=None, debug=None, load_dotenv=True, **options):
........

_host = "127.0.0.1"
_port = 5000
server_name = self.config.get("SERVER_NAME")
sn_host, sn_port = None, None

if server_name:
sn_host, _, sn_port = server_name.partition(":")

host = host or sn_host or _host
port = int(next((p for p in (port, sn_port) if p is not None), _port))

from werkzeug.serving import run_simple

try:
# 注意第三个参数是self,也就是这个Flask的实例对象
run_simple(host, port, self, **options) #<<<<<<<<
finally:
self._got_first_request = False

.....
def run_simple(hostname, port, application, use_reloader=False, use_debugger=False, use_evalex=True, extra_files=None, reloader_interval=1, reloader_type="auto", threaded=False, processes=1, request_handler=None, static_files=None, passthrough_errors=False, ssl_context=None,):
.......

def inner():
try:
fd = int(os.environ["WERKZEUG_SERVER_FD"])
except (LookupError, ValueError):
fd = None
srv = make_server(
hostname,
port,
application,
threaded,
processes,
request_handler,
passthrough_errors,
ssl_context,
fd=fd,
) # <<<<<<<<<<<<<<<<<<<<<<<<
if fd is None:
log_startup(srv.socket)
srv.serve_forever()

inner()

.......
def make_server(host=None, port=None, app=None, threaded=False, processes=1, request_handler=None, passthrough_errors=False, ssl_context=None, fd=None,):
# 省略多线程/ 多进程情况

return BaseWSGIServer( #<<<<<<<<<<<<<
host, port, app, request_handler, passthrough_errors, ssl_context, fd=fd
)
..........
# 调用了Python的HTTPServer模块
class BaseWSGIServer(HTTPServer, object):
.........

def serve_forever(self):
self.shutdown_signal = False
try:
HTTPServer.serve_forever(self) # 最终调用HTTPServer.serve_forever方法
except KeyboardInterrupt:
pass
finally:
self.server_close()

整体流程为:
flask.run –> app.run –> werkzeug.run_simple –> werkzeug.BaseWSGIServer –> Python.HTTPServer.serve_forever

路由匹配

使用 @app.route装饰器与app.add_url_rule是一样的

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
def route(self, rule, **options):
def decorator(f):
endpoint = options.pop("endpoint", None)
self.add_url_rule(rule, endpoint, f, **options)
return f
return decorator

def add_url_rule(self, rule, endpoint=None, view_func=None, provide_automatic_options=None, **options):
# endpoint是视图函数的名字
if endpoint is None:
endpoint = _endpoint_from_view_func(view_func)

methods = options.pop("methods", None)
# url_rule_class 是werkzeug.routeing.Rule的实例
rule = self.url_rule_class(rule, methods=methods, **options)
rule.provide_automatic_options = provide_automatic_options
# url_map 是werkzeug.routeing.Map的实例
self.url_map.add(rule)
if view_func is not None:
# view_functions是一个字典
old_func = self.view_functions.get(endpoint)
if old_func is not None and old_func != view_func:
raise AssertionError(
"View function mapping is overwriting an "
"existing endpoint function: %s" % endpoint
)
self.view_functions[endpoint] = view_func

werkzeug 的 url_map类似:

1
2
3
4
5
m = Map([
Rule('/', endpoint='index'),
Rule('/downloads/', endpoint='downloads/index'),
Rule('/downloads/<int:id>', endpoint='downloads/show')
])

view_functions就是个字典:

1
2
3
4
{
"end_point_1" : view_function_1,
"end_point_2" : view_function_2
}

因此endpoint是一个连接rule跟view_function的重要的桥梁
上面说明了路由添加管理,下面来看路由分发过程:

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
class Flask:
def __call__(self, environ, start_response):
return self.wsgi_app(environ, start_response)

def wsgi_app(self, environ, start_response):
ctx = self.request_context(environ)
error = None

ctx.push() # 先推送一个请求上下文
response = self.full_dispatch_request() #<<<<<<<<<<<<

return response(environ, start_response)
ctx.auto_pop(error)

def full_dispatch_request(self):
self.try_trigger_before_first_request_functions()
try:
request_started.send(self)
rv = self.preprocess_request()
if rv is None:
rv = self.dispatch_request() #<<<<<<<<<<<<<<
except Exception as e:
rv = self.handle_user_exception(e)
return self.finalize_request(rv)

def dispatch_request(self):
# 先取请求上下文的信息
req = _request_ctx_stack.top.request
if req.routing_exception is not None:
self.raise_routing_exception(req)
# 从上下文中获取url_rule
rule = req.url_rule
# if we provide automatic options for this URL and the
# request came with the OPTIONS method, reply automatically
if (
getattr(rule, "provide_automatic_options", False)
and req.method == "OPTIONS"
):
return self.make_default_options_response()
# otherwise dispatch to the handler for that endpoint
# 从view_functions字典中取对应url_rule.endpoint的方法运行
return self.view_functions[rule.endpoint] (**req.view_args)

然后来看看请求上下文做了什么,在flask/ctx.py

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
50
51
52
53
54
55
56
57
58
59
class RequestContext(object):    
def __init__(self, app, environ, request=None, session=None):
self.app = app
if request is None:
request = app.request_class(environ)
self.request = request
self.url_adapter = None
try:
# 创建了url_adapter
self.url_adapter = app.create_url_adapter(self.request) #<<<<<<<<<<<<<<<
except HTTPException as e:
self.request.routing_exception = e
self.flashes = None
self.session = session

def push(self):
"""Binds the request context to the current context."""
top = _request_ctx_stack.top
# Before we push the request context we have to ensure that there
# is an application context.
app_ctx = _app_ctx_stack.top
if app_ctx is None or app_ctx.app != self.app:
app_ctx = self.app.app_context()
app_ctx.push() #如果没有app上下文,推送一个
self._implicit_app_ctx_stack.append(app_ctx)
else:
self._implicit_app_ctx_stack.append(None)
# 把当前请求上下文推送到栈
_request_ctx_stack.push(self)
if self.url_adapter is not None:
# 匹配路由
self.match_request() #<<<<<<<<<<<<<<<<<<

def match_request(self):
"""Can be overridden by a subclass to hook into the matching
of the request.
"""
try:
result = self.url_adapter.match(return_rule=True) #<<<<<<<<<<<<<<<<
self.request.url_rule, self.request.view_args = result
except HTTPException as e:
self.request.routing_exception = e

def create_url_adapter(self, request):
if request is not None:
# If subdomain matching is disabled (the default), use the
# default subdomain in all cases. This should be the default
# in Werkzeug but it currently does not have that feature.
subdomain = (
(self.url_map.default_subdomain or None)
if not self.subdomain_matching
else None
)
# 把url_map绑定到wsgi到environ变量上
return self.url_map.bind_to_environ( #<<<<<<<<<<<<<
request.environ,
server_name=self.config["SERVER_NAME"],
subdomain=subdomain,
)

werkzeug.routeing.match的方法注释里就有很简单的例子,逻辑是用compile的正则表达式去匹配给出的真实路径信息,把所有的匹配组件转换成对应的值,保存在字典中(这就是传递给视图函数的参数列表)并返回。

1
2
3
4
5
6
7
8
9
10
m = Map([
Rule('/', endpoint='index'),
Rule('/downloads/', endpoint='downloads/index'),
Rule('/downloads/<int:id>', endpoint='downloads/show')
])
urls = m.bind("example.com", "/")
urls.match("/", "GET")
>>> ('index', {})
urls.match("/downloads/42")
>>> ('downloads/show', {'id': 42})

整体流程:

  1. 通过@app.route或者app.add_url_rule注册应用url对应的处理函数
  2. 每次请求过来的时候,推送请求上下文到栈,然后调用路由匹配的逻辑,把路由结果保存起来
  3. dispatch_request分发保存的路由结果,调用对应的视图函数

上下文

在拿request信息的时候,我们会像这样引入一个全局变量

1
from flask import request

但是在不同的进程/线程/协程中,request的值是不一样的。

  1. 进程比较好理解,进程间本来就是资源独立的。
  2. 在线程/协程中,可以实现类似thread.local,实现一个字典,然后不同的线程id对应不同的值
    flask/global.py 这里保存了这些上下文的信息
    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
    from functools import partial

    from werkzeug.local import LocalProxy
    from werkzeug.local import LocalStack

    def _lookup_req_object(name):
    top = _request_ctx_stack.top
    if top is None:
    raise RuntimeError(_request_ctx_err_msg)
    return getattr(top, name)


    def _lookup_app_object(name):
    top = _app_ctx_stack.top
    if top is None:
    raise RuntimeError(_app_ctx_err_msg)
    return getattr(top, name)


    def _find_app():
    top = _app_ctx_stack.top
    if top is None:
    raise RuntimeError(_app_ctx_err_msg)
    return top.app

    # context locals
    _request_ctx_stack = LocalStack() #<<<<<<<<<<<<<
    _app_ctx_stack = LocalStack() #<<<<<<<<<<<<<
    current_app = LocalProxy(_find_app)
    request = LocalProxy(partial(_lookup_req_object, "request"))
    session = LocalProxy(partial(_lookup_req_object, "session"))
    g = LocalProxy(partial(_lookup_app_object, "g"))

LocalStack是werkzegu.local的一个栈实现,提供了隔离的栈访问。

1
2
3
4
5
6
7
8
9
10
11
12
class LocalStack(object):
def __init__(self):
self._local = Local()

def __call__(self):
def _lookup():
rv = self.top
if rv is None:
raise RuntimeError("object unbound")
return rv

return LocalProxy(_lookup)

Local主要实现了线程id/协程id对应字典,提供了多线程或者多协程隔离的属性访问。

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
try:
from greenlet import getcurrent as get_ident
except ImportError:
try:
from thread import get_ident
except ImportError:
from _thread import get_ident

class Local(object):
__slots__ = ("__storage__", "__ident_func__")

def __init__(self):
# 变量的值的值都存在__storage__里
object.__setattr__(self, "__storage__", {})
object.__setattr__(self, "__ident_func__", get_ident)

def __iter__(self):
return iter(self.__storage__.items())

def __call__(self, proxy):
"""Create a proxy for a name."""
return LocalProxy(self, proxy)

def __release_local__(self):
self.__storage__.pop(self.__ident_func__(), None)

# 取值、设值、删值的时候,都是通过__ident_func__获取线程/协程对象id,再在这个id字典下对变量进行操作
def __getattr__(self, name):
try:
return self.__storage__[self.__ident_func__()][name]
except KeyError:
raise AttributeError(name)

def __setattr__(self, name, value):
ident = self.__ident_func__()
storage = self.__storage__
try:
storage[ident][name] = value
except KeyError:
storage[ident] = {name: value}

def __delattr__(self, name):
try:
del self.__storage__[self.__ident_func__()][name]
except KeyError:
raise AttributeError(name)

LocalProxy是一个Local对象的代理,负责把所有对自己的操作转发给内部的Local对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class LocalProxy(object):
__slots__ = ('__local', '__dict__', '__name__')

def __init__(self, local, name=None):
# 把通过参数传递进来的 Local 实例保存在 __local 属性中
object.__setattr__(self, '_LocalProxy__local', local)
object.__setattr__(self, '__name__', name)

# 通过_get_current_object() 方法获取当前线程或者协程对应的对象。
def _get_current_object(self):
"""Return the current object."""
if not hasattr(self.__local, '__release_local__'):
return self.__local()
try:
return getattr(self.__local, self.__name__)
except AttributeError:
raise RuntimeError('no object bound to %s' % self.__name__)

def __getattr__(self, name):
if name == '__members__':
return dir(self._get_current_object())
return getattr(self._get_current_object(), name)

因此,

  1. 每次有请求过来的时候,flask会先创建当前线程或者进程需要处理的两个重要上下文对象,把它们保存到隔离的栈里面,这样视图函数进行处理的时候就能直接从栈上获取这些信息。
  2. 为什么区分app上下文、请求上下文:一个请求对应一个app上下文、一个请求上下文,但是在测试或者 python shell 中运行的时候,用户可以单独创建 请求上下文或者app上下文,这种灵活度方便用户的不同的使用场景。
  3. 为什么用栈结构:在多个App的时候,无论有多少个App,只要主动去Push它的app context,context stack就会累积起来,这样,栈顶永远是当前操作的 App Context。当一个 App Context 结束的时候,相应的栈顶元素也随之出栈。如果在执行过程中抛出了异常,对应的 App Context 中注册的 teardown函数被传入带有异常信息的参数。因此,只有栈结构才能保存多个 Context 并在其中定位出哪个才是“当前”。

参考:
flask 源码解析
Flask的Context(上下文)学习笔记

《Flask Web开发实战》笔记

Posted on 2019-07-24

基础

  1. pipenv 使用

    1
    2
    3
    pipenv install #创建.venv/ pipfile pipfile.lock
    pipenv install flask
    pipenv shell # source .venv/bin/activate
  2. 动态路由

    1
    2
    3
    4
    @app.route('/greet',defaluts = {"name":"defaultname"})
    @app.route('/greet/<name>')
    def greet(name):
    return "welcome %s" % name
  3. Flask内置了简单的开发服务器(由Werkzeug提供),旧的app.run()方法已不建议使用。

    1
    2
    3
    4
    5
    6
    flask run
    """"
    查找规则:
    1. 当前目录下寻找app.py wsgi.py,并从中寻找名为app/application的实例
    2. 从环境变量读取FLASK_APP的模块名/导入路径,寻找名为app/application的实例
    3. 如果安装了python-dotenv,启动时会从.flaskenv .env加载环境变量
  4. 检测文件变化 watchdog

  5. flask的扩展包 falsk_xxxx
  6. flask自定义命令
    1
    2
    3
    4
    # flask hello
    @app.cli.command()
    def hello():
    click.echo('Hello')
123…10
Angel Teng

Angel Teng

97 posts
14 categories
37 tags
© 2021 Angel Teng
Powered by Hexo
|
Theme — NexT.Muse v5.1.4