网络拾遗

Kafka相关:

kafka性能调优:https://blog.csdn.net/vegetable_bird_001/article/details/51858915

kafka高性能揭秘:https://www.cnblogs.com/qcloud1001/p/8984590.html

SpringBoot:

程序员DD的教程:http://blog.didispace.com/categories/Spring-Boot/

SpringCloud:

程序员DD的教程:http://blog.didispace.com/categories/Spring-Cloud/

ElasticSearch:

与springboot集成: https://blog.csdn.net/li521wang/article/details/83792552

blog精华推荐

  1. 张强-纯洁的微笑
  2. 方志明-博客专栏
  3. 程序猿DD的博客
  4. Spring Cloud Zuul 的 route 运行机制分析-黑洞之谜
  5. 简书 二月_春风
  6. 芋道源码解析
  7. Aoho的博客(security,权限服务整合)
  8. 知秋大佬的视频

Linux 保护进程不被OOM Killer

From: https://blog.csdn.net/flysqrlboy/article/details/89011635

最近在预发环境上有一个重要的进程隔三差五就被OOM Killer干掉(通过查看CentOS系统日志/var/log/messages揪出来是OOM Killer干的)。该机器上跑着各种进程,内存是有些吃紧。这当然可以通过加大机器内存或者迁走某些进程来解决。但一时又没有多余的机器和内存资源,只能自己动手丰衣足食了(资源短缺确实是更能激发人去思考更优更节省的方案)。现在我要解决的是如下两个问题:

为什么被OOM Killer干掉的是这个进程而不是其他的?
能保护某个进程不被OOM Killer 干掉吗?
Surviving the Linux OOM Killer》一文正是对上面两个问题的介绍。

基本概念:

Linux 内核有个机制叫OOM killer(Out Of Memory killer),该机制会监控那些占用内存过大,尤其是瞬间占用内存很快的进程,然后防止内存耗尽而自动把该进程杀掉。内核检测到系统内存不足、挑选并杀掉某个进程的过程可以参考内核源代码linux/mm/oom_kill.c,当系统内存不足的时候,out_of_memory()被触发,然后调用select_bad_process()选择一个”bad”进程杀掉。如何判断和选择一个”bad进程呢?linux选择”bad”进程是通过调用oom_badness(),挑选的算法和想法都很简单很朴实:最bad的那个进程就是那个最占用内存的进程。

如何查看:

grep "Out of memory" /var/log/messages

查看系统日志方法:

运行egrep -i -r ‘killed process’ /var/log命令, 也可运行dmesg命令

sudo dmesg -T | grep "(java)" 

How does OOM Killer choose which process to kill?

Linux 内核会给每个运行中的进程分配一个叫 oom_score 的分数,它表示当系统可用内存很低时,一个进程被kill掉的可能性有多大。分数越高,越有可能被kill掉。分数值很简单:等于进程的内存占用百分比乘以10。比如一个进程占50%的内存,它的oom_score值就是 50 X 10 = 500 .
一个进程的oom_score被记录在/proc/$pid/oom_score 文件中。

Can I ensure some important processes do not get killed by OOM Killer?

OOM Killer会检查 /proc/$pid/oom_score_adj文件来调整最终的分数 (oom_score)。所以我们可以通过在这个文件中给一个大的负数,以降低该进程被选中并终止的可能性。oom_score_adj可以在-1000到1000间变化。如果你给了-1000,进程即使使用了100%的内存也不会被OOM Killer干掉。可通过下面命令修改oom_score_adj(比如设为-200):

sudo echo -200 > /proc/$pid/oom_score_adj

或者,我们可以:

echo -17 > /proc/<pid>/oom_adj

-17表示禁用OOM我们也可以对把整个系统的OOM给禁用掉:

sysctl -w vm.panic_on_oom=1sysctl -p

参数/proc/sys/vm/overcommit_memory可以控制进程对内存过量使用的应对策略
当overcommit_memory=0 允许进程轻微过量使用内存,但对于大量过载请求则不允许
当overcommit_memory=1 永远允许进程overcommit

当overcommit_memory=2 永远禁止overcommit

版权声明:本文为CSDN博主「keke_xin」的原创文章,遵循CC 4.0 by-sa版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/keke_Xin/article/details/84829816

Caveats of adjusting OOM scores

警告:解决内存不足的最好办法还是增加可用内存(例如更好的硬件)或者将某些进程移到别的地方去,又或者优化代码以减少内存消耗。

oom killer理解和日志分析:知识储备

oom killer理解和日志分析:日志分析

JavaScript AST 抽象语法树

转自: https://www.overtaking.top/2018/07/25/20180725130233/

AST 抽象语法树简介

AST(Abstract Syntax Tree)是源代码的抽象语法结构树状表现形式,Webpack、ESLint、JSX、TypeScript 的编译和模块化规则之间的转化都是通过 AST 来实现对代码的检查、分析以及编译等操作。

JavaScript 语法的 AST 语法树

JavaScript 中想要使用 AST 进行开发,要知道抽象成语法树之后的结构是什么,里面的字段名称都代表什么含义以及遍历的规则,可以通过 http://esprima.org/demo/parse.html 来实现 JavaScript 语法的在线转换。

通过在线编译工具,可以将 function fn(a, b) {} 编译为下面的结构。

{
  "type": "Program",
  "body": [
    {
      "type": "FunctionDeclaration",
      "id": {
        "type": "Identifier",
        "name": "fn"
      },
      "params": [
        {
          "type": "Identifier",
          "name": "a"
        },
        {
          "type": "Identifier",
          "name": "b"
        }
      ],
      "body": {
        "type": "BlockStatement",
        "body": []
      },
      "generator": false,
      "expression": false,
      "async": false
    }
  ],
  "sourceType": "script"
}

将 JavaScript 语法编译成抽象语法树后,需要对它进行遍历、修该并重新编译,遍历树结构的过程为 “先序深度优先”。

esprima、estraverse 和 escodegen

esprimaestraverse 和 escodegen 模块是操作 AST 的三个重要模块,也是实现 babel 的核心依赖,下面是分别介绍三个模块的作用。

esprima 将 JS 转换成 AST

esprima 模块的用法如下:文件:esprima-test.js

const esprima = require('esprima');

let code = 'function fn() {}';

// 生成语法树
let tree = esprima.parseScript(code);

console.log(tree);

// Script {
//   type: 'Program',
//   body:
//   [ FunctionDeclaration {
//     type: 'FunctionDeclaration',
//     id: [Identifier],
//     params: [],
//     body: [BlockStatement],
//     generator: false,
//     expression: false,
//     async: false } ],
//   sourceType: 'script' }

通过上面的案例可以看出,通过 esprima 模块的 parseScript 方法将 JS 代码块转换成语法树,代码块需要转换成字符串,也可以通过 parseModule 方法转换一个模块。

estraverse 遍历和修改 AST

查看遍历过程:文件:estraverse-test.js

const esprima = require('esprima');
const estraverse = require('estraverse');

let code = 'function fn() {}';

// 遍历语法树
estraverse.traverse(esprima.parseScript(code), {
  enter(node) {
    console.log('enter', node.type);
  },
  leave() {
    console.log('leave', node.type);
  }
});

// enter Program
// enter FunctionDeclaration
// enter Identifier
// leave Identifier
// enter BlockStatement
// leave BlockStatement
// leave FunctionDeclaration
// leave Program

上面代码通过 estraverse 模块的 traverse 方法将 esprima 模块转换的 AST 进行了遍历,并打印了所有的 type 属性并打印,每含有一个 type 属性的对象被叫做一个节点,修改是获取对应的类型并修改该节点中的属性即可。

其实深度遍历 AST 就是在遍历每一层的 type 属性,所以遍历会分为两个阶段,进入阶段和离开阶段,在 estraverse 的 traverse 方法中分别用参数指定的 entry 和 leave 两个函数监听,但是我们一般只使用 entry

escodegen 将 AST 转换成 JS

下面的案例是一个段 JS 代码块被转换成 AST,并将遍历、修改后的 AST 重新转换成 JS 的全过程。文件:escodegen-test.js

const esprima = require('esprima');
const estraverse = require('estraverse');
const escodegen = require('escodegen');

let code = 'function fn() {}';

// 生成语法树
let tree = esprima.parseScript(code);

// 遍历语法树
estraverse.traverse(tree, {
  enter(node) {
    // 修改函数名
    if (node.type === 'FunctionDeclaration') {
      node.id.name = 'ast';
    }
  }
});

// 编译语法树
let result = escodegen.generate(tree);

console.log(result);

// function ast() {
// }

在遍历 AST 的过程中 params 值为数组,没有 type 属性。

实现 Babel 语法转换插件

实现语法转换插件需要借助 babel-core 和 babel-types 两个模块,其实这两个模块就是依赖 esprimaestraverse 和 escodegen 的。

使用这两个模块需要安装,命令如下:

1
npm install babel-core babel-types

plugin-transform-arrow-functions

plugin-transform-arrow-functions 是 Babel 家族成员之一,用于将箭头函数转换 ES5 语法的函数表达式。

文件:plugin-transform-arrow-functions.js

const babel = require('babel-core');
const types = require('babel-types');

// 箭头函数代码块
let sumCode = `
const sum = (a, b) => {
  return a + b;
}`;
let minusCode = `const minus = (a, b) => a - b;`;

// 转化 ES5 插件
const ArrowPlugin = {
  // 访问者(访问者模式)
  visitor: {
    // path 是树的路径
    ArrowFunctionExpression(path) {
      // 获取树节点
      let node = path.node;

      // 获取参数和函数体
      let params = node.params;
      let body = node.body;

      // 判断函数体是否是代码块,不是代码块则添加 return 和 {}
      if (!types.isBlockStatement(body)) {
        let returnStatement = types.returnStatement(body);
        body = types.blockStatement([returnStatement]);
      }

      // 生成一个函数表达式树结构
      let func = types.functionExpression(null, params, body, false, false);

      // 用新的树结构替换掉旧的树结构
      path.replaceWith(func);
    }
  }
};

// 生成转换后的代码块
let sumResult = babel.transform(sumCode, {
  plugins: [ArrowPlugin]
});

let minusResult = babel.transform(minusCode, {
  plugins: [ArrowPlugin]
});

console.log(sumResult.code);
console.log(minusResult.code);

// let sum = function(a, b) {
//   return a + b;
// };
// let minus = function(a, b) {
//   return a - b;
// };

我们主要使用 babel-core 的 transform 方法将 AST 转化成代码块,第一个参数为转换前的代码块(字符串),第二个参数为配置项,其中 plugins 值为数组,存储修改 babal-core 转换的 AST 的插件(对象),使用 transform 方法将旧的 AST 处理成新的代码块后,返回值为一个对象,对象的 code 属性为转换后的代码块(字符串)。

内部修改通过 babel-types 模块提供的方法实现,API 可以到 https://github.com/babel/babel/tree/6.x/packages/babel-types 中查看。

ArrowPlugin 就是传入 transform 方法的插件,必须含有 visitor 属性(固定),值同为对象,用于存储修改语法树的方法,方法名要严格按照 API,对应的方法会修改 AST 对应的节点。

在 types.functionExpression 方法中参数分别代表,函数名(匿名函数为 null)、函数参数(必填)、函数体(必填)、是否为 generator 函数(默认 false)、是否为 async 函数(默认 false),返回值为修改后的 AST,path.replaceWith 方法用于替换 AST,参数为新的 AST。

plugin-transform-classes

plugin-transform-classes 也是 Babel 家族中的成员之一,用于将 ES6 的 class 类转换成 ES5 的构造函数。

文件:plugin-transform-classes.js

const babel = require('babel-core');
const types = require('babel-types');

// 类
let code = `
class Person {
  constructor(name) {
    this.name = name;
  }
  getName () {
    return this.name;
  }
}`;

// 将类转化 ES5 构造函数插件
const ClassPlugin = {
  visitor: {
    ClassDeclaration(path) {
      let node = path.node;
      let classList = node.body.body;

      // 将取到的类名转换成标识符 { type: 'Identifier', name: 'Person' }
      let className = types.identifier(node.id.name);
      let body = types.blockStatement([]);
      let func = types.functionDeclaration(
        className,
        [],
        body,
        false,
        false
      );
      path.replaceWith(func);

      // 用于存储多个原型方法
      let es5Func = [];

      // 获取 class 中的代码体
      classList.forEach((item, index) => {
        // 函数的代码体
        let body = classList[index].body;

        // 获取参数
        let params = item.params.length ?
          item.params.map(val => val.name) :
          [];

        // 转化参数为标识符
        params = types.identifier(params);

        // 判断是否是 constructor,如果构造函数那就生成新的函数替换
        if (item.kind === 'constructor') {
          // 生成一个构造函数树结构
          func = types.functionDeclaration(
            className,
            [params],
            body,
            false,
            false
          );
        } else {
          // 其他情况是原型方法
          let proto = types.memberExpression(
            className,
            types.identifier('prototype')
          );

          // 左侧层层定义标识符 Person.prototype.getName
          let left = types.memberExpression(
            proto,
            types.identifier(item.key.name)
          );

          // 右侧定义匿名函数
          let right = types.functionExpression(
            null,
            [params],
            body,
            false,
            false
          );

          // 将左侧和右侧进行合并并存入数组
          es5Func.push(types.assignmentExpression('=', left, right));
        }
      });

      // 如果没有原型方法,直接替换
      if (es5Func.length === 0) {
        path.replaceWith(func);
      } else {
        es5Func.push(func);
        // 替换 n 个节点
        path.replaceWithMultiple(es5Func);
      }
    }
  }
};

// 生成转换后的代码块
result = babel.transform(code, {
  plugins: [ClassPlugin]
});

console.log(result.code);

// Person.prototype.getName = function() {
//     return this.name;
// }
// function Person(name) {
//     this.name = name;
// }

上面这个插件的实现要比 plugin-transform-arrow-functions 复杂一些,归根结底还是将要互相转换的 ES6 和 ES5 语法树做对比,找到他们的不同,并使用 babel-types 提供的 API 对语法树对应的节点属性进行修改并替换语法树,值得注意的是 path.replaceWithMultiple 与 path.replaceWith 不同,参数为一个数组,数组支持多个语法树结构,可根据具体修改语法树的场景选择使用,也可根据不同情况使用不同的替换方法。

总结

通过本节我们了解了什么是 AST 抽象语法树、抽象语法树在 JavaScript 中的体现以及在 NodeJS 中用于生成、遍历和修改 AST 抽象语法树的核心依赖,并通过使用 babel-core 和 babel-types 两个模块简易模拟了 ES6 新特性转换为 ES5 语法的过程,希望可以为后面自己实现一些编译插件提供了思路。

AST – 抽象语法树

转自: http://blog.chinaunix.net/uid-26750235-id-3139100.html

抽象语法树简介

()简介

抽象语法树(abstract syntax code,AST)是源代码的抽象语法结构的树状表示,树上的每个节点都表示源代码中的一种结构,这所以说是抽象的,是因为抽象语法树并不会表示出真实语法出现的每一个细节,比如说,嵌套括号被隐含在树的结构中,并没有以节点的形式呈现。抽象语法树并不依赖于源语言的语法,也就是说语法分析阶段所采用的上下文无文文法,因为在写文法时,经常会对文法进行等价的转换(消除左递归,回溯,二义性等),这样会给文法分析引入一些多余的成分,对后续阶段造成不利影响,甚至会使合个阶段变得混乱。因些,很多编译器经常要独立地构造语法分析树,为前端,后端建立一个清晰的接口。

抽象语法树在很多领域有广泛的应用,比如浏览器,智能编辑器,编译器。

()抽象语法树实例

(1)四则运算表达式

表达式: 1+3*(4-1)+2

抽象语法树为:

(2)xml

代码2.1

  1. <letter>
  2.   <address>
  3.     <city>ShiChuang</city>
  4.   </address>
  5.   <people>
  6.     <id>12478</id>
  7.     <name>Nosic</name>
  8.   </people>
  9. </letter>

抽象语法树

(3)程序1

代码2.2

  1. while b != 0
  2. {
  3.     if a > b
  4.         a = a-b
  5.     else
  6.         b = b-a
  7. }
  8. return a

抽象语法树

(4)程序2

代码2.3

  1. sum=0
  2. for i in range(0,100)
  3.     sum=sum+i
  4. end

抽象语法树

()为什么需要抽象语法树

当在源程序语法分析工作时,是在相应程序设计语言的语法规则指导下进行的。语法规则描述了该语言的各种语法成分的组成结构,通常可以用所谓的前后文无关文法或与之等价的Backus-Naur范式(BNF)将一个程序设计语言的语法规则确切的描述出来。前后文无关文法有分为这么几类:LL(1),LR(0),LR(1), LR(k) ,LALR(1)等。每一种文法都有不同的要求,如LL(1)要求文法无二义性和不存在左递归。当把一个文法改为LL(1)文法时,需要引入一些隔外的文法符号与产生式。

例如,四则运算表达式的文法为:

文法1.1

  1. E->T|EAT
  2. T->F|TMF
  3. F->(E)|i
  4. A->+|-
  5. M->*|/

改为LL(1)后为:

文法1.2

  1. E->TE’
  2. E’->ATE’|e_symbol
  3. T->FT’
  4. T’->MFT’|e_symbol
  5. F->(E)|i
  6. A->+|-
  7. M->*|/

例如,当在开发语言时,可能在开始的时候,选择LL(1)文法来描述语言的语法规则,编译器前端生成LL(1)语法树,编译器后端对LL(1)语法树进行处理,生成字节码或者是汇编代码。但是随着工程的开发,在语言中加入了更多的特性,用LL(1)文法描述时,感觉限制很大,并且编写文法时很吃力,所以这个时候决定采用LR(1)文法来描述语言的语法规则,把编译器前端改生成LR(1)语法树,但在这个时候,你会发现很糟糕,因为以前编译器后端是对LL(1)语树进行处理,不得不同时也修改后端的代码。

抽象语法树的第一个特点为:不依赖于具体的文法。无论是LL(1)文法,还是LR(1),或者还是其它的方法,都要求在语法分析时候,构造出相同的语法树,这样可以给编译器后端提供了清晰,统一的接口。即使是前端采用了不同的文法,都只需要改变前端代码,而不用连累到后端。即减少了工作量,也提高的编译器的可维护性。

抽象语法树的第二个特点为:不依赖于语言的细节。在编译器家族中,大名鼎鼎的gcc算得上是一个老大哥了,它可以编译多种语言,例如c,c++,java,ADA,Object C, FORTRAN, PASCAL,COBOL等等。在前端gcc对不同的语言进行词法,语法分析和语义分析后,产生抽象语法树形成中间代码作为输出,供后端处理。要做到这一点,就必须在构造语法树时,不依赖于语言的细节,例如在不同的语言中,类似于if-condition-then这样的语句有不同的表示方法

在c中为:

  1. if(condition)
  2. {
  3.     do_something();
  4. }

     在fortran中为:

  1. If condition then
  2.     do_somthing()
  3. end if

在构造if-condition-then语句的抽象语法树时,只需要用两个分支节点来表于,一个为condition,一个为if_body。如下图:

在源程序中出现的括号,或者是关键字,都会被丢掉。

正则表达式 – 匹配XML标签

开发中经常用到html标签匹配, 这里做个整理:

1.匹配标签 <[^>]+>

private static final String REGEX = "<[^>]+>";
static Pattern pattern = Pattern.compile(REGEX, Pattern.CASE_INSENSITIVE);
 Matcher matcher = pattern.matcher(content);
    while (matcher.find()) {
      MatchResult result = matcher.toMatchResult();
      System.out.println(result.group(0));
    }

2. 获取标签里面的某个属性值 attr=\”([^\”]*)\”

private static final String REGEX = "name=\"([^\"]*)\""; //取name属性
static Pattern pattern = Pattern.compile(REGEX, Pattern.CASE_INSENSITIVE);
 Matcher matcher = pattern.matcher(tag);
    if(matcher.find()) {
      MatchResult result = matcher.toMatchResult();
      System.out.println(result.group(1));
    }

3. 结合标签, 直接获取所有input标签里面的name属性: <TAG[^>]*ATTR=\”([^\”]*)\”[^>]+>

private static final String REGEX = "<input[^>]*name=\"([^\"]*)\"[^>]+>";
static Pattern pattern = Pattern.compile(REGEX, Pattern.CASE_INSENSITIVE);
while (matcher.find()) {
      MatchResult result = matcher.toMatchResult();
      System.out.println(result.group(1));
    }

Maven 编译 打包注意事项

1. 父子项目同时编译问题: 编译时找不到父项目的pom.xml文件

报错信息: Non-resolvable parent POM for XXXXX

解决: 在子项目pom.xml的parent中, 加入父项目的相对路径relativePath

<parent>
      <artifactId>demo-server</artifactId>
      <groupId>net.abc.demo</groupId>
      <version>1.0.0</version>
      <relativePath>../pom.xml</relativePath>
</parent>

2. 多个模块打包的依赖问题

报错信息: class net.abc.demo.XXXXX .java:[24,9] cannot find symbol: class XXX

解决: 在被依赖的模块项目pom.xml中, 加入maven-compiler-plugin插件

<build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <encoding>utf-8</encoding>
                    <fork>true</fork>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
        </plugins>
    </build>

如果依赖的是springboot模块, 则pom.xml为:

<build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <classifier>exec</classifier>
                </configuration>
            </plugin>
        </plugins>
    </build>

Python 安装 – anaconda

anaconda是各种python库的集成环境, 可以虚拟多个python环境.

1. 安装后, 解决和原来python的冲突

修改环境变量: 将anaconda的环境变量(Library\bin, Scripts和Home目录)放在原来的python配置之前;

2. 升级工具包

conda upgrade --all
conda install requests

3. 升级pip版本

python -m pip install --upgrade pip 

4. 虚拟Python环境

1.创建
conda create -n learn python=3
备注: 环境在envs/learn目录下, 可以在Pycharm中用这个目录作为python环境
2.查看
conda env list
3.切换
activate learn

activate  // 切换到base环境
activate learn // 切换到learn环境
conda create -n learn python=3  // 创建一个名为learn的环境并指定python版本为3(的最新版本)
conda env list // 列出conda管理的所有环境
conda list // 列出当前环境的所有包
conda install requests 安装requests包
conda remove requests 卸载requets包
conda remove -n learn --all // 删除learn环境及下属所有包
conda update requests 更新requests包
conda env export > environment.yaml  // 导出当前环境的包信息
conda env create -f environment.yaml  // 用配置文件创建新的虚拟环境

5. 安装数据分析相关插件

# 分词
pip install wordcloud
# 统计
pip install jieba
# 机器学习
pip install gensim

6. 启动jupyter

#默认目录在user目录下
jupyter notebook

(转)Jersey2.x对REST请求处理流程的分析

一个REST请求,始于一个RESTful Web Service资源的地址,终于一个可接受的对资源的表述(比如JSON)。

因此,流程分析的关键点有2个:

  • 将请求地址对应到资源类的相应方法上,并触发该方法。
  • 将返回值转换成请求所需要的表述,并返回给客户端。

我们使用Eclipse的断点调试服务器端(代码对应本例https://github.com/feuyeux/jax-rs2-guide/),使用cURL脚本作为客户端。

1 cURL测试

curl -H “Accept:application/json” http://localhost:8080/simple-service-webapp-spring-jpa-jquery/webapi/books/book?id=1

该脚本发送了一个REST GET请求,地址是:

http://localhost:8080/simple-service-webapp-spring-jpa-jquery/webapi/books/book?id=1

对应的服务器端方法是:

com.example.resource.BookResource.getBookByQuery(Integer)

要求返回的类型是JSON:

Accept:application/json

2 Jersey2.x流程

2.1 请求地址到REST方法

2.1.1 ServletContainer.service

ServletContainer.service(HttpServletRequest, HttpServletResponse) line: 248

  • baseUri http://localhost:8080/simple-service-webapp-spring-jpa-jquery/webapi/
  • requestUri http://localhost:8080/simple-service-webapp-spring-jpa-jquery/webapi/books/book?id=1

ServletContainer是HttpServlet的子类,位于Jersey容器包(.m2\repository\org\glassfish\jersey\containers\jersey-container-servlet-core\2.2\jersey-container-servlet-core-2.2.jar)。我们知道,Servlet的service方法是请求的入口,作为子类,HTTP任何方法的请求都要先经过该类的service方法。

断点堆栈中,重要的两个变量是请求地址信息的baseUri和requestUri。

dependencies 

Jersey包依赖关系图

ServletContainer.service(URI, URI, HttpServletRequest, HttpServletResponse) line: 372

  • requestContext.header {user-agent=[curl/7.26.0], host=[localhost:8080], accept=[application/json]}

在容器层级,请求上下文变量除了包括请求地址信息外,还包括请求头信息。这里我们关注accept信息。

2.1.2 对应方法

ApplicationHandler.handle(ContainerRequest) line: 982

ServerRuntime.process(ContainerRequest) line: 211 final Runnable task

new Runnable() {
 public void run() {
  final ContainerRequest data = Stages.process(request, requestProcessingRoot, endpointRef);
  final Endpoint endpoint = endpointRef.get();
  • endpoint ResourceMethodInvoker (id=2553)
    public com.example.domain.Book com.example.resource.BookResource.getBookByQuery(java.lang.Integer)

从上面的代码片段,可以看到请求被对应到了一个Endpoint对象,该对象是一个资源方法Invoker,从断点堆栈中可以看到,其内容就是我们期待的那个方法,此后的invoke将调用这个对应REST请求的处理方法。

2.1.3 调用方法

ResourceMethodInvoker.invoke(ContainerRequest, Object) line: 353
dispatcher.dispatch(resource, requestContext);

JavaResourceMethodDispatcherProvider$TypeOutInvoker(AbstractJavaResourceMethodDispatcher).invoke(Object, Object…) line: 158
invokeMethodAction.run();

ResourceMethodInvocationHandlerFactory$1.invoke(Object, Method, Object[]) line: 81

从堆栈中可以轻松定位这个方法:**BookResource.getBookByQuery(Integer) line: 71**

到此,请求地址到方法调用的流程就结束了。在处理业务逻辑方法BookResource.getBookByQuery后,Jersey将开始处理响应信息。

2.2 REST方法到表述

JavaResourceMethodDispatcherProvider$TypeOutInvoker.doDispatch(Object, Request) line: 198

  • o Book (id=2686)
  • Response response = Response.ok().entity(o).build();

REST请求的返回信息应包括表述信息和HTTP响应代码(通常一个成功的请求应该得到200 OK)。这就是上面代码所做的事情。在设置好response的entity后,需要将该entity对象转化成请求所接受的表述,流程如下。

2.2.1 写向response

ServerRuntime$Responder.process(ContainerResponse) line: 351ServerRuntime$Responder.writeResponse(ContainerResponse) line: 486

  • entity Book (id=7076)
  • executor WriterInterceptorExecutor (id=7125)

从上面的堆栈中可以看到,这个阶段重点的两个对象是返回对象和写处理对象,就是使用后者将前者恰当地写入response中。

2.2.2 JSON表述

REST的表述不局限于JSON,这里我们以JSON作为常用的表述类型为例,来讨论Jersey的表述处理。 Jersey对JSON的支持有4种方式,在第3章会有详细的讲解,本例使用的是EclipseLink项目的MOXy。其底层依赖是JPA的实现之一(另一个著名的JPA实现是JBoss项目下大名鼎鼎的Hibernate)。

    <dependency>
        <groupId>org.glassfish.jersey.media</groupId>
        <artifactId>jersey-media-moxy</artifactId>
        <version>${jersey.version}</version>
    </dependency>

moxy

MOXy依赖关系图

在下面的堆栈中,可以看到JAXB的身影,因为MOXy同样实现了JAXB,其内部实现是以XML的Marshal方式处理对象(OXM:Object-XML-Mapping),然后转化为JSON数据(XML-2-JSON)。

JsonWithPaddingInterceptor.aroundWriteTo(WriterInterceptorContext) line: 91

WriterInterceptorExecutor$TerminalWriterInterceptor.aroundWriteTo(WriterInterceptorContext) line: 188

ConfigurableMoxyJsonProvider(MOXyJsonProvider).**writeTo**(Object, Class<?>, Type, Annotation[], MediaType, MultivaluedMap<string,object style=”margin: 0px; padding: 0px;”>, OutputStream) line: 782

  • object Book (id=7076)
  • type Class (com.example.domain.Book) (id=568)
  • genericType Class (com.example.domain.Book) (id=568)
  • annotations Annotation3
  • mediaType AcceptableMediaType (id=7120)
  • httpHeaders StringKeyIgnoreCaseMultivaluedMap (id=7121)
  • entityStream CommittingOutputStream (id=7124)

开始从对象转化为JSON:

JAXBMarshaller.marshal(Object, OutputStream) line: 395

  • object Book (id=7076)
  • outputStream CommittingOutputStream (id=7124)

XMLMarshaller(XMLMarshaller<abstract_session,context,descriptor,media_type,namespace_prefix_mapper,object_builder style=”margin: 0px; padding: 0px;”>).marshal(Object, OutputStream, ABSTRACT_SESSION, DESCRIPTOR) line: 852

XMLMarshaller(XMLMarshaller<abstract_session,context,descriptor,media_type,namespace_prefix_mapper,object_builder style=”margin: 0px; padding: 0px;”>).marshal(Object, Writer, ABSTRACT_SESSION, DESCRIPTOR) line: 1031

  • object Book (id=7305)
  • writer OutputStreamWriter (id=7310)
    -> BufferedWriter (id=7333)

XMLMarshaller(XMLMarshaller<abstract_session,context,descriptor,media_type,namespace_prefix_mapper,object_builder style=”margin: 0px; padding: 0px;”>).marshal(Object, MarshalRecord, ABSTRACT_SESSION, DESCRIPTOR, boolean) line: 583

  • object Book (id=7305)
  • marshalRecord JSONWriterRecord (id=7342)
  • session DatabaseSessionImpl (id=7351)
  • descriptor XMLDescriptor (id=7353)
  • isXMLRoot false

几经辗转,写入对象变成了MarshalRecord。

JSONWriterRecord.startDocument(String, String) line: 171

TreeObjectBuilder.marshalAttributes(MarshalRecord, Object, CoreAbstractSession) line: 122

XPathObjectBuilder.marshalAttributes(MarshalRecord, Object, CoreAbstractSession) line: 552

这里是可以想见的流程…

ServerRuntime$Responder.processResponse(ContainerResponse) line: 362

最后是将response返回并释放。流程到此结束。

转自:http://feuyeux.iteye.com/blog/1938910

Jersey 过滤器

Jersey的过滤器分为:

ContainerRequestFilter: 请求阶段的过滤

ContainerResponseFilter: 响应阶段的过滤

一、使用注解名称绑定

1.1 创建绑定的注解

使用@NameBinding注解,可以定义一个运行时的自定义注解,该注解可以用于定义类级别名称和泪的方法。例如,我们定义一个用户访问的注解。

@NameBinding            //标识名称绑定的注解
@Target({ElementType.TYPE, ElementType.METHOD}) //表示该注解可以使用在类和方法上。
@Retention(value = RetentionPolicy.RUNTIME)
public @interface UserLogger {
}

上面代码我们定义了一个名称绑定注解UserLogger。

1.2 注解绑定过滤器

创建了注解之后,我们需要将注解和Jersey中的Provider 组件绑定,示例中我们使用的是过滤器。

@Provider
@UserLogger
@Priority(Priorities.USER)
public class LoggerFilter implements ContainerRequestFilter ,ContainerResponseFilter{
@Override
public void filter(ContainerRequestContext requestContext) throws IOException {
System.out.println("访问请求日志过滤器执行了>>>>>>>>>>>>");
}
@Override
public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) throws IOException {
System.out.println("访问响应日志过滤器执行了>>>>>>>>>>>");
}
}

@Provider 注解Jersey注解,Jersey扫描到该注解,就会创建对应的组件对象。 @UserLogger 就是我们自定义的名称绑定注解 @Priority 是用于表示该过滤器的执行顺序,其中参数为long类型,对于请求过滤器,该数值越小越先执行,响应过滤器则相反。 ContainerRequestFilter 为请求过滤器 ContainerResponseFilter 为响应过滤器 在请求和响应的过滤方法中,我们简单的打印输出。 需要注意的是,我们创建了过滤器之后需要在Jersey中进行声明,我们在Jerysey的ResourceConfig 子类中,注册该过滤器,注册有两种方式,一种是扫描包的形式,这时需要在过滤器上加上@Provider注解,另一种是直接注册该过滤器。

packages("com.xxxx.xxxx.xxxx.filter"); //扫描包的形式 过滤器所在的包

register(LoggerFilter.class); //直接注册过滤器

1.3 注解绑定接口

上面我们创建好注解和过滤器之后,需要将在我们需要使用过滤器的接口方法上使用注解。

@Path("/test")
public class TestResource {
@GET
@Path("/1")
public String test1(){
return "不带过滤器";
}
@GET
@Path("/2")
@UserLogger
public String test2(){
return "带过滤器";
}
}

我们创建了两个接口,一个路径是/test/1 没有使用过滤器,一个路径是/test/2使用注解,按照我们的设计,当访问./test/1时,日志过滤器不起作用,访问/test/2时日志过滤器起作用。

二、动态绑定

上面介绍的名称保定的形式需要通过自定义注解的形式来实现过滤器绑定,而动态绑定则不需要新增注解,而是需要编码的形式,实现动态绑定。动态绑定需要实现动态特征接口javax.ws.rs.container,DynamiFeature,定义扩展点方法,请求方法类型等匹配信息,在运行期,一旦Provider匹配到当前处理类或方法,面向切面的Provider方法就是触发。

2.1 实现动态绑定特征

public class LoggerDynaimcFeature implements DynamicFeature {
@Override
public void configure(ResourceInfo resourceInfo, FeatureContext context) {
String name = resourceInfo.getResourceMethod().getName();
if("test2".equals(name)){
context.register(LoggerFilter.class);
}
}
}

上面的代码实现的是当我们访问的是test2方法时,就会注册LoggerFilter,实现该过滤器的方法。    我们需要在同样我们需要注册该动态特征来    

  1. register(LoggerDynaimcFeature .class);

   这时我们重新启动项目时,分别访问test/1和test/2,就会看到只有test2时,日志过滤器才会起作用。    三. 名称绑定和动态绑定对比    动态绑定相比于名称绑定,不需要自定义注解,使用纯编码的形式实现。    名称绑定相比于动态绑定使用范围更广,因为我们使用注解的方式,可以对任意资源,任意方法进行控制。而使用动态绑定的形式,我们需要在动态特征类进行对应的匹配,适用范围较窄。

Jersey 方法拦截器

如果我们需要控制用户对接口的访问,例如登陆控制,权限控制等,就需要使用方法拦截器。
    由于Jersey中的AOP是基于HK2框架实现的,所以对应的拦截器的功能也是由HK2框架实现。
    现在我们模拟实现一个登陆拦截器的功能。
3.1 创建自定义注解
  1. @Documented
  2. @Target({ElementType.METHOD, ElementType.TYPE})
  3. @Retention(RetentionPolicy.RUNTIME)
  4. public @interface LoginTest {
  5. }
 
3.2 方法拦截器
  1. @LoginTest
  2. public class LoginTestMethodInterceptor implements MethodInterceptor {
  3. @Override
  4. public Object invoke(MethodInvocation methodInvocation) throws Throwable {
  5. return "没有权限访问";
  6. }
  7. }
    
3.3 方法拦截器的绑定
    Jersey在调用方法拦截器的时候,需要InterceptionService的实现。
   该接口中有三个方法,在执行对应的接口方法之前会调用getMethodInteceptors()方法,获取对应的拦截器,并执行拦截器。
public class JerseyInterceptor implements InterceptionService {
private static Map<Annotation, MethodInterceptor> map = new HashMap<>();
static{
Annotation[] annotations = LoginTestMethodInterceptor.class.getAnnotations();
for(Annotation annotation : annotations){
map.put(annotation, new LoginTestMethodInterceptor());
}
}
@Override
public Filter getDescriptorFilter() {
return new Filter() {
public boolean matches(Descriptor descriptor) {
return true;
}
};
}
@Override
public List<MethodInterceptor> getMethodInterceptors(Method method) {
Annotation[] annotations = method.getAnnotations();
List<MethodInterceptor> list = new ArrayList<>();
for (Annotation annotation :annotations){
if(map.get(annotation) != null){
list.add(map.get(annotation));
}
}
return list;
}
@Override
public List<ConstructorInterceptor> getConstructorInterceptors(Constructor<?> constructor) {
return null;
}
}
上面代码可以看到,我们是将注解与拦截器绑定,通过方法上的注解,获取方法对应的拦截器,并执行拦截器。 我们创建的拦截服务实现,需要与拦截服务进行绑定,这时需要AbstracctBinding对象的实现‘。
public class JerseyBinding extends AbstractBinder {
@Override
protected void configure() {
this.bind(JerseyInterceptor.class).to(InterceptionService.class).in(Singleton.class);
}
}

为了使绑定生效,我们需要定义特征类,用于注册该绑定

 
  1. public class JerseyFeature implements Feature {
  2. @Override
  3. public boolean configure(FeatureContext context) {
  4. context.register(new JerseyBinding());
  5. return true;
  6. }
  7. }
同样我们需要在ResourceConfig中注册该特征类
  1. register(JerseyFeature.class);
3,4 测试
    测试接口如下
 
  1. @Path("/test")
  2. public class TestResource {
  3. @Path("/1")
  4. @GET
  5. public String test1(){
  6. return "不带拦截器";
  7. }
  8. @Path("/2")
  9. @GET
  10. @LoginTest
  11. public String test2(){
  12. return "不带拦截器";
  13. }
  14. }
我们访问test/1的时候可以看到不带拦截器,访问test/2的时候可以看到没有访问权限