Process
Library version:
3.0.4 Library scope: | global Named arguments: | supported Introduction Robot Framework test library for running processes. This library utilizes Python's subprocess module and its Popen class. The library has following main usages: Running processes in system and waiting for their completion using Run Process keyword. Starting processes on background using Start Process . Waiting started process to complete using Wait For Process or stopping them with Terminate Process or Terminate All Processes . This library is new in Robot Framework 2.8. Table of contents Specifying command and arguments Process configuration Active process Result object Boolean arguments Example Shortcuts Keywords Specifying command and arguments Both Run Process and Start Process accept the command to execute and all arguments passed to the command as separate arguments. This makes usage convenient and also allows these keywords to automatically escape possible spaces and other special characters in commands and arguments. Notice that if a command accepts options that themselves accept values, these options and their values must be given as separate arguments. When running processes in shell , it is also possible to give the whole command to execute as a single string. The command can then contain multiple commands to be run together. When using this approach, the caller is responsible on escaping. Examples: Run Process | Run Process | Run Process ${tools}${/}prog.py | java | prog.py "one arg" && tool.sh argument | -jar | shell=yes second arg with spaces | ${jars}${/}example.jar | cwd=${tools} | --option | | value | Starting from Robot Framework 2.8.6, possible non-string arguments are converted to strings automatically. Process configuration Run Process and Start Process keywords can be configured using optional **configuration keyword arguments. Configuration arguments must be given after other arguments passed to these keywords and must use syntax like name=value . Available configuration arguments are listed below and discussed further in sections afterwards. Name Explanation shell | Specifies whether to run the command in shell or not. |
---|
cwd | Specifies the working directory. | env | Specifies environment variables given to the process. | env: | Overrides the named environment variable(s) only. | stdout | Path of a file where to write standard output. | stderr | Path of a file where to write standard error. output_encoding | alias Encoding to use when reading command outputs. | Alias given to the process. | Note that because **configuration is passed using name=value syntax, possible equal signs in other arguments passed to Run Process and Start Process must be escaped with a backslash like name\=value . See Run Process for an example. Running processes in shell The shell argument specifies whether to run the process in a shell or not. By default shell is not used, which means that shell specific commands, like copy and dir on Windows, are not available. You can, however, run shell scripts and batch files without using a shell. Giving the shell argument any non-false value, such as shell=True , changes the program to be executed in a shell. It allows using the shell capabilities, but can also make the process invocation operating system dependent. Having a shell between the actually started process and this library can also interfere communication with the process such as stopping it and reading its outputs. Because of these problems, it is recommended to use the shell only when absolutely necessary. When using a shell it is possible to give the whole command to execute as a single string. See Specifying command and arguments section for examples and more details in general. Current working directory By default the child process will be executed in the same directory as the parent process, the process running tests, is executed. This can be changed by giving an alternative location using the cwd argument. Forward slashes in the given path are automatically converted to backslashes on Windows. Standard output and error streams , when redirected to files, are also relative to the current working directory possibly set using the cwd argument. Example: Run Process prog.exe cwd=${ROOT}/directory stdout=stdout.txt Environment variables By default the child process will get a copy of the parent process's environment variables. The env argument can be used to give the child a custom environment as a Python dictionary. If there is a need to specify only certain environment variable, it is possible to use the env:= format to set or override only that named variables. It is also possible to use these two approaches together. Examples: Run Process | Run Process | Run Process program | program | program env=${environ} | env:http_proxy=10.144.1.10:8080 | env=${environ} | env:PATH=%{PATH}${:}${PROGDIR} | env:EXTRA=value Standard output and error streams By default processes are run so that their standard output and standard error streams are kept in the memory. This works fine normally, but if there is a lot of output, the output buffers may get full and the program can hang. Additionally on Jython, everything written to these in-memory buffers can be lost if the process is terminated. To avoid the above mentioned problems, it is possible to use stdout and stderr arguments to specify files on the file system where to redirect the outputs. This can also be useful if other processes or other keywords need to read or manipulate the outputs somehow. Given stdout and stderr paths are relative to the current working directory . Forward slashes in the given paths are automatically converted to backslashes on Windows. As a special feature, it is possible to redirect the standard error to the standard output by using stderr=STDOUT . Regardless are outputs redirected to files or not, they are accessible through the result object returned when the process ends. Commands are expected to write outputs using the console encoding, but output encoding can be configured using the output_encoding argument if needed. Examples: ${result} = | Log Many | ${result} = | Log Run Process | stdout: ${result.stdout} | Run Process | all output: ${result.stdout} program | stderr: ${result.stderr} | program | stdout=${TEMPDIR}/stdout.txt | | stderr=STDOUT | stderr=${TEMPDIR}/stderr.txt | | | Note that the created output files are not automatically removed after the test run. The user is responsible to remove them if needed. Output encoding Executed commands are, by default, expected to write outputs to the standard output and error streams using the encoding used by the system console. If the command uses some other encoding, that can be configured using the output_encoding argument. This is especially useful on Windows where the console uses a different encoding than rest of the system, and many commands use the general system encoding instead of the console encoding. The value used with the output_encoding argument must be a valid encoding and must match the encoding actually used by the command. As a convenience, it is possible to use strings CONSOLE and SYSTEM to specify that the console or system encoding is used, respectively. If produced outputs use different encoding then configured, values got through the result object will be invalid. Examples: Start Process | Run Process program | program output_encoding=UTF-8 | stdout=${path} | output_encoding=SYSTEM The support to set output encoding is new in Robot Framework 3.0. Alias A custom name given to the process that can be used when selecting the active process . Examples: Start Process | Run Process program | python alias=example | -c | print 'hello' | alias=hello Active process The test library keeps record which of the started processes is currently active. By default it is latest process started with Start Process , but Switch Process can be used to select a different one. Using Run Process does not affect the active process. The keywords that operate on started processes will use the active process by default, but it is possible to explicitly select a different process using the handle argument. The handle can be the identifier returned by Start Process or an alias explicitly given to Start Process or Run Process . Result object Run Process , Wait For Process and Terminate Process keywords return a result object that contains information about the process execution as its attributes. The same result object, or some of its attributes, can also be get using Get Process Result keyword. Attributes available in the object are documented in the table below. Attribute Explanation rc | Return code of the process as an integer. |
---|
stdout | Contents of the standard output stream. | stderr | Contents of the standard error stream. stdout_path | stderr_path Path where stdout was redirected or None if not redirected. | Path where stderr was redirected or None if not redirected. | Example: ${result} = | Run Process | program |
---|
Should Be Equal As Integers | ${result.rc} | 0 | Should Match | ${result.stdout} | Some t?xt* | Should Be Empty | ${result.stderr} | | ${stdout} = | Get File | ${result.stdout_path} Should Be Equal | File Should Be Empty ${stdout} | ${result.stderr_path} ${result.stdout} | | Boolean arguments Some keywords accept arguments that are handled as Boolean values true or false. If such an argument is given as a string, it is considered false if it is either an empty string or case-insensitively equal to false , none or no . Other strings are considered true regardless their value, and other argument types are tested using the same rules as in Python . True examples: Terminate Process | kill=True | # Strings are generally true. |
---|
Terminate Process | kill=yes | # Same as the above. Terminate Process | Terminate Process kill=${TRUE} | kill=${42} # Python True is true. | # Numbers other than 0 are true. | False examples: Terminate Process | kill=False | # String false is false. |
---|
Terminate Process | kill=no | # Also string no is false. Terminate Process | Terminate Process kill=${EMPTY} | kill=${FALSE} # Empty string is false. | # Python False is false. | Prior to Robot Framework 2.9, all non-empty strings, including false and no , were considered to be true. Considering none false is new in Robot Framework 3.0.3. Example *** Settings *** Library Process Suite Teardown Terminate All Processes kill=True *** Test Cases *** Example Start Process program arg1 arg2 alias=First ${handle} = Start Process command.sh arg | command2.sh shell=True cwd=/path ${result} = Run Process ${CURDIR}/script.py Should Not Contain ${result.stdout} FAIL Terminate Process ${handle} ${result} = Wait For Process First Should Be Equal As Integers ${result.rc} 0 Shortcuts Get Process Id · Get Process Object · Get Process Result · Is Process Running · Join Command Line · Process Should Be Running · Process Should Be Stopped · Run Process · Send Signal To Process · Split Command Line · Start Process · Switch Process · Terminate All Processes · Terminate Process · Wait For Process Keywords Keyword Arguments Documentation Get Process Id | handle=None | Returns the process ID (pid) of the process as an integer. If handle is not given, uses the current active process . Notice that the pid is not the same as the handle returned by Start Process that is used internally by this library. |
---|
Get Process Object | handle=None | Return the underlying subprocess.Popen object. If handle is not given, uses the current active process . | Get Process Result | handle=None, rc=False,stdout=False, stderr=False,stdout_path=False,stderr_path=False | Returns the specified result object or some of its attributes. The given handle specifies the process whose results should be returned. If no handle is given, results of the current active process are returned. In either case, the process must have been finishes before this keyword can be used. In practice this means that processes started with Start Process must be finished either with Wait For Process or Terminate Process before using this keyword. If no other arguments than the optional handle are given, a whole result object is returned. If one or more of the other arguments are given any true value, only the specified attributes of the result object are returned. These attributes are always returned in the same order as arguments are specified in the keyword signature. See Boolean arguments section for more details about true and false values. Examples: | Run Process | python | -c | print 'Hello, world!' | alias=myproc | | # Get result object | | | | | | ${result} = | Get Process Result | myproc | | | | Should Be Equal | ${result.rc} | ${0} | | | | Should Be Equal | ${result.stdout} | Hello, world! | | | | Should Be Empty | ${result.stderr} | | | | | # Get one attribute | | | | | | ${stdout} = | Get Process Result | myproc | stdout=true | | Should Be Equal | # Multiple attributes | ${stdout} | Should Be Equal | Should Be Empty ${stdout} | | ${stderr} = | ${stdout} | ${stderr} Hello, world! | | Get Process Result | Hello, world! | | | myproc | | | | stdout=yes | | | | stderr=yes | | | Although getting results of a previously executed process can be handy in general, the main use case for this keyword is returning results over the remote library interface. The remote interface does not support returning the whole result object, but individual attributes can be returned without problems. New in Robot Framework 2.8.2. Is Process Running | handle=None | Checks is the process running or not. If handle is not given, uses the current active process . Returns True if the process is still running and False otherwise. |
---|
Join Command Line | *args | Joins arguments into one command line string. In resulting command line string arguments are delimited with a space, arguments containing spaces are surrounded with quotes, and possible quotes are escaped with a backslash. If this keyword is given only one argument and that is a list like object, then the values of that list are joined instead. Example: | ${cmd} = | Should Be Equal Join Command Line | ${cmd} --option | --option "value with spaces" value with spaces | New in Robot Framework 2.9.2. Process Should Be Running | handle=None,error_message=Process is not running. | Verifies that the process is running. If handle is not given, uses the current active process . Fails if the process has stopped. |
---|
Process Should Be Stopped | handle=None,error_message=Process is running. | Verifies that the process is not running. If handle is not given, uses the current active process . Fails if the process is still running. | Run Process | command, *arguments,**configuration | Runs a process and waits for it to complete. command and *arguments specify the command to execute and arguments passed to it. See Specifying command and arguments for more details. **configuration contains additional configuration related to starting processes and waiting for them to finish. See Process configuration for more details about configuration related to starting processes. Configuration related to waiting for processes consists of timeout and on_timeout arguments that have same semantics as with Wait For Process keyword. By default there is no timeout, and if timeout is defined the default action on timeout is terminate . Returns a result object containing information about the execution. Note that possible equal signs in *arguments must be escaped with a backslash (e.g. name\=value ) to avoid them to be passed in as **configuration . Examples: | ${result} = | Run Process | python | -c | print 'Hello, world!' Should Be Equal | ${result} = | ${result} = | ${result} = ${result.stdout} | Run Process | Run Process | Run Process Hello, world! | ${command} | ${command} | java -Dname\=value Example | stderr=STDOUT | timeout=1min | shell=True | timeout=10s | on_timeout=continue | cwd=${EXAMPLE} | This keyword does not change the active process . timeout and on_timeout arguments are new in Robot Framework 2.8.4. Send Signal To Process | signal, handle=None,group=False | Sends the given signal to the specified process. If handle is not given, uses the current active process . Signal can be specified either as an integer as a signal name. In the latter case it is possible to give the name both with or without SIG prefix, but names are case-sensitive. For example, all the examples below send signal INT (2) : |
---|
Send Signal To Process | Send Signal To Process | Send Signal To Process 2 | INT | SIGINT | | myproc # Send to active process | | # Send to named process This keyword is only supported on Unix-like machines, not on Windows. What signals are supported depends on the system. For a list of existing signals on your system, see the Unix man pages related to signal handling (typically man signal or man 7 signal ). By default sends the signal only to the parent process, not to possible child processes started by it. Notice that when running processes in shell , the shell is the parent process and it depends on the system does the shell propagate the signal to the actual started process. To send the signal to the whole process group, group argument can be set to any true value (see Boolean arguments ). This is not supported by Jython, however. New in Robot Framework 2.8.2. Support for group argument is new in Robot Framework 2.8.5. Split Command Line | args, escaping=False | Splits command line string into a list of arguments. String is split from spaces, but argument surrounded in quotes may contain spaces in them. If escaping is given a true value, then backslash is treated as an escape character. It can escape unquoted spaces, quotes inside quotes, and so on, but it also requires using double backslashes when using Windows paths. Examples: |
---|
@{cmd} = | Should Be True Split Command Line | $cmd == ['--option', 'value with spaces'] --option "value with spaces" | New in Robot Framework 2.9.2. Start Process | command, *arguments,**configuration | Starts a new process on background. See Specifying command and arguments and Process configuration for more information about the arguments, and Run Process keyword for related examples. Makes the started process new active process . Returns an identifier that can be used as a handle to activate the started process if needed. Starting from Robot Framework 2.8.5, processes are started so that they create a new process group. This allows sending signals to and terminating also possible child processes. This is not supported by Jython in general nor by Python versions prior to 2.7 on Windows. |
---|
Switch Process | handle | Makes the specified process the current active process . The handle can be an identifier returned by Start Process or the alias given to it explicitly. Example: | Start Process | prog1 | alias=process1 | Start Process | prog2 | alias=process2 | # currently active process is process2 | | Switch Process | # now active process is process1 process1 | | |
| Terminate All Processes | kill=False | Terminates all still running processes started by this library. This keyword can be used in suite teardown or elsewhere to make sure that all processes are stopped, By default tries to terminate processes gracefully, but can be configured to forcefully kill them immediately. See Terminate Process that this keyword uses internally for more details. Terminate Process | Wait For Process handle=None, kill=False | handle=None, timeout=None,on_timeout=continue Stops the process gracefully or forcefully. If handle is not given, uses the current active process . By default first tries to stop the process gracefully. If the process does not stop in 30 seconds, or kill argument is given a true value, (see Boolean arguments ) kills the process forcefully. Stops also all the child processes of the originally started process. Waits for the process to stop after terminating it. Returns a result object containing information about the execution similarly as Wait For Process . On Unix-like machines graceful termination is done using TERM (15) signal and killing using KILL (9) . Use Send Signal To Process instead if you just want to send either of these signals without waiting for the process to stop. On Windows graceful termination is done using CTRL_BREAK_EVENT event and killing using Win32 API function TerminateProcess() . Examples: ${result} = | Should Be Equal As Integers | Terminate Process Terminate Process | ${result.rc} | myproc | -15 | kill=true | # On Unixes | Limitations: Graceful termination is not supported on Windows by Jython nor by Python versions prior to 2.7. Process is killed instead. Stopping the whole process group is not supported by Jython at all nor by Python versions prior to 2.7 on Windows. On Windows forceful kill only stops the main process, not possible child processes. Automatically killing the process if termination fails as well as returning a result object are new features in Robot Framework 2.8.2. Terminating also possible child processes, including using CTRL_BREAK_EVENT on Windows, is new in Robot Framework 2.8.5. | Waits for the process to complete or to reach the given timeout. The process to wait for must have been started earlier with Start Process . If handle is not given, uses the current active process . timeout defines the maximum time to wait for the process. It can be given in various time formats supported by Robot Framework, for example, 42 , 42 s , or 1 minute 30 seconds . on_timeout defines what to do if the timeout occurs. Possible values and corresponding actions are explained in the table below. Notice that reaching the timeout never fails the test. Value Action continue | The process is left running (default). terminate | kill The process is gracefully terminated. | The process is forcefully stopped. |
---|
See Terminate Process keyword for more details how processes are terminated and killed. If the process ends before the timeout or it is terminated or killed, this keyword returns a result object containing information about the execution. If the process is left running, Python None is returned instead. Examples: # Process ends cleanly | | | |
---|
${result} = | Wait For Process | example | | Process Should Be Stopped | example | | | Should Be Equal As Integers | ${result.rc} | 0 | | # Process does not end | | | | ${result} = | Wait For Process | timeout=42 secs | | Process Should Be Running | | | | Should Be Equal | ${result} | ${NONE} | | # Kill non-ending process | | | ${result} = | Process Should Be Stopped | Should Be Equal As Integers Wait For Process | | ${result.rc} timeout=1min 30s | | -9 on_timeout=kill | | | timeout and on_timeout are new in Robot Framework 2.8.2. Altogether 15 keywords. Generated by Libdoc on 2018-04-25 23:41:29. 处理 图书馆版本: | 3.0.4 图书馆范围: | 全球 命名参数: | 支持的 介绍 用于运行进程的Robot Framework测试库。 该库使用Python的 子进程 模块及其 Popen 类。 该图书馆有以下主要用途: 使用 Run Process 关键字在系统中运行进程并等待其完成。 使用“ 启动过程” 在后台 启动进程 。 等待启动过程以完成使用 等待进程 或使用 终止进程 或 终止所有进程 停止它们。 这个库是Robot Framework 2.8中的新增功能。 目录 指定命令和参数 流程配置 积极的过程 结果对象 布尔参数 例 快捷键 关键词 指定命令和参数 无论 运行过程 和 启动过程 接受执行命令,并传递给命令作为独立参数,所有参数。这使得使用方便,并且还允许这些关键字自动转义命令和参数中的可能空格和其他特殊字符。请注意,如果命令接受自己接受值的选项,则必须将这些选项及其值作为单独的参数给出。 当 运行在壳进程 ,它也可以给整个命令来执行作为单个字符串。然后,该命令可以包含多个要一起运行的命令。使用此方法时,调用者负责转义。 例子: 运行流程 | 运行流程 | 运行流程 $ {工具} $ {/} prog.py | java的 | prog.py“one arg”&& tool.sh 论据 | -罐 | 壳= YES 第二个arg有空格 | $ {罐} $ {/} example.jar | CWD = $ {}工具 | - 选项 | | 值 | 从Robot Framework 2.8.6开始,可能的非字符串参数会自动转换为字符串。 流程配置 可以使用可选的关键字参数配置 Run Process 和 Start Process 关键字 **configuration 。必须在传递给这些关键字的其他参数之后给出配置参数,并且必须使用类似语法 name=value 。下面列出了可用的配置参数,然后在后面的章节中进一步讨论。 名称 说明 贝壳 | 指定是否在shell中运行命令。 |
---|
CWD | 指定工作目录。 | ENV | 指定为进程指定的环境变量。 | ENV:<名称> | 仅覆盖指定的环境变量。 | 标准输出 | 文件的路径写入标准输出的位置。 | 标准错误 | 文件的路径在哪里写标准错误。 output_encoding | 别号 在读取命令输出时使用的编码。 | 别名给予该过程。 | 请注意,因为 **configuration 使用 name=value 语法传递,所以传递给 Run Process 和 Start Process的 其他参数中的可能等号必须使用反斜杠转义 name\=value 。有关示例,请参阅 运行过程 。 在shell中运行进程 该 shell 参数指定是否在外壳或无法运行的过程。默认情况下,外壳没有使用,这意味着外壳特定命令,就像 copy 和 dir 在Windows,不可用。但是,您可以在不使用shell的情况下运行shell脚本和批处理文件。 为 shell 参数赋予任何非假值,例如 shell=True ,更改要在shell中执行的程序。它允许使用shell功能,但也可以使进程调用操作系统依赖。在实际启动的进程和此库之间具有shell也可能干扰与进程的通信,例如停止它并读取其输出。由于这些问题,建议仅在绝对必要时才使用shell。 使用shell时,可以将整个命令作为单个字符串执行。有关示例和更多详细信息,请参阅 指定命令和参数 部分。 当前的工作目录 默认情况下,子进程将在与父进程相同的目录中执行,执行运行测试的进程。这可以通过使用 cwd 参数提供替代位置来更改。在给定路径中的正斜杠自动转换为Windows上的反斜杠。 重定向到文件时, 标准输出和错误流 也与可能使用 cwd 参数设置的当前工作目录相关。 例: 运行流程 prog.exe CWD = $ {ROOT} /目录 标准输出= stdout.txt 环境变量 默认情况下,子进程将获取父进程的环境变量的副本。该 env 参数可用于为子项提供自定义环境作为Python字典。如果只需要指定某个环境变量,则可以使用该 env:= 格式来设置或仅覆盖该命名变量。也可以将这两种方法结合使用。 例子: 运行流程 | 运行流程 | 运行流程 程序 | 程序 | 程序 ENV = $ {} ENVIRON | ENV:HTTP_PROXY = 10.144.1.10:8080 | ENV = $ {} ENVIRON | ENV:PATH =%{PATH} $ {:} $ {} PROGDIR | ENV:EXTRA =值 标准输出和错误流 默认情况下,运行进程以使其标准输出和标准错误流保留在内存中。这通常工作正常,但如果有很多输出,输出缓冲区可能会满,程序可能会挂起。此外,在Jython上,如果进程终止,写入这些内存缓冲区的所有内容都可能会丢失。 为了避免上述问题,可以使用 stdout 和 stderr 参数来指定文件系统上重定向输出的文件。如果其他进程或其他关键字需要以某种方式读取或操作输出,这也很有用。 给定 stdout 和 stderr 路径相对于 当前工作目录 。在给定路径中的正斜杠自动转换为Windows上的反斜杠。 作为一项特殊功能,可以使用标准错误将标准错误重定向到标准输出 stderr=STDOUT 。 无论是否重定向到文件的输出,都可以通过进程结束时返回的 结果对象 访问它们。命令期望使用控制台编码写入输出,但是如果需要,可以使用参数配置 输出编码 output_encoding 。 例子: $ {result} = | 记录很多 | $ {result} = | 日志 运行流程 | stdout:$ {result.stdout} | 运行流程 | 所有输出:$ {result.stdout} 程序 | stderr:$ {result.stderr} | 程序 | 标准输出= $ {TEMPDIR} /stdout.txt | | 标准错误= STDOUT | 标准错误= $ {TEMPDIR} /stderr.txt | | | 请注意,测试运行后不会自动删除创建的输出文件。如果需要,用户有责任将其删除。 输出编码 默认情况下,执行的命令希望使用系统控制台使用的编码将输出写入 标准输出和错误流 。如果该命令使用某些其他编码,则可以使用该 output_encoding 参数进行配置。这在Windows上使用与系统其他部分不同的编码时非常有用,并且许多命令使用通用系统编码而不是控制台编码。 与 output_encoding 参数一起使用的值必须是有效编码,并且必须与命令实际使用的编码匹配。为方便起见,可以使用字符串 CONSOLE 并 SYSTEM 分别指定使用控制台或系统编码。如果生成的输出使用随后配置的不同编码,则通过 结果对象 获得的值将无效。 例子: 开始流程 | 运行流程 程序 | 程序 output_encoding = UTF-8 | 标准输出= $ {路径} | output_encoding = SYSTEM 设置输出编码的支持是Robot Framework 3.0中的新增功能。 别号 选择 活动进程 时可以使用的自定义名称。 例子: 开始流程 | 运行流程 程序 | 蟒蛇 别名=示例 | -C | 打印'你好' | 别名=你好 积极的过程 测试库记录哪些已启动的进程当前处于活动状态。默认情况下,它是使用 Start Process启动的 最新 流程 ,但 Switch Process 可用于选择其他 流程 。使用 “运行进程 ”不会影响活动进程。 默认情况下,对已启动进程执行操作的关键字将使用活动进程,但可以使用该 handle 参数显式选择其他进程。该手柄可以是该标识符由返回 开始处理 或 alias 显式地提供给 启动过程 或 运行过程 。 结果对象 “运行进程” ,“ 等待进程” 和“ 终止进程” 关键字返回一个结果对象,该对象包含有关进程执行的信息作为其属性。使用 Get Process Result 关键字也可以获得相同的结果对象或其某些属性。对象中可用的属性记录在下表中。 属性 说明 RC | 将进程的代码作为整数返回。 |
---|
标准输出 | 标准输出流的内容。 | 标准错误 | 标准错误流的内容。 stdout_path | stderr_path stdout被重定向或未重定向的路径 None 。 | 重定向stderr或未重定向的路径 None 。 | 例: $ {result} = | 运行流程 | 程序 |
---|
应该与整数相等 | $ {} result.rc | 0 | 应该匹配 | $ {} result.stdout | 一些t?xt * | 应该是空的 | $ {} result.stderr | | $ {stdout} = | 获取文件 | $ {} result.stdout_path 应该是平等的 | 文件应该是空的 $ {}标准输出 | $ {} result.stderr_path $ {} result.stdout | | 布尔参数 某些关键字接受以布尔值true或false处理的参数。如果这样的参数以字符串形式给出,则如果它是空字符串或不区分大小写,则被视为false false , none 或 no 。无论其值如何,其他字符串都被视为true,其他参数类型使用与 Python 相同的 规则 进行测试。 真实的例子: 终止流程 | 杀=真 | #字符串通常是正确的。 |
---|
终止流程 | 杀= YES | #与上述相同。 终止流程 | 终止流程 杀= $ {TRUE} | 杀= $ {42} #Pcthon True 是真的。 | #0以外的数字为真。 | 错误的例子: 终止流程 | 杀=假 | #String false 为false。 |
---|
终止流程 | 杀=无 | #字符串 no 也是false。 终止流程 | 终止流程 杀= $ {EMPTY} | 杀= $ {FALSE} #Empty字符串为false。 | #Python False 是假的。 | 在Robot Framework 2.9之前,所有非空字符串(包括 false 和 no )都被认为是真的。 none 在Robot Framework 3.0.3中考虑false是新的。 例 *** 设置 *** 库进程 套件拆解 终止所有进程 kill = True *** 测试用例 *** 示例 启动进程 程序arg1 arg2 alias = First $ {handle} = 启动进程 command.sh arg | command2.sh shell = True cwd = / path $ {result} = 运行流程 $ {CURDIR} /script.py 不应包含 $ {result.stdout} FAIL 终止流程 $ {handle} $ {result} = 等待流程 优先 应该与整数相等 $ {result.rc} 0 快捷键 获取进程ID · 获取进程对象 · 获取进程结果 · 进程正在运行 · 加入命令行 · 进程应该运行 · 进程应该停止 · 运行进程 · 发送信号进行处理 · 拆分命令行 · 启动进程 · 切换进程 · 终止所有进程 · 终止进程 · 等待进程 关键词 关键词 参数 文档 获取进程ID | 手柄=无 | 以整数形式返回进程的进程ID(pid)。 如果 handle 未给出,则使用当前 活动进程 。 请注意,pid与此库内部使用的 Start Process 返回的句柄不同。 |
---|
获取流程对象 | 手柄=无 | 返回底层 subprocess.Popen 对象。 如果 handle 未给出,则使用当前 活动进程 。 | 获取流程结果 | handle = None , rc = False , stdout = False , stderr = False , stdout_path = False , stderr_path = False | 返回指定的 结果对象 或其某些属性。 给定 handle 指定应返回其结果的进程。如果不 handle 给出,则返回当前 活动进程的 结果。在任何一种情况下,都必须在使用此关键字之前完成该过程。实际上,这意味着在使用此关键字之前,必须使用“ 等待进程” 或“ 终止进程” 完成以“ 启动进程”启动的进程 。 如果没有 handle 给出除可选之外的其他参数,则返回整个 结果对象 。如果一个或多个其他参数被赋予任何真值,则仅返回 结果对象 的指定属性。始终以与关键字签名中指定的参数相同的顺序返回这些属性。有关true和false值的更多详细信息,请参阅 布尔参数 部分。 例子: | 运行流程 | 蟒蛇 | -C | 打印'你好,世界!' | 别名= myproc的 | | #获取结果对象 | | | | | | $ {result} = | 获取流程结果 | 在myproc | | | | 应该是平等的 | $ {} result.rc | $ {0} | | | | 应该是平等的 | $ {} result.stdout | 你好,世界! | | | | 应该是空的 | $ {} result.stderr | | | | | #获取一个属性 | | | | | | $ {stdout} = | 获取流程结果 | 在myproc | 标准输出=真 | | 应该是平等的 | #多个属性 | $ {}标准输出 | 应该是平等的 | 应该是空的 $ {}标准输出 | | $ {stderr} = | $ {}标准输出 | $ {}标准错误 你好,世界! | | 获取流程结果 | 你好,世界! | | | 在myproc | | | | 标准输出= YES | | | | 标准错误= YES | | | 虽然获取先前执行的进程的结果通常很方便,但此关键字的主要用例是通过远程库接口返回结果。远程接口不支持返回整个结果对象,但可以毫无问题地返回各个属性。 机器人框架2.8.2中的新功能。 流程正在运行 | 手柄=无 | 检查是否正在运行。 如果 handle 未给出,则使用当前 活动进程 。 True 如果进程仍在运行, False 则返回。 |
---|
加入命令行 | * ARGS | 将参数连接到一个命令行字符串。 在结果命令行中,字符串参数用空格分隔,包含空格的参数用引号括起来,可能的引号用反斜杠转义。 如果此关键字只给出一个参数,并且是一个像object这样的列表,那么将加入该列表的值。 例: | $ {cmd} = | 应该是平等的 加入命令行 | $ {cmd},在 - 选项 | - 选择“带空格的价值” 空格的价值 | Robot Framework 2.9.2中的新功能。 流程应该运行 | handle = None , error_message =进程未运行。 | 验证进程是否正在运行。 如果 handle 未给出,则使用当前 活动进程 。 如果进程已停止,则会失败。 |
---|
流程应该停止 | handle = None , error_message =进程正在运行。 | 验证进程未运行。 如果 handle 未给出,则使用当前 活动进程 。 如果进程仍在运行,则会失败。 | 运行流程 | 命令 , *参数 , **配置 | 运行一个进程并等待它完成。 command 并 *arguments 指定要执行的命令和传递给它的参数。有关更多详细信息,请参阅 指定命令和参数 。 **configuration 包含与启动进程和等待它们完成相关的其他配置。有关与启动进程相关的 配置 的详细信息,请参阅 进程配置 。有关等待进程配置包括 timeout 与 on_timeout 具有相同的语义与参数 等待过程 的关键字。默认情况下没有超时,如果定义了超时,则超时的默认操作为 terminate 。 返回包含有关执行信息的 结果对象 。 请注意, *arguments 必须使用反斜杠(例如 name\=value )转义可能的等号,以避免它们被传入 **configuration 。 例子: | $ {result} = | 运行流程 | 蟒蛇 | -C | 打印'你好,世界!' 应该是平等的 | $ {result} = | $ {result} = | $ {result} = $ {} result.stdout | 运行流程 | 运行流程 | 运行流程 你好,世界! | $ {}命令 | $ {}命令 | java -Dname \ = value示例 | 标准错误= STDOUT | 超时= 1分钟 | 壳=真 | 超时= 10S | on_timeout =继续 | CWD = $ {实施例} | 此关键字不会更改 活动进程 。 timeout 和 on_timeout 参数是Robot Framework 2.8.4中的新功能。 发送信号进行处理 | signal , handle = None , group = False | 将给定发送 signal 到指定的进程。 如果 handle 未给出,则使用当前 活动进程 。 可以将信号指定为整数作为信号名称。在后一种情况下,可以给出带或不带 SIG 前缀的名称,但名称区分大小写。例如,以下所有示例都发送信号 INT (2) : |
---|
发送信号进行处理 | 发送信号进行处理 | 发送信号进行处理 2 | INT | SIGINT | | 在myproc #发送到活动进程 | | #发送到命名进程 此关键字仅在类Unix机器上支持,而不在Windows上支持。支持哪些信号取决于系统。有关系统上现有信号的列表,请参阅与信号处理相关的Unix手册页(通常 man signal 或 man 7 signal )。 默认情况下,仅将信号发送到父进程,而不是发送给它启动的子进程。请注意, 在shell中运行进程时 ,shell是父进程,它取决于系统shell是否将信号传播到实际启动的进程。 要将信号发送到整个进程组, group 可以将参数设置为任何真值(请参阅 布尔参数 )。但是,Jython不支持此功能。 机器人框架2.8.2中的新功能。支持 group 参数是Robot Framework 2.8.5中的新功能。 拆分命令行 | args , escaping = False | 将命令行字符串拆分为参数列表。 字符串从空格中拆分,但用引号括起来的参数可能包含空格。如果 escaping 给出一个真值,则反斜杠被视为转义字符。它可以转义不带引号的空格,引号内的引号等,但在使用Windows路径时也需要使用双反斜杠。 例子: |
---|
@ {cmd} = | 应该是真的 拆分命令行 | $ cmd == [' - 选项','带空格的值'] - 选择“带空格的价值” | Robot Framework 2.9.2中的新功能。 开始流程 | 命令 , *参数 , **配置 | 在后台启动新流程。 有关 参数 的详细信息,请参阅 指定命令和参数 以及 进程配置 ;有关相关示例,请参阅 Run Process 关键字。 使启动的进程成为新的 活动进程 。返回一个标识符,该标识符可用作句柄以在需要时激活已启动的进程。 从Robot Framework 2.8.5开始,启动进程以便创建新的进程组。这允许向可能的子进程发送信号并终止它们。Jython一般也不支持这种方法,也不支持Windows上2.7之前的Python版本。 |
---|
切换过程 | 处理 | 使指定的进程成为当前 活动进程 。 该手柄可以是由返回的标识符 开始处理 或 alias 明确地给它。 例: | 开始流程 | PROG1 | 别名=过程1 | 开始流程 | PROG2 | 别名=过程2 | #当前活动的进程是process2 | | 切换过程 | #now active进程是process1 过程1 | | |
| 终止所有进程 | 杀=假 | 终止此库启动的所有仍在运行的进程。 此关键字可以在套件拆解或其他地方使用,以确保所有进程都已停止, 默认情况下尝试正常终止进程,但可以配置为立即强制终止它们。有关详细信息,请参阅此关键字在内部使用的 终止进程 。 终止流程 | 等待过程 handle = None , kill = False | handle = None , timeout = None , on_timeout = continue 优雅地或强制地停止该过程。 如果 handle 未给出,则使用当前 活动进程 。 默认情况下,首先尝试正常停止该过程。如果进程在30秒内没有停止,或者 kill 参数被赋予真值,则(参见 布尔参数 )强制终止进程。停止最初启动的进程的所有子进程。 在终止进程后等待进程停止。返回一个 结果对象, 其中包含与 Wait For Process 类似的执行信息。 在类似Unix的机器上,使用 TERM (15) 信号和kill进行优雅终止 KILL (9) 。如果您只想发送这些信号中的任何一个而不等待进程停止,请使用 发送信号来处理 。 在Windows上 CTRL_BREAK_EVENT ,使用Win32 API函数使用事件和查杀完成正常终止 TerminateProcess() 。 例子: $ {result} = | 应该与整数相等 | 终止流程 终止流程 | $ {} result.rc | 在myproc | -15 | 杀=真 | #在Unix上 | 限制: Windows上不支持Jython的优雅终止,也不支持2.7之前的Python版本。进程被杀死了。 Jython根本不支持停止整个进程组,也不支持Windows上2.7之前的Python版本。 在Windows上强制kill只会停止主进程,而不是子进程。 如果终止失败以及返回结果对象,则自动终止进程是Robot Framework 2.8.2中的新功能。终止可能的子进程,包括 CTRL_BREAK_EVENT 在Windows上使用,是Robot Framework 2.8.5中的新功能。 | 等待进程完成或达到给定的超时。 必须先使用“ 启动过程” 启动等待 过程 。如果 handle 未给出,则使用当前 活动进程 。 timeout 定义等待进程的最长时间。它可以被赋予 不同的时间格式 由机器人框架的支持,例如 42 , 42 s 或 1 minute 30 seconds 。 on_timeout 定义超时发生时要执行的操作。可能的值和相应的操作在下表中说明。请注意,达到超时永远不会失败测试。 值 行动 继续 | 该过程保持运行(默认)。 终止 | 杀 该过程优雅地终止。 | 该过程被强制停止。 |
---|
有关如何终止和 终止进程 的详细信息,请参阅 Terminate Process 关键字。 如果进程在超时之前结束或者终止或终止,则此关键字返回包含有关执行信息的 结果对象 。如果进程保持运行, None 则返回Python 。 例子: #流程彻底结束 | | | |
---|
$ {result} = | 等待过程 | 例 | | 流程应该停止 | 例 | | | 应该与整数相等 | $ {} result.rc | 0 | | #进程没有结束 | | | | $ {result} = | 等待过程 | 超时= 42秒 | | 流程应该运行 | | | | 应该是平等的 | $ {}结果 | $ {无} | | #终止不结束的过程 | | | $ {result} = | 流程应该停止 | 应该与整数相等 等待过程 | | $ {} result.rc 超时= 1分30秒 | | -9 on_timeout =杀 | | | timeout 并且 on_timeout 是Robot Framework 2.8.2中的新功能。 共有15个关键字。 由 Libdoc 于2018-04-25 23:41:29 生成。 第一:Redis 是什么? Redis是基于内存、可持久化的日志型、Key-Value数据库 高性能存储系统,并提供多种语言的API. 第二:出现背景 数据结构(Data Structure)需求越来越多, 但memcache中没有, 影响开发效率 性能需求, 随着读操作的量的上升需要解决,经历的过程有: 数据库读写分离(M/S)–>数据库使用多个Slave–>增加Cache (memcache)–>转到Redis 解决写的问题: 水平拆分,对表的拆分,将有的用户放在这个表,有的用户放在另外一个表; 可靠性需求 Cache的"雪崩"问题让人纠结 Cache面临着快速恢复的挑战 开发成本需求 Cache和DB的一致性维护成本越来越高(先清理DB, 再清理缓存, 不行啊, 太慢了!) 开发需要跟上不断涌入的产品需求 硬件成本最贵的就是数据库层面的机器,基本上比前端的机器要贵几倍,主要是IO密集型,很耗硬件; 维护性复杂 一致性维护成本越来越高; BerkeleyDB使用B树,会一直写新的,内部不会有文件重新组织;这样会导致文件越来越大;大的时候需要进行文件归档,归档的操作要定期做; 这样,就需要有一定的down time; 基于以上考虑, 选择了Redis 第三:Redis 在新浪微博中的应用 Redis简介 1. 支持5种数据结构 支持strings, hashes, lists, sets, sorted sets string是很好的存储方式,用来做计数存储。sets用于建立索引库非常棒; 2. K-V 存储 vs K-V 缓存 新浪微博目前使用的98%都是持久化的应用,2%的是缓存,用到了600+服务器 Redis中持久化的应用和非持久化的方式不会差别很大: 非持久化的为8-9万tps,那么持久化在7-8万tps左右; 当使用持久化时,需要考虑到持久化和写性能的配比,也就是要考虑redis使用的内存大小和硬盘写的速率的比例计算; 3. 社区活跃 Redis目前有3万多行代码, 代码写的精简,有很多巧妙的实现,作者有技术洁癖 Redis的社区活跃度很高,这是衡量开源软件质量的重要指标,开源软件的初期一般都没有商业技术服务支持,如果没有活跃社区做支撑,一旦发生问题都无处求救; Redis基本原理 redis持久化(aof) append online file: 写log(aof), 到一定程度再和内存合并. 追加再追加, 顺序写磁盘, 对性能影响非常小 1. 单实例单进程 Redis使用的是单进程,所以在配置时,一个实例只会用到一个CPU; 在配置时,如果需要让CPU使用率最大化,可以配置Redis实例数对应CPU数, Redis实例数对应端口数(8核Cpu, 8个实例, 8个端口), 以提高并发: 单机测试时, 单条数据在200字节, 测试的结果为8~9万tps; 2. Replication 过程: 数据写到master–>master存储到slave的rdb中–>slave加载rdb到内存。 存储点(save point): 当网络中断了, 连上之后, 继续传. Master-slave下第一次同步是全传,后面是增量同步;、 3. 数据一致性 长期运行后多个结点之间存在不一致的可能性; 开发两个工具程序: 1.对于数据量大的数据,会周期性的全量检查; 2.实时的检查增量数据,是否具有一致性; 对于主库未及时同步从库导致的不一致,称之为延时问题; 对于一致性要求不是那么严格的场景,我们只需要要保证最终一致性即可; 对于延时问题,需要根据业务场景特点分析,从应用层面增加策略来解决这个问题; 例如: 1.新注册的用户,必须先查询主库; 2.注册成功之后,需要等待3s之后跳转,后台此时就是在做数据同步。 第四:分布式缓存的架构设计 1.架构设计 由于redis是单点,项目中需要使用,必须自己实现分布式。基本架构图如下所示: 2.分布式实现 通过key做一致性哈希,实现key对应redis结点的分布。 一致性哈希的实现: hash值计算:通过支持MD5与MurmurHash两种计算方式,默认是采用MurmurHash,高效的hash计算。 一致性的实现:通过java的TreeMap来模拟环状结构,实现均匀分布 3.client的选择 对于jedis修改的主要是分区模块的修改,使其支持了跟据BufferKey进行分区,跟据不同的redis结点信息,可以初始化不同的 ShardInfo,同时也修改了JedisPool的底层实现,使其连接pool池支持跟据key,value的构造方法,跟据不同 ShardInfos,创建不同的jedis连接客户端,达到分区的效果,供应用层调用 4.模块的说明 脏数据处理模块,处理失败执行的缓存操作。 屏蔽监控模块,对于jedis操作的异常监控,当某结点出现异常可控制redis结点的切除等操作。 整个分布式模块通过hornetq,来切除异常redis结点。对于新结点的增加,也可以通过reload方法实现增加。(此模块对于新增结点也可以很方便实现)。 对于以上分布式架构的实现满足了项目的需求。另外使用中对于一些比较重要用途的缓存数据可以单独设置一些redis结点,设定特定的优先级。另外对 于缓存接口的设计,也可以跟据需求,实现基本接口与一些特殊逻辑接口。对于cas相关操作,以及一些事物操作可以通过其watch机制来实现。 RobotFramework之Collections 背景 继续学习RobotFramework,这次看的是Collections的源码。 Collections库是RobotFramework用来处理列表和字典的库, 官方文档 是这么介绍的 A test library providing keywords for handling lists and dictionaries. Append To List 示例代码 *** Settings *** Library Collections *** Test Cases *** TestCase001 ${l1} create list a b append to list ${l1} 1 2 log ${l1} 执行结果: KEYWORD BuiltIn . Log ${l1} Documentation: Logs the given message with the given level. Start / End / Elapsed: 20170422 19:11:52.624 / 20170422 19:11:52.624 / 00:00:00.000 19:11:52.624 INFO [u'a', u'b', u'1', u'2'] 源代码 def append_to_list(self, list_, *values): for value in values: list_.append(value) Combine List 示例代码 *** Settings *** Library Collections *** Test Cases *** TestCase001 ${l1} create list a b ${l2} create list 1 2 ${l} combine lists ${l1} ${l2} log ${l} 执行结果 KEYWORD ${l} = Collections . Combine Lists ${l1}, ${l2} Documentation: Combines the given lists together and returns the result. Start / End / Elapsed: 20170422 19:15:52.086 / 20170422 19:15:52.087 / 00:00:00.001 19:15:52.087 INFO ${l} = [u'a', u'b', u'1', u'2'] 源代码 def combine_lists(self, *lists): ret = [] for item in lists: ret.extend(item) return ret 说明 源代码中的extend方法和append方法是不一样的,append方法是追加一个元素,extend方法是追加一个列表的内容,如果用append方法,则结果会变成如下情况: l1 = [1,2,3] l2 = [4,5,6] l1.append(l2) # ==>[1,2,3,[4,5,6]] l1.extend(l2) # ==>[1,2,3,,4,5,6] 所以合并列表的时候使用 extend 而不是 append Count Values In List 计算某一个值在列表中重复的次数 示例 *** Settings *** Library Collections *** Test Cases *** TestCase001 ${l1} create list 1 2 3 4 3 ${count} count values in list ${l1} 3 log ${count} 执行结果 KEYWORD ${int} = Collections . Count Values In List ${l1}, 3 Documentation: Returns the number of occurrences of the given value in list. Start / End / Elapsed: 20170422 19:30:33.807 / 20170422 19:30:33.807 / 00:00:00.000 19:30:33.807 INFO ${int} = 2 源代码 def count_values_in_list(self, list_, value, start=0, end=None): return self.get_slice_from_list(list_, start, end).count(value) def get_slice_from_list(self, list_, start=0, end=None): start = self._index_to_int(start, True) if end is not None: end = self._index_to_int(end) return list_[start:end] def _index_to_int(self, index, empty_to_zero=False): if empty_to_zero and not index: return 0 try: return int(index) except ValueError: raise ValueError("Cannot convert index '%s' to an integer." % index) 说明 这里方法是默认从第一位开始计算,如果有需要,可以输入一个区间,比如指定起始位置和结束位置,比如如下代码 *** Settings *** Library Collections *** Test Cases *** TestCase001 ${l1} create list 1 2 3 4 3 3 ${count} count values in list ${l1} 3 3 5 log ${count} 执行结果就是1 Dictionaries Should Be Equal 示例代码 *** Settings *** Library Collections *** Test Cases *** TestCase001 ${dict1} create dictionary a=1 b=2 ${dict2} create dictionary a=1 b=3 dictionaries should be equal ${dict1} ${dict2} 执行结果 KEYWORD Collections . Dictionaries Should Be Equal ${dict1}, ${dict2} Documentation: Fails if the given dictionaries are not equal. Start / End / Elapsed: 20170422 19:49:36.865 / 20170422 19:49:36.866 / 00:00:00.001 19:49:36.866 FAIL Following keys have different values: Key b: 2 != 3 源代码 def dictionaries_should_be_equal(self, dict1, dict2, msg=None, values=True): keys = self._keys_should_be_equal(dict1, dict2, msg, values) self._key_values_should_be_equal(keys, dict1, dict2, msg, values) def _keys_should_be_equal(self, dict1, dict2, msg, values): keys1 = self.get_dictionary_keys(dict1) keys2 = self.get_dictionary_keys(dict2) miss1 = [unic(k) for k in keys2 if k not in dict1] miss2 = [unic(k) for k in keys1 if k not in dict2] error = [] if miss1: error += ['Following keys missing from first dictionary: %s' % ', '.join(miss1)] if miss2: error += ['Following keys missing from second dictionary: %s' % ', '.join(miss2)] _verify_condition(not error, '\n'.join(error), msg, values) return keys1 def _key_values_should_be_equal(self, keys, dict1, dict2, msg, values): diffs = list(self._yield_dict_diffs(keys, dict1, dict2)) default = 'Following keys have different values:\n' + '\n'.join(diffs) _verify_condition(not diffs, default, msg, values) def _yield_dict_diffs(self, keys, dict1, dict2): for key in keys: try: assert_equal(dict1[key], dict2[key], msg='Key %s' % (key,)) except AssertionError as err: yield unic(err) 说明 本来Python中比较两个字典是否相等只要用 == 就行了,这里为了能够精确的报错,遍历了所有的key和value来做比较。 Dictionary Should Contain Item 示例代码 *** Settings *** Library Collections *** Test Cases *** TestCase001 ${dict1} create dictionary a=1 b=2 dictionary should contain item ${dict1} a 1 结果 KEYWORD Collections . Dictionary Should Contain Item ${dict1}, a, 1 Documentation: An item of key``/``value must be found in a `dictionary`. Start / End / Elapsed: 20170422 19:58:37.086 / 20170422 19:58:37.087 / 00:00:00.001 源代码 def dictionary_should_contain_item(self, dictionary, key, value, msg=None): self.dictionary_should_contain_key(dictionary, key, msg) actual, expected = unic(dictionary[key]), unic(value) default = "Value of dictionary key '%s' does not match: %s != %s" % (key, actual, expected) _verify_condition(actual == expected, default, msg) def dictionary_should_contain_key(self, dictionary, key, msg=None): default = "Dictionary does not contain key '%s'." % key _verify_condition(key in dictionary, default, msg) 说明 判断一个k-v是否在一个字典里,同样是通过遍历原有字典的方式来处理,只不过这里传值必须是按照示例代码中那样传递,不能传入a=1或者一个字典。 Dictionary Should Contain Sub Dictionary 示例 *** Settings *** Library Collections *** Test Cases *** TestCase001 ${dict1} create dictionary a=1 b=2 ${dict2} create dictionary a=1 Dictionary Should Contain Sub Dictionary ${dict1} ${dict2} 结果 KEYWORD Collections . Dictionary Should Contain Sub Dictionary ${dict1}, ${dict2} Documentation: Fails unless all items in dict2 are found from dict1. Start / End / Elapsed: 20170422 20:09:41.864 / 20170422 20:09:41.865 / 00:00:00.001 源代码 def dictionary_should_contain_sub_dictionary(self, dict1, dict2, msg=None, values=True): keys = self.get_dictionary_keys(dict2) diffs = [unic(k) for k in keys if k not in dict1] default = "Following keys missing from first dictionary: %s" \ % ', '.join(diffs) _verify_condition(not diffs, default, msg, values) self._key_values_should_be_equal(keys, dict1, dict2, msg, values) 说明 上一个方法只能传入单一的k-v,使用的局限性比较大,这个方法就可以支持传入一个字典 Get Dictionary Items 示例 *** Settings *** Library Collections *** Test Cases *** TestCase001 ${dict1} create dictionary a=1 b=2 ${dict2} create dictionary a=1 ${dict3} Get Dictionary Items ${dict1} 执行结果 KEYWORD ${dict3} = Collections . Get Dictionary Items ${dict1} Documentation: Returns items of the given dictionary. Start / End / Elapsed: 20170424 00:39:49.766 / 20170424 00:39:49.766 / 00:00:00.000 00:39:49.766 INFO ${dict3} = [u'a', u'1', u'b', u'2'] 源代码 def get_dictionary_items(self, dictionary): ret = [] for key in self.get_dictionary_keys(dictionary): ret.extend((key, dictionary[key])) return ret 说明 这个方法就是把字典里面的成员以列表的形式返回出来,具体应用场景还不是很确定 Get Dictionary Values 示例代码 *** Settings *** Library Collections *** Test Cases *** test1 ${dict} create dictionary a=1 b=2 ${rst} get from dictionary ${dict} a log ${rst} 执行结果 Documentation: Logs the given message with the given level. Start / End / Elapsed: 20170428 19:45:59.199 / 20170428 19:45:59.200 / 00:00:00.001 19:45:59.200 INFO 1 源代码 def get_from_dictionary(self, dictionary, key): try: return dictionary[key] except KeyError: raise RuntimeError("Dictionary does not contain key '%s'." % key) 说明 源代码其实非常简单,传入一个字典和一个key,然后就返回这个key对应的值 Get From List 示例代码 *** Settings *** Library Collections *** Test Cases *** test1 ${lst} create list 1 2 3 ${new_lst} get from list ${lst} 0 log ${new_lst} 执行结果 KEYWORD BuiltIn . Log ${new_lst} Documentation: Logs the given message with the given level. Start / End / Elapsed: 20170428 19:50:42.533 / 20170428 19:50:42.533 / 00:00:00.000 19:50:42.533 INFO 1 源代码 def get_from_list(self, list_, index): try: return list_[self._index_to_int(index)] except IndexError: self._index_error(list_, index) 说明 执行方法就是获取列表和索引,然后返回列表的索引,值得一提的是,不论你传入的值是str类型还是int类型,都会被 python 代码转成int类型,所以源代码中 ${new_lst} get from list ${lst} 0 这样写,执行结果也是一样的。附上转换的源代码。 def _index_to_int(self, index, empty_to_zero=False): if empty_to_zero and not index: return 0 try: return int(index) except ValueError: raise ValueError("Cannot convert index '%s' to an integer." % index) Get Index From List 示例代码 *** Settings *** Library Collections *** Test Cases *** test1 ${lst} create list 1 2 3 4 5 4 ${rst} get index from list ${lst} 3 log ${rst} 执行结果 KEYWORD BuiltIn . Log ${rst} Documentation: Logs the given message with the given level. Start / End / Elapsed: 20170428 19:59:05.532 / 20170428 19:59:05.532 / 00:00:00.000 19:59:05.532 INFO 2 源代码 def get_index_from_list(self, list_, value, start=0, end=None): if start == '': start = 0 list_ = self.get_slice_from_list(list_, start, end) try: return int(start) + list_.index(value) except ValueError: return -1 说明 源代码中的 self.get_slice_from_list 方法是一个切片方法,在 Get Index From List 中还可以传入两个参数, start 表示列表的开始索引, end 表示列表结束的索引,如果没有给,默认就是整个列表来取索引结果。 Get Match Count 示例代码 *** Settings *** Library Collections *** Test Cases *** test1 ${count} get match count aaabbcc a log ${count} 执行结果 KEYWORD BuiltIn . Log ${count} Documentation: Logs the given message with the given level. Start / End / Elapsed: 20170428 20:08:08.699 / 20170428 20:08:08.700 / 00:00:00.001 20:08:08.700 INFO 3 源代码 def get_match_count(self, list, pattern, case_insensitive=False, whitespace_insensitive=False): return len(self.get_matches(list, pattern, case_insensitive, whitespace_insensitive)) def _get_matches_in_iterable(iterable, pattern, case_insensitive=False, whitespace_insensitive=False): if not is_string(pattern): raise TypeError("Pattern must be string, got '%s'." % type_name(pattern)) regexp = False if pattern.startswith('regexp='): pattern = pattern[7:] regexp = True elif pattern.startswith('glob='): pattern = pattern[5:] matcher = Matcher(pattern, caseless=is_truthy(case_insensitive), spaceless=is_truthy(whitespace_insensitive), regexp=regexp) return [string for string in iterable if is_string(string) and matcher.match(string)] 说明 这个方法是返回一个字符在字符串中重复的次数。 源代码中的第二个方法才是真正的方法, Get Match Count 方法是非常强大的,可以做普通匹配,也可以做正则匹配,首先,传入的必须是字符串,否则报错,然后判定传入的条件是否带有正则,如果传入的条件是 regexp= 开头的,表示要使用正则匹配,否则就是普通查找。 第三个参数表示是否忽略大小写,第四个参数表示是否忽略空格。 Get Matchs 此方法与上面一个方法调用的是一样的底层方法,请参考上面的内容。 Get Slice From List 示例代码 *** Settings *** Library Collections *** Test Cases *** test1 ${lst} create list 1 2 3 4 5 6 7 8 ${slice_list} get slice from list ${lst} 3 6 log ${slice_list} 执行结果 KEYWORD BuiltIn . Log ${slice_list} Documentation: Logs the given message with the given level. Start / End / Elapsed: 20170428 20:20:00.780 / 20170428 20:20:00.780 / 00:00:00.000 20:20:00.780 INFO [u'4', u'5', u'6'] 源代码 def get_slice_from_list(self, list_, start=0, end=None): start = self._index_to_int(start, True) if end is not None: end = self._index_to_int(end) return list_[start:end] 说明 源代码还是比较容易看懂的,如果开始和结束不传值,那么就是返回原来的列表,否则就返回切片的列表。 Insert into list 示例代码 *** Settings *** Library Collections *** Test Cases *** test1 ${lst} create list 1 2 3 4 5 6 7 8 insert into list ${lst} 0 a log ${lst} 执行结果 KEYWORD BuiltIn . Log ${lst} Documentation: Logs the given message with the given level. Start / End / Elapsed: 20170428 20:27:02.736 / 20170428 20:27:02.737 / 00:00:00.001 20:27:02.737 INFO [u'a', u'1', u'2', u'3', u'4', u'5', u'6', u'7', u'8'] 源代码 def insert_into_list(self, list_, index, value): list_.insert(self._index_to_int(index), value) 说明 调用的就是 Python 中列表的 insert 方法。如果传入的索引大于列表的长度,那么值会默认插入到列表的最后一位,如果传入的是负数,比如传入-1,那么就会在列表的倒数第二位加入该元素。 根据实现的 Python 代码可以知道,传入的索引是int或者str类型都可以,实现的时候会强制转为int类型。 关于插入索引大于列表长度的问题,可以参考以下代码 Python 2.7.10 (default, Feb 6 2017, 23:53:20) [GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.34)] on darwin Type "help", "copyright", "credits" or "license" for more information. >>> a = [1,2,3,4,5] >>> a [1, 2, 3, 4, 5] >>> a.insert(9, 'a') >>> a [1, 2, 3, 4, 5, 'a'] Keep In Dictionary 示例代码 *** Settings *** Library Collections *** Test Cases *** test1 ${dict} create dictionary a=1 b=2 c=3 d=4 keep in dictionary ${dict} a b log ${dict} 执行结果 KEYWORD BuiltIn . Log ${dict} Documentation: Logs the given message with the given level. Start / End / Elapsed: 20170428 20:47:47.528 / 20170428 20:47:47.529 / 00:00:00.001 20:47:47.529 INFO {u'a': u'1', u'b': u'2'} 源代码 def keep_in_dictionary(self, dictionary, *keys): remove_keys = [k for k in dictionary if k not in keys] self.remove_from_dictionary(dictionary, *remove_keys) def remove_from_dictionary(self, dictionary, *keys): for key in keys: if key in dictionary: value = dictionary.pop(key) logger.info("Removed item with key '%s' and value '%s'." % (key, value)) else: logger.info("Key '%s' not found." % key) 说明 该方法是用来保留字典里指定的 key ,源代码也比较好理解,不过此处的注释有问题, Keeps the given keys in the dictionary and removes all other. If the given key cannot be found from the dictionary, it is ignored. 第二句是说明如果传入的 key 如果不在这个字典里,那么这个操作就会被忽略。但是实际上我们可以看到代码执行时并不是这样的,第一个方法会把字典中与传入的 keys 不匹配的 key 全部拿出来,调用第二个方法来全部从字典里移除,也就是说传入的key如果在字典里不存在,那么原字典就会变为一个空字典。 实际上的执行结果就是这样 KEYWORD ${dict} = BuiltIn . Create Dictionary a=1, b=2, c=3, d=4 00:00:00.001KEYWORD Collections . Keep In Dictionary ${dict}, e Documentation: Keeps the given keys in the dictionary and removes all other. Start / End / Elapsed: 20170428 20:52:04.051 / 20170428 20:52:04.052 / 00:00:00.001 20:52:04.052 INFO Removed item with key 'a' and value '1'. 20:52:04.052 INFO Removed item with key 'b' and value '2'. 20:52:04.052 INFO Removed item with key 'c' and value '3'. 20:52:04.052 INFO Removed item with key 'd' and value '4'. 00:00:00.000KEYWORD BuiltIn . Log ${dict} Documentation: Logs the given message with the given level. Start / End / Elapsed: 20170428 20:52:04.052 / 20170428 20:52:04.052 / 00:00:00.000 20:52:04.052 INFO {} List Should Contain Sub List 示例代码 *** Settings *** Library Collections *** Test Cases *** test1 ${lst1} create list 1 2 3 4 ${lst2} create list 2 3 list should contain sub list ${lst1} ${lst2} 执行结果 KEYWORD Collections . List Should Contain Sub List ${lst1}, ${lst2} Documentation: Fails if not all of the elements in list2 are found in list1. 源代码 def list_should_contain_sub_list(self, list1, list2, msg=None, values=True): diffs = ', '.join(unic(item) for item in list2 if item not in list1) default = 'Following values were not found from first list: ' + diffs _verify_condition(not diffs, default, msg, values) List Should Not Contain Duplicates 示例代码 *** Settings *** Library Collections *** Test Cases *** test1 ${lst1} create list 1 2 3 4 2 list should not contain duplicates ${lst1} 2 执行结果 KEYWORD Collections . List Should Not Contain Duplicates ${lst1}, 2 Documentation: Fails if any element in the list is found from it more than once. Start / End / Elapsed: 20170428 21:08:59.719 / 20170428 21:08:59.720 / 00:00:00.001 21:08:59.719 INFO '2' found 2 times. 21:08:59.720 FAIL 2 源代码 def list_should_not_contain_duplicates(self, list_, msg=None): if not isinstance(list_, list): list_ = list(list_) dupes = [] for item in list_: if item not in dupes: count = list_.count(item) if count > 1: logger.info("'%s' found %d times." % (item, count)) dupes.append(item) if dupes: raise AssertionError(msg or '%s found multiple times.' % seq2str(dupes)) 说明 该方法用于断言某个元素在列表中只会出现一次,如果出现多次则报错。 Pop From Dictionary 示例代码 *** Settings *** Library Collections *** Test Cases *** test1 ${dict} create dictionary a=1 b=2 c=3 ${newdict} pop from dictionary ${dict} a log ${newdict} log ${dict} 执行结果 KEYWORD BuiltIn . Log ${dict} Documentation: Logs the given message with the given level. Start / End / Elapsed: 20170428 21:21:05.523 / 20170428 21:21:05.523 / 00:00:00.000 21:21:05.523 INFO {u'b': u'2', u'c': u'3'} 源代码 def pop_from_dictionary(self, dictionary, key, default=NOT_SET): if default is NOT_SET: self.dictionary_should_contain_key(dictionary, key) return dictionary.pop(key) return dictionary.pop(key, default) 说明 这里的方法有一个可选参数 default ,是用来处理不存在的键值,如果pop的键值在字典里不存在,那么框架是会报错处理的,但是如果给了一个default,那么传入的键值不存在时,会返回 default 的值。 Remove Duplicates 示例代码 *** Settings *** Library Collections *** Test Cases *** test1 ${lst} create list 1 2 3 4 1 2 ${rst} remove duplicates ${lst} 执行结果 KEYWORD ${rst} = Collections . Remove Duplicates ${lst} Documentation: Returns a list without duplicates based on the given list. Start / End / Elapsed: 20170428 21:52:22.522 / 20170428 21:52:22.522 / 00:00:00.000 21:52:22.522 INFO 2 duplicates removed. 21:52:22.522 INFO ${rst} = [u'1', u'2', u'3', u'4'] 源代码 def remove_duplicates(self, list_): ret = [] for item in list_: if item not in ret: ret.append(item) removed = len(list_) - len(ret) logger.info('%d duplicate%s removed.' % (removed, plural_or_not(removed))) return ret 说明 该方法是一个除重方法,可以吧列表中重复的元素移除。 Sort List 示例代码 *** Settings *** Library Collections *** Test Cases *** test1 ${lst} create list 1 2 3 4 1 2 and dib sort list ${lst} log ${lst} 执行结果 KEYWORD BuiltIn . Log ${lst} Documentation: Logs the given message with the given level. Start / End / Elapsed: 20170428 22:13:57.288 / 20170428 22:13:57.288 / 00:00:00.000 22:13:57.288 INFO [u'1', u'1', u'2', u'2', u'3', u'4', u'and', u'dib'] 源代码 def sort_list(self, list_): list_.sort() 说明 该方法调用了列表的 sort 方法,需要注意的是,如果原始列表还需要使用的话,最好先保留一份,否则原始数据就会被破坏。 Note that the given list is changed and nothing is returned. Use Copy List first, if you need to keep also the original order. 总结 Collections 库是一个非常简单的库,不过里面包含了大部分的字典和列表的操作,本文仅仅覆盖了一些关键字,还有一些重复的,或者一眼就能看出来怎么用的关键字我就没有写了。通过这些示例代码,基本上能够了解Robot Framework框架的使用方法,也能够对 Robot Framework 框架的编写方式和编写思想有了一定的认识。 缘起 写多了业务代码,一些遗留系统里处于基本没有单测的状态,因此最近对 TDD 的开发方式很感兴趣,看了不少 TDD 介绍和实践的书。 TDD 对测试的执行次数有很高的要求,但是平常在 idea 里面写代码运行测试所需时间较长,需要先 build 才能启动测试,这就成了我们践行 TDD 的障碍,我们需要一款对测试友好的插件来帮助我们。 infinitest 插件可以让我们免除手动执行测试的尴尬,它检测文件的 class 变动,当文件有变动时,可以自动执行测试,开箱即用。infinitest 也可以使用简单的配置,让我们把 infinitest 和 Junit 相关特性结合起来,更显方便 安装与使用 安装很简单: Settings -> Plugins -> Browse repositories -> 查找infinitest -> Install -> Restart idea 添加到项目中: Project Structure(ctrl+shift+alt+s) -> Modules -> 添加infinitest idea 默认不会自动 compile文件,我们需要开启一下 Settings -> Build, Execution, Deployment -> Compiler -> Build project automatically 打上勾 如果项目中只有简单的测试,这样就可以了,如果我们需要区分不同的测试,我们需要自己添加文件来配置一下。 配置 总的文档目录 infinitest docs filters 这个配置可以让我们指定需要执行哪些测试,不执行哪些测试,支持 Junit4 的 category 和 Junit5 的 tags 添加配置文件 自己新建一个 infinitest.filters , 将文件放在 .iml 同级目录下即可。 通过 class name,支持正则表达式 # Include tests that end in ITest or TestCase include .*ITest, .*TestCase # Exclude Inner Classes exclude .*\$.* # Include tests in package com.mycompany.mypackage and sub-packages include com\.mycompany\.mypackage\..* # Include tests in package com.mycompany.mypackage and not in sub-packages include com\.mycompany\.mypackage\.[^.]* 通过 Junit5 的 tags # Include tests with "Fast" and "Regression" tags includeGroups Fast, Regression # Exclude tests with "Slow" tag excludeGroups Slow 通过 Junit4 的 category # Include tests in FastTests and RegressionTests categories includeGroups com.example.FastTests, com.example.RegressionTests # Exclude SlowTests category excludeGroups com.example.SlowTests testNg 框架相关的不做介绍,具体可以参见 infinitest filters JVM options 新建 infinitest.args 文件,位置与filters的一样,里面每一行是一个虚拟机参数,这个配置会覆盖 infinitest 的默认配置 最后 大家如果遇到什么问题可以先 Google 一下,如果没有找到答案,可以在 GitHub 上自行提问。 @Autowired DataSource ds; connection = ds.getConnection(); tring dbName = connection.getCatalog(); connection.close(); 命令模式: 将“请求”封装成对象,以便使用不同的请求、队列或者日志来参数化其他对象。命令模式也支持可撤销的操作。 四种角色: Command:定义命令的统一接口 ConcreteCommand:Command接口的实现者,用来执行具体的命令,某些情况下可以直接用来充当Receiver。 Receiver:命令的实际执行者 Invoker:命令的请求者,是命令模式中最重要的角色。这个角色用来对各个命令进行控制。 一个命令对象通过在特定接收者上绑定一组动作来封装一个请求。命令对象将动作和接收者包进对象中。这个对象只暴露出一个execute()方法,当此方法被调用的时候,接收者就会进行这些动作。从外面来看,其他队形不知道究竟哪个接收者进行了哪些动作,只知道如果调用execute()方法,请求的目的就能达到。 举个栗子: 要实现一个智能开关的遥控器,可以控制客厅和卧室的灯、电视的开关等功能。 定义命令接口: public interface Command { public void execute(); } 实现灯的开关命令: // 开灯命令 public class LightOnCommand implements Command { Light light; public LightOnCommand(Light light){ this.light = light; } public void execute() { light.on(); } } // 关灯命令 public class LightOffCommand implements Command { Light light; public LightOffCommand(Light light){ this.light = light; } public void execute() { light.off(); } } 遥控器: // 遥控器有七个开关,初始化都是无命令的 public class RemoteControl { Command[] onCommands; Command[] offCommands; // 初始化遥控器 public RemoteControl() { onCommands = new Command[7]; offCommands = new Command[7]; Command noCommand = new NoCommand(); for (int i = 0; i < 7; i++) { onCommands[i] = noCommand; offCommands[i] = noCommand; } } // 配置按钮对应的命令 public void setCommand(int index, Command onCommand, Command offCommand) { onCommands[index] = onCommand; offCommands[index] = offCommand; } // 按下开按钮 public void onButtonWasPushed(int index) { onCommands[index].execute(); } // 按下关按钮 public void offButtonWasPushed(int index) { onCommands[index].execute(); } } 测试遥控器: public class RemoteTest { RemoteControl remoteControl = new RemoteControl(); Light livingRoomLight = new Light("Living Room"); Light kitchenLight = new Light("kitchen"); // 设置遥控器按钮命令 LightOnCommand livingRoomLightOnCommand = new LightOnCommand(livingRoomLight); LightOffCommand livingRoomLightOffCommand = new LightOffCommand(livingRoomLight); LightOnCommand kitchenLightOnCommand = new LightOnCommand(kitchenLight); LightOffCommand kitchenLightOffCommand = new LightOffCommand(kitchenLight); remoteControl.setCommand(0, livingRoomLightOnCommand, livingRoomLightOffCommand); remoteControl.setCommand(1, kitchenLightOnCommand, kitchenLightOffCommand); // 卧室灯开关操作 remoteControl.onButtonWasPushed(0); remoteControl.offButtonWasPushed(0); // 厨房灯开关操作 remoteControl.onButtonWasPushed(1); remoteControl.offButtonWasPushed(1); }
1. 架构概述 J2EE体系包括java server pages(JSP) ,java SERVLET, enterprise bean,WEB service等技术。这些技术的出现给电子商务时代的WEB应用程序的开发提供了一个非常有竞争力的选择。怎样把这些技术组合起来形成一个适应项目需要的稳定架构是项目开发过程中一个非常重要的步骤。完成这个步骤可以形成一个主要里程碑基线。形成这个基线有很多好处: 各种因数初步确定 为了形成架构基线,架构设计师要对平台(体系)中的技术进行筛选,各种利弊的权衡。往往架构设计师在这个过程中要阅读大量的技术资料,听取项目组成员的建议,考虑领域专家的需求,考虑赞助商成本(包括开发成本和运行维护成本)限额。一旦架构设计经过评审,这些因数初步地就有了在整个项目过程中的对项目起多大作用的定位。 定向技术培训 一旦架构师设计的架构得到了批准形成了基线,项目开发和运行所采用的技术基本确定下来了。众多的项目经理都会对预备项目组成员的技术功底感到担心;他们需要培训部门提供培训,但就架构师面对的技术海洋,项目经理根本就提不出明确的技术培训需求。怎不能够对体系中所有技术都进行培训吧!有了架构里程碑基线,项目经理能确定这个项目开发会采用什么技术,这是提出培训需求应该是最精确的。不过在实际项目开发中,技术培训可以在基线确定之前与架构设计并发进行。 角色分工 有了一个好的架构蓝图,我们就能准确划分工作。如网页设计,JSP 标签处理类设计,SERVLET 设计,session bean设计,还有各种实现。这些任务在架构蓝图上都可以清晰地标出位置,使得项目组成员能很好地定位自己的任务。一个好的架构蓝图同时也能规范化任务,能很好地把任务划分为几类,在同一类中的任务的工作量和性质相同或相似。这样工作量估计起来有一个非常好的基础。 运行维护 前面说过各个任务在架构图上都有比较好的定位。任何人能借助它很快地熟悉整个项目的运行情况,错误出现时能比较快速地定位错误点。另外,有了清晰的架构图,项目版本管理也有很好的版本树躯干。 扩展性 架构犹如一颗参天大树的躯干,只要躯干根系牢,树干粗,长一些旁支,加一些树叶轻而易举无疑。同样,有一个稳定的经得起考验的架构,增加一两个业务组件是非常快速和容易的。 大家都知道这些好处,一心想形成一个这样的J2EE应用程序架构(就像在windows平台中的MFC)。在这个路程中经历了两个大的阶段: 1.1. 模型1 模型1其实不是一个什么稳定架构,甚至谈不上形成了架构。模型1的基础是JSP文件。它从HTTP的请求中提取参数,调用相应的业务逻辑,处理HTTP会话,最后生成HTTP文档。一系列这样的JSP文件形成一个完整的模型1应用,当然可能会有其他辅助类或文件。早期的ASP 和 PHP 技术就属于这个情况。 总的看来,这个模型的好处是简单,但是它把业务逻辑和表现混在一块,对大应用来说,这个缺点是令人容忍不了的。 1.2. 模型2 在经过一番实践,并广泛借鉴和总结经验教训之后,J2EE应用程序终于迎来了MVC(模型-视图-控制)模式。MVC模式并不是J2EE行业人士标新立异的,所以前面我谈到广发借鉴。MVC的核心就是做到三层甚至多层的松散耦合。这对基于组件的,所覆盖的技术不断膨胀的J2EE体系来说真是福音和救星。 它在浏览器(本文对客户代理都称浏览器)和JSP或SERVLET之间插入一个控制组件。这个控制组件集中了处理浏览器发过来的HTTP请求的分发逻辑,也就是说,它会根据HTTP请求的URL,输入参数,和目前应用的内部状态,把请求分发给相应的WEB 层的JSP 或SERVLET。另外它也负责选择下一个视图(在J2EE中,JSP,SERVLET会生成回给浏览器的html从而形成视图)。集中的控制组件也有利于安全验证,日志纪录,有时也封装请求数据给下面的WEB tier层。这一套逻辑的实现形成了一个像MFC的应用框架,位置如图:
1.3. 多层应用 下图为J2EE体系中典型的多层应用模型。 Client tier客户层 一般为浏览器或其他应用。客户层普遍地支持HTTP协议,也称客户代理。 WEB tier WEB应用层 在J2EE中,这一层由WEB 容器运行,它包括JSP, SERVLET等WEB部件。 EJB tier 企业组件层 企业组件层由EJB容器运行,支持EJB, JMS, JTA 等服务和技术。 EIS tier 企业信息系统层 企业信息系统包含企业内传统信息系统如财务,CRM等,特点是有数据库系统的支持。
应用框架目前主要集中在WEB层,旨在规范这一层软件的开发。其实企业组件层也可以实现这个模型,但目前主要以设计模式的形式存在。而且有些框架可以扩充,有了企业组件层组件的参与,框架会显得更紧凑,更自然,效率会更高。 2. 候选方案 目前,实现模型2的框架也在不断的涌现,下面列出比较有名的框架。 2.1. Apache Struts Struts是一个免费的开源的WEB层的应用框架,apache软件基金致力于struts的开发。Struts具是高可配置的性,和有一个不断增长的特性列表。一个前端控制组件,一系列动作类,动作映射,处理XML的实用工具类,服务器端java bean 的自动填充,支持验证的WEB 表单,国际化支持,生成HTML,实现表现逻辑和模版组成了struts的灵魂。 2.1.1. Struts和MVC 模型2的目的和MVC的目的是一样的,所以模型2基本可以和MVC等同起来。下图体现了Struts的运作机理:
2.1.1.1. 控制 如图所示,它的主要部件是一个通用的控制组件。这个控制组件提供了处理所有发送到Struts 的HTTP请求的入口点。它截取和分发这些请求到相应的动作类(这些动作类都是Action类的子类)。另外控制组件也负责用相应的请求参数填充 From bean,并传给动作类。动作类实现核心商业逻辑,它可以通过访问java bean 或调用EJB。最后动作类把控制权传给后续的JSP 文件,后者生成视图。所有这些控制逻辑利用一个叫struts-config.xml文件来配置。 2.1.1.2. 模型 模型以一个或几个java bean的形式存在。这些bean分为三种: Form beans(表单Beans) 它保存了HTTP post请求传来的数据,在Struts里,所有的Form beans都是 ActionFrom 类的子类。 业务逻辑beans 专门用来处理业务逻辑。 系统状态beans 它保存了跨越多个HTTP 请求的单个客户的会话信息,还有系统状态。 2.1.1.3. 视图 控制组件续传HTTP请求给实现了视图的JSP文件。JSP能访问beans 并生成结果文档反馈到客户。Struts提供JSP 标签库: Html,Bean,Logic,Template等来达到这个目的,并有利于分开表现逻辑和程序逻辑。 2.1.2. Struts的细节分析 2.1.2.1. 视图-控制-模型 用户发出一个*.do的HTTP请求,控制组件接收到这个请求后,查找针对这个请求的动作映射,再检查是否曾创建过相应的动作对象(action实例),如果没有则调用actionmapping生成一个动作对象,控制组件会保存这个动作对象供以后使用。接着调用actionmapping的方法得到actionForm对象。之后把actionForm作为参数传给动作对象的perform方法,这个方法结束之后会返回给控制组件一个 actionforward对象。控制组件接着从这个对象中获取下一个视图的路径和重定向属性。如果为重定向则调用HTTPSERVLETREPONSE的方法来显示下一个视图,否则相继调用requestdispatcher, SERVLETcontext的方法续传HTTP请求到下一个视图。 当动作对象运行perform方法时,可能出现错误信息。动作对象可以保存这些错误信息到一个error对象中,接着调用自身的saveerrors方法把这个错误保存到request对象的属性中。接着动作对象调用actionmapping对象的getInput方法从动作映射中获取input参数,也就是产生输入的视图,并以这个input为参数生成一个actionforward对象返回。这个input参数的JSP中一般有HTTP:errors定制标签读取这些错误信息并显示在页面上。
2.1.2.2. 模型到视图 模型到视图指视图在显示之前装载系统数据到视图的过程。系统数据一般为模型内java bean的信息。示意图表现了由控制组件forward过来的有html:form定制标签的JSP 的处理逻辑。 html:form定制标签处理对象从application scope(通过查询SERVLETCONTEXT对象的属性来实现)获取先前由控制组件actionSERVLET放在那里的动作映射等对象,由html:form 的action属性查得actionform名字、类型和范围等信息,在相应的范围内查找actionform,如果有则利用它的信息填充html form表单[实际填充动作在嵌套的html:text等定制标签的处理对象中]。否则在相应范围内创建一个actionform 对象。 Confluence 并不能比较容易的对外部站点进行搜索,这个是因为 Confluence 使用的是 Lucene 内部查找,但是你还是有下面 2 个可选的方案: 嵌入外部页面到 Confluence 替换 Confluence 查找 嵌入外部页面到 Confluence 如果你有少量的外部页面内容需要你的 Confluence 站点进行索引,你最好可以启用 HTML Include Macro 宏,使用这个宏将外部页面嵌入到你的 Confluence 页面中。 替换 Confluence 查找 如果你有足够的技术力量,你可以将 Confluence 的内部搜索用 crawler 进行替换,这样你可以用 crawler 搜索你的 Confluence 站点和外部站点。这个是 Confluence 提供的一个高级选项,相对于对 Confluence 的内部搜索进行修改来说,这个选项更加容易进行操作。这个要求删除你 Confluence 内部索引中的所有页面,将你 Confluence 的内部索引结果替换为你自己的 crawler 前段。 设置和替换你的联合查询来查询 Confluence 站点和你使用的其他站点,同时提供结果。你需要配置 open-source crawlers 服务器。注意,你可以通过 Confluence API 对 Confluence 进行查询。 通过 Customizing Site and Space Layouts 来参考内部的搜索,这样可以将查询的前端连接到你的 Confluence 中。 使用其他服务器提供查找的前端。你可能需要在你的应用服务器上插入合适的上下文路径,这样能够在 Confluence 中显示正确的路径。Tomcat 针对 Confluence 上下文设置的路径是在 install\confluence\WEBINF\web.xml 文件中定义的。 https://www.cwiki.us/display/CONF6ZH/Setting+Up+Confluence+to+Index+External+Sites 官网下载:toolbox jetbrains公司的软件管理工具 http://www.jetbrains.com/ 安装 IntelliJ IDEA 启动IntelliJ IDEA 激活:http://active.chinapyg.com/ https://blog.csdn.net/u014236541/article/details/79851531 有用的的插件:https://blog.csdn.net/lgd_guangdong/article/details/80062049 快捷键:https://blog.csdn.net/troy__/article/details/52145446 注释模版:https://blog.csdn.net/zwj1030711290/article/details/80673482 IntelliJ 主题:http://www.riaway.com/ 默认配置:https://blog.csdn.net/wo541075754/article/details/70154604 发布:https://blog.csdn.net/yanjiangdi/article/details/77864610?locationNum=4&fps=1 补充快捷键: Ctrl+Shift + Enter,语句完成 “!”,否定完成,输入表达式时按 “!”键 Ctrl+E,最近的文件 Ctrl+Shift+E,最近更改的文件 Shift+Click,可以关闭文件 Ctrl+[ OR ],可以跑到大括号的开头与结尾 Ctrl+F12,可以显示当前文件的结构 Ctrl+F7,可以查询当前元素在当前文件中的引用,然后按 F3 可以选择 Ctrl+N,可以快速打开类 Ctrl+Shift+N,可以快速打开文件 Alt+Q,可以看到当前方法的声明 Ctrl+P,可以显示参数信息 Ctrl+Shift+Insert,可以选择剪贴板内容并插入 Alt+Insert,可以生成构造器/Getter/Setter等 Ctrl+Alt+V,可以引入变量。例如:new String(); 自动导入变量定义 Ctrl+Alt+T,可以把代码包在一个块内,例如:try/catch Ctrl+Enter,导入包,自动修正 Ctrl+Alt+L,格式化代码 Ctrl+Alt+I,将选中的代码进行自动缩进编排,这个功能在编辑 JSP 文件时也可以工作 Ctrl+Alt+O,优化导入的类和包 Ctrl+R,替换文本 Ctrl+F,查找文本 Ctrl+Shift+Space,自动补全代码 Ctrl+空格,代码提示(与系统输入法快捷键冲突) Ctrl+Shift+Alt+N,查找类中的方法或变量 Alt+Shift+C,最近的更改 Alt+Shift+Up/Down,上/下移一行 Shift+F6,重构 – 重命名 Ctrl+X,删除行 Ctrl+D,复制行 Ctrl+/或Ctrl+Shift+/,注释(//或者/**/) Ctrl+J,自动代码(例如:serr) Ctrl+Alt+J,用动态模板环绕 Ctrl+H,显示类结构图(类的继承层次) Ctrl+Q,显示注释文档 Alt+F1,查找代码所在位置 Alt+1,快速打开或隐藏工程面板 Ctrl+Alt+left/right,返回至上次浏览的位置 Alt+left/right,切换代码视图 Alt+Up/Down,在方法间快速移动定位 Ctrl+Shift+Up/Down,向上/下移动语句 F2 或 Shift+F2,高亮错误或警告快速定位 Tab,代码标签输入完成后,按 Tab,生成代码 Ctrl+Shift+F7,高亮显示所有该文本,按 Esc 高亮消失 Alt+F3,逐个往下查找相同文本,并高亮显示 Ctrl+Up/Down,光标中转到第一行或最后一行下 Ctrl+B/Ctrl+Click,快速打开光标处的类或方法(跳转到定义处) Ctrl+Alt+B,跳转到方法实现处 Ctrl+Shift+Backspace,跳转到上次编辑的地方 Ctrl+O,重写方法 Ctrl+Alt+Space,类名自动完成 Ctrl+Alt+Up/Down,快速跳转搜索结果 Ctrl+Shift+J,整合两行 Alt+F8,计算变量值 Ctrl+Shift+V,可以将最近使用的剪贴板内容选择插入到文本 Ctrl+Alt+Shift+V,简单粘贴 Shift+Esc,不仅可以把焦点移到编辑器上,而且还可以隐藏当前(或最后活动的)工具窗口 F12,把焦点从编辑器移到最近使用的工具窗口 Shift+F1,要打开编辑器光标字符处使用的类或者方法 Java 文档的浏览器 Ctrl+W,可以选择单词继而语句继而行继而函数 Ctrl+Shift+W,取消选择光标所在词 Alt+F7,查找整个工程中使用地某一个类、方法或者变量的位置 Ctrl+I,实现方法 Ctrl+Shift+U,大小写转化 Ctrl+Y,删除当前行 Shift+Enter,向下插入新行 psvm/sout,main/System.out.println(); Ctrl+J,查看更多 Ctrl+Shift+F,全局查找 Ctrl+F,查找/Shift+F3,向上查找/F3,向下查找 Ctrl+Shift+S,高级搜索 Ctrl+U,转到父类 Ctrl+Alt+S,打开设置对话框 Alt+Shift+Inert,开启/关闭列选择模式 Ctrl+Alt+Shift+S,打开当前项目/模块属性 Ctrl+G,定位行 Alt+Home,跳转到导航栏 Ctrl+Enter,上插一行 Ctrl+Backspace,按单词删除 Ctrl+”+/-”,当前方法展开、折叠 Ctrl+Shift+”+/-”,全部展开、折叠 【调试部分、编译】 Ctrl+F2,停止 Alt+Shift+F9,选择 Debug Alt+Shift+F10,选择 Run Ctrl+Shift+F9,编译 Ctrl+Shift+F10,运行 Ctrl+Shift+F8,查看断点 F8,步过 F7,步入 Shift+F7,智能步入 Shift+F8,步出 Alt+Shift+F8,强制步过 Alt+Shift+F7,强制步入 Alt+F9,运行至光标处 Ctrl+Alt+F9,强制运行至光标处 F9,恢复程序 Alt+F10,定位到断点 Ctrl+F8,切换行断点 Ctrl+F9,生成项目 Alt+1,项目 Alt+2,收藏 Alt+6,TODO Alt+7,结构 Ctrl+Shift+C,复制路径 Ctrl+Alt+Shift+C,复制引用,必须选择类名 Ctrl+Alt+Y,同步 Ctrl+~,快速切换方案(界面外观、代码风格、快捷键映射等菜单) Shift+F12,还原默认布局 Ctrl+Shift+F12,隐藏/恢复所有窗口 Ctrl+F4,关闭 Ctrl+Shift+F4,关闭活动选项卡 Ctrl+Tab,转到下一个拆分器 Ctrl+Shift+Tab,转到上一个拆分器 【重构】 Ctrl+Alt+Shift+T,弹出重构菜单 Shift+F6,重命名 F6,移动 F5,复制 Alt+Delete,安全删除 Ctrl+Alt+N,内联 【查找】 Ctrl+F,查找 Ctrl+R,替换 F3,查找下一个 Shift+F3,查找上一个 Ctrl+Shift+F,在路径中查找 Ctrl+Shift+R,在路径中替换 Ctrl+Shift+S,搜索结构 Ctrl+Shift+M,替换结构 Alt+F7,查找用法 Ctrl+Alt+F7,显示用法 Ctrl+F7,在文件中查找用法 Ctrl+Shift+F7,在文件中高亮显示用法 当在 Confluence 中的快速导航进行查找的时候(请查看 Searching Confluence )能够帮助你显示页面下拉列表和其他的项目,这个是通过查找页面标题进行比对的。在默认情况下,这个功能是启用的,并且最大允许用户同时使用这个功能的用户数量被限制为 40。这些参数可以通过下面描述的方法进行修改。 最大数量的快速导航同时搜索功能的数量限制了同时在 Confluence 服务器上使用这个功能的用户数量。如果你的 Confluence 服务器上有大量的独立用户,同时这些用户有都经常使用这个快速搜索功能的话,一些用户可能会被拒绝使用这个功能,这个时候你可以考虑增加使用的限制。 希望配置快速导航: 在屏幕的右上角单击 控制台按钮 ,然后选择 General Configuration 链接。 选择左侧面板中的 进一步配置(Further Configuration) 。 选择 编辑(Edit) 。 希望禁用快速导航功能,取消选择 快速导航(Quick Navigation) 的选择框。 希望修改同时使用快速导航请求的最大数量,在字段 最大同时请求(Max Simultaneous Requests) 输入框中输入相应的值。 选择 保存(Save)。 https://www.cwiki.us/display/CONF6ZH/Configuring+Quick+Navigation 所有的其他数据库,包括有页面,内容都存储在数据库中。如果你安装的 Confluence 是用于评估或者你选择使用的是 Embedded H2 Database 数据库。数据库有关的文件将会存储在 database/ 目录中,这个目录位于 Home 目录下面。否则数据库将会存储你 Confluence 站点所使用的所有数据。 https://www.cwiki.us/display/CONF6ZH/Confluence+Home+and+other+important+directories 【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>> Exception in thread "main" org.springframework.beans.factory.parsing.BeanDefinitionParsingException: Configuration problem: Unable to locate Spring NamespaceHandler for XML schema namespace [http://www.springframework.org/schema/context] 本来比较习惯直接用eclipse里面的export成可运行jar包,开始选择的library handling 是第一个选项,结果运行后一直报下面的错误; java -jar testmq.jar 六月 18, 2018 7:11:16 下午 org.springframework.context.support.ClassPathXmlApplicationContext prepareRefresh 信息: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@17a7cec2: startup date [Mon Jun 18 19:11:16 CST 2018]; root of context hierarchy 六月 18, 2018 7:11:16 下午 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions 信息: Loading XML bean definitions from class path resource [Producer.xml] Exception in thread "main" org.springframework.beans.factory.parsing.BeanDefinitionParsingException: Configuration problem: Unable to locate Spring NamespaceHandler for XML schema namespace [http://www.springframework.org/schema/context] Offending resource: class path resource [Producer.xml] at org.springframework.beans.factory.parsing.FailFastProblemReporter.error(FailFastProblemReporter.java:70) at org.springframework.beans.factory.parsing.ReaderContext.error(ReaderContext.java:85) at org.springframework.beans.factory.parsing.ReaderContext.error(ReaderContext.java:80) at org.springframework.beans.factory.xml.BeanDefinitionParserDelegate.error(BeanDefinitionParserDelegate.java:301) at org.springframework.beans.factory.xml.BeanDefinitionParserDelegate.parseCustomElement(BeanDefinitionParserDelegate.java:1408) at org.springframework.beans.factory.xml.BeanDefinitionParserDelegate.parseCustomElement(BeanDefinitionParserDelegate.java:1401) at org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader.parseBeanDefinitions(DefaultBeanDefinitionDocumentReader.java:172) at org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader.doRegisterBeanDefinitions(DefaultBeanDefinitionDocumentReader.java:142) at org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader.registerBeanDefinitions(DefaultBeanDefinitionDocumentReader.java:94) at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.registerBeanDefinitions(XmlBeanDefinitionReader.java:508) at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.doLoadBeanDefinitions(XmlBeanDefinitionReader.java:392) at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions(XmlBeanDefinitionReader.java:336) at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions(XmlBeanDefinitionReader.java:304) at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:181) at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:217) at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:188) at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:252) at org.springframework.context.support.AbstractXmlApplicationContext.loadBeanDefinitions(AbstractXmlApplicationContext.java:127) at org.springframework.context.support.AbstractXmlApplicationContext.loadBeanDefinitions(AbstractXmlApplicationContext.java:93) at org.springframework.context.support.AbstractRefreshableApplicationContext.refreshBeanFactory(AbstractRefreshableApplicationContext.java:129) at org.springframework.context.support.AbstractApplicationContext.obtainFreshBeanFactory(AbstractApplicationContext.java:613) at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:514) at org.springframework.context.support.ClassPathXmlApplicationContext.(ClassPathXmlApplicationContext.java:139) at org.springframework.context.support.ClassPathXmlApplicationContext.(ClassPathXmlApplicationContext.java:83) at com.autohome.mq.testmq.ProducerMain.main(ProducerMain.java:16) 查询了好多同样的问题,以为是直接export不太对,需要用maven打包,比如看到了下面这个链接https://www.cnblogs.com/icewee/articles/3703491.html,是因为缺什么包导致; 然后换成了mavn打包,结果还是不太对,和我用export导出来的效果一样,查了很久都没有查到原因; 刚开始,用mvn打包,直接都是打成文件的 https://www.cnblogs.com/dzblog/p/6913809.html 按这个方式去打包,结果都生成了别的lib包,没有直接打进jar包;所以只能在当前目录下运行; 然后看到了这篇文章 https://blog.csdn.net/birdben/article/details/51951651 明白了原因,是因为因为的plugin不对; 主要使用了maven-jar-plugin和maven-dependency-plugin这两个Maven的插件,maven-jar-plugin负责构建jar包,maven-dependency-plugin负责导出所有依赖的jar包,这里我们使用了maven-assembly-plugin插件,这个插件可以将所有当前jar依赖的所有jar包打包到当前jar里面,这样就不需要导出所有依赖的jar包了,直接运行jar就可以了,实在太方便了 ^_^ 但是这样还是不行,感觉和export的效果其实是一致的;于是又找了一些文章 https://blog.csdn.net/qing0706/article/details/51612040 ,总算找到了原因;结果也是确实是因为 spring.handlers、spring.schemas,打开这两个文件一看,里面都只包含了spring-mvc的配置 需要在pom的打包后面添加别的插件, 再一想,我用maven和eclipse打包效果一样,是不是eclipse本身就有这样的方法了, 于是,尝试了一下,过然如此; 来源于 https://www.cnblogs.com/jiangwangxiang/p/8596785.html 中间部分 【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>> Spring Cloud集成项目有很多,下面我们列举一下和Spring Cloud相关的优秀项目,我们的企业架构中用到了很多的优秀项目,说白了,也是站在巨人的肩膀上去整合的。在学习Spring Cloud之前大家必须了解一下相关项目,希望可以帮助到大家。 Spring Cloud Config 配置管理工具包,让你可以把配置放到远程服务器,集中化管理集群配置,目前支持本地存储、Git以及Subversion。 Spring Cloud Bus 事件、消息总线,用于在集群(例如,配置变化事件)中传播状态变化,可与Spring Cloud Config联合实现热部署。 Eureka 云端服务发现,一个基于 REST 的服务,用于定位服务,以实现云端中间层服务发现和故障转移。 Hystrix 熔断器,容错管理工具,旨在通过熔断机制控制服务和第三方库的节点,从而对延迟和故障提供更强大的容错能力。 Zuul Zuul 是在云平台上提供动态路由,监控,弹性,安全等边缘服务的框架。Zuul 相当于是设备和 Netflix 流应用的 Web 网站后端所有请求的前门。 Archaius 配置管理API,包含一系列配置管理API,提供动态类型化属性、线程安全配置操作、轮询框架、回调机制等功能。 Consul 封装了Consul操作,consul是一个服务发现与配置工具,与Docker容器可以无缝集成。 Spring Cloud for Cloud Foundry 通过Oauth2协议绑定服务到CloudFoundry,CloudFoundry是VMware推出的开源PaaS云平台。 Spring Cloud Sleuth 日志收集工具包,封装了Dapper和log-based追踪以及Zipkin和HTrace操作,为SpringCloud应用实现了一种分布式追踪解决方案。 Spring Cloud Data Flow 大数据操作工具,作为Spring XD的替代产品,它是一个混合计算模型,结合了流数据与批量数据的处理方式。 Spring Cloud Security 基于spring security的安全工具包,为你的应用程序添加安全控制。 Spring Cloud Zookeeper 操作Zookeeper的工具包,用于使用zookeeper方式的服务发现和配置管理。 Spring Cloud Stream 数据流操作开发包,封装了与Redis,Rabbit、Kafka等发送接收消息。 Spring Cloud CLI 基于 Spring Boot CLI,可以让你以命令行方式快速建立云组件。 Ribbon 提供云端负载均衡,有多种负载均衡策略可供选择,可配合服务发现和断路器使用。 Turbine Turbine是聚合服务器发送事件流数据的一个工具,用来监控集群下hystrix的metrics情况。 Feign Feign是一种声明式、模板化的HTTP客户端。 Spring Cloud Task 提供云端计划任务管理、任务调度。 Spring Cloud Connectors 便于云端应用程序在各种PaaS平台连接到后端,如:数据库和消息代理服务。 Spring Cloud Cluster 提供Leadership选举,如:Zookeeper, Redis, Hazelcast, Consul等常见状态模式的抽象和实现。 Spring Cloud Starters Spring Boot式的启动项目,为Spring Cloud提供开箱即用的依赖管理。 从现在开始,我这边会将近期研发的spring cloud微服务云架构的搭建过程和精髓记录下来,帮助更多有兴趣研发spring cloud框架的朋友,大家来一起探讨spring cloud架构的搭建过程及如何运用于企业项目。 【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>> 一个c#开发的开源ide 可以支持很多语言但是用来开发c#最爽不过 简单来张图: 下载地址直接百度搜名字就有 【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>> Paxos算法在分布式领域具有非常重要的地位。但是Paxos算法有两个比较明显的缺点:1.难以理解 2.工程实现更难。 网上有很多讲解Paxos算法的文章,但是质量参差不齐。看了很多关于Paxos的资料后发现,学习Paxos最好的资料是论文《Paxos Made Simple》,其次是中、英文版维基百科对Paxos的介绍。本文试图带大家一步步揭开Paxos神秘的面纱。 Paxos是什么 Paxos算法是基于 消息传递 且具有 高度容错特性 的 一致性算法 ,是目前公认的解决 分布式一致性 问题 最有效 的算法之一。 Google Chubby的作者Mike Burrows说过这个世界上 只有一种 一致性算法,那就是Paxos,其它的算法都是 残次品 。 虽然Mike Burrows说得有点夸张,但是至少说明了Paxos算法的地位。然而,Paxos算法也因为晦涩难懂而臭名昭著。本文的目的就是带领大家深入浅出理解Paxos算法,不仅理解它的执行流程,还要理解算法的推导过程,作者是怎么一步步想到最终的方案的。只有理解了推导过程,才能深刻掌握该算法的精髓。而且理解推导过程对于我们的思维也是非常有帮助的,可能会给我们带来一些解决问题的思路,对我们有所启发。 问题产生的背景 在常见的分布式系统中,总会发生诸如 机器宕机 或 网络异常 (包括消息的延迟、丢失、重复、乱序,还有网络分区)等情况。Paxos算法需要解决的问题就是如何在一个可能发生上述异常的分布式系统中,快速且正确地在集群内部对 某个数据的值 达成 一致 ,并且保证不论发生以上任何异常,都不会破坏整个系统的一致性。 注:这里 某个数据的值 并不只是狭义上的某个数,它可以是一条日志,也可以是一条命令(command)。。。根据应用场景不同, 某个数据的值 有不同的含义。 相关概念 在Paxos算法中,有三种角色: Proposer Acceptor Learners 在具体的实现中,一个进程可能 同时充当多种角色 。比如一个进程可能 既是Proposer又是Acceptor又是Learner 。 还有一个很重要的概念叫 提案(Proposal) 。最终要达成一致的value就在提案里。 注: 暂且 认为『 提案=value 』,即提案只包含value。在我们接下来的推导过程中会发现如果提案只包含value,会有问题,于是我们再对提案 重新设计 。 暂且 认为『 Proposer可以直接提出提案 』。在我们接下来的推导过程中会发现如果Proposer直接提出提案会有问题,需要增加一个学习提案的过程。 Proposer可以提出(propose)提案;Acceptor可以接受(accept)提案;如果某个提案被选定(chosen),那么该提案里的value就被选定了。 回到刚刚说的『对某个数据的值达成一致』,指的是Proposer、Acceptor、Learner都认为同一个value被选定(chosen)。那么,Proposer、Acceptor、Learner分别在什么情况下才能认为某个value被选定呢? Proposer:只要Proposer发的提案被Acceptor接受(刚开始先认为只需要一个Acceptor接受即可,在推导过程中会发现需要半数以上的Acceptor同意才行),Proposer就认为该提案里的value被选定了。 Acceptor:只要Acceptor接受了某个提案,Acceptor就任务该提案里的value被选定了。 Learner:Acceptor告诉Learner哪个value被选定,Learner就认为那个value被选定。 问题描述 假设有一组可以 提出(propose)value (value在提案Proposal里)的 进程集合 。一个一致性算法需要保证提出的这么多value中, 只有一个 value被选定(chosen)。如果没有value被提出,就不应该有value被选定。如果一个value被选定,那么所有进程都应该能 学习(learn) 到这个被选定的value。对于一致性算法, 安全性(safaty) 要求如下: 只有被提出的value才能被选定。 只有一个value被选定,并且 如果某个进程认为某个value被选定了,那么这个value必须是真的被选定的那个。 我们不去精确地定义其 活性(liveness) 要求。我们的目标是保证 最终有一个提出的value被选定 。当一个value被选定后,进程最终也能学习到这个value。 Paxos的目标:保证最终有一个value会被选定,当value被选定后,进程最终也能获取到被选定的value。 假设不同角色之间可以通过发送消息来进行通信,那么: 每个角色以任意的速度执行,可能因出错而停止,也可能会重启。一个value被选定后,所有的角色可能失败然后重启,除非那些失败后重启的角色能记录某些信息,否则等他们重启后无法确定被选定的值。 消息在传递过程中可能出现任意时长的延迟,可能会重复,也可能丢失。但是消息不会被损坏,即消息内容不会被篡改(拜占庭将军问题)。 推导过程 最简单的方案——只有一个Acceptor 假设只有一个Acceptor(可以有多个Proposer),只要Acceptor接受它收到的第一个提案,则该提案被选定,该提案里的value就是被选定的value。这样就保证只有一个value会被选定。 但是,如果这个唯一的Acceptor宕机了,那么整个系统就 无法工作 了! 因此,必须要有 多个Acceptor ! 多个Acceptor 多个Acceptor的情况如下图。那么,如何保证在多个Proposer和多个Acceptor的情况下选定一个value呢? 下面开始寻找解决方案。 如果我们希望即使只有一个Proposer提出了一个value,该value也最终被选定。 那么,就得到下面的约束: P1:一个Acceptor必须接受它收到的第一个提案。 但是,这又会引出另一个问题:如果每个Proposer分别提出不同的value,发给不同的Acceptor。根据P1,Acceptor分别接受自己收到的value,就导致不同的value被选定。出现了不一致。如下图: 刚刚是因为『一个提案只要被一个Acceptor接受,则该提案的value就被选定了』才导致了出现上面不一致的问题。因此,我们需要加一个规定: 规定:一个提案被选定需要被 半数以上 的Acceptor接受 这个规定又暗示了:『一个Acceptor必须能够接受不止一个提案!』不然可能导致最终没有value被选定。比如上图的情况。v1、v2、v3都没有被选定,因为它们都只被一个Acceptor的接受。 最开始讲的『 提案=value 』已经不能满足需求了,于是重新设计提案,给每个提案加上一个提案编号,表示提案被提出的顺序。令『 提案=提案编号+value 』。 虽然允许多个提案被选定,但必须保证所有被选定的提案都具有相同的value值。否则又会出现不一致。 于是有了下面的约束: P2:如果某个value为v的提案被选定了,那么每个编号更高的被选定提案的value必须也是v。 一个提案只有被Acceptor接受才可能被选定,因此我们可以把P2约束改写成对Acceptor接受的提案的约束P2a。 P2a:如果某个value为v的提案被选定了,那么每个编号更高的被Acceptor接受的提案的value必须也是v。 只要满足了P2a,就能满足P2。 但是,考虑如下的情况:假设总的有5个Acceptor。Proposer2提出[M1,V1]的提案,Acceptor2~5(半数以上)均接受了该提案,于是对于Acceptor2~5和Proposer2来讲,它们都认为V1被选定。Acceptor1刚刚从宕机状态恢复过来(之前Acceptor1没有收到过任何提案),此时Proposer1向Acceptor1发送了[M2,V2]的提案(V2≠V1且M2>M1),对于Acceptor1来讲,这是它收到的第一个提案。根据P1(一个Acceptor必须接受它收到的第一个提案。),Acceptor1必须接受该提案!同时Acceptor1认为V2被选定。这就出现了两个问题: Acceptor1认为V2被选定,Acceptor2~5和Proposer2认为V1被选定。出现了不一致。 V1被选定了,但是编号更高的被Acceptor1接受的提案[M2,V2]的value为V2,且V2≠V1。这就跟P2a(如果某个value为v的提案被选定了,那么每个编号更高的被Acceptor接受的提案的value必须也是v)矛盾了。 所以我们要对P2a约束进行强化! P2a是对Acceptor接受的提案约束,但其实提案是Proposer提出来的,所有我们可以对Proposer提出的提案进行约束。得到P2b: P2b:如果某个value为v的提案被选定了,那么之后任何Proposer提出的编号更高的提案的value必须也是v。 由P2b可以推出P2a进而推出P2。 那么,如何确保在某个value为v的提案被选定后,Proposer提出的编号更高的提案的value都是v呢? 只要满足P2c即可: P2c:对于任意的N和V,如果提案[N, V]被提出,那么存在一个半数以上的Acceptor组成的集合S,满足以下两个条件中的任意一个: S中每个Acceptor都没有接受过编号小于N的提案。 S中Acceptor接受过的最大编号的提案的value为V。 Proposer生成提案 为了满足P2b,这里有个比较重要的思想:Proposer生成提案之前,应该先去 『学习』 已经被选定或者可能被选定的value,然后以该value作为自己提出的提案的value。如果没有value被选定,Proposer才可以自己决定value的值。这样才能达成一致。这个学习的阶段是通过一个 『Prepare请求』 实现的。 于是我们得到了如下的 提案生成算法 : Proposer选择一个 新的提案编号N ,然后向 某个Acceptor集合 (半数以上)发送请求,要求该集合中的每个Acceptor做出如下响应(response)。 (a) 向Proposer承诺保证 不再接受 任何编号 小于N的提案 。 (b) 如果Acceptor已经接受过提案,那么就向Proposer响应 已经接受过 的编号小于N的 最大编号的提案 。 我们将该请求称为 编号为N 的 Prepare请求 。 如果Proposer收到了 半数以上 的Acceptor的 响应 ,那么它就可以生成编号为N,Value为V的 提案[N,V] 。这里的V是所有的响应中 编号最大的提案的Value 。如果所有的响应中 都没有提案 ,那 么此时V就可以由Proposer 自己选择 。 生成提案后,Proposer将该 提案 发送给 半数以上 的Acceptor集合,并期望这些Acceptor能接受该提案。我们称该请求为 Accept请求 。(注意:此时接受Accept请求的Acceptor集合 不一定 是之前响应Prepare请求的Acceptor集合) Acceptor接受提案 Acceptor 可以忽略任何请求 (包括Prepare请求和Accept请求)而不用担心破坏算法的 安全性 。因此,我们这里要讨论的是什么时候Acceptor可以响应一个请求。 我们对Acceptor接受提案给出如下约束: P1a:一个Acceptor只要尚 未响应过 任何 编号大于N 的 Prepare请求 ,那么他就可以 接受 这个 编号为N的提案 。 如果Acceptor收到一个编号为N的Prepare请求,在此之前它已经响应过编号大于N的Prepare请求。根据P1a,该Acceptor不可能接受编号为N的提案。因此,该Acceptor可以忽略编号为N的Prepare请求。当然,也可以回复一个error,让Proposer尽早知道自己的提案不会被接受。 因此,一个Acceptor 只需记住 :1. 已接受的编号最大的提案 2. 已响应的请求的最大编号。 Paxos算法描述 经过上面的推导,我们总结下Paxos算法的流程。 Paxos算法分为 两个阶段 。具体如下: 阶段一: (a) Proposer选择一个 提案编号N ,然后向 半数以上 的Acceptor发送编号为N的 Prepare请求 。 (b) 如果一个Acceptor收到一个编号为N的Prepare请求,且N 大于 该Acceptor已经 响应过的 所有 Prepare请求 的编号,那么它就会将它已经 接受过的编号最大的提案(如果有的话) 作为响应反馈给Proposer,同时该Acceptor承诺 不再接受 任何 编号小于N的提案 。 阶段二: (a) 如果Proposer收到 半数以上 Acceptor对其发出的编号为N的Prepare请求的 响应 ,那么它就会发送一个针对 [N,V]提案 的 Accept请求 给 半数以上 的Acceptor。注意:V就是收到的 响应 中 编号最大的提案的value ,如果响应中 不包含任何提案 ,那么V就由Proposer 自己决定 。 (b) 如果Acceptor收到一个针对编号为N的提案的Accept请求,只要该Acceptor 没有 对编号 大于N 的 Prepare请求 做出过 响应 ,它就 接受该提案 。 Learner学习被选定的value Learner学习(获取)被选定的value有如下三种方案: 如何保证Paxos算法的活性 通过选取 主Proposer ,就可以保证Paxos算法的活性。至此,我们得到一个 既能保证安全性,又能保证活性 的 分布式一致性算法 —— Paxos算法 。 【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>> 注意: 实现类UserServiceImpl,MyUserServiceImpl 需要区分:@Service("userServicel") @Service("myUserService") https://blog.csdn.net/russle/article/details/80287763 【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>> 在任何时候,如果你希望某一个页面称为你空间的主页,你可以非常容易的从 编辑空间细节(Edit Space Details)标签页中进行修改。 希望编辑空间的细节: 进入空间后,然后从边栏的底部选择 空间工具(Space tools) > 外观和感觉(Look and Feel) 。. 选择 编辑空间细节(Edit Space Details) 。 在 主页面(Home page) 中输入你希望使用的页面,然后单击 保存(Save) 。 你可以修改你空间的主页面,名字和描述。但是你不能修改空间的标识(Space key)。 https://www.cwiki.us/display/CONF6ZH/Set+up+a+Space+Home+Page 【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>> Spring Boot 可以使用经典的开发工具或者使用安装的命令行工具。不管使用何种方式,你都需要确定你的 Java 版本为 Java SDK v1.8 或者更高的版本。在你开始安装之前,你需要确定你当前安装的 Java 版本满足系统运行的需要。 你可以使用下面的命令进行查看: $ java -version 如果你是 Java 项目开发的新手或者你希望实践使用 Spring Boot。你应该使用 Spring Boot 命令行工具( Spring Boot CLI ),否则的话,请阅读有关经典安装指南。 针对 Java 开发人员的安装指南 对于Java 开发者来说,使用 Spring Boot 就跟使用其他 Java 库一样,只需要在你的 classpath 下引入适当的 spring-boot-*.jar 文件。 Spring Boot不需要集成任何特殊的工具,所以你可以使用任何IDE或文本编辑器;同时,Spring Boot应用也没有什么特殊之处,你可以像对待其他Java程序那样运行,调试它。 尽管可以拷贝 Spring Boot jars,但我们还是更加建议你使用支持依赖管理的构建工具,比如 Maven 或 Gradle。 Maven 安装 Spring Boot 兼容 Apache Maven 3.3 或更高版本。如果本地没有安装Maven,你可以参考 maven.apache.org 上的指南在你本地安装 Maven。 在很多操作系统中,Maven 可以通过包管理器进行安装。如果你使用 OSX Homebrew 操作系统,你可以考虑使用 brwe 安装 Maven。 在 Ubuntu 中,你可以运行 sudo apt-get install 命令来安装 Maven。 Windows 用户,如果你使用了 Chocolatey ,你可以从弹出的管理员控制台中运行 choco install mave n 命令。 Spring Boot依赖使用的 groupId 为 org.springframework.boot 。 通常,你的 Maven POM 文件会继承 spring-boot-starter-parent 工程,并声明一个或多个 Starters 依赖。此外,Spring Boot提供了一个可选的 Maven 插件 ,用于创建可执行的 jars。 下面的 XML 文件中显示了一个常用的 pom.xml 文件。 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.0.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 > |
---|
通常来说 spring-boot-starter-parent 是使用 Spring Boot 的一种不错的方式,但它可能并不总是最合适的。有时你可能需要继承一个不同的父 POM,或者不喜欢我们的默认配置。 在这种情况下,你可以使用 import 作用域(import scope)来替代默认的父 POM 继承,具体请查看:这种替代方案,具体查看 Section 13.2.2, “Using Spring Boot without the Parent POM” 页面中的内容。 Gradle 安装 Spring Boot 现在能够兼容 Gradle 4.4 及其后续版本。如果你的系统中还没有安装 Gradle, 你可以参考 gradle.org 页面中的内容。 Spring Boot 的依赖可通过 groupId 为 org.springframework.boot 来进行声明。通常,你的项目将声明一个或多个 “Starters” 依赖。Spring Boot 同时还提供了一个有用的 Gradle plugin 插件。这个插件通常可以用来简化依赖声明和创建可以执行的 jars。 Gradle Wrapper 当你需要构建项目时,Gradle Wrapper提供一种有效的获取 Gradle 的方式。它是一小段脚本和库,跟你的代码一块提交,用于启动构建进程,具体参考页面 docs.gradle.org/4.2.1/userguide/gradle_wrapper.html 中的内容。 更多有关开始使用 Spring Boot 和 Gradle 的细节可以在 Getting Started section 页面中的 Gradle 插件参考指南中找到。 https://www.cwiki.us/display/SpringBootZH/Installing+Spring+Boot 【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>> 写程序一定要有思路,思路很重要! 一、我们分两步第一步先实现手写tomcat,第二部写servlet 所用技术: 1、soket通信 IO流 2、http请求与相应 3、解析xml 4、java反射技术 导入所需要的jar: dom4j dom4j 1.6.1 项目目录结构: 现在开始我们的第一步,手写tomcat 新建 Request 类: import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; public class Request { private String method; private String url; public Request(InputStream inputStream) throws IOException { BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); String[] methodAndUrl = bufferedReader.readLine().split(" "); this.method= methodAndUrl[0]; this.url=methodAndUrl[1]; System.out.println(method); System.out.println(url); } public String getMethod() { return method; } public void setMethod(String method) { this.method = method; } public String getUrl() { return url; } public void setUrl(String url) { this.url = url; } } Response 类: import java.io.IOException; import java.io.OutputStream; public class Response { public OutputStream outputStream; public String wirte; public static final String responseHeader="HTTP/1.1 200 \r\n" + "Content-Type: text/html\r\n" + "\r\n"; public Response(OutputStream outputStream) throws IOException { this.outputStream= outputStream; } public String getWirte() { return wirte; } public void setWirte(String wirte) { this.wirte = wirte; } } SocketProcess 类: import java.io.OutputStream; import java.net.Socket; import java.util.Map; public class SocketProcess extends Thread{ protected Socket socket; public SocketProcess(Socket socket){ this.socket = socket; } @Override public void run() { try { Request request = new Request(socket.getInputStream()); Response response = new Response(socket.getOutputStream()); // Map map = Mytomcat.servletMapping; // // System.out.println("map大小为:"+map.size()); // for (Map.Entry entry : map.entrySet()) { // System.out.println("Key = " + entry.getKey() + ", Value = " + entry.getValue()); // } //从映射中找 System.out.println(request.getUrl()); String servelName = (String) Mytomcat.servletMapping.get(request.getUrl()); System.out.println(servelName); if(servelName!=null && !servelName.isEmpty()) { //映射有的话找到对应的对象 Servelt servlet = (Servelt) Mytomcat.servlet.get(servelName); if(servlet!=null) { servlet.doGet(request, response); }else { System.out.println("找不到对应的servlet"); } }else { System.out.println("找不到对应的servletMapping"); } String res = Response.responseHeader+response.getWirte(); OutputStream outputStream = socket.getOutputStream(); outputStream.write(res.getBytes("GBK")); outputStream.flush(); outputStream.close(); }catch (Exception ex){ ex.printStackTrace(); }finally { if (socket != null) { try { socket.close(); } catch (Exception e) { e.printStackTrace(); } } } } } Mytomcat import java.io.IOException; import java.io.InputStream; import java.net.ServerSocket; import java.net.Socket; import java.util.HashMap; import java.util.List; import org.dom4j.Element; public class Mytomcat { private static final int port = 8099; public static final HashMap servletMapping = new HashMap(); public static final HashMap servlet = new HashMap(); private void init() { InputStream io = null; String basePath; try { System.out.println("加载配置文件开始"); //读取web.xml UtilsXml xml = new UtilsXml(UtilsXml.class.getResource("/")+"web.xml"); //讲所有的类都存储到容器中 并且创造对象 List list = xml.getNodes("servlet"); for (Element element : list) { servlet.put(element.element("servlet-name").getText(), Class.forName(element.element("servlet-class").getText()).newInstance()); } //映射关系创建 List list2 = xml.getNodes("servlet-mapping"); for (Element element : list2) { servletMapping.put(element.element("url-pattern").getText(), element.element("servlet-name").getText()); } System.out.println("加载配置文件结束"); } catch (Exception ex) { ex.printStackTrace(); } finally { if (io != null) { try { io.close(); } catch (Exception e) { e.printStackTrace(); } } } } private void start() { try { ServerSocket serverSocket = new ServerSocket(port); System.out.println("Tomcat 服务已启动,地址:localhost ,端口:" + port); this.init(); //持续监听 do { Socket socket = serverSocket.accept(); System.out.println(socket); //处理任务 Thread thread = new SocketProcess(socket); thread.start(); } while (true); } catch (IOException e) { e.printStackTrace(); } } public static void main(String[] args) { Mytomcat tomcat = new Mytomcat(); tomcat.start(); } } UtilsXml: import java.util.List; import org.dom4j.Document; import org.dom4j.DocumentException; import org.dom4j.Element; import org.dom4j.io.SAXReader; public class UtilsXml { //定义解析器和文档对象 public SAXReader saxReader; public Document document; public UtilsXml(String path){ //获取解析器 saxReader = new SAXReader(); try { //获取文档对象 document = saxReader.read(path); } catch (DocumentException e) { // TODO Auto-generated catch block e.printStackTrace(); } } /** * 根据节点名称获取内容 * @param name 节点名称 * @return 节点内容 */ public String getElementText(String name){ //定位根节点 Element root = document.getRootElement(); List mapp = root.elements("servlet-mapping"); List servlet = root.elements("servlet"); String serveltName = ""; String classpath = ""; for (Element e : mapp) { // System.out.println(e.element("url-pattern").getText()); if(e.element("url-pattern").getText().equals(name)){ serveltName = e.element("servlet-name").getText(); break; } } for (Element e : servlet) { // System.out.println(e.element("servlet-name").getText()); if(e.element("servlet-name").getText().equals(serveltName)){ classpath = e.element("servlet-class").getText(); break; } } return classpath; // //根据名称定位节点 // Element element = root.element(name); // //返回节点内容 // return element.getText(); } /** * 获取节点下的所有节点 * @param root * @param name * @return */ public List getNodes(String name){ Element root = document.getRootElement(); return root.elements(name); } public static void main(String[] args) { UtilsXml xml = new UtilsXml(UtilsXml.class.getResource("/")+"web.xml"); //System.out.println(xml.getElementText("/myhtml.html")); List list = xml.getNodes("servlet"); for (Element element : list) { System.out.println(element.element("servlet-name").getText() ); System.out.println(element.element("servlet-class").getText() ); } } Servelt 抽象类: import com.siyuan.http.Request; import com.siyuan.http.Response; public abstract class Servelt { public void service(Request request, Response response) { //判断是调用doget 还是 dopost if ("get".equalsIgnoreCase(request.getMethod())) { this.doGet(request, response); } else { this.doPost(request, response); } } public abstract void doGet(Request request, Response response); public abstract void doPost(Request request, Response response); } 第一个servlet : import com.siyuan.http.Request; import com.siyuan.http.Response; public class MyfisrtServlet extends Servelt { @Override public void doGet(Request request, Response response) { System.out.println("进入了我的第一个servlet"); response.setWirte("进入了第一个servlet"); } @Override public void doPost(Request request, Response response) { } } 第二个servlet: import com.siyuan.http.Request; import com.siyuan.http.Response; public class ScoendServlet extends Servelt{ @Override public void doGet(Request request, Response response) { System.out.println("进入了我的第二个servlet"); response.setWirte("进入了第二个servlet"); } @Override public void doPost(Request request, Response response) { // TODO Auto-generated method stub } } 新建web.xml myhtml.html com.siyuan.servlet.MyfisrtServlet myhtml.html /myhtml.html myhtml2.html com.siyuan.servlet.ScoendServlet myhtml2.html /myhtml2.html 运行效果:
有问题讨论可以加群一起讨论: 600922504 【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>> 上一篇文章我们介绍了eureka服务注册中心的搭建,这篇文章介绍一下如何使用eureka服务注册中心,搭建一个简单的服务端注册服务,客户端去调用服务使用的案例。愿意了解源码的朋友直接求求交流分享技术:二一四七七七五六三三 案例中有三个角色:服务注册中心、服务提供者、服务消费者,其中服务注册中心就是我们上一篇的eureka单机版启动既可,流程是首先启动注册中心,服务提供者生产服务并注册到服务中心中,消费者从服务中心中获取服务并执行。 服务提供 我们假设服务提供者有一个hello方法,可以根据传入的参数,提供输出“hello ,this is first messge”的服务 1、pom包配置 创建一个springboot项目,pom.xml中添加如下配置: org.springframework.cloud spring-cloud-starter-eureka org.springframework.boot spring-boot-starter-test test 2、配置文件 application.properties配置如下: spring.application.name=spring-cloud-producer server.port=9000 eureka.client.serviceUrl.defaultZone=http://localhost:8000/eureka/ 3、启动类 启动类中添加 @EnableDiscoveryClient 注解 @SpringBootApplication @EnableDiscoveryClient public class ProducerApplication { public static void main(String[] args) { SpringApplication.run(ProducerApplication.class, args); } } 4、controller 提供hello服务 @RestController public class HelloController { @RequestMapping("/hello") public String index(@RequestParam String name) { return "hello "+name+",this is first messge"; } } 添加 @EnableDiscoveryClient 注解后,项目就具有了服务注册的功能。启动工程后,就可以在注册中心的页面看到SPRING-CLOUD-PRODUCER服务。 到此服务提供者配置就完成了。 服务调用 1、pom包配置 和服务提供者一致 org.springframework.cloud spring-cloud-starter-eureka org.springframework.boot spring-boot-starter-test test 2、配置文件 application.properties配置如下: spring.application.name=spring-cloud-consumer server.port=9001 eureka.client.serviceUrl.defaultZone=http://localhost:8000/eureka/ 3、启动类 启动类添加 @EnableDiscoveryClient 和 @EnableFeignClients 注解。 @SpringBootApplication @EnableDiscoveryClient @EnableFeignClients public class ConsumerApplication { public static void main(String[] args) { SpringApplication.run(ConsumerApplication.class, args); } } @EnableDiscoveryClient :启用服务注册与发现 @EnableFeignClients:启用feign进行远程调用 Feign是一个声明式Web Service客户端。使用Feign能让编写Web Service客户端更加简单, 它的使用方法是定义一个接口,然后在上面添加注解,同时也支持JAX-RS标准的注解。Feign也支持可拔插式的编码器。Spring Cloud对Feign进行了封装,使其支持了Spring MVC标准注解和HttpMessageConverters。Feign可以与Eureka和Ribbon组合使用以支持负载均衡。 4、feign调用实现 @FeignClient(name= "spring-cloud-producer") public interface HelloRemote { @RequestMapping(value = "/hello") public String hello(@RequestParam(value = "name") String name); } name:远程服务名,及spring.application.name配置的名称 此类中的方法和远程服务中contoller中的方法名和参数需保持一致。 5、web层调用远程服务 将HelloRemote注入到controller层,像普通方法一样去调用即可。 @RestController public class ConsumerController { @Autowired HelloRemote HelloRemote; @RequestMapping("/hello/{name}") public String index(@PathVariable("name") String name) { return HelloRemote.hello(name); } } 到此,最简单的一个服务注册与调用的例子就完成了。 测试 简单调用 依次启动spring-cloud-eureka、spring-cloud-producer、spring-cloud-consumer三个项目 先输入:http://localhost:9000/hello?name=neo 检查spring-cloud-producer服务是否正常 返回:hello neo,this is first messge 说明spring-cloud-producer正常启动,提供的服务也正常。 浏览器中输入:http://localhost:9001/hello/neo 返回:hello neo,this is first messge 说明客户端已经成功的通过feign调用了远程服务hello,并且将结果返回到了浏览器。 负载均衡 以上面spring-cloud-producer为例子修改,将其中的controller改动如下: @RestController public class HelloController { @RequestMapping("/hello") public String index(@RequestParam String name) { return "hello "+name+",this is producer 2 send first messge"; } } 在配置文件中改动端口: spring.application.name=spring-cloud-producer server.port=9003 eureka.client.serviceUrl.defaultZone=http://localhost:8000/eureka/ 打包启动后,在eureka就会发现两个服务提供者,如下图: 然后在浏览器再次输入:http://localhost:9001/hello/neo 进行测试: 第一次返回结果:hello neo,this is first messge 第二次返回结果:hello neo,this is producer 2 send first messge 不断的进行测试下去会发现两种结果交替出现,说明两个服务中心自动提供了服务均衡负载的功能。如果我们将服务提供者的数量在提高为N个,测试结果一样,请求会自动轮询到每个服务端来处理。 整体代码结构如下: 【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>> 提供了认证,授权,加密,会话管理等功能 在spring配置文件中配置shiro,需要配置的有shiro的过滤器工厂,在里面我们可以配置什么页面需要认证,什么认证不需要认证,认证成功后跳转的路径,认证失败跳转的路径,还有授权校验失败跳转的路径。 【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>> 介绍 鸿鹄云架构【SSO单点登录系统】为所有微服务提供统一的用户认证服务,系统本身属于微服务模式,使用JWT+Redis分布式存储方案,确保不同微服务、系统之间的安全通讯和统一用户校验、认证。在整个服务平台中起着用户枢纽中心的作用。 平台基础功能 用户注册&登录、用户登录&校验(APP)、用户登录&校验(PC)、用户登出、用户密码修改、用户密码重置 运行环境支持 开发工具:Eclipse、MyEclipse、Idea WEB容器:内置Tomcat JDK版本:1.8+ 系统支持:Window、Linux 数据库:Mysql、Alibaba Druid 分布式服务:SpringCloud、Eureka、Config 服务框架:SpringBoot、SpringMVC、Mybatis Plus、Restful、Token认证 构建方式:Maven、Jenkins 前端架构:Bootstrap 4、H5、CSS3 后面的章节我们详细介绍一下每个平台的使用和规划,希望可以帮助到大家! 【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>> Spring Cloud是一系列框架的有序集合。利用Spring Boot的开发模式简化了分布式系统基础设施的开发,如 服务发现、注册、配置中心、消息总线、负载均衡、断路器、数据监控 等(这里只简单的列了一部分),都可以用Spring Boot的开发风格做到一键启动和部署。Spring Cloud将目前比较成熟、经得起实际考验的服务框架组合起来,通过Spring Boot风格进行再封装,屏蔽掉了复杂的配置和实现原理,最终整合出一套简单易懂、易部署和易维护的分布式系统架构平台。 Spring Cloud组成 Spring Cloud的子项目,大致可分成两类: 一类是对现有成熟框架Spring Boot的封装和抽象,也是数量最多的项目; 第二类是开发了一部分分布式系统的基础设施的实现,如Spring Cloud Stream就是kafka, ActiveMQ这样的角色。开发人员进行 微服务 的实践,第一类子项目就已经足够使用,如: Spring Cloud Netflix 是对Netflix开发的一套分布式服务框架的封装,包括服务的发现和注册,负载均衡、断路器、REST客户端、请求路由等。 Spring Cloud Config 将配置信息中央化保存, 配置Spring Cloud Bus可以实现动态修改配置文件。 Spring Cloud Bus 分布式消息队列,是对Kafka, MQ的封装。 Spring Cloud Security 对Spring Security的封装,并能配合Netflix使用。 Spring Cloud Zookeeper 对Zookeeper的封装,使之能配置其它Spring Cloud的子项目使用。 Spring Cloud Eureka Spring Cloud Eureka 是 Spring Cloud Netflix 微服务套件中的一部分,它基于Netflix Eureka 做了二次分装,主要负责完成微服务架构中的服务治理功能。 Spring Cloud未来 Spring Cloud为未来互联网企业提供分布式基础设施解决方案。同时,随着近几年微服务架构和Docker容器概念的火爆,也会让Spring Cloud在未来越来越“云”化的软件开发风格中立有一席之地,尤其是在目前五花八门的分布式解决方案中提供了标准化的、全站式的技术方案,有效推进服务端软件系统技术水平提升。 从现在开始,我这边会将近期研发的spring cloud微服务云架构的搭建过程和精髓记录下来,帮助更多有兴趣研发spring cloud框架的朋友,大家来一起探讨spring cloud架构的搭建过程及如何运用于企业项目。 【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>> 环境搭建 本文环境指的 Spring Boot下1.4.2版本下 pom.xml (核心内容) org.springframework.boot spring-boot-starter-parent 1.4.2.RELEASE org.springframework.boot spring-boot-starter-web 1 2 3 4 5 6 7 8 9 10 11 12 如果单纯想用RestTemplate 引上面的应该是多余了,博主没去查阅具体那个包,可以自行去Maven 中央仓库搜索有没有专门的RestTemplate包 RestTemplate有简写方法,但不具备通用性且无法做细节设置故本文不再赘述 举例: restTemplate.getForObject(url, String.class); // 向url发送 Get类型请求,将返回结果 以String类型显示 // 能很明显发现无法设置编码格式等,乱码等问题无法解决 1 2 3 有兴趣可访问,下文只提通用性方法 RestTemplate 官方 API 注意 默认的 RestTemplate 有个机制是请求状态码非200 就抛出异常,会中断接下来的操作,如果要排除这个问题那么需要覆盖默认的 ResponseErrorHandler ,下面为用什么也不干覆盖默认处理机制 RestTemplate restTemplate = new RestTemplate(); ResponseErrorHandler responseErrorHandler = new ResponseErrorHandler() { @Override public boolean hasError(ClientHttpResponse clientHttpResponse) throws IOException { return true; } @Override public void handleError(ClientHttpResponse clientHttpResponse) throws IOException { } }; restTemplate.setErrorHandler(responseErrorHandler); 1 2 3 4 5 6 7 8 9 10 11 12 13 实例 application/json类型请求 RestTemplate restTemplate=new RestTemplate(); String url="http://www.testXXX.com"; /* 注意:必须 http、https……开头,不然报错,浏览器地址栏不加 http 之类不出错是因为浏览器自动帮你补全了 */ HttpHeaders headers = new HttpHeaders(); /* 这个对象有add()方法,可往请求头存入信息 */ headers.setContentType(MediaType.APPLICATION_JSON_UTF8); /* 解决中文乱码的关键 , 还有更深层次的问题 关系到 StringHttpMessageConverter,先占位,以后补全*/ HttpEntity entity = new HttpEntity(body, headers); /* body是Http消息体例如json串 */ restTemplate.exchange(url, HttpMethod.POST, entity, String.class); /*上面这句返回的是往 url发送 post请求 请求携带信息为entity时返回的结果信息 String.class 是可以修改的,其实本质上就是在指定反序列化对象类型,这取决于你要怎么解析请求返回的参数*/ 1 2 3 4 5 6 7 8 9 10 11 12 另post、get、delete……等其他http请求类型只需将exchange参数修改为HttpMethod.GET 等等。 application/x-www-form-urlencoded类型请求 RestTemplate restTemplate=new RestTemplate(); /* 注意:必须 http、https……开头,不然报错,浏览器地址栏不加 http 之类不出错是因为浏览器自动帮你补全了 */ String url="http://www.testXXX.com"; String bodyValTemplate = "var1=" + URLEncoder.encode("测试数据1", "utf-8") + "&var2=" + URLEncoder.encode("test var2", "utf-8"); HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); HttpEntity entity = new HttpEntity(bodyValTemplate, headers); restTemplate.exchange(url, HttpMethod.POST, entity, String.class); 1 2 3 4 5 6 7 8 9 另一种构造 HttpEntity 的方法 LinkedMultiValueMap body=new LinkedMultiValueMap(); body.add("var1","测试数据1"); body.add("var2","test Val2"); HttpEntity entity = new HttpEntity(body, headers); 1 2 3 4 上方请求在PostMan里等效于图 RestTemplate还有专门支持异步请求的特殊类 AsyncRestTemplate,具体用法和RestTemplate类似(需要 ListenableFutureCallback 基础) --------------------- 作者:SolidCocoi 来源:CSDN 原文:https://blog.csdn.net/u014430366/article/details/65633679?utm_source=copy 版权声明:本文为博主原创文章,转载请附上博文链接! 【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>> 在Java Web开发中,我们通常需要通过GET、POST请求其他系统提供的服务。其中, JDK自带的HttpURLConnection 、 Apache HttpClient 等方式都可以实现。当然,这些方式都有一个很明显的缺陷,那就是代码很繁琐。而Spring提供的RestTemplate封装了这些库的实现,可以让我们的HTTP请求更加简洁、直观。 在RestTemplate中定义了11个独立的操作,它们分别是: 方法 | 描述 |
---|
delete() | 在特定的URL上对资源执行HTTP DELETE操作 | exchange() | 在URL上执行特定的HTTP方法,返回的ResponseEntity包含了响应体所映射成的对象 | execute() | 在URL上执行特定的HTTP方法,返回一个从响应体映射得到的对象 | getForEntity() | 发送一个HTTP GET请求,返回的ResponseEntity包含了响应体所映射成的对象 | getForObject() | 发送一个HTTP GET请求,返回根据响应体映射形成的对象 | postForEntity() | POST数据到一个URL,返回的ResponseEntity包含了响应体所映射成的对象 | postForLocation() | POST数据到一个URL,返回新创建资源的URL | postForObject() | POST数据到一个URL,返回根据响应体映射形成的对象 | put() | PUT资源到特定的URL headForHeaders() | optionsForAllow() 发送HTTP HEAD请求,返回包含特定资源URL的HTTP头 | 发送HTTP OPTIONS请求,返回对特定URL的Allow头信息 |
接下来,我将对常用的几个方法分别介绍。 (1)在项目中添加依赖: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 com.fasterxml.jackson.jaxrs jackson-jaxrs-json-provider com.fasterxml.jackson.dataformat jackson-dataformat-xml org.apache.httpcomponents httpclient com.alibaba fastjson 1.2.46 (2)在项目中注入RestTemplate: 在注入RestTemplate的bean的时候,可以通过ClientHttpRequestFactory指定RestTemplate发起HTTP请求的底层实现所采用的类库。对此,ClientHttpRequestFactory接口主要提供了以下两种实现方法: i)SimpleClientHttpRequestFactory: 也就是底层使用java.net包提供的方式创建Http连接请求。示例代码如下: 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 package cn.zifangsky.springbootdemo.config; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.List; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.client.ClientHttpRequestFactory; import org.springframework.http.client.SimpleClientHttpRequestFactory; import org.springframework.http.converter.FormHttpMessageConverter; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.StringHttpMessageConverter; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; import org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter; import org.springframework.web.client.RestTemplate; @Configuration public class RestTemplateConfig { /** * 返回RestTemplate * @param factory * @return */ @Bean public RestTemplate restTemplate(ClientHttpRequestFactory factory){ //消息转换器,一般情况下可以省略,只需要添加相关依赖即可 // List > messageConverters = new ArrayList<>(); // messageConverters.add(new StringHttpMessageConverter(Charset.forName("UTF-8"))); // messageConverters.add(new FormHttpMessageConverter()); // messageConverters.add(new MappingJackson2XmlHttpMessageConverter()); // messageConverters.add(new MappingJackson2HttpMessageConverter()); RestTemplate restTemplate = new RestTemplate(factory); // restTemplate.setMessageConverters(messageConverters); return restTemplate; } /** * ClientHttpRequestFactory接口的第一种实现方式,即: * SimpleClientHttpRequestFactory:底层使用java.net包提供的方式创建Http连接请求 * @return */ @Bean public SimpleClientHttpRequestFactory simpleClientHttpRequestFactory(){ SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory(); requestFactory.setReadTimeout(5000); requestFactory.setConnectTimeout(5000); return requestFactory; } } ii)HttpComponentsClientHttpRequestFactory(推荐使用): 也就是底层使用Httpclient连接池的方式创建Http连接请求。示例代码如下: 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 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 package cn.zifangsky.springbootdemo.config; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; import org.apache.http.Header; import org.apache.http.client.HttpClient; import org.apache.http.impl.client.DefaultConnectionKeepAliveStrategy; import org.apache.http.impl.client.DefaultHttpRequestRetryHandler; import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; import org.apache.http.message.BasicHeader; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.client.ClientHttpRequestFactory; import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; import org.springframework.http.converter.FormHttpMessageConverter; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.StringHttpMessageConverter; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; import org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter; import org.springframework.web.client.RestTemplate; @Configuration public class RestTemplateConfig { /** * 返回RestTemplate * @param factory * @return */ @Bean public RestTemplate restTemplate(ClientHttpRequestFactory factory){ //消息转换器,Spring Boot环境可省略,只需要添加相关依赖即可 // List> messageConverters = new ArrayList<>(); // messageConverters.add(new StringHttpMessageConverter(Charset.forName("UTF-8"))); // messageConverters.add(new FormHttpMessageConverter()); // messageConverters.add(new MappingJackson2XmlHttpMessageConverter()); // messageConverters.add(new MappingJackson2HttpMessageConverter()); RestTemplate restTemplate = new RestTemplate(factory); // restTemplate.setMessageConverters(messageConverters); return restTemplate; } /** * ClientHttpRequestFactory接口的另一种实现方式(推荐使用),即: * HttpComponentsClientHttpRequestFactory:底层使用Httpclient连接池的方式创建Http连接请求 * @return */ @Bean public HttpComponentsClientHttpRequestFactory httpComponentsClientHttpRequestFactory(){ //Httpclient连接池,长连接保持30秒 PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(30, TimeUnit.SECONDS); //设置总连接数 connectionManager.setMaxTotal(1000); //设置同路由的并发数 connectionManager.setDefaultMaxPerRoute(1000); //设置header List headers = new ArrayList(); headers.add(new BasicHeader("User-Agent", "Mozilla/5.0 (Windows NT 6.3; rv:36.0) Gecko/20100101 Firefox/36.04")); headers.add(new BasicHeader("Accept-Encoding", "gzip, deflate")); headers.add(new BasicHeader("Accept-Language", "zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3")); headers.add(new BasicHeader("Connection", "keep-alive")); //创建HttpClient HttpClient httpClient = HttpClientBuilder.create() .setConnectionManager(connectionManager) .setDefaultHeaders(headers) .setRetryHandler(new DefaultHttpRequestRetryHandler(3, true)) //设置重试次数 .setKeepAliveStrategy(new DefaultConnectionKeepAliveStrategy()) //设置保持长连接 .build(); //创建HttpComponentsClientHttpRequestFactory实例 HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(httpClient); //设置客户端和服务端建立连接的超时时间 requestFactory.setConnectTimeout(5000); //设置客户端从服务端读取数据的超时时间 requestFactory.setReadTimeout(5000); //设置从连接池获取连接的超时时间,不宜过长 requestFactory.setConnectionRequestTimeout(200); //缓冲请求数据,默认为true。通过POST或者PUT大量发送数据时,建议将此更改为false,以免耗尽内存 requestFactory.setBufferRequestBody(false); return requestFactory; } } (3)使用getForObject()方法发起GET请求: getForObject()方法实际上是对getForEntity()方法的进一步封装,二者用法类似。 唯一的区别在于getForObject()方法只返回所请求类型的对象, 而getForEntity()方法会返回请求的对象以及响应的Header、响应状态码等额外信息。 三个getForObject()方法的签名如下: 1 2 3 4 5 T getForObject(String url, Class responseType, Object... uriVariables) throws RestClientException; T getForObject(String url, Class responseType, Map uriVariables) throws RestClientException; T getForObject(URI url, Class responseType) throws RestClientException; 示例代码如下: 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 package cn.zifangsky.SpringBootDemo.controller; import java.util.HashMap; import java.util.Map; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.ResponseEntity; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.web.WebAppConfiguration; import org.springframework.web.client.RestTemplate; import cn.zifangsky.springbootdemo.config.RestTemplateConfig; import cn.zifangsky.springbootdemo.config.WebMvcConfig; import cn.zifangsky.springbootdemo.model.DemoObj; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes={WebMvcConfig.class,RestTemplateConfig.class}) @WebAppConfiguration("src/main/resources") public class TestRestTemplate { @Autowired private RestTemplate restTemplate; /** * 测试最基本的Get请求 */ @Test public void testGetMethod1(){ DemoObj obj = restTemplate.getForObject("http://127.0.0.1:9090/rest/testJson2?id={1}&name={2}" , DemoObj.class , 1,"Tom"); System.out.println(obj); } } 注:本篇文章的单元测试请求的REST服务均来源于上一篇文章: https://www.zifangsky.cn/1215.html 上面代码设置请求参数使用了数字占位符,同时getForObject()方法的最后一个参数是一个可变长度的参数,用于一一替换前面的占位符。当然,除了这种方式之外,还可以使用Map来设置参数,比如: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 /** * 测试Get请求另一种设置参数的方式 */ @Test public void testGetMethod2(){ Map uriVariables = new HashMap(); uriVariables.put("var_id", "1"); uriVariables.put("var_name", "Tom"); DemoObj obj = restTemplate.getForObject("http://127.0.0.1:9090/rest/testJson2?id={var_id}&name={var_name}" , DemoObj.class , uriVariables); System.out.println(obj); } 运行单元测试之后,最后输出如下: DemoObj [id=2, name=Tom Ret] 此外需要注意的是,由于上面代码只是简单的单元测试,因此请求URL就直接硬编码在代码中了。实际开发则需要将之配置到配置文件或者Zookeeper、Redis中。比如这样: 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 package cn.zifangsky.springbootdemo.controller; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; import cn.zifangsky.springbootdemo.model.DemoObj; @RestController @RequestMapping("/restTemplate") public class RestTemplateController { @Value("${SERVER_URL}") private String SERVER_URL; @Autowired private RestTemplate restTemplate; @RequestMapping(path="/getDemoObj",produces={MediaType.APPLICATION_JSON_UTF8_VALUE}) public DemoObj getDemoObj(){ DemoObj obj = restTemplate.getForObject(SERVER_URL + "/rest/testXML?id={1}&name={2}" , DemoObj.class , 1,"Tom"); return obj; } } (4)使用getForEntity()方法发起GET请求: 三个getForEntity()方法的签名如下: 1 2 3 4 5 ResponseEntity getForEntity(String url, Class responseType, Object... uriVariables) throws RestClientException; ResponseEntity getForEntity(String url, Class responseType, Map uriVariables) throws RestClientException; ResponseEntity getForEntity(URI url, Class responseType) throws RestClientException; 示例代码如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 /** * 测试Get请求返回详细信息,包括:响应正文、响应状态码、响应Header等 */ @Test public void testGetMethod3(){ ResponseEntity responseEntity = restTemplate.getForEntity("http://127.0.0.1:9090/rest/testJson2?id={1}&name={2}" , DemoObj.class , 1,"Tom"); DemoObj body = responseEntity.getBody(); int statusCodeValue = responseEntity.getStatusCodeValue(); HttpHeaders headers = responseEntity.getHeaders(); System.out.println("responseEntity.getBody():" + body); System.out.println("responseEntity.getStatusCodeValue():" + statusCodeValue); System.out.println("responseEntity.getHeaders():" + headers); } 运行单元测试之后,最后输出如下: responseEntity.getBody():DemoObj [id=2, name=Tom Ret] responseEntity.getStatusCodeValue():200 responseEntity.getHeaders():{Date=[Fri, 09 Feb 2018 06:22:28 GMT], Content-Type=[application/json;charset=utf-8], Transfer-Encoding=[chunked]} (5)使用postForObject()方法发起POST请求: 在RestTemplate中,POST请求跟GET请求类似,也可以使用如下三个方法来请求: 1 2 3 4 5 6 7 T postForObject(String url, Object request, Class responseType, Object... uriVariables) throws RestClientException; T postForObject(String url, Object request, Class responseType, Map uriVariables) throws RestClientException; T postForObject(URI url, Object request, Class responseType) throws RestClientException; 示例代码如下: 1 2 3 4 5 6 7 8 9 10 11 12 /** * 测试最基本的Post请求 */ @Test public void testPostMethod1(){ DemoObj request = new DemoObj(1l, "Tim"); DemoObj obj = restTemplate.postForObject("http://127.0.0.1:9090/rest/testJson1" , request, DemoObj.class); System.out.println(obj); } 运行单元测试之后,最后输出如下: DemoObj [id=2, name=Tim Ret] (6)使用postForEntity()方法发起POST请求: 三个postForEntity()方法的签名如下: 1 2 3 4 5 6 7 ResponseEntity postForEntity(String url, Object request, Class responseType, Object... uriVariables) throws RestClientException; ResponseEntity postForEntity(String url, Object request, Class responseType, Map uriVariables) throws RestClientException; ResponseEntity postForEntity(URI url, Object request, Class responseType) throws RestClientException; 示例代码如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 /** * 测试Post请求返回详细信息,包括:响应正文、响应状态码、响应Header等 */ @Test public void testPostMethod2(){ DemoObj request = new DemoObj(1l, "Tim"); ResponseEntity responseEntity = restTemplate.postForEntity("http://127.0.0.1:9090/rest/testJson1" , request, DemoObj.class); DemoObj body = responseEntity.getBody(); int statusCodeValue = responseEntity.getStatusCodeValue(); HttpHeaders headers = responseEntity.getHeaders(); System.out.println("responseEntity.getBody():" + body); System.out.println("responseEntity.getStatusCodeValue():" + statusCodeValue); System.out.println("responseEntity.getHeaders():" + headers); } 运行单元测试之后,最后输出如下: responseEntity.getBody():DemoObj [id=2, name=Tim Ret] responseEntity.getStatusCodeValue():200 responseEntity.getHeaders():{Date=[Fri, 09 Feb 2018 06:32:02 GMT], Content-Type=[application/json;charset=utf-8], Transfer-Encoding=[chunked]} (7)使用exchange()方法执行指定的HTTP请求: exchange()方法跟上面的getForObject()、getForEntity()、postForObject()、postForEntity()等方法不同之处在于它可以指定请求的HTTP类型。示例代码如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 /** * 测试Exchange请求 */ @Test public void testExchange(){ //设置header HttpHeaders headers = new HttpHeaders(); headers.add("Content-Type", "application/x-zifangsky"); //设置参数 String requestBody = "1#Converter"; HttpEntity requestEntity = new HttpEntity(requestBody,headers); ResponseEntity responseEntity = restTemplate.exchange("http://127.0.0.1:9090/convert" , HttpMethod.POST, requestEntity, String.class); System.out.println("responseEntity.getBody():" + responseEntity.getBody()); System.out.println("responseEntity.getHeaders():" + responseEntity.getHeaders()); } 运行单元测试之后,最后输出如下: responseEntity.getBody():{“id”:2,”name”:”Converter Ret”} responseEntity.getHeaders():{Date=[Fri, 09 Feb 2018 06:42:29 GMT], Content-Type=[application/x-zifangsky], Transfer-Encoding=[chunked]} 参考: https://segmentfault.com/a/1190000011093597 http://rensanning.iteye.com/blog/2362105 【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>> Amitabha n. <梵>(佛)阿弥陀佛 佛曰: 因果 那么一天 看见文章1. 发现还有2.这么个中间件 于是追寻源码,发现了3. 再然后寻思自己也可以写一个什么中间件,以便学习掌握 于是便有了 Amitabha 因果线 1. asp.net core 教程(六)-中间件 2. Microsoft.AspNetCore.Diagnostics 3. https://github.com/aspnet/Diagnostics 4. WelcomePage 获取 建议通过 NuGet 获取包并使用 当然通过 Source Code 获取使用也是可以的 示例 新建一个空的 ASP.NET Core Web 应用程序 获取 Amitabha 在 Startup.cs 中添加以下内容,运行后,就能看见我佛了... // Startup.cs using Amitabha; public void Configure(IApplicationBuilder app, IHostingEnvironment env) { // ... app.UseAmitabha(); // ... } 【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>> 盘符获取 std::shared_ptr> disks(void) { wchar_t data[255]; return [&](int length)->std::shared_ptr> { auto result = std::make_shared< std::list>(); std::wstring disk = L"ABCDEFGHIJKLMNOPQRSTUVWXYZ"; for (int i = 0; i < length; i++) { if (std::wstring::npos != disk.find(data[i])) { result->push_back(data[i]); } } return result; } (GetLogicalDriveStrings(sizeof(data), data)); } 【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>> 最近,美国一个程序员因为同事不写注释,代码不规范,最严重的是天天使用 git push -f 参数强行覆盖仓库,该程序员忍无可忍向四名同事开抢,其中一人情况危急!!! (此信息后来证实枪击事件的确发生,但并非代码原因,但从另外的角度也可看出强推代码所引发的后果让人非常愤怒) 不写注释、代码不规范是一个非常普遍的问题,其严重性还不足以导致枪击事件发生,毕竟算是个人行为,不会对别人的工作产生大的破坏作用。但是 git push -f 的仓库强推参数,则直接导致别人辛辛苦苦编写的代码付之一炬。这种被删代码的愤怒之心想必有过此遭遇的人都深有体会。 但是我们在谴责这种强行推送仓库的行为之时也应该注意到,有挺大一部分开发人员对 -f 参数所产生的破坏并不知晓,另外也可能可能存在一些无心的误操作。而我们宁愿相信绝大多数人并不会恶意强行覆盖同事的仓库,他们只是在遇到代码冲突时无所适从,再加上网上一些文章的误导,只要能解决推送,就不顾及任何后果。 由于很多用户跟我们反馈各种因为强推导致仓库被重置、代码被删除、提交记录消失等问题,甚至还有用户直接甩锅给平台,认为是平台的故障导致他们仓库出现问题,这让我们意识到不应该再做壁上观,于是码云限制强推的功能就推出了: 珍爱生命,远离强推。想了解更多关于“限制强推”的功能请访问 https://blog.gitee.com/2018/08/09/git_push_unallowed/ 【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>> 将一些必须必要条件准备好 1,安装chorme浏览器 1,安装依赖 sudo apt-get install libxss1 libappindicator1 libindicator7 2,下载google安装包 wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb 3,安装 sudo dpkg -i google-chrome*.deb sudo apt-get install -f 2,安装chormedrive 1,安装xvfb以便我们可以无头奔跑地运行Chrome sudo apt-get install xvfb 2,安装依赖 sudo apt-get install unzip 3,下载安装包 wget -N http://chromedriver.storage.googleapis.com/2.26/chromedriver_linux64.zip 4,解压缩+添加执行权限 unzip chromedriver_linux64.zip 5,移动 sudo mv -f chromedriver /usr/local/share/chromedriver 6,建立软连接 sudo ln -s /usr/local/share/chromedriver /usr/local/bin/chromedriver sudo ln -s /usr/local/share/chromedriver /usr/bin/chromedriver 3,安装python依赖 1,安装Python依赖 pip3 install selenium 百度的模拟登录 ''' 百度的模拟登录,但手机的验证码只能手动输入 ''' from selenium import webdriver import time sel = webdriver.Chrome() myurl = "https://www.baidu.com/" sel.get(myurl) #找到登录节点,进行登录 try: sel.find_element_by_css_selector("#u1 a:nth-child(7)").click() print("click success!!") except: print("click failed") #js页面加载出来的页面必须要有time.sleep来取加载页面 # 等待页面加载时间 time.sleep(2) #找到用户名登录的节点,进行登录 try: sel.find_element_by_css_selector("p#TANGRAM__PSP_10__footerULoginBtn").click() print("click username login success!") except: print("not find username login!") #设置休眠时间.模拟用户点击 time.sleep(1) #查找手机号输入框,username就是自己要登录的手机帐号 try: sel.find_element_by_css_selector("#TANGRAM__PSP_10__userName").send_keys("username") print("input success!") except: print("input fail please input again") #查找密码输入框,进行输入密码,password就是对应的密码 try: sel.find_element_by_css_selector("#TANGRAM__PSP_10__password").send_keys("password") print("input password success") except: print("input password failed") #点击登录 try: sel.find_element_by_css_selector("#TANGRAM__PSP_10__submit").click() print("click success!") except: print("click failed") time.sleep(1) #点击发送验证码 try: sel.find_element_by_css_selector("#TANGRAM__36__button_send_mobile").click() print("send information to your moblephone") except: print("send information failed") time.sleep(1) #如何得到验证码并输入 #手机验证码暂时没有办法自动获取到,只能手动输入,这个问题主要是由于设置的安全登录引起的 try: info = input("输入验证码:") sel.find_element_by_css_selector("#TANGRAM__36__input_vcode").send_keys(info) print("input success") except: print("input failed") time.sleep(1) ###点击确定键 try: sel.find_element_by_css_selector("#TANGRAM__36__button_submit").click() print("click ensure success") except: print("click failed") 【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>> Motan Motan是一套高性能,易于使用的RPC框架。提供了服务治理,包括服务节点自动发现,摘除,高可用和负载均衡。Motan具有良好扩展性,主要模块都提供了不同实现,例如多种注册中心,多种rpc协议等。 功能 支持通过spring配置方式集成,无需额外编写代码即可以为服务提供分布式调用能力。 支持集成consul,zk等配置服务组件,提供集群环境服务发现及治理能力。 支持动态自定义负载均衡,跨机房流量调整等高级服务调度能力。 基于高并发,高负载场景进行优化,保障生产环境下RPC服务高可用。 模块 Motan框架中主要有register,transport,serialize,protocol几个功能,各个模块都支持通过SPI进行扩展。 registry 用来和注册中心交互,包括服务注册,服务订阅,服务变更通知,服务心跳发送等。server端会在系统初始化时通过registry模块注册服务,client端在系统初始化时通过registry模块订阅到服务提供者列表,当server列表变更时由registry模块通知client。 protocol 用来进行RPC服务端描述和RPC服务端配置管理,可以添加不同filter来统计并发限制等功能。 serialize 将RPC请求中的参数,结果等对象进行序列化和反序列化,进行对象与字节流互相转换,默认使用对java友好的hessian2。 transport 用来进行远程通信,默认使用Netty nio的TCP长链接方式。 cluster Client端使用的模块,cluster是一组可用的server在逻辑上的封装,包含若干可以提供RPC服务的server,实际请求时,会根据不同的高可用和负载均衡策略选择一个可用server发起远程调用。 在进行RPC请求时,client通过代理机制调用cluster模块,cluster根据配置和HA和LoadBalance选出一个可用的server,通过serialize模块把RPC请求转换成字节流,然后通过transport发送到server端。 配置 Motan将功能模块抽象为四个可配置的元素,分别为: Protocol:服务通信协议,服务提供方与消费方进行远程调用的协议,默认为motan协议,使用hessian2进行序列化,netty作为endpoint及使用motan自定义的协议编码。 Registry:注册中心,服务提供方将服务信息(ip,端口,服务策略等信息)注册到注册中心,服务消费方通过注册中心发现服务,当服务发生变更,注册中心负责通知各个消费方。 Service:服务提供方提供的服务。使用方将核心业务抽取出来,作为独立的服务,通过暴露服务并将服务注册到注册中心,从而使调用方调用。 Referer:服务消费方对服务的引用,服务调用方。 Motan扩展来6个自定义的Spring xml标签: motan:protocol motan:registry Motan:basicservice motan:service motan:basicreferer motan:referer |