数据专栏

智能大数据搬运工,你想要的我们都有

科技资讯:

科技学院:

科技百科:

科技书籍:

网站大全:

软件大全:

【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>>
条码打印软件 是一款应用广泛、功能灵活、操作简单的 标签设计软 件。软件工具箱中有很多功能按钮,使其能够在很短的时间内根据自己的需求在软件中设计出符合自己需要的标签。那么,在条码打印软件中该如何绘制圆角矩形呢?下面就给大家演示下绘制 圆角矩形 的方法,具体步骤如下:
打开条码打印软件,新建标签之后,点击软件左侧的“绘制圆角矩形”按钮,在画布上绘制一个圆角矩形对象。
双击圆角矩形,可以在图形属性-基本中,设置线条的线型、颜色、粗细、圆角等,都可以根据自己的需求在软件中自定义进行设置的。


通过上图我们可以看到圆角矩形内是空的,如果想要在矩形内填充类型,如:无、实心、斜线、反斜线、竖线、水平线、正网格、斜网格、左端起渐变、右端起渐变、顶端起渐变等,背景色、前景色,都可以可以根据自己的需求选择合适的类型。这里设置填充类型为左端起渐变、背景色为紫色、前景色为黄色,点击确定,效果如图所示:


条码打印软件支持绘制的图形类型众多,工具箱中有些功能隐藏看不到的话,可以在菜单栏绘图下拉列表中查看,功能还是比较齐全的。
以上就是在 条码打印软件 中绘制圆角矩形的操作方法,只要选择正确的绘图工具,就可以在画布中绘制出对应的图形。条码打印软件绘制的 圆角矩形 ,默认是直角的。如果对圆角弧度不满意的话,可以根据自己的需求进行调整。条码打印软件支持的图形类型众多,感兴趣的朋友,可以下载条码打印软件,自己动手尝试。
项目管理
2020-05-06 14:27:00
【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>>
学习在 Spring Cloud 中使用 Gateway 实现服务网关,包括基本使用、自动代理、 Predicate 、 Filter 等功能。
1 概述
Gateway 的主要功能如下: 限流 路径重写 动态路由 集成 Spring Cloud DiscoveryClient 集成 Hystrix 断路器
和 Zuul 相比,有如下区别: Zuul 是 Netflix 公司的开源产品, Spring Cloud Gateway 是 Spring 家族中的产品,可以和 Spring 家族中的其他组件更好的融合。 Zuul 不支持长连接(版本一),例如 Websocket 。 Spring Cloud Gateway 支持限流。 Spring Cloud Gateway 基于 Netty 来开发,实现了异步和非阻塞,占用资源更小,性能强于 Zuul 。
2 准备工作
2.1 服务注册
创建 Spring Boot 项目 gateway-client-provider ,作为我们的 服务提供者 ,添加 Web/Eureka Client 依赖,如下:
最终的依赖如下: org.springframework.boot spring-boot-starter-web org.springframework.cloud spring-cloud-starter-netflix-eureka-client org.springframework.boot spring-boot-starter-test test org.junit.vintage junit-vintage-engine
项目创建成功后,修改 application.properties 配置文件,将 gateway-client-provider 注册到 Eureka Server 上(服务注册中心使用 Eureka Server ),如下: # 当前服务的名称 spring.application.name=gateway-client-provider # 当前服务的端口 server.port=7000 # 服务注册中心地址 eureka.client.service-url.defaultZone=http://127.0.0.1:1111/eureka
接下来,启动 Eureka Server ,待服务注册中心启动成功后,再启动 gateway-client-provider ,两者都启动成功后,访问 http://127.0.0.1:1111 可以看到 gateway-client-provider 的注册信息。
当然 gateway-client-provider 也可以集群化部署,下面对 gateway-client-provider 进行打包,之后我们在命令行启动两个 provider 实例: java -jar gateway-client-provider-0.0.1-SNAPSHOT.jar --server.port=7000 java -jar gateway-client-provider-0.0.1-SNAPSHOT.jar --server.port=7001
最后在 gateway-client-provider 提供 hello 和 hello2 接口,用于后续服务消费者 gateway-client-consumer 来消费,如下: @RestController public class ProviderController { @Value("${server.port}") Integer port; // 支持启动多个实例,做负载均衡,用端口区分 @GetMapping("/hello") public String hello() { return "hello cxy35: " + port; } @GetMapping("/hello2") public String hello2(String name) { return "hello " + name; } }
2.2 服务消费
创建 Spring Boot 项目 gateway-client-consumer ,作为我们的 服务消费者 ,添加 Eureka Client/Gateway 依赖,如下:
最终的依赖如下: org.springframework.cloud spring-cloud-starter-gateway org.springframework.cloud spring-cloud-starter-netflix-eureka-client org.springframework.boot spring-boot-starter-test test org.junit.vintage junit-vintage-engine
项目创建成功后,新建 application.yml 配置文件,将 gateway-client-consumer 注册到 Eureka Server 上(服务注册中心使用 Eureka Server ),如下: # 当前服务的名称 spring: application: name: gateway-client-consumer # 当前服务的端口 server: port: 7002 # 服务注册中心地址 eureka: client: service-url: defaultZone: http://127.0.0.1:1111/eureka
接下来,启动 gateway-client-consumer ,访问 http://127.0.0.1:1111 可以看到 gateway-client-consumer 的注册信息。
3 基本使用
Spring Cloud Gateway 支持两种不同的配置方法: 编码配置、 YML 配置 。 编码配置
在 gateway-client-consumer 项目启动类上配置一个 RouteLocator 类型的 Bean,就可以实现请求转发,如下: @SpringBootApplication public class GatewayClientConsumerApplication { public static void main(String[] args) { SpringApplication.run(GatewayClientConsumerApplication.class, args); } @Bean RouteLocator routeLocator(RouteLocatorBuilder builder) { return builder.routes() .route("cxy35_route", r -> r.path("/get").uri("http://httpbin.org")) // 这是一个测试的地址 .build(); } }
配置完成后,重启项目,访问 http://127.0.0.1:7002/get 完成测试。 YML 配置
注释掉上述 Java 代码,再修改 gateway-client-consumer 中的 application.yml 配置文件: # 当前服务的名称 spring: application: name: gateway-client-consumer cloud: gateway: routes: - id: cxy35_route uri: http://httpbin.org # 这是一个测试的地址 predicates: - Path=/get # 当前服务的端口 server: port: 7002 # 服务注册中心地址 eureka: client: service-url: defaultZone: http://127.0.0.1:1111/eureka
配置完成后,重启项目,访问 http://127.0.0.1:7002/get 完成测试。
4 结合微服务使用
修改 gateway-client-consumer 中的 application.yml 配置文件: # 当前服务的名称 spring: application: name: gateway-client-consumer cloud: gateway: routes: - id: cxy35_route uri: http://httpbin.org # 这是一个测试的地址 predicates: - Path=/get discovery: locator: enabled: true # 开启自动代理 # 当前服务的端口 server: port: 7002 # 服务注册中心地址 eureka: client: service-url: defaultZone: http://127.0.0.1:1111/eureka logging: level: org.springframework.cloud.gateway: debug
接下来,就可以通过 Gateway 访问到其他注册在 Eureka 上的服务了,如 http://127.0.0.1:7002/GATEWAY-CLIENT-PROVIDER/hello ,注意大小写问题。
5 Predicate
路由配置 Route 中的 Predicate 支持多种配置方式: 通过时间匹配: After (表示在某个时间点之前进行请求转发)/ Before (表示在某个时间点之前进行请求转发)/ Between (表示在两个时间点之间,两个时间点用 , 隔开)。 spring: cloud: gateway: routes: - id: cxy35_route2 uri: http://httpbin.org predicates: - After=2021-01-01T01:01:01+08:00[Asia/Shanghai] 通过请求方法匹配: Method 。 spring: cloud: gateway: routes: - id: cxy35_route2 uri: http://httpbin.org predicates: - Method=GET 通过请求路径匹配: Path 。 spring: cloud: gateway: routes: - id: cxy35_route2 uri: http://httpbin.org predicates: - Path=/2020/01/{segment}
上述配置表示路径满足 /2020/01/ 这个规则,都会被进行转发。 通过请求参数名或值匹配: Query 。 spring: cloud: gateway: routes: - id: cxy35_route2 uri: http://httpbin.org predicates: - Query=name
上述配置表示请求中一定要有 name 参数才会进行转发,否则不会进行转发。
也可以同时指定参数名和参数值,例如参数的 key 为 name , value 必须要以 java 开始: spring: cloud: gateway: routes: - id: cxy35_route2 uri: http://httpbin.org predicates: - Query=name,java.* 多种匹配方式组合使用。 spring: cloud: gateway: routes: - id: cxy35_route2 uri: http://httpbin.org predicates: - After=2021-01-01T01:01:01+08:00[Asia/Shanghai] - Method=GET - Query=name,java.*
6 Filter
Spring Cloud Gateway 中的过滤器分为两大类: GatewayFilter / GlobalFilter 。
比如 AddRequestParameter 过滤器会在请求转发路由的时候,自动额外添加参数,如: spring: cloud: gateway: routes: - id: cxy35_route3 uri: lb://gateway-client-provider filters: - AddRequestParameter=name,cxy35 predicates: - Method=GET
配置完成后,重启项目,访问 http://127.0.0.1:7002/hello2 完成测试。 Spring Cloud 教程合集 (微信左下方 阅读全文 可直达)。 Spring Cloud 教程合集示例代码: https://github.com/cxy35/spring-cloud-samples 本文示例代码: https://github.com/cxy35/spring-cloud-samples/tree/master/spring-cloud-gateway
扫码关注微信公众号 程序员35 ,获取最新技术干货,畅聊 #程序员的35,35的程序员# 。独立站点: https://cxy35.com
项目管理
2020-05-05 09:26:00
【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>>
version: '3'
services:
jenkins:
image: jenkinsci/blueocean
user: root
restart: always
ports:
- 8080:8080
- 50000:50000
volumes:
- /opt/jenkins/jenkins/:/var/jenkins_home/
- /opt/jenkins/docker.sock/:/var/run/docker.sock/
项目管理
2020-04-27 15:37:00
【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>>
公司使用gitlab管理项目,顺便温习git相关命令操作,方便以后查阅。
清空记录的账号和密码:git config --system --unset credential.helper

每次需要填写账户和密码时,cmd到项目根目录:git config --global credential.helper store,输入一次账号名密码即可。

查看项目信息:
查看git同步地址:git remote -v
移除同步地址:git remote rm origin
添加同步地址:git remote add origin 项目地址

拉取项目
ssh:
默认端口:git clone git@ip地址:用户名/text-classification.git
指定端口: git clone ssh://git@ip地址:端口/root/test.git

提交项目
更新提交:git push -u origin master
强制提交:git push -f origin master

忽略文件:
项目根目录创建爱你 .gitignore文件,添加忽略的文件夹和文件
public/
static.txt
项目管理
2020-04-21 14:33:00
【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>>
用手机怎么制作钻石雨视频?最近网络上一个钻石雨的视频开始爆火,新出的视频总会有很多人争相去模仿,甚至有人拿出水晶拍摄了低配版的七彩水晶雨,让大家心生羡慕。今天小编就教大家不出门怎么完成钻石雨视频。
打开“清爽视频编辑”APP,在软件首页下拉到模板块,在“上新”中找到“为你下场钻石雨”模板,点击进去,进入模板详情页面,在详情页面可以看到需要提供的资源只有一张图片。
点击详情页面下方的一键制作进入图片选择页面,在照片页面我们可以看到自己手机中的各种图片,根据需要选择一张插入到视频中的合适图片,选好之后点击页面右下角的下一步开始编辑。
在编辑页面,我们可以看到需要调整的有两个部分,一是素材输入,点击图片缩略图,这里我们可以对图片进行编辑和替换,第二个则是背景音乐的音量,模板提供的不止有视频,还有配套的音乐、特效等等,大家根据自己的习惯对音量进行调整就可以了。
调整好之后点击页面右上角的导出,选择导出分辨率将视频保存到手机里。以上就是用手机制作钻石雨视频的具体操作步骤,感兴趣的小伙伴可以打开清爽视频编辑,软件中还有很多模板可以套用,快动手试试吧。
项目管理
2020-05-30 11:36:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
观察者模式
当对象间存在一对多关系时,则使用观察者模式(Observer Pattern)。比如,当一个对象被修改时,则会自动通知它的依赖对象。观察者模式属于行为型模式。
介绍
意图: 定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
主要解决: 一个对象状态改变给其他对象通知的问题,而且要考虑到易用和低耦合,保证高度的协作。
何时使用: 一个对象(目标对象)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知,进行广播通知。
如何解决: 使用面向对象技术,可以将这种依赖关系弱化。
关键代码: 在抽象类里有一个 ArrayList 存放观察者们。
应用实例: 1、拍卖的时候,拍卖师观察最高标价,然后通知给其他竞价者竞价。 2、西游记里面悟空请求菩萨降服红孩儿,菩萨洒了一地水招来一个老乌龟,这个乌龟就是观察者,他观察菩萨洒水这个动作。
优点: 1、观察者和被观察者是抽象耦合的。 2、建立一套触发机制。
缺点: 1、如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。 2、如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。 3、观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。
使用场景: 一个抽象模型有两个方面,其中一个方面依赖于另一个方面。将这些方面封装在独立的对象中使它们可以各自独立地改变和复用。 一个对象的改变将导致其他一个或多个对象也发生改变,而不知道具体有多少对象将发生改变,可以降低对象之间的耦合度。 一个对象必须通知其他对象,而并不知道这些对象是谁。 需要在系统中创建一个触发链,A对象的行为将影响B对象,B对象的行为将影响C对象……,可以使用观察者模式创建一种链式触发机制。
注意事项: 1、JAVA 中已经有了对观察者模式的支持类。 2、避免循环引用。 3、如果顺序执行,某一观察者错误会导致系统卡壳,一般采用异步方式。
实现
观察者模式使用三个类 Subject、Observer 和 Client。Subject 对象带有绑定观察者到 Client 对象和从 Client 对象解绑观察者的方法。我们创建 Subject 类、 Observer 抽象类和扩展了抽象类 Observer 的实体类。
ObserverPatternDemo ,我们的演示类使用 Subject 和实体类对象来演示观察者模式。

步骤 1
创建 Subject 类。 // 被观察者 class Subject{ private: list _observers; int _state; public: int GetState(){ return _state; } void SetState(int state){ _state = state; NotifyAllObservers(); } void Attach(Observer * observer){ _observers.push_back(observer); } void NotifyAllObservers(){ list::iterator iter = _observers.begin(); for(;iter != _observers.end();iter++){ (*iter)->update(); } } };
步骤 2
创建 Observer 类。 class Subject; class Observer{ protected: Subject * _subject; public: Observer(Subject *subject) : _subject(subject) {} virtual ~Observer(){} virtual void update() = 0; };
步骤 3
创建实体观察者类。 class BinaryObserver : public Observer{ public: BinaryObserver(Subject *subject) : Observer(subject) {_subject->Attach(this);} void update() override { std::cout << "Binary string :" << _subject->GetState() << std::endl; } }; class OctalObserver : public Observer{ public: OctalObserver(Subject *subject) : Observer(subject) {_subject->Attach(this);} void update() override { std::cout << "Octal String:" << _subject->GetState() << std::endl; } }; class HexaObserver : public Observer{ public: HexaObserver(Subject *subject) : Observer(subject) {_subject->Attach(this);} void update() override { std::cout << "Hex String:" << _subject->GetState() << std::endl; } };
步骤 4
使用 Subject 和实体观察者对象。 Subject * subject = new Subject(); Observer * o1 = new BinaryObserver(subject); Observer * o2 = new OctalObserver(subject); Observer * o3 = new HexaObserver(subject); subject->SetState(1); subject->SetState(3);
步骤 5
验证输出。 Binary string :1 Octal String:1 Hex String:1 Binary string :3 Octal String:3 Hex String:3


项目管理
2018-06-03 21:25:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
解释器模式
解释器模式(Interpreter Pattern)提供了评估语言的语法或表达式的方式,它属于行为型模式。这种模式实现了一个表达式接口,该接口解释一个特定的上下文。这种模式被用在 SQL 解析、符号处理引擎等。
适用性:
在以下情况下可以考虑使用解释器模式:
(1) 可以将一个需要解释执行的语言中的句子表示为一个抽象语法树。
(2) 一些重复出现的问题可以用一种简单的语言来进行表达。
(3) 一个语言的文法较为简单。
(4) 执行效率不是关键问题。(注:高效的解释器通常不是通过直接解释抽象语法树来实现的,而是需要将它们转换成其他形式,使用解释器模式的执行效率并不高。)
优缺点:
优点:
(1) 易于改变和扩展文法。由于在解释器模式中使用类来表示语言的文法规则,因此可以通过继承等机制来改变或扩展文法。
(2) 每一条文法规则都可以表示为一个类,因此可以方便地实现一个简单的语言。
(3) 实现文法较为容易。在抽象语法树中每一个表达式节点类的实现方式都是相似的,这些类的代码编写都不会特别复杂,还可以通过一些工具自动生成节点类代码。
(4) 增加新的解释表达式较为方便。如果用户需要增加新的解释表达式只需要对应增加一个新的终结符表达式或非终结符表达式类,原有表达式类代码无须修改,符合“开闭原则”。
缺点:
(1) 对于复杂文法难以维护。在解释器模式中,每一条规则至少需要定义一个类,因此如果一个语言包含太多文法规则,类的个数将会急剧增加,导致系统难以管理和维护,此时可以考虑使用语法分析程序等方式来取代解释器模式。
(2) 执行效率较低。由于在解释器模式中使用了大量的循环和递归调用,因此在解释较为复杂的句子时其速度很慢,而且代码的调试过程也比较麻烦。
总论:
尽量不要在重要模块中使用解释器模式,因为维护困难。在项目中,可以使用脚本语言来代替解释器模式。
实现

抽象表达式角色 (AbstractExpression): 声明一个抽象的解释操作,这个接口为所有具体表达式角色都要实现的。
终结符表达式角色 (TerminalExpression): 实现与文法中的元素相关联的解释操作,通常一个解释器模式中只有一个终结符表达式,但有多个实例对应不同的终结符,
终结符就是语言中用到的基本元素,一般不能再被分解,如: x -> xa, 这里a是终结符,因为没有别的规则可以把a变成别的符号,不过x可以变成别的符号,所以x是非终结符。
非终结符表达式角色 (NonterminalExpression): 文法中的每条规则对应于一个非终结表达式, 非终结表达式根据逻辑的复杂程度而增加,原则上每个文法规则都对应一个非终结符表达式。
环境角色 (Context):包含解释器之外的一些全局信息。
步骤 1
创建上下文 class Contex{ private: map _valueMap; public: int getValue(const string & key){ return _valueMap[key]; } void addValue( const string & key,int value){ _valueMap.insert(pair(key,value)); } };

步骤 2
创建抽象表达式接口 class AbstractExpression{ public: virtual ~ AbstractExpression(){} virtual int interpreter(Contex & contex) = 0; };

步骤 3
创建两种非终结表达式 class AddNonterminalExpression : public AbstractExpression{ private: AbstractExpression * _left; AbstractExpression * _right; public: AddNonterminalExpression(AbstractExpression * left,AbstractExpression * right):_left(left) ,_right(right){} int interpreter(Contex &contex) override { return _left->interpreter(contex) + _right->interpreter(contex); } }; class SubNonterminalExpression : public AbstractExpression{ private: AbstractExpression * _left; AbstractExpression * _right; public: SubNonterminalExpression(AbstractExpression *_left, AbstractExpression *_right) : _left(_left), _right(_right) {} int interpreter(Contex &contex) override { return _left->interpreter(contex) - _right->interpreter(contex); } };

步骤 4
创建终结表达式实现 class TerminalExpresiion : public AbstractExpression{ private: int _i;//终结符不能再切割 public: TerminalExpresiion(int i) : _i(i) {} int interpreter(Contex &contex) override { return _i; } };

步骤 5
验证 Contex contex; contex.addValue("a",1); contex.addValue("b",2); contex.addValue("c",3); AbstractExpression * sub_left = new TerminalExpresiion(contex.getValue("a")); AbstractExpression * sub_right = new TerminalExpresiion(contex.getValue("b")); AbstractExpression * subvalue = new SubNonterminalExpression(sub_left,sub_right); AbstractExpression * add_right = new TerminalExpresiion(contex.getValue("c")); AbstractExpression * addvalue = new AddNonterminalExpression(subvalue,add_right); std::cout << addvalue->interpreter(contex) << std::endl; delete(addvalue); delete(add_right); delete(subvalue); delete(sub_right); delete(sub_left);
输出结果 2
项目管理
2018-06-03 20:52:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
装饰器模式
装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构。这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装。
这种模式创建了一个装饰类,用来包装原有的类,并在保持类方法签名完整性的前提下,提供了额外的功能。
我们通过下面的实例来演示装饰器模式的用法。其中,我们将把一个形状装饰上不同的颜色,同时又不改变形状类。
介绍
意图: 动态地给一个对象添加一些额外的职责。就增加功能来说,装饰器模式相比生成子类更为灵活。
主要解决: 一般的,我们为了扩展一个类经常使用继承方式实现,由于继承为类引入静态特征,并且随着扩展功能的增多,子类会很膨胀。
何时使用: 在不想增加很多子类的情况下扩展类。
如何解决: 将具体功能职责划分,同时继承装饰者模式。
关键代码: 1、Component 类充当抽象角色,不应该具体实现。 2、修饰类引用和继承 Component 类,具体扩展类重写父类方法。
应用实例: 1、孙悟空有 72 变,当他变成"庙宇"后,他的根本还是一只猴子,但是他又有了庙宇的功能。 2、不论一幅画有没有画框都可以挂在墙上,但是通常都是有画框的,并且实际上是画框被挂在墙上。在挂在墙上之前,画可以被蒙上玻璃,装到框子里;这时画、玻璃和画框形成了一个物体。
优点: 装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能。
缺点: 多层装饰比较复杂。
使用场景: 1、扩展一个类的功能。 2、动态增加功能,动态撤销。
注意事项: 可代替继承。
实现
我们将创建一个 Shape 接口和实现了 Shape 接口的实体类。然后我们创建一个实现了 Shape 接口的抽象装饰类 ShapeDecorator ,并把 Shape 对象作为它的实例变量。
RedShapeDecorator 是实现了 ShapeDecorator 的实体类。
DecoratorPatternDemo ,我们的演示类使用 RedShapeDecorator 来装饰 Shape 对象。

步骤 1
创建一个接口。 class Shape{ public: virtual ~Shape(){}; virtual void draw() = 0; };
步骤 2
创建实现接口的实体类。 class Circle : public Shape{ public: void draw() override { std::cout << "draw a circle" << std::endl; } }; class Rectangle : public Shape{ public: void draw() override { std::cout << "draw a rectangle" << std::endl; } };
步骤 3
创建实现了 Shape 接口的抽象装饰类。 class ShapDecorator: public Shape{ public: explicit ShapDecorator(Shape * shape):_shape(shape){} virtual void draw() override { _shape->draw(); } protected: Shape * _shape; };
步骤 4
创建扩展了 ShapeDecorator 类的实体装饰类。 class RedShapeDecorator:public ShapDecorator{ public: explicit RedShapeDecorator(Shape *shape) : ShapDecorator(shape) { } void draw() override { std::cout << "" << std::endl; _shape->draw(); std::cout << "" << std::endl; } }; class GreenShapeDecorator:public ShapDecorator{ public: explicit GreenShapeDecorator(Shape *shape) : ShapDecorator(shape) { } void draw() override { std::cout << ""<< std::endl; _shape->draw(); std::cout << "" << std::endl; } };
步骤 5
使用 RedShapeDecorator 和 GreenShapeDecorator 来装饰 Shape 对象。 Shape * circle = new Circle(); Shape * rectangle = new Rectangle(); circle->draw(); rectangle->draw(); Shape * redDecorator = new RedShapeDecorator(circle); redDecorator->draw(); Shape * greenDecorator = new GreenShapeDecorator(circle); greenDecorator->draw(); delete(greenDecorator); delete(redDecorator); delete(rectangle); delete(circle);
步骤 6
验证输出。 draw a circle draw a rectangle draw a circle draw a circle

项目管理
2018-06-03 20:02:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
迭代器模式
迭代器模式(Iterator Pattern)是 Java 和 .Net 编程环境中非常常用的设计模式。这种模式用于顺序访问集合对象的元素,不需要知道集合对象的底层表示。
迭代器模式属于行为型模式。
介绍
意图: 提供一种方法顺序访问一个聚合对象中各个元素, 而又无须暴露该对象的内部表示。
主要解决: 不同的方式来遍历整个整合对象。
何时使用: 遍历一个聚合对象。
如何解决: 把在元素之间游走的责任交给迭代器,而不是聚合对象。
关键代码: 定义接口:First, Next,isEnd,CurItem,。
应用实例: JAVA 中的 iterator。
优点: 1、它支持以不同的方式遍历一个聚合对象。 2、迭代器简化了聚合类。 3、在同一个聚合上可以有多个遍历。 4、在迭代器模式中,增加新的聚合类和迭代器类都很方便,无须修改原有代码。
缺点: 由于迭代器模式将存储数据和遍历数据的职责分离,增加新的聚合类需要对应增加新的迭代器类,类的个数成对增加,这在一定程度上增加了系统的复杂性。
使用场景: 1、访问一个聚合对象的内容而无须暴露它的内部表示。 2、需要为聚合对象提供多种遍历方式。 3、为遍历不同的聚合结构提供一个统一的接口。
注意事项: 迭代器模式就是分离了集合对象的遍历行为,抽象出一个迭代器类来负责,这样既可以做到不暴露集合的内部结构,又可让外部代码透明地访问集合内部的数据。
实现
我们将创建一个叙述导航方法的 Iterator 接口和一个返回迭代器的 Container 接口。实现了 Container 接口的实体类将负责实现 Iterator 接口。
IteratorPatternDemo ,我们的演示类使用实体类 NamesRepository 来打印 NamesRepository 中存储为集合的 Names 。

步骤 1
创建接口
Iterator // interface for iterator template class Iterator{ public: virtual ~Iterator(){} virtual void First() = 0; virtual void Next() = 0; virtual bool isEnd() = 0; virtual Item * CurItem() = 0; };
Container //iterface for container template class Container{ public: Container() = default; virtual ~Container(){} virtual int getSize() = 0; virtual void pushItem(const Item & item) = 0; virtual Item &operator[](int index) = 0; virtual Iterator * getIterator() = 0; };
步骤 2
创建实现了 Container 接口的实体类 。 template class ConcreteContainer : public Container{ public: virtual ~ConcreteContainer(){ if(_iterator) delete(_iterator); } int getSize() override { return _data.size(); } void pushItem(const Item &item) override { _data.push_back(item); } Item &operator[](int index) override { return _data[index]; } Iterator *getIterator() override { if(_iterator) return _iterator; else return _iterator = new ConcreteIterator(this); } private: std::vector _data; Iterator * _iterator{nullptr}; };
步骤 3
创建迭代器实现类 template class ConcreteIterator : public Iterator{ public: ConcreteIterator(Container * container):_container(container),_curIndex(0){ } void First() override { _curIndex = 0; } void Next() override { if(_curIndex < _container->getSize()) _curIndex++; } bool isEnd() override { return _curIndex >= _container->getSize(); } Item * CurItem() override { if(_curIndex < _container->getSize()){ return &(*_container)[_curIndex]; }else{ return nullptr; } } private: Container * _container; int _curIndex; };
步骤 4
验证 Container * container = new ConcreteContainer(); container->pushItem(1); container->pushItem(2); container->pushItem(3); container->pushItem(4); Iterator * iterator = container->getIterator(); for(iterator->First();!iterator->isEnd();iterator->Next()){ std::cout << *iterator->CurItem() << std::endl; }
输出 1 2 3 4
项目管理
2018-06-03 14:41:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
概念
什么是JPA JPA(Java Persistence API)是Sun官方提出的Java持久化规范。它为Java开发人员提供了一种对象/关系映射工具来管理Java应用中的关系数据。 JPA通过JDK5.0注解或者XML描述对象 - 关系表的映射关系,并将运行期的实体对象持久化到数据库中。 持久化(Persistence),即把数据保存到可永久保存的存储设备中,如磁盘。持久化的主要应用是将内存中的对象存储在数据库中,或者存储在磁盘文件中、XML数据文件中等...
什么是Spring Data Spring Data是一个用于简化数据库访问,并支持云服务的开源框架。其主要目的是使用数据库访问变得方便快捷,并支持MapReduce框架和云计算数据服务。它还支持基于关系型数据库的数据服务,如Oracle RAC等。对于拥有海量数据的项目,可以使用Spring Data来简化项目的开发,就如Spring Framework对JDBC、ORM的支持一样,Spring Data会让数据的访问变得更加方便。
什么是Spring Data JPA Spring Data JPA:可以简化JPA的写法,可以在不用写实现的情况下,实现对数据的访问和操作。除了CRUD(Create、Retrieve、Update、Delete)外,还包括分页、排序等一些常用的功能。 我们清楚的是Spring Data是一个开源框架,在这个框架中Spring Data JPA只是这个框架中的一个模块,所以名称才叫Spring Data JPA。如果单独使用JPA开发,会发现这个代码量和使用JDBC开发一样有点烦人,所以Spring Data JPA的出现就为了简化JPA的写法,让你只需要编写一个接口继承一个类就能实现CRUD操作了。

配置Spring JPA
修改pom.xml配置文件 mysql mysql-connector-java org.springframework.boot spring-boot-starter-data-jpa
application.properties文件加入配置 #指定MySql数据库连接信息 spring.datasource.url=jdbc:mysql://localhost:3306/test spring.datasource.username=root spring.datasource.password=root spring.datasource.diverClassName=com.mysql.jdbc.Driver spring.datasource.max-active==20 spring.datasource.max-idle=8 spring.datasource.min-idle=8 spring.datasource.initial-size=10 #配置spring JPA信息 spring.jpa.database=MYSQL #控制台是否打印SQL信息 spring.jpa.show-sql=true #当执行DDL语句时,是创建还是更新还是删除等...create、update、drop-create spring.jpa.hibernate.ddl-auto=update #持久化命名策略,当创建一个实体类,会在相应的数据库中新增一张该实体类对应的数据库表结构 spring.jpa.hibernate.naming-strategy=org.hibernate.cfg.ImprovedNamingStrategy spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5Dialect
创建实体类 package com.cori.demo.bean; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; /** * 创建一个用户实体类 * * 使用@Entity进行实体类持久化操作,当JPA检测到实体类当中有@Entity注解时,则会在数据库中生成该类对应的表结构性信息, * 如属性为驼峰命名法,则对应生成的表字段为,如:userId -> user_id(可自己定义); * 使用@Id指定主键 * * @author Administrator * */ @Entity public class UserBean { /** * @Id指定主键 * @GeneratedValue(strategy=GenerationType.AUTO) 指定主键的生成策略,MySQL为自动增长 */ @Id @GeneratedValue(strategy = GenerationType.AUTO) private Integer userId; private String userName; private Integer userAge; private Integer userSex; private String idCard; public Integer getUserId() { return userId; } public void setUserId(Integer userId) { this.userId = userId; } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public Integer getUserAge() { return userAge; } public void setUserAge(Integer userAge) { this.userAge = userAge; } public Integer getUserSex() { return userSex; } public void setUserSex(Integer userSex) { this.userSex = userSex; } public String getIdCard() { return idCard; } public void setIdCard(String idCard) { this.idCard = idCard; } }
创建实体类之后,这一步启动服务,然后进入MySQL数据库中查看,可以看到对应的user_bean表结构已经创建好了。

创建接口 创建接口继承CrudRepository package com.cori.demo.Repository; import org.springframework.data.repository.CrudRepository; import com.cori.demo.bean.UserBean; public interface UserRepository extends CrudRepository{ }

创建Service package com.cori.demo.service; import javax.annotation.Resource; import javax.transaction.Transactional; import org.springframework.stereotype.Service; import com.cori.demo.Repository.UserRepository; import com.cori.demo.bean.UserBean; @Service public class UserService { @Resource private UserRepository userRepository; //新增记录 @Transactional public void save(UserBean bean) { userRepository.save(bean); } //删除记录 @Transactional public void delete(int id) { userRepository.delete(id); } //查询记录 public Iterable findAll() { return userRepository.findAll(); } }

创建Controller package com.cori.demo.controller; import javax.annotation.Resource; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import com.cori.demo.bean.UserBean; import com.cori.demo.service.UserService; @RestController @RequestMapping("/user") public class UserController { @Resource private UserService userService; @RequestMapping("/save") public String save() { UserBean bean = new UserBean(); bean.setUserName("张三"); bean.setUserAge(28); bean.setUserSex(0); bean.setIdCard("123456789123456789"); userService.save(bean); return "ok"; } @RequestMapping("/delete") public String delete() { userService.delete(1); return "ok"; } @RequestMapping("/getAll") public Iterable findAll(){ return userService.findAll(); } }
到这里已经实现了简单的增删查功能了,可以启动服务进行测试了。

总结 需要在POM.xml文件中添加相应的依赖包; 需要在application.properties配置文件中添加配置信息; 需要创建实体类,比如:User; 需要创建接口类并继承CrudRepository; 需要创建Service类; 需要创建Controller类; 测试;

项目管理
2018-06-03 01:15:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
详解介绍Selenium常用API的使用--Java语言(完整版)
项目管理
2018-06-02 09:52:00
「深度学习福利」大神带你进阶工程师,立即查看>>> 如果 Confluence 提示没有 class 文件,你可能将你的 JDBC 驱动放置到了错误的文件夹。 如果你不能从你从 Confluence 中连接到 PostgreSQL ,并且这 2 个服务器分别在不同的机器上,可能是因为你 2 台机器上的防火墙阻止了你的连接,或者 pg_hba.conf 文件没有被正确配置。检查你的防火墙配置,能够允许端口 5432 能够正常通行,并且再次检查你的 hba configuration 配置文件。 下面的连接包含了一些你在使用 PostgreSQL 数据库连接的时候可能会遇到的常见问题,请参考: Known issues for PostgreSQL 。

https://www.cwiki.us/display/CONFLUENCEWIKI/Database+Setup+for+PostgreSQL
项目管理
2018-06-02 03:50:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
在你的数据库设置界面,有一个 测试连接(Test connection) 按钮可以检查: Confluence 可以连接你的数据库服务器 数据库字符集的编码是否设置正确 你的数据库用户是否有正确的权限可以访问和使用数据库
一旦上面的测试成功了,单击 下一步(Next ) 来继续进行 Confluence 的安装进程。
如果 Confluence 和 PostgreSQL 分别在不同的服务器上,请参考 PostgreSQL documentation on how to set up pg_hba.conf 中的内容来确定 Confluence 和 PostgreSQL 之间能正常通信。

https://www.cwiki.us/display/CONFLUENCEWIKI/Database+Setup+for+PostgreSQL
项目管理
2018-06-02 03:46:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
由于Spring Boot能够快速开发、便捷部署等特性,相信有很大一部分Spring Boot的用户会用来构建RESTful API。而我们构建RESTful API的目的通常都是由于多终端的原因,这些终端会共用很多底层业务逻辑,因此我们会抽象出这样一层来同时服务于多个移动端或者Web前端。
这样一来,我们的RESTful API就有可能要面对多个开发人员或多个开发团队:IOS开发、Android开发或是Web开发等。为了减少与其他团队平时开发期间的频繁沟通成本,传统做法我们会创建一份RESTful API文档来记录所有接口细节,然而这样的做法有以下几个问题: 由于接口众多,并且细节复杂(需要考虑不同的HTTP请求类型、HTTP头部信息、HTTP请求内容等),高质量地创建这份文档本身就是件非常吃力的事,下游的抱怨声不绝于耳。 随着时间推移,不断修改接口实现的时候都必须同步修改接口文档,而文档与代码又处于两个不同的媒介,除非有严格的管理机制,不然很容易导致不一致现象。
为了解决上面这样的问题,本文将介绍RESTful API的重磅好伙伴Swagger2,它可以轻松的整合到Spring Boot中,并与Spring MVC程序配合组织出强大RESTful API文档。它既可以减少我们创建文档的工作量,同时说明内容又整合入实现代码中,让维护文档和修改代码整合为一体,可以让我们在修改代码逻辑的同时方便的修改文档说明。另外Swagger2也提供了强大的页面测试功能来调试每个RESTful API。具体效果如下图所示:
alt=
下面来具体介绍,如果在Spring Boot中使用Swagger2。首先,我们需要一个Spring Boot实现的RESTful API工程,若您没有做过这类内容,建议先阅读
Spring Boot构建一个较为复杂的RESTful APIs和单元测试 。
下面的内容我们会以 教程样例 中的Chapter3-1-1进行下面的实验(Chpater3-1-5是我们的结果工程,亦可参考)。
添加Swagger2依赖
在 pom.xml 中加入Swagger2的依赖 io.springfox springfox-swagger2 2.2.2 io.springfox springfox-swagger-ui 2.2.2
创建Swagger2配置类
在 Application.java 同级创建Swagger2的配置类 Swagger2 。 @Configuration @EnableSwagger2 public class Swagger2 { @Bean public Docket createRestApi() { return new Docket(DocumentationType.SWAGGER_2) .apiInfo(apiInfo()) .select() .apis(RequestHandlerSelectors.basePackage("com.didispace.web")) .paths(PathSelectors.any()) .build(); } private ApiInfo apiInfo() { return new ApiInfoBuilder() .title("Spring Boot中使用Swagger2构建RESTful APIs") .description("更多Spring Boot相关文章请关注:http://blog.didispace.com/") .termsOfServiceUrl("http://blog.didispace.com/") .contact("程序猿DD") .version("1.0") .build(); } }
如上代码所示,通过 @Configuration 注解,让Spring来加载该类配置。再通过 @EnableSwagger2 注解来启用Swagger2。
再通过 createRestApi 函数创建 Docket 的Bean之后, apiInfo() 用来创建该Api的基本信息(这些基本信息会展现在文档页面中)。 select() 函数返回一个 ApiSelectorBuilder 实例用来控制哪些接口暴露给Swagger来展现,本例采用指定扫描的包路径来定义,Swagger会扫描该包下所有Controller定义的API,并产生文档内容(除了被 @ApiIgnore 指定的请求)。
项目管理
2018-06-05 17:23:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
在完成配置之后,举一个简单的例子,在快速入门工程的基础上,举一个简单的示例来通过Thymeleaf渲染一个页面。 @Controller public class HelloController { @RequestMapping("/") public String index(ModelMap map) { // 加入一个属性,用来在模板中读取 map.addAttribute("host", "http://blog.didispace.com"); // return模板文件的名称,对应src/main/resources/templates/index.html return "index"; } }

Hello World


如上页面,直接打开html页面展现Hello World,但是启动程序后,访问 http://localhost:8080/ ,则是展示Controller中host的值: http://blog.didispace.com ,做到了不破坏HTML自身内容的数据逻辑分离。
更多Thymeleaf的页面语法,还请访问Thymeleaf的官方文档查询使用。
Thymeleaf的默认参数配置
如有需要修改默认配置的时候,只需复制下面要修改的属性到 application.properties 中,并修改成需要的值,如修改模板文件的扩展名,修改默认的模板路径等。 # Enable template caching. spring.thymeleaf.cache=true # Check that the templates location exists. spring.thymeleaf.check-template-location=true # Content-Type value. spring.thymeleaf.content-type=text/html # Enable MVC Thymeleaf view resolution. spring.thymeleaf.enabled=true # Template encoding. spring.thymeleaf.encoding=UTF-8 # Comma-separated list of view names that should be excluded from resolution. spring.thymeleaf.excluded-view-names= # Template mode to be applied to templates. See also StandardTemplateModeHandlers. spring.thymeleaf.mode=HTML5 # Prefix that gets prepended to view names when building a URL. spring.thymeleaf.prefix=classpath:/templates/ # Suffix that gets appended to view names when building a URL. spring.thymeleaf.suffix=.html spring.thymeleaf.template-resolver-order= # Order of the template resolver in the chain. spring.thymeleaf.view-names= # Comma-separated list of view names that can be resolved.
项目管理
2018-06-05 16:52:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
静态资源访问
在我们开发Web应用的时候,需要引用大量的js、css、图片等静态资源。
默认配置
Spring Boot默认提供静态资源目录位置需置于classpath下,目录名需符合如下规则: /static /public /resources /META-INF/resources
举例:我们可以在 src/main/resources/ 目录下创建 static ,在该位置放置一个图片文件。启动程序后,尝试访问 http://localhost:8080/D.jpg 。如能显示图片,配置成功。
渲染Web页面
在之前的示例中,我们都是通过@RestController来处理请求,所以返回的内容为json对象。那么如果需要渲染html页面的时候,要如何实现呢?
模板引擎
在动态HTML实现上Spring Boot依然可以完美胜任,并且提供了多种模板引擎的默认配置支持,所以在推荐的模板引擎下,我们可以很快的上手开发动态网站。
Spring Boot提供了默认配置的模板引擎主要有以下几种: Thymeleaf FreeMarker Velocity Groovy Mustache
Spring Boot建议使用这些模板引擎,避免使用JSP,若一定要使用JSP将无法实现Spring Boot的多种特性,具体可见后文:支持JSP的配置
当你使用上述模板引擎中的任何一个,它们默认的模板配置路径为: src/main/resources/templates 。当然也可以修改这个路径,具体如何修改,可在后续各模板引擎的配置属性中查询并修改。
Thymeleaf
Thymeleaf是一个XML/XHTML/HTML5模板引擎,可用于Web与非Web环境中的应用开发。它是一个开源的Java库,基于Apache License 2.0许可,由Daniel Fernández创建,该作者还是Java加密库Jasypt的作者。
Thymeleaf提供了一个用于整合Spring MVC的可选模块,在应用开发中,你可以使用Thymeleaf来完全代替JSP或其他模板引擎,如Velocity、FreeMarker等。Thymeleaf的主要目标在于提供一种可被浏览器正确显示的、格式良好的模板创建方式,因此也可以用作静态建模。你可以使用它创建经过验证的XML与HTML模板。相对于编写逻辑或代码,开发者只需将标签属性添加到模板中即可。接下来,这些标签属性就会在DOM(文档对象模型)上执行预先制定好的逻辑。
示例模板:
Name Price
Oranges 0.99

可以看到Thymeleaf主要以属性的方式加入到html标签中,浏览器在解析html时,当检查到没有的属性时候会忽略,所以Thymeleaf的模板可以通过浏览器直接打开展现,这样非常有利于前后端的分离。
在Spring Boot中使用Thymeleaf,只需要引入下面依赖,并在默认的模板路径 src/main/resources/templates 下编写模板文件即可完成。 org.springframework.boot spring-boot-starter-thymeleaf
项目管理
2018-06-05 16:49:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
今天写多表关联写法:要使用到了mybatis的多表关联查询。
附表:营业执照表 CREATE TABLE `vfc_lp_busi_license_info` ( `SCC` varchar(64) NOT NULL COMMENT '统一社会信用代码', `ENT_TYPE` varchar(10) NOT NULL COMMENT '企业类型', `LEGAL_REPRESENTATIVE` varchar(10) NOT NULL COMMENT '发人代表', `ID_TYPE` varchar(10) NOT NULL COMMENT ' 证件类型', `ID_No` varchar(30) NOT NULL COMMENT '证件号码', `BUSINESS_TERM_BEGIN` datetime NOT NULL COMMENT '营业期限起始时间', `BUSINESS_TERM_END` datetime NOT NULL COMMENT '营业期限结束时间', `REG_ADDRess` varchar(60) NOT NULL COMMENT '注册地址', `REG_MONEY` varchar(10) NOT NULL COMMENT '注册资本币种', `REGISTERFUNDS` decimal(16,2) NOT NULL COMMENT '注册资本金额', `REGISTER_OFFICE` varchar(60) NOT NULL COMMENT '注册登记机关', `BUSINESS_SCOPE` varchar(1024) NOT NULL COMMENT '经营范围', ` lp_no ` varchar(64) DEFAULT NULL COMMENT '法人编号', `ent_no` varchar(64) DEFAULT NULL COMMENT '企业ID', `create_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '创建时间', `update_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', `create_instance` varchar(8) DEFAULT NULL COMMENT '创建服务器', `update_instance` varchar(8) DEFAULT NULL COMMENT '修改服务器', `reg_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '注册时间' ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='营业执照信息表';
主表:法人基本信息表 CREATE TABLE `vfc_lp_base_info` ( ` lp_no ` varchar(64) NOT NULL COMMENT '法人编号', `lp_name` varchar(64) NOT NULL COMMENT '法人名称', `lp_classify` varchar(10) NOT NULL COMMENT '法人基本分类', `ENGLISH_NAME` varchar(30) DEFAULT NULL COMMENT '英文名称', `ENTERPRISE_SIZE` varchar(10) NOT NULL COMMENT '企业规模', `LISTED_COMPANY_LOGO` varchar(6) NOT NULL COMMENT '上市公司标识', `COUNTRY` varchar(30) NOT NULL COMMENT '所在国家', `PROV` varchar(30) NOT NULL COMMENT '所在省', `CITY` varchar(30) NOT NULL COMMENT '所在城市', `INDUSTRY` varchar(64) DEFAULT NULL COMMENT '所属行业', `OPEN_MARKET_RATING` varchar(64) DEFAULT NULL COMMENT '公开市场评级', `WEB_SITE` varchar(255) DEFAULT NULL COMMENT '公司网站', `created_by` varchar(30) NOT NULL COMMENT '主办人', `ent_no` varchar(64) DEFAULT NULL COMMENT '所属企业', `BP_ID` varchar(64) DEFAULT NULL COMMENT '合作伙伴ID', `beginVersion` int(30) NOT NULL COMMENT '起始版本号', `endVersion` int(30) NOT NULL COMMENT '终止版本号', `id_type` varchar(16) DEFAULT NULL COMMENT '证件类型', `id_no` varchar(64) DEFAULT NULL COMMENT '证件号码', `lp_type` varchar(16) DEFAULT NULL COMMENT '法人类型', `register_address` varchar(255) DEFAULT NULL COMMENT '注册地址', `reg_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '登记时间 审核通过时间', `create_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '创建时间', `updated_by` varchar(32) DEFAULT NULL COMMENT '修改人', `update_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', `coordinator` varchar(32) DEFAULT NULL COMMENT '协办人', `created_office` varchar(16) DEFAULT NULL COMMENT '创建机构', `valid_flag` varchar(3) DEFAULT NULL COMMENT '有效标识 0-失效,1-有效,仅实体数据必须', `create_instance` varchar(8) DEFAULT NULL COMMENT '创建服务器', `update_instance` varchar(8) DEFAULT NULL COMMENT '修改服务器', PRIMARY KEY (`lp_no`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='法人基本信息';
主表和附表关联关系:是根据主表的lp_No法人编号 跟附表中相互关联一对一的关系
既然要实现多表关联:没有在那个主表的mapper和mapper.xml里面操作而是从新复制了一份一样的东西。就是没有使用mybatis自动生成的东西

同样mapper.xml也是从新拉出来一个新的。

@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = {"classpath:spring-mybatis.xml"}) public class Demo { @Autowired private VfcLpBpInfoMapper bpInfoMapper; @Autowired private UserDao userDao; @Autowired private LpBaseInfoMapper lpBaseInfoMapper; @Test public void testCase(){ System.out.println("====================="); // List list = userDao.selectId("11"); List vfcLpBpInfos = bpInfoMapper.queryList(); LpListDTO lpListDTO=new LpListDTO(); // lpListDTO.setLpName(""); // lpListDTO.setIdNo(""); Listlist=new ArrayList<>(); // list.add("01"); // list.add("02"); lpListDTO.setIdType(list); List< LpBaseInfo > list1 = lpBaseInfoMapper.getList(lpListDTO); // List list1 = bpInfoMapper.getList(lpListDTO); System.out.println("==========="); }
LpBaseInfo public class LpBaseInfo extends VfcLpBaseInfo implements Serializable { private VfcLpBusiLicenseInfo vfcLpBusiLicenseInfo; //附表对象 想关联多少个表这里就写多少个对象就可以了 public VfcLpBusiLicenseInfo getVfcLpBusiLicenseInfo() { return vfcLpBusiLicenseInfo; } public void setVfcLpBusiLicenseInfo(VfcLpBusiLicenseInfo vfcLpBusiLicenseInfo) { this.vfcLpBusiLicenseInfo = vfcLpBusiLicenseInfo; } }
项目管理
2018-06-05 11:00:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
步骤 1:对你的插件进行记录
对你近期在 Confluence 中安装和启用的插件进行记录,这你可以在后期对插件进行重新安装或者调整。针对你安装的插件,你需要记录下面的一些内容: 插件名称 版本号 启用或禁用状态。这个对你自己启用禁用 Confluence 的一些模块能够起到帮助作用,能够帮你在后期修改 Confluence 的默认配置。
步骤 2: 备份你的数据 针对你已经存在的数据,创建一个 XML 备份。请参考 Manually Backing Up the Site 页面中的内容。你需要记录创建的 XML 备份的位置,你需要这个文件在后面导入到 Confluence 使用的新数据库中。 停止 Confluence。 复制 Confluence Home 目录。这个能够保证你在备份恢复的时候可能出现错误的重写和数据丢失,能够让你在出现问题的时候重新恢复数据。 如果你使用的是一个外部数据库,请确保你外部数据库也创建了一个备份。你可以使用外部数据库提供的各种工具来创建这个备份。
步骤 3 :设置新的数据库
为你的新数据库选择 数据库设置指南 ,并且运行这些指南能帮你做下面的一些事情: 安装数据库服务器。 针对数据库服务器对平台和性能进行一些调整。 添加 Confluence 数据库和用户。在这一步的时候记录你使用的数据库用户名和密码。你需要在下一步运行 Confluence 安装向导的时候使用。
步骤 4:在新的位置安装 Confluence(相同的版本号)
现在你需要再次对 Confluence 进行安装,这次安装将会按照在不同的 Home 目录路径和安装路径。
注意: 你必须使用和你前期 Confluence 安装完全相同的版本(如果你希望对 Confluence 进行升级,你需要单独进行进行升级)。例如,如果你当前 Confluence 运行的是 5.1.2,你新安装的 Confluence 也必须是 5.1.2 版本。
当运行 Confluence installer 的时候: 选择 自定义安装(Custom Install)。(不要选择升级已经存在的安装实例。) 选择 一个新的安装目录 。 这个安装目录是针对你新安装的 Confluence。这个安装目录必须与你当前安装的 Confluence 目录完全不同。 选择 一个新的 home 目录。 这个安装目录是针对你新安装的 Confluence。这个安装目录必须与你当前安装的 Confluence 目录完全不同。
步骤 5 :如果需要的话下载和安装数据库驱动
注意,Confluence 捆绑了一些数据库驱动,如果你选择使用的数据库驱动没有捆绑到 Confluence 中的话,你需要对这些数据库驱动进行安装。请参考 数据库设置指南 中的内容来设置你的新数据库。如果必要的话,你需要下载和安装新的数据库驱动。
步骤 6:运行 Confluence 安装向导然后拷贝你的数据到新的数据库中
当运行 Confluence 设置向导: 和平常一样输入你的许可证秘钥。 在安装类型中选择 生产安装(Production Installation) 。 从 数据类型(Database type) 下拉菜单中选择 我自己的数据库(My own database) 然后选择你安装使用的数据库。 当弹出需要选择 我自己的数据库(My own database) ,的时候,你需要选择你新的 数据类型(Database type) 。 输入你的数据库细节。使用 测试连接(test connection) 来确定你的数据库已经被正确的设置了。 在载入内容步骤,选择 从备份中恢复(Restore From Backup) 。这个是从你备份的 XML 文件中导入内容到数据库中的步骤,有下面 2 种方法可以访问你备份出来的 XML 文件: 从网络中浏览访问你的 XML 备份文件,然后选择 上传和恢复(Upload and Restore) 。 可选的,你可以将你的 XML 备份文件上传到你新安装 Confluence 的 home 目录中( \restore )然后选择 恢复(Restore) ,针对大的 XML 文件,我们推荐使用这个方法。
说明: 如果你选择不在 Confluence 安装过程中对数据进行回复,你可以在安装成功后再将数据进行导入。进入 Confluence 的管理员控制台,然后选择从 XML 备份中恢复。请参考 Site Backup and Restore 页面中的内容。
步骤 7 :重新安装你所有的插件
重新安装没有捆绑到 Confluence 中的插件。 使用与你老的 Confluence 安装一致的插件。 插件创建的数据已经存在在你新安装的 Confluence 了,因为你在对数据库进行备份的时候已经备份出来再 XML 文件中了。
步骤 8 :检查新机器的设置
如果你移动到 Confluence 到不同的服务器上了,你需要检查下面的一些设置: 配置你新的 Base URL,请参考: Configuring the Server Base URL . 检查你的应用连接,请参考: Linking to Another Application . 更新为外部站点的针对 Confluence 使用的任何小程序订购。例如,如果你的 Jira 站点订购了 Confluence 小程序,你需要更新你的 JIRA 站点。 检查任何其他消费使用 Confluence 的其他站点。
https://www.cwiki.us/display/CONFLUENCEWIKI/Migrating+to+Another+Database
项目管理
2018-06-05 01:44:04
「深度学习福利」大神带你进阶工程师,立即查看>>>
使用 H2 console 连接到你嵌入的 H2 数据库
可以选的,你可以使用 H2 console 来连接到你的 H2 数据库。最简单的访问 Console 的方法是双击 H2 数据库的 jar 文件。这个文件在 \confluence\WEB-INF\lib\h2-x.x.x.jar .
整合到支持的外部数据库
如果你使用的是 H2 数据库,但是你的 Confluence 是以生产环境运行的,你需要计划尽快将你现在使用 H2 数据库真整合到支持的外部数据库上。
希望整合到支持的外部数据库: 选择 Supported Platforms 来找到外部数据库中支持的版本。 进入 Migrating to Another Database 页面来按照提供的步骤,一步一步的执行。
https://www.cwiki.us/display/CONFLUENCEWIKI/Embedded+H2+Database
项目管理
2018-06-04 23:14:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
本篇文章讲解了计算机的原码, 反码和补码. 并且进行了深入探求了为何要使用反码和补码, 以及更进一步的论证了为何可以用反码, 补码的加法计算原码的减法. 论证部分如有不对的地方请各位牛人帮忙指正! 希望本文对大家学习计算机基础有所帮助!
一. 机器数和真值
在学习原码, 反码和补码之前, 需要先了解机器数和真值的概念.
1、机器数
一个数在计算机中的二进制表示形式, 叫做这个数的机器数。机器数是带符号的,在计算机用一个数的最高位存放符号, 正数为0, 负数为1.
比如,十进制中的数 +3 ,计算机字长为8位,转换成二进制就是00000011。如果是 -3 ,就是 10000011 。
那么,这里的 00000011 和 10000011 就是机器数。
2、真值 因为第一位是符号位,所以机器数的形式值就不等于真正的数值。例如上面的有符号数 10000011,其最高位1代表负,其真正数值是 -3 而不是形式值131(10000011转换成十进制等于131)。所以,为区别起见,将带符号位的机器数对应的真正数值称为机器数的真值。
例:0000 0001的真值 = +000 0001 = +1,1000 0001的真值 = –000 0001 = –1

二. 原码, 反码, 补码的基础概念和计算方法.
在探求为何机器要使用补码之前, 让我们先了解原码, 反码和补码的概念.对于一个数, 计算机要使用一定的编码方式进行存储. 原码, 反码, 补码是机器存储一个具体数字的编码方式.
1. 原码
原码就是符号位加上真值的绝对值, 即用第一位表示符号, 其余位表示值. 比如如果是8位二进制: [+1]原 = 0000 0001
[-1]原 = 1000 0001
第一位是符号位. 因为第一位是符号位, 所以8位二进制数的取值范围就是: [1111 1111 , 0111 1111]
即 [-127 , 127]
原码是人脑最容易理解和计算的表示方式.
2. 反码
反码的表示方法是:
正数的反码是其本身
负数的反码是在其原码的基础上, 符号位不变,其余各个位取反. [+1] = [00000001]原 = [00000001]反
[-1] = [10000001]原 = [11111110]反
可见如果一个反码表示的是负数, 人脑无法直观的看出来它的数值. 通常要将其转换成原码再计算.
3. 补码
补码的表示方法是:
正数的补码就是其本身
负数的补码是在其原码的基础上, 符号位不变, 其余各位取反, 最后+1. (即在反码的基础上+1) [+1] = [00000001]原 = [00000001]反 = [00000001]补
[-1] = [10000001]原 = [11111110]反 = [11111111]补
对于负数, 补码表示方式也是人脑无法直观看出其数值的. 通常也需要转换成原码在计算其数值.

三. 为何要使用原码, 反码和补码
在开始深入学习前, 我的学习建议是先"死记硬背"上面的原码, 反码和补码的表示方式以及计算方法.
现在我们知道了计算机可以有三种编码方式表示一个数. 对于正数因为三种编码方式的结果都相同: [+1] = [00000001]原 = [00000001]反 = [00000001]补
所以不需要过多解释. 但是对于负数: [-1] = [10000001]原 = [11111110]反 = [11111111]补
可见原码, 反码和补码是完全不同的. 既然原码才是被人脑直接识别并用于计算表示方式, 为何还会有反码和补码呢?
首先, 因为人脑可以知道第一位是符号位, 在计算的时候我们会根据符号位, 选择对真值区域的加减. (真值的概念在本文最开头). 但是对于计算机, 加减乘数已经是最基础的运算, 要设计的尽量简单. 计算机辨别"符号位"显然会让计算机的基础电路设计变得十分复杂! 于是人们想出了将符号位也参与运算的方法. 我们知道, 根据运算法则减去一个正数等于加上一个负数, 即: 1-1 = 1 + (-1) = 0 , 所以机器可以只有加法而没有减法, 这样计算机运算的设计就更简单了.
于是人们开始探索 将符号位参与运算, 并且只保留加法的方法. 首先来看原码:
计算十进制的表达式: 1-1=0 1 - 1 = 1 + (-1) = [00000001]原 + [10000001]原 = [10000010]原 = -2
如果用原码表示, 让符号位也参与计算, 显然对于减法来说, 结果是不正确的.这也就是为何计算机内部不使用原码表示一个数.
为了解决原码做减法的问题, 出现了反码:
计算十进制的表达式: 1-1=0 1 - 1 = 1 + (-1) = [0000 0001]原 + [1000 0001]原= [0000 0001]反 + [1111 1110]反 = [1111 1111]反 = [1000 0000]原= -0
发现用反码计算减法, 结果的真值部分是正确的. 而唯一的问题其实就出现在"0"这个特殊的数值上. 虽然人们理解上+0和-0是一样的, 但是0带符号是没有任何意义的. 而且会有[0000 0000]原和[1000 0000]原两个编码表示0.
于是补码的出现, 解决了0的符号以及两个编码的问题: 1-1 = 1 + (-1) = [0000 0001]原 + [1000 0001]原 = [0000 0001]补 + [1111 1111]补 = [0000 0000]补=[0000 0000]原
这样0用[0000 0000]表示, 而以前出现问题的-0则不存在了.而且可以用[1000 0000]表示-128: (-1) + (-127) = [1000 0001]原 + [1111 1111]原 = [1111 1111]补 + [1000 0001]补 = [1000 0000]补
-1-127的结果应该是-128, 在用补码运算的结果中, [1000 0000]补 就是-128. 但是注意因为实际上是使用以前的-0的补码来表示-128, 所以-128并没有原码和反码表示.(对-128的补码表示[1000 0000]补算出来的原码是[0000 0000]原, 这是不正确的)
使用补码, 不仅仅修复了0的符号以及存在两个编码的问题, 而且还能够多表示一个最低数. 这就是为什么8位二进制, 使用原码或反码表示的范围为[-127, +127], 而使用补码表示的范围为[-128, 127].
因为机器使用补码, 所以对于编程中常用到的32位int类型, 可以表示范围是: [-231, 231-1] 因为第一位表示的是符号位.而使用补码表示时又可以多保存一个最小值.

四 原码, 反码, 补码 再深入
计算机巧妙地把符号位参与运算, 并且将减法变成了加法, 背后蕴含了怎样的数学原理呢?
将钟表想象成是一个1位的12进制数. 如果当前时间是6点, 我希望将时间设置成4点, 需要怎么做呢?我们可以: 1. 往回拨2个小时: 6 - 2 = 4
2. 往前拨10个小时: (6 + 10) mod 12 = 4
3. 往前拨10+12=22个小时: (6+22) mod 12 =4
2,3方法中的mod是指取模操作, 16 mod 12 =4 即用16除以12后的余数是4.
所以钟表往回拨(减法)的结果可以用往前拨(加法)替代!
现在的焦点就落在了如何用一个正数, 来替代一个负数. 上面的例子我们能感觉出来一些端倪, 发现一些规律. 但是数学是严谨的. 不能靠感觉.
首先介绍一个数学中相关的概念: 同余

同余的概念
两个整数a,b,若它们除以整数m所得的余数相等,则称a,b对于模m同余
记作 a ≡ b (mod m)
读作 a 与 b 关于模 m 同余。
举例说明: 4 mod 12 = 4
16 mod 12 = 4
28 mod 12 = 4
所以4, 16, 28关于模 12 同余.

负数取模
正数进行mod运算是很简单的. 但是负数呢?
下面是关于mod运算的数学定义:
上面是截图, "取下界"符号找不到如何输入(word中粘贴过来后乱码). 下面是使用"L"和"J"替换上图的"取下界"符号: x mod y = x - y L x / y J
上面公式的意思是:
x mod y等于 x 减去 y 乘上 x与y的商的下界.
以 -3 mod 2 举例: -3 mod 2
= -3 - 2xL -3/2 J
= -3 - 2xL-1.5J
= -3 - 2x(-2)
= -3 + 4 = 1
所以: (-2) mod 12 = 12-2=10
(-4) mod 12 = 12-4 = 8
(-5) mod 12 = 12 - 5 = 7

开始证明
再回到时钟的问题上: 回拨2小时 = 前拨10小时
回拨4小时 = 前拨8小时
回拨5小时= 前拨7小时
注意, 这里发现的规律!
结合上面学到的同余的概念.实际上: (-2) mod 12 = 10
10 mod 12 = 10
-2与10是同余的. (-4) mod 12 = 8
8 mod 12 = 8
-4与8是同余的.
距离成功越来越近了. 要实现用正数替代负数, 只需要运用同余数的两个定理:
反身性: a ≡ a (mod m)
这个定理是很显而易见的.
线性运算定理: 如果a ≡ b (mod m),c ≡ d (mod m) 那么:
(1)a ± c ≡ b ± d (mod m)
(2)a * c ≡ b * d (mod m)
如果想看这个定理的证明, 请看: http://baike.baidu.com/view/79282.htm
所以: 7 ≡ 7 (mod 12)
(-2) ≡ 10 (mod 12)
7 -2 ≡ 7 + 10 (mod 12)
现在我们为一个负数, 找到了它的正数同余数. 但是并不是7-2 = 7+10, 而是 7 -2 ≡ 7 + 10 (mod 12) , 即计算结果的余数相等.
接下来回到二进制的问题上, 看一下: 2-1=1的问题. 2-1=2+(-1) = [0000 0010]原 + [1000 0001]原= [0000 0010]反 + [1111 1110]反
先到这一步, -1的反码表示是1111 1110. 如果这里将[1111 1110]认为是原码, 则[1111 1110]原 = -126, 这里将符号位除去, 即认为是126.
发现有如下规律: (-1) mod 127 = 126
126 mod 127 = 126
即: (-1) ≡ 126 (mod 127)
2-1 ≡ 2+126 (mod 127)
2-1 与 2+126的余数结果是相同的! 而这个余数, 正式我们的期望的计算结果: 2-1=1
所以说一个数的反码, 实际上是这个数对于一个膜的同余数. 而这个膜并不是我们的二进制, 而是所能表示的最大值! 这就和钟表一样, 转了一圈后总能找到在可表示范围内的一个正确的数值!
而2+126很显然相当于钟表转过了一轮, 而因为符号位是参与计算的, 正好和溢出的最高位形成正确的运算结果.
既然反码可以将减法变成加法, 那么现在计算机使用的补码呢? 为什么在反码的基础上加1, 还能得到正确的结果? 2-1=2+(-1) = [0000 0010]原 + [1000 0001]原 = [0000 0010]补 + [1111 1111]补
如果把[1111 1111]当成原码, 去除符号位, 则: [0111 1111]原 = 127
其实, 在反码的基础上+1, 只是相当于增加了膜的值: (-1) mod 128 = 127
127 mod 128 = 127
2-1 ≡ 2+127 (mod 128)
此时, 表盘相当于每128个刻度转一轮. 所以用补码表示的运算结果最小值和最大值应该是[-128, 128].
但是由于0的特殊情况, 没有办法表示128, 所以补码的取值范围是[-128, 127]
本人一直不善于数学, 所以如果文中有不对的地方请大家多多包含, 多多指点!
项目管理
2018-06-04 14:19:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
软件开发是一场需要集体智慧的运动,它的成功不完全属于团队中任何一个人。然而,团队成员们做人做事的风格却不完全一样,因此我们需要一种叫做“团队文化”的东西,通过它让大家的心聚集在一起,齐心协力完成目标。
本文将从团队文化入手,站在软件开发的角度,讲述工程师文化是如何打造出来的。文中会包含一些可立即落地的实践方法,大家可根据自身实际情况,灵活运用。
下面,我们就一起聊聊关于团队文化的那点事儿吧。
什么是团队文化?
我们认为,团队文化主要包含以下三个方面: 团队气氛 做人原则 做事方式
我们先说“团队气氛”。我们每个人都希望团队有个好的气氛,都不希望气氛变得糟糕,因为气氛糟糕会让人的心情变得糟糕。我们的工作性质决定了,我们每天都要与人协作和沟通,如果缺乏好的团队气氛,合作关系将变得冷漠,工作也将失去激情。然而,营造好的团队文化,起到决定性作用的就是团队领袖。公司就是一个最大的团队,公司文化取决于公司老板。道理很简单,团队成员能否加入这个团队,是由这位团队领袖决定的,他决定了人的构成,人决定了团队的气氛。
团队文化也包括“做人原则”。坦率地说,团队中每个人的性格可能不尽相同,性格必然多元化,性格也必须求同存异,同类型的人只会让团队的短板更加明显。我们相信,没有完美的个人,只有完美的团队。但是,做人原则与人的性格不同,做人原则是态度和行为的根基,如果这个根基就不对,那么也很难做成一件成功的事情。团队领袖就是团队做人原则的根基,他说的每一句话,做的每一件事,团队都看在眼里,大家都以他为标准。如果团队领袖是一位正直的人,那么邪恶的人也无法留在他的团队中。正所谓,物以类聚,人以群分。
做人原则决定了“做事方式”。如果团队都是性格直爽的人,那么一定是有话直说,对事不对人;如果团队都是溜须拍马的人,那么一定会让领导笑口常开,用表象去掩盖事实。不得不说,在一些互联网公司中很多老板(也包括一些领导),他们很喜欢看数据,很相信数据,认为公司是一家数据驱动型企业。如果没有一个求实的企业文化,员工就会拿数据来欺骗老板,因为老板相信数据,员工就会给老板看到数据。事情做不做都在那里,做的方式不同,效果也会完全不同。一位求实的员工,一定会用数据说话,但不是单纯地给老板看数据,他会将数据背后的本质原因分析给老板听,帮助老板正确地做出决策。团队的做事方式同样也取决于团队领袖,他是怎样的人,就会带出怎样的团队。
综上所述,团队气氛、做人原则、做事方式这三点构成了团队文化,而且这三点都由团队领袖来决定,如果你就是这位团队领袖,不妨思考这样三个问题: 你想要怎样的团队气氛? 你的做人原则是怎样的? 你的做事方式是怎样的?
把以上三个问题的答案写在纸上,反复思考自己是不是这样的,想明白后就去亲自实践,用写下来的这些准则去要求自己。当自己违反了这些准则时,思考为什么会这样?到底出了什么问题?
我曾经写过以下 5 条行为准则,自己一直都在努力践行,这些也是我们的团队文化。 守信 —— 为自己的承诺负责 进取 —— 勇敢面对新的挑战 高效 —— 追求高效工作方式 学习 —— 不断学习新的技能 分享 —— 乐于分享个人收获
其中,守信是做人的原则,进取是做事的态度,高效是做事的方式,学习是对自己的要求,分享是对团队的贡献。
需要注意的是,团队文化必须体现在平时的日常工作中,通过文化来影响整个每个人,通过文化来指导团队的行为,通过文化来融入更多的新人。团队文化不是喊出来的口号,也不是贴墙上的标语。只有我们心中认可的文化,才是真正的团队文化。
既然我们都是软件工程师,那么我们也需要有自己的文化,我们需要的是“工程师文化”。
什么是工程师文化?
弹性的工作时间、优雅的办公环境、穿着自由且随意、做自己喜欢的工作、工程师们说了算……这些是工程师文化吗?显然不是,这些只是工程师文化的表象,而非本质。学习工程师文化,一定要抓住本质,否则舍本逐末,不伦不类。
有些互联网创业公司,对外号称弹性工作时间,但当员工做完当天工作并按时下班时,老板却认为这些员工不够努力,他们都在打工,而不是在创业,老板因此感到心寒,但束手无策,只能在内心里埋怨。当这样的事情屡次发生后,老板对员工会产生更加强烈的不满,离员工的距离也会越来越远,从而不再信任他们。
我有一位朋友,他在一家公司做技术高管,公司提倡工程师文化。他的老板经常对工程师们说:“大家白天写代码,晚上可以做业务嘛,这么早下班回家干嘛呢?做业务的同事天天加班到深夜,凌晨给他们发微信,他们都是秒回的”,当老板说出这句话时,也就注定了,这家公司不可能拥有真正的工程师文化,他们公司所号称的工程师文化其实都是表象,老板根本就不理解工程师文化,公司也留不住优秀的工程师。果不其然,这位技术高管无法承受老板的态度和行为,选择了主动离职,他的离开影响了整个技术团队的稳定性,导致优秀工程师大量流失,公司业务也受到了影响。
我们认为,工程师文化是以共同解决实际问题为目标的团队文化。工程师文化并非由工程师们自发创建,更不是工程师们的一种自嗨行为。工程师文化是由企业文化所决定,企业文化由老板所决定,老板必须理解工程师这样的人群,才能把合适的工程师放在合适的位置上,才能真正领悟工程师文化的真谛,才能做成一家真正的互联网产品技术型公司。
可以抽象地认为,工程师文化只是一个代号,一种象征。工程师文化强调的是团队有目标、有分工、有协作,而且团队所解决的是当前面临的实际问题,并非是一些不切实际的目标,更不是加班这种表面上的东西。
工程师文化并非一个人或几个人就能打造起来的,需要公司老板以及高管们的认同,自顶向下的影响并鼓励自己的团队。老板必须懂得工程师这个职业,必须深刻理解工程师文化。
怎样打造工程师文化?
如果你是一位技术负责人,你的老板却并太不懂技术,你很希望打造出工程师文化,这样对外容易招聘,对内也能让团队更加融合,工作更加高效。此时你需要掌握正确的操作方法,才能打造出良好的工程师文化,否则后面迎接你的将是一场噩梦。
你要做的第一件事情是,让老板理解软件开发是一项工程,需要工程师们一起合力完成,还要让老板看清工程师的价值。因为在不懂技术的老板眼中,工程师的成本是相当昂贵的,他们做的东西自己又看不懂,不知道钱花得有没有价值,工程师们每天都在做什么?为何做一个项目不能直接开干,还要先去估时?估时会不会有水分?我们必须站在老板的角度去理解他,把他心中的这些顾虑全部排除掉,让他觉得软件开发是一项伟大而复杂的过程,需要工程师们用科学的方法来避免风险,从而让项目得以顺利交付。很多技术负责人可能都忽略了这个过程,导致后面做的一系列事情,老板都看不懂,也不认为有价值。
当你让老板理解了软件工程以及工程师价值以后,接下来可以做以下几件事情: 让团队扁平,自己和团队一起工作 没有 Leader,只有 Owner 没有“你们”,只有“我们” 一切都以数据说话,分析数据的本质 可以加班,但拒绝无意义的加班
以上这些观点,首先你需要确保自己能做到,你的团队才会做到,你才有可能打造出真正的工程师文化。
接下来,你需要不断培养队员们的软技能,其中有三点非常重要的态度,这些态度决定了团队做事的行为: 主动担当任务,并非被动等待 具备用户思维,产出有用成果 具备服务意识,乐于帮助同事
除此以外,你还可以考虑做到这些事情: 让大家成为朋友 缩短项目迭代周期 为每个项目找到 Owner 选择最合适的技术 技术尽可能自动化 注重代码质量 建立开放与共享 预留学习时间 不追究任何责任 只招最对的人,不招最贵的人
你可以组织一些有意思的活动,比如:茶话会、小黑屋、经验分享、内部演讲、摄影比赛、话题辩论、体育活动、健身、户外运动、技能培训、读书会、黑客马拉松等,只要对团队成长有帮助的活动,你都可以去大胆尝试。
写在最后
打造一支高效的研发团队,我们至少需要关注四个方面:组织架构、研发流程、绩效考核、团队文化,这四点缺一不可。其中,团队文化是至关重要的,它是团队价值观的主要体现,是大家做人做事的行为准则。
我们非常提倡工程师文化,但也要有正确的方法去打造工程师文化,需要让自己的上级产生信任感,也要让自己的下级产生幸福感,我们必须做到这些,才能打造出真正的工程师文化。
我已发表了 4 篇关于“打造高效研发团队”的系列文章,这几篇文章的内容还比较片面,不一定能完全帮助大家解决现在所面临的问题。深知自己水平有限,但我愿意分享,愿意和大家交流成长的点点滴滴,还请大家不吝赐教。
作者简介
黄勇,现任特赞科技 CTO,图书《架构探险》作者,开源项目作者,技术大会讲师,企业培训师。十年以上互联网软件架构与技术管理经验,擅长敏捷开发,推崇“轻量级”架构思想。喜欢阅读,热爱交流,乐于分享。
系列文章 打造高效研发团队 (1) —— 组织架构篇 打造高效研发团队 (2) —— 研发流程篇 打造高效研发团队 (3) —— 绩效考核篇 打造高效研发团队 (4) —— 团队文化篇
项目管理
2018-06-04 10:30:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
前阵子 Android 端的线上崩溃比较多,热修复被提上日程。实现方案是 Tinker,Jenkins 打包,最后补丁包上传到 Bugly 进行分发。主要在 Jenkins 打包这一块爬了不少坑,现记录下来,供大家参考。
1. Tinker + Bugly热修复实现
首先是本地实现,按照官方文档,只要一步一步按照文档来,这个步骤还是比较容易的,这里就不再赘述了,不懂的可以先参考官方文档: Bugly Android热更新使用指南 、 Bugly Android热更新详解 。这里贴一下接入流程: 打基准包安装并上报联网(注:填写唯一的 tinkerId) 对基准包的 bug 修复(可以是 Java 代码变更,资源的变更) 修改基准包路径、修改补丁包 tinkerId、mapping 文件路径(如果开启了混淆需要配置)、resId 文件路径 执行 buildTinkerPatchRelease 打 Release 版本补丁包 选择 app/build/outputs/ patch目录 下的补丁包并上传(注: 不要选择 tinkerPatch 目录下的补丁包,不然上传会有问题 ) 编辑下发补丁规则,点击立即下发 杀死进程并重启基准包,请求补丁策略( SDK 会自动下载补丁并合成) 再次重启基准包,检验补丁应用结果 查看页面,查看激活数据的变化
这里说一下 使用指南 中的 第三步:初始化 SDK ,我这里使用的是 enableProxyApplication = false 的方式,原本想用 enableProxyApplication = true 的这种比较灵活的方式,但是程序编译报错,没时间去深究报错的原因,加上直接继承的方式接入也没什么代价,就没管是为什么了,知道原因的可以顺手告知下。 ┑( ̄Д  ̄)┍
一通撸下来还是比较容易的,完成代码的接入后,先打个包(基准包),安装到手机上运行一遍,使程序联网上报到 Bugly 。之后,再按照打基准包的基线版本,修改 tinker-support.gradle 文件中的 baseApkDir 参数,然后就可以打补丁包了。
2. 结合 Jenkins 所遇到的坑
先说明一下我司使用 Jenkins 打包 apk 的背景知识。Jenkins 打包 apk 使用的是 Ant 插件,打包脚本由于公司项目的原因,不方便展示出来,大家如果有疑问的话,可以在评论里说明,本人会私下里帮助大家解决。
下面爬坑 /(ㄒoㄒ)/~~
坑1 ☞ 打补丁包时,基准包哪里找?
由于公司 Jenkins 的打包策略是,在构建之前,先执行 clean 命令,这也就意味着,像本地打包一样在 app/build/bakApk/app-xxxx-xx-xx-xx 目录下找到 基准包 已是不可能。那怎么办,没有基准包怎么打增量包?苦思良久,愚笨的我最终想到,在项目工程路径下创建一个文件夹,要打增量包时,将基准包拷贝到该文件夹,然后上传 SVN。这时,旁边同学来了句:可以找运维同学,双方约定一个目录,打基准包时将基准包由脚本拷贝过去,打补丁包时从约定的目录取就行((ಥ _ ಥ) 我咋就想不到...)。
然后屁颠屁颠的跑去找运维同学,沟通后发现,Jenkins 每次打包都会在 Jenkins 目录下的 /jobs/pipeline名称/builds/构建编号/archive/app/build/outputs/apk/kungeek/release/ 保存一份 apk 文件的副本。路径中 构件编号 如图所示:
接下来,打补丁包时将 tinker-support.gradle 文件中的 baseApkDir 参数修改为 /jobs/pipeline名称/builds/构建编号/archive/app/build/outputs/apk/kungeek/release/ 即可。代码如下: /** * 此处填写每次构建生成的基准包目录,注意变量要自定义 */ def baseApkDir = "${rootProject.projectDir}/../../jobs/${pipeline名称}/builds/${baseApkBuildNumber}/archive/app/build/outputs/apk/kungeek/release"
坑2 ☞ Linux 下文件拷贝通配符问题
由于构建基准包的同时生成的 mapping 文件 (如果开启了混淆需要配置)、 resId 文件 在构建补丁包时也需要用到,所以,在构建基准包时,需要将这两个文件拷贝到 /jobs/pipeline名称/builds/构建编号/archive/app/build/outputs/apk/kungeek/release/ 目录下,拷贝代码如下: 注1: 代码中相对路径问题读者有疑问的话,麻烦再评论去提问。
注2: 代码中构建编码使用到了 Jenkins 的环境变量,需要先在 Ant 的构建脚本文件的 project 的标签下添加 来导入。
这里遇到的坑是:因为 Tinker 构建的 apk 文件是存放在 app-xxxx-xx-xx-xx 目录下,所以需要使用通配符来辅助复制文件,运维同学原本是想将通配符加到 fileset 中形成以后完整的路径,经过一段痛苦的尝试以及百度后发现,通配符只能在 include 标签中使用。(ノへ ̄、)
坑3 ☞ 构建补丁包完成后找不到生成的补丁包?
踩过前面一个一个的坑,终于在 Jenkins 上打了基准包之后, /jobs/pipeline名称/builds/构建编号/archive/app/build/outputs/apk/kungeek/release/ 目录下有了 基准包 apk 文件 、 mapping 文件 、 resId 文件 。
接下来,我以为,只需要配置好基准包的 构件编号 等相关配置参数,再构建补丁包就没问题了。然后 Jenkins 在构建好补丁包 apk 文件后,展示成果时报出的 apk 文件未找到 给了我当头一棒,依然失败。挫败感油然而生~~~
之后,经运维同学确认,Jenkins 构建期间是有在 app/build/outputs/patch 目录下生成 patch_signed_7zip.apk 文件的,但是构建完成之后,又没了。然后我试着看了下构建过程中执行的命令,长这样的: sh gradlew clean buildTinkerPatchRelease --stacktrace sh gradlew checklist
执行了 buildTinkerPatchRelease 后,还执行的 checklist 任务,难道是执行 checklist 时把 patch 给清空了,之后我尝试把这个命令注释掉,再次打补丁包时成功。果然是这个 checklist 惹的事啊,事后发现,打补丁包后,再次执行 gradle task,基本都会清空 patch 目录,这是个坑,大家记得避免。
坑4 ☞ 一个项目中多个 application 时,打补丁包不成功?
我们知道,在 Android Studio 中,一个 project 可以有多个 module ,包括 application 类型的 module,一般情况下,执行 gradlew assembleRelease 任务会将所有的 APP 都打包,这里打基准包也没问题,但是打补丁包时就不行了,只能成功一个。
这里提供分开打包一个方案:在每个 application 的 build.gradle 中配置 productFlavors ,且每个 application 的命名都得不一样,这样,针对不同的 APP 就会产生不同的构建 task,比如:在 A 的 build.gradle 中配置名为 a_app ,则回产生一个名为 buildTinkerPatchA_appRelease 的 task,最终使用此 task 来打补丁包即可。
那么问题来了,最终打包的形式是什么呢?是这样? sh gradlew buildTinkerPatchA_appRelease buildTinkerPatchA_appRelease
还是这样? sh gradlew buildTinkerPatchA_appRelease sh gradlew buildTinkerPatchA_appRelease
都不是,这两种方式其实和不配置 productFlavors 的打包方式是一样的,那么如何打包呢?
答案是在 Ant 的打包脚本中,执行多次打包,关键代码如下:
构建脚本 generated_apk_hotfix.sh 文件关键代码如下: #!/bin/sh command=$1; ​ # 增量包需分开打包,否则会失败 sh gradlew ${command} --stacktrace
3. 总结
上面说到的坑只有 4 点,但实际上也遇到过挺多小问题的,但那些就不用多说了,很容易解决。
最后,总结一下结合 Jenkins 构建补丁包的思路。
首先,约定好基线版本的基准包 apk 包、mapping 文件、R.txt 文件的存放路径,打基准包时将这三个文件存入该目录。如果跟本文一样存放在 Jenkins 的 pipeline 构建目录下的话,记得要 调整 pipeline 的清理策略 ,否则等需要打补丁包的时候,发现基线版本 apk 包什么的被清理掉就尴尬了,我这里是考虑到重复利用空间,所以放入此目录下。
其次,通过约定的路径,找到基准包、mapping 文件、R.txt 文件,打补丁包。这里需要确定一个找到基准包的策略,比如,我这里是通过 构建编号 来匹配存放基准包的路径,然后通过固定命名格式(如:app_release_版本号.apk)来匹配基准包以及 mapping 文件和 R.txt 文件,如此下来,我只需要确定基线版本的版本号和构建编号即可。
最后,贴一下我最终的 tinker-support.gradle 文件代码内容,大家有需要的可以参考: apply plugin: 'com.tencent.bugly.tinker-support' ​ def bakPath = file("${buildDir}/bakApk/") ​ /** 基准包的 Jenkins 构建编号*/ def baseApkBuildNumber = project.property("baseApkBuildNumber") /** 基准包的版本号*/ def baseApkVersion = project.property("baseApkVersion") ​ /** * 此处填写每次构建生成的基准包目录 */ def baseApkDir = "${rootProject.projectDir}/../../jobs/Android_Trunk/builds/${baseApkBuildNumber}/archive/app/build/outputs/apk/release" ​ /** 基准包的 apk 文件名*/ def baseApkFileName = "app-v${baseApkVersion}" ​ /** * 对于插件各参数的详细解析请参考 */ tinkerSupport { ​ // 开启tinker-support插件,默认值true enable = true ​ // tinkerEnable功能开关 tinkerEnable = true ​ // 指定归档目录,默认值当前module的子目录tinker autoBackupApkDir = "${bakPath}" ​ autoGenerateTinkerId = true ​ // 打基准包时生成 R.txt、mapping.txt 文件名的前缀 // rootProject.ext.android_version 指打包时的版本号 targetFileNamePrefix = "app-v${rootProject.ext.android_version}" ​ // 是否启用覆盖tinkerPatch配置功能,默认值false // 开启后tinkerPatch配置不生效,即无需添加tinkerPatch overrideTinkerPatchConfiguration = true // 编译补丁包时,必需指定基线版本的apk,默认值为空 // 如果为空,则表示不是进行补丁包的编译 // @{link tinkerPatch.oldApk } baseApk = "${baseApkDir}/${baseApkFileName}.apk" ​ // 对应tinker插件applyMapping baseApkProguardMapping = "${baseApkDir}/${baseApkFileName}-mapping.txt" ​ // 对应tinker插件applyResourceMapping baseApkResourceMapping = "${baseApkDir}/${baseApkFileName}-R.txt" ​ tinkerId = "base-1.0.1" ​ // buildAllFlavorsDir = "${bakPath}/${baseApkDir}" // 是否开启加固模式,默认为false // isProtectedApp = true ​ // 是否开启反射Application模式 enableProxyApplication = false ​ supportHotplugComponent = true ​ } ​ /** * 一般来说,我们无需对下面的参数做任何的修改 * 对于各参数的详细介绍请参考: * https://github.com/Tencent/tinker/wiki/Tinker-%E6%8E%A5%E5%85%A5%E6%8C%87%E5%8D%97 */ tinkerPatch { //oldApk ="${bakPath}/${appName}/app-release.apk" ​ // tinkerEnable功能开关 tinkerEnable = true ignoreWarning = false useSign = true dex { dexMode = "jar" pattern = ["classes*.dex"] loader = [] } lib { pattern = ["lib/*/*.so"] } ​ res { pattern = ["res/*", "r/*", "assets/*", "resources.arsc", "AndroidManifest.xml"] ignoreChange = [] largeModSize = 100 } ​ packageConfig { } sevenZip { zipArtifact = "com.tencent.mm:SevenZip:1.1.10" } buildConfig { keepDexApply = false } }
然后是维护在 gradle.properties 文件中的两个变量: # 打增量包时基准包的 Jenkins 构建编号 baseApkBuildNumber = 1 # 打增量包时基准包的版本号 baseApkVersion = 1.0.0.197094
项目管理
2018-06-04 06:51:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
反射是学习框架的基础
使用前提条件:必须先得到代表的字节码的Class, Class类用于表示.class文件(字节码)
反射的概述:对类的解剖,在运行状态中,对于任意一个类,都能知道这个类的所有属性和方法,对于任意一个对象,都能调用他的任意一个方法和属性,动态获取信息以及动态调用对象的方法的功能成为反射。
类的加载(简要版):
当程序要使用某个类时,如果该类还未被加载到内存中,则系统会通过加载,连接,初始化三步来实现对这个类进行初始化。
* 加载
* 就是指将class文件读入内存,并为之创建一个Class对象。任何类被使用时系统都会建立一个Class对象。
* 连接
* 验证 是否有正确的内部结构,并和其他类协调一致
* 准备 负责为类的静态成员分配内存,并设置默认初始化值
* 解析 将类的二进制数据中的符号引用替换为直接引用
要想解剖一个类,必须先获取该类的字节码文件对象,解剖使用的就是Class类中的方法,所以需要先获取到每一个字节码文件对应的class类型的对象。
总结:反射就是把Java类中的各种成分映射成一个个的java对象。
例如:一个类有:成员变量、方法、构造方法、包等等信息,利用反射技术可以对一个类进行解剖,把个个组成部分映射成一个个对象。
(其实:一个类中这些成员方法、构造方法、在加入类中都有一个类来描述)
如图是类的正常加载过程:反射的原理在与class对象。
熟悉一下加载的时候:Class对象的由来是将class文件读入内存,并为之创建一个Class对象。
其中class对象很特殊
Class类在javaApi详解:
Class 类的实例表示正在运行的 Java 应用程序中的类和接口。也就是jvm中有N多的实例每个类都有该Class对象。(包括基本数据类型)
Class 没有公共构造方法。 Class 对象是在加载类时由 Java 虚拟机以及通过调用类加载器中的 defineClass 方法自动构造的。也就是这不需要我们自己去处理创建,JVM已经帮我们创建好了。
没有公共的构造方法,方法共有64个太多了。下面用到哪个就详解哪个吧
反射的使用:
1、获取Class对象的三种方式
1.1 Object ——> getClass();
1.2 任何数据类型(包括基本数据类型)都有一个“静态”的class属性
1.3 通过Class类的静态方法:forName(String className)(常用)
其中1.1是因为Object类中的getClass方法、因为所有类都继承Object类。从而调用Object类来获取
/** * 获取Class对象的三种方式 * 1 Object ——> getClass(); * 2 任何数据类型(包括基本数据类型)都有一个“静态”的class属性 * 3 通过Class类的静态方法:forName(String className)(常用) * */ public class Fanshe { public static void main(String[] args) { //第一种方式获取Class对象 Student stu1 = new Student();//这一new 产生一个Student对象,一个Class对象。 Class stuClass = stu1.getClass();//获取Class对象 System.out.println(stuClass.getName()); //第二种方式获取Class对象 Class stuClass2 = Student.class; System.out.println(stuClass == stuClass2);//判断第一种方式获取的Class对象和第二种方式获取的是否是同一个 //第三种方式获取Class对象 try { Class stuClass3 = Class.forName("fanshe.Student");//注意此字符串必须是真实路径,就是带包名的类路径,包名.类名 System.out.println(stuClass3 == stuClass2);//判断三种方式是否获取的是同一个Class对象 } catch (ClassNotFoundException e) { e.printStackTrace(); } } }
注意:在运行期间,一个类,只有一个Class对象产生。
三种方式常用第三种,第一种对象都有了还要反射干什么。第二种需要导入类的包,依赖太强,不导包就抛编译错误。一般都第三种,一个字符串可以传入也可写在配置文件中等多种方法。
2、通过反射获取构造方法并使用: package fanshe; public class Student { //---------------构造方法------------------- //(默认的构造方法) Student(String str){ System.out.println("(默认)的构造方法 s = " + str); } //无参构造方法 public Student(){ System.out.println("调用了公有、无参构造方法执行了。。。"); } //有一个参数的构造方法 public Student(char name){ System.out.println("姓名:" + name); } //有多个参数的构造方法 public Student(String name ,int age){ System.out.println("姓名:"+name+"年龄:"+ age);//这的执行效率有问题,以后解决。 } //受保护的构造方法 protected Student(boolean n){ System.out.println("受保护的构造方法 n = " + n); } //私有构造方法 private Student(int age){ System.out.println("私有的构造方法 年龄:"+ age); } }
测试类: package fanshe; import java.lang.reflect.Constructor; /* * 通过Class对象可以获取某个类中的:构造方法、成员变量、成员方法;并访问成员; * * 1.获取构造方法: * 1).批量的方法: * public Constructor[] getConstructors():所有"公有的"构造方法 public Constructor[] getDeclaredConstructors():获取所有的构造方法(包括私有、受保护、默认、公有) * 2).获取单个的方法,并调用: * public Constructor getConstructor(Class... parameterTypes):获取单个的"公有的"构造方法: * public Constructor getDeclaredConstructor(Class... parameterTypes):获取"某个构造方法"可以是私有的,或受保护、默认、公有; * * 调用构造方法: * Constructor-->newInstance(Object... initargs) */ public class Constructors { public static void main(String[] args) throws Exception { //1.加载Class对象 Class clazz = Class.forName("fanshe.Student"); //2.获取所有公有构造方法 System.out.println("**********************所有公有构造方法*********************************"); Constructor[] conArray = clazz.getConstructors(); for(Constructor c : conArray){ System.out.println(c); } System.out.println("************所有的构造方法(包括:私有、受保护、默认、公有)***************"); conArray = clazz.getDeclaredConstructors(); for(Constructor c : conArray){ System.out.println(c); } System.out.println("*****************获取公有、无参的构造方法*******************************"); Constructor con = clazz.getConstructor(null); //1>、因为是无参的构造方法所以类型是一个null,不写也可以:这里需要的是一个参数的类型,切记是类型 //2>、返回的是描述这个无参构造函数的类对象。 System.out.println("con = " + con); //调用构造方法 Object obj = con.newInstance(); // System.out.println("obj = " + obj); // Student stu = (Student)obj; System.out.println("******************获取私有构造方法,并调用*******************************"); con = clazz.getDeclaredConstructor(char.class); System.out.println(con); //调用构造方法 con.setAccessible(true);//暴力访问(忽略掉访问修饰符) obj = con.newInstance('男'); } }
输出: **********************所有公有构造方法********************************* public fanshe.Student(java.lang.String,int) public fanshe.Student(char) public fanshe.Student() ************所有的构造方法(包括:私有、受保护、默认、公有)*************** private fanshe.Student(int) protected fanshe.Student(boolean) public fanshe.Student(java.lang.String,int) public fanshe.Student(char) public fanshe.Student() fanshe.Student(java.lang.String) *****************获取公有、无参的构造方法******************************* con = public fanshe.Student() 调用了公有、无参构造方法执行了。。。 ******************获取私有构造方法,并调用******************************* public fanshe.Student(char) 姓名:男
调用构造方法:
1.获取构造方法:
1).批量的方法:
public Constructor[] getConstructors():所有"公有的"构造方法
public Constructor[] getDeclaredConstructors():获取所有的构造方法(包括私有、受保护、默认、公有)

2).获取单个的方法,并调用:
public Constructor getConstructor(Class... parameterTypes):获取单个的"公有的"构造方法:
public Constructor getDeclaredConstructor(Class... parameterTypes):获取"某个构造方法"可以是私有的,或受保护、默认、公有;
调用构造方法:
Constructor-->newInstance(Object... initargs)
2、newInstance是 Constructor类的方法(管理构造函数的类)
api的解释为:
newInstance(Object... initargs)
使用此 Constructor 对象表示的构造方法来创建该构造方法的声明类的新实例,并用指定的初始化参数初始化该实例。
它的返回值是T类型,所以newInstance是创建了一个构造方法的声明类的新实例对象。并为之调用
3、获取成员变量并调用 public class Student { public Student(){ } //**********字段*************// public String name; protected int age; char sex; private String phoneNum; @Override public String toString() { return "Student [name=" + name + ", age=" + age + ", sex=" + sex + ", phoneNum=" + phoneNum + "]"; } }
测试类: import java.lang.reflect.Field; /* * 获取成员变量并调用: * * 1.批量的 * 1).Field[] getFields():获取所有的"公有字段" * 2).Field[] getDeclaredFields():获取所有字段,包括:私有、受保护、默认、公有; * 2.获取单个的: * 1).public Field getField(String fieldName):获取某个"公有的"字段; * 2).public Field getDeclaredField(String fieldName):获取某个字段(可以是私有的) * * 设置字段的值: * Field --> public void set(Object obj,Object value): * 参数说明: * 1.obj:要设置的字段所在的对象; * 2.value:要为字段设置的值; * */ public class Fields { public static void main(String[] args) throws Exception { //1.获取Class对象 Class stuClass = Class.forName("fanshe.field.Student"); //2.获取字段 System.out.println("************获取所有公有的字段********************"); Field[] fieldArray = stuClass.getFields(); for(Field f : fieldArray){ System.out.println(f); } System.out.println("************获取所有的字段(包括私有、受保护、默认的)********************"); fieldArray = stuClass.getDeclaredFields(); for(Field f : fieldArray){ System.out.println(f); } System.out.println("*************获取公有字段**并调用***********************************"); Field f = stuClass.getField("name"); System.out.println(f); //获取一个对象 Object obj = stuClass.getConstructor().newInstance();//产生Student对象--》Student stu = new Student(); //为字段设置值 f.set(obj, "刘德华");//为Student对象中的name属性赋值--》stu.name = "刘德华" //验证 Student stu = (Student)obj; System.out.println("验证姓名:" + stu.name); System.out.println("**************获取私有字段****并调用********************************"); f = stuClass.getDeclaredField("phoneNum"); System.out.println(f); f.setAccessible(true);//暴力反射,解除私有限定 f.set(obj, "18888889999"); System.out.println("验证电话:" + stu); } }
输出: ************获取所有公有的字段******************** public java.lang.String fanshe.field.Student.name ************获取所有的字段(包括私有、受保护、默认的)******************** public java.lang.String fanshe.field.Student.name protected int fanshe.field.Student.age char fanshe.field.Student.sex private java.lang.String fanshe.field.Student.phoneNum *************获取公有字段**并调用*********************************** public java.lang.String fanshe.field.Student.name 验证姓名:刘德华 **************获取私有字段****并调用******************************** private java.lang.String fanshe.field.Student.phoneNum 验证电话:Student [name=刘德华, age=0, sex=
由此可见
调用字段时:需要传递两个参数:
Object obj = stuClass.getConstructor().newInstance();//产生Student对象--》Student stu = new Student();
//为字段设置值
f.set(obj, "刘德华");//为Student对象中的name属性赋值--》stu.name = "刘德华"、
修改私有属性时,需要先暴力反射,解除私有权限f.setAccessible(true);
第一个参数:要传入设置的对象,第二个参数:要传入实参
4、获取成员方法并调用 public class Student { //**************成员方法***************// public void show1(String s){ System.out.println("调用了:公有的,String参数的show1(): s = " + s); } protected void show2(){ System.out.println("调用了:受保护的,无参的show2()"); } void show3(){ System.out.println("调用了:默认的,无参的show3()"); } private String show4(int age){ System.out.println("调用了,私有的,并且有返回值的,int参数的show4(): age = " + age); return "abcd"; } }
测试类: import java.lang.reflect.Method; /* * 获取成员方法并调用: * * 1.批量的: * public Method[] getMethods():获取所有"公有方法";(包含了父类的方法也包含Object类) * public Method[] getDeclaredMethods():获取所有的成员方法,包括私有的(不包括继承的) * 2.获取单个的: * public Method getMethod(String name,Class... parameterTypes): * 参数: * name : 方法名; * Class ... : 形参的Class类型对象 * public Method getDeclaredMethod(String name,Class... parameterTypes) * * 调用方法: * Method --> public Object invoke(Object obj,Object... args): * 参数说明: * obj : 要调用方法的对象; * args:调用方式时所传递的实参; ): */ public class MethodClass { public static void main(String[] args) throws Exception { //1.获取Class对象 Class stuClass = Class.forName("fanshe.method.Student"); //2.获取所有公有方法 System.out.println("***************获取所有的”公有“方法*******************"); stuClass.getMethods(); Method[] methodArray = stuClass.getMethods(); for(Method m : methodArray){ System.out.println(m); } System.out.println("***************获取所有的方法,包括私有的*******************"); methodArray = stuClass.getDeclaredMethods(); for(Method m : methodArray){ System.out.println(m); } System.out.println("***************获取公有的show1()方法*******************"); Method m = stuClass.getMethod("show1", String.class); System.out.println(m); //实例化一个Student对象 Object obj = stuClass.getConstructor().newInstance(); m.invoke(obj, "刘德华"); System.out.println("***************获取私有的show4()方法******************"); m = stuClass.getDeclaredMethod("show4", int.class); System.out.println(m); m.setAccessible(true);//解除私有限定 Object result = m.invoke(obj, 20);//需要两个参数,一个是要调用的对象(获取有反射),一个是实参 System.out.println("返回值:" + result); } }
输出: ***************获取所有的”公有“方法******************* public void fanshe.method.Student.show1(java.lang.String) public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException public final void java.lang.Object.wait() throws java.lang.InterruptedException public boolean java.lang.Object.equals(java.lang.Object) public java.lang.String java.lang.Object.toString() public native int java.lang.Object.hashCode() public final native java.lang.Class java.lang.Object.getClass() public final native void java.lang.Object.notify() public final native void java.lang.Object.notifyAll() ***************获取所有的方法,包括私有的******************* public void fanshe.method.Student.show1(java.lang.String) private java.lang.String fanshe.method.Student.show4(int) protected void fanshe.method.Student.show2() void fanshe.method.Student.show3() ***************获取公有的show1()方法******************* public void fanshe.method.Student.show1(java.lang.String) 调用了:公有的,String参数的show1(): s = 刘德华 ***************获取私有的show4()方法****************** private java.lang.String fanshe.method.Student.show4(int) 调用了,私有的,并且有返回值的,int参数的show4(): age = 20 返回值:abcd
由此可见:
m = stuClass.getDeclaredMethod("show4", int.class);//调用制定方法(所有包括私有的),需要传入两个参数,第一个是调用的方法名称,第二个是方法的形参类型,切记是类型。
System.out.println(m);
m.setAccessible(true);//解除私有限定
Object result = m.invoke(obj, 20);//需要两个参数,一个是要调用的对象(获取有反射),一个是实参
System.out.println("返回值:" + result);//
5、反射main方法 public class Student { public static void main(String[] args) { System.out.println("main方法执行了。。。"); } }
测试类: import java.lang.reflect.Method; /** * 获取Student类的main方法、不要与当前的main方法搞混了 */ public class Main { public static void main(String[] args) { try { //1、获取Student对象的字节码 Class clazz = Class.forName("fanshe.main.Student"); //2、获取main方法 Method methodMain = clazz.getMethod("main", String[].class);//第一个参数:方法名称,第二个参数:方法形参的类型, //3、调用main方法 // methodMain.invoke(null, new String[]{"a","b","c"}); //第一个参数,对象类型,因为方法是static静态的,所以为null可以,第二个参数是String数组,这里要注意在jdk1.4时是数组,jdk1.5之后是可变参数 //这里拆的时候将 new String[]{"a","b","c"} 拆成3个对象。。。所以需要将它强转。 methodMain.invoke(null, (Object)new String[]{"a","b","c"});//方式一 // methodMain.invoke(null, new Object[]{new String[]{"a","b","c"}});//方式二 } catch (Exception e) { e.printStackTrace(); } } }
6、反射方法的其它使用之---通过反射运行配置文件内容 public class Student { public void show(){ System.out.println("is show()"); } }
配置文件以txt文件为例子(pro.txt): className = cn.fanshe.Student methodName = show
测试类: import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; import java.lang.reflect.Method; import java.util.Properties; /* * 我们利用反射和配置文件,可以使:应用程序更新时,对源码无需进行任何修改 * 我们只需要将新类发送给客户端,并修改配置文件即可 */ public class Demo { public static void main(String[] args) throws Exception { //通过反射获取Class对象 Class stuClass = Class.forName(getValue("className"));//"cn.fanshe.Student" //2获取show()方法 Method m = stuClass.getMethod(getValue("methodName"));//show //3.调用show()方法 m.invoke(stuClass.getConstructor().newInstance()); } //此方法接收一个key,在配置文件中获取相应的value public static String getValue(String key) throws IOException{ Properties pro = new Properties();//获取配置文件的对象 FileReader in = new FileReader("pro.txt");//获取输入流 pro.load(in);//将流加载到配置文件对象中 in.close(); return pro.getProperty(key);//返回根据key获取的value值 } }
需求:
当我们升级这个系统时,不要Student类,而需要新写一个Student2的类时,这时只需要更改pro.txt的文件内容就可以了。代码就一点不用改动
要替换的student2类: public class Student2 { public void show2(){ System.out.println("is show2()"); } }
配置文件更改为: public class Student2 { public void show2(){ System.out.println("is show2()"); } }
7、反射方法的其它使用之---通过反射越过泛型检查
泛型用在编译期,编译过后泛型擦除(消失掉)。所以是可以通过反射越过泛型检查的
测试类: import java.lang.reflect.Method; import java.util.ArrayList; /* * 通过反射越过泛型检查 * * 例如:有一个String泛型的集合,怎样能向这个集合中添加一个Integer类型的值? */ public class Demo { public static void main(String[] args) throws Exception{ ArrayList strList = new ArrayList<>(); strList.add("aaa"); strList.add("bbb"); // strList.add(100); //获取ArrayList的Class对象,反向的调用add()方法,添加数据 Class listClass = strList.getClass(); //得到 strList 对象的字节码 对象 //获取add()方法 Method m = listClass.getMethod("add", Object.class); //调用add()方法 m.invoke(strList, 100); //遍历集合 for(Object obj : strList){ System.out.println(obj); } } }
反射的使用场景(动态代理)
A:动态代理概述
* 代理:本来应该自己做的事情,请了别人来做,被请的人就是代理对象。
* 举例:春节回家买票让人代买
* 动态代理:在程序运行过程中产生的这个对象,而程序运行过程中产生对象其实就是我们刚才反射讲解的内容,所以,动态代理其实就是通过反射来生成一个代理
* 在Java中java.lang.reflect包下提供了一个Proxy类和一个InvocationHandler接口,通过使用这个类和接口就可以生成动态代理对象。JDK提供的代理只能针对接口做代理。我们有更强大的代理cglib,Proxy类中的方法创建动态代理类对象
* public static Object newProxyInstance(ClassLoader loader,Class[] interfaces,InvocationHandler h)
* 最终会调用InvocationHandler的方法
* InvocationHandler Object invoke(Object proxy,Method method,Object[] args)
项目管理
2018-06-03 22:03:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
Dubbo的启动主要是发布服务的过程,起到核心作用的就是ServiceConfig(ServiceConfig就是我们在Dubbo的配置文件中配置的dubbo:service这些配置项对应的实体类)。服务的启动初始位置也基本是在这里,下面我们来看看具体的实现内容。
讲基本内容前首先理清楚几个名词概念:
Invoker:Invoker的概念我们在动态代理的时候就接触过,中文的意思大概是执行者,这里其实可以理解为具体方法的执行者。其核心内容大致如下: Class getInterface(); Result invoke(Invocation invocation) throws RpcException; URL getUrl();

通过以上的三个方法们就可以执行到具体的方法并且获得方法的执行结果。通过getUrl获得需要执行的方法具体实现细节,主要是获得具体的ref;其次就是组装方法的参数信息等等,这些信息在invocation里面都有封装;最后通过执行invoke方法触发具体的方法并返回结果。从这里可以看出Invoker是具体方法执行的最后一个守关者,获得了Invoker,就获得了具体接口的代码,然后执行代理就可以。
Invoker仅仅是作为一个代理的门面,其不仅可以代表本地执行Duubo调用的代理,还可以充当RPC时候的代理,更是可以将自己包装成一个多个Invoker聚合而成的代理(主要是处理集群的一些策略,包括负载均衡和路由等)。
Exporter:服务暴露的过程中会将Invoker转换成Exporter(暴露者),及Exporter其实包含了Invoker,主要是用于不同层面的服务发布。
其实Dubbo 还有一些比较重要的对象,像Protocol,Exchanger等等。我认为在这里直接说明不太合适,所以等到我们用到之后再开始说明。
1. 核心的属性信息
一些基本的属性:group,version,interfaceName,interfaceClass,timeout等等。我们凡是可以在dubbo:service上配置的属性都在ServiceConfig中可以找得到对应的属性;
//dubbo对应的服务发布协议,这里可以清楚地看到Dubbo在这里使用的自己的spi机制,来保证灵活性。(至于SPI机制的具体实现,之后有机会的话会讲到,简单理解就是通过getExtensionLoader获得对应类的扩展类实现类)
private static final Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
private static final ProxyFactory proxyFactory = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();
private final List urls = new ArrayList();
private final List> exporters = new ArrayList>();
2.服务暴露过程
对于服务暴露来说,在ServiceConfig里面的初始方法就是export()方法了,下面我们从export方法开始来看看: public synchronized void export() { if (provider != null) { //默认取provider的配置 if (export == null) { export = provider.getExport(); } if (delay == null) { delay = provider.getDelay(); } } //如果export设置为false的话就直接返回 if (export != null && ! export.booleanValue()) { return; } //如果设置延迟时间的话就延迟指定时间然后进行暴露 if (delay != null && delay > 0) { Thread thread = new Thread(new Runnable() { public void run() { try { Thread.sleep(delay); } catch (Throwable e) { } doExport(); } }); thread.setDaemon(true);//将暴露接口的线程设置为守护线程 thread.setName("DelayExportServiceThread"); thread.start(); } else { doExport(); //一切暴露的核心还都是要看doExport方法。 } } protected synchronized void doExport() { // 防止服务多次暴露 // 设置默认的基本属性 // 针对泛化接口做单独处理 if (ref instanceof GenericService) { interfaceClass = GenericService.class; if (StringUtils.isEmpty(generic)) { generic = Boolean.TRUE.toString(); } } else { try {//通过反射初始化接口(interfaceName是实现类的全称) interfaceClass = Class.forName(interfaceName, true, Thread.currentThread() .getContextClassLoader()); } catch (ClassNotFoundException e) { throw new IllegalStateException(e.getMessage(), e); } //检查定义的方法是否为接口中的方法 checkInterfaceAndMethods(interfaceClass, methods); //检查引用不为空,并且引用必需实现接口 checkRef(); //如果到这一步的话说明类实现是自己定义的,所以设置generic为false generic = Boolean.FALSE.toString(); } // 处理Local和Stub代理处理 // 检查Application,Registry,Protocol的配置情况 //将配置的属性绑定到当前对象 appendProperties(this); //针对Local,Stub和Mock进行校验 //上面的操作主要是做一些检验和初始化的操作,没有涉及到具体的暴露服务逻辑 doExportUrls(); } private void doExportUrls() { //取到注册中心的URL List registryURLs = loadRegistries(true); for (ProtocolConfig protocolConfig : protocols) { //根据配置的通信协议将服务暴露到注册中心 doExportUrlsFor1Protocol(protocolConfig, registryURLs); } } private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List registryURLs) { // 默认采用Dubbo协议 //之后的部分逻辑就是想尽一切办法取到host //取到端口号(之后的部分关于端口的逻辑就是想尽一切办法取端口号) // 这个map十分重要,它的意义在于所有存储所有最终使用到的属性,我们知道一个属性例如timeout,可能在Application,provider,service中都有配置,具体以哪个为准,都是这个map处理的事情。 Map map = new HashMap(); if (anyhost) { //如果此时anyhost为true的话 map.put(Constants.ANYHOST_KEY, "true"); } // 存储简单的服务信息 //将application,module,provider,protocol和service的信息设置到map里面 //将应用配置的树勇按照层级存入map中。注意这里的层级关系,是一层层覆盖的 即关系为:ServiceConfig->PrtocolConfig->ProviderConfig->ModuleConfig->ApplicaionConfig //单独处理好method层级的参数关系 //判断有没有配置通配协议 if (ProtocolUtils.isGeneric(generic)) { map.put("generic", generic); map.put("methods", Constants.ANY_VALUE); } else { String revision = Version.getVersion(interfaceClass, version); if (revision != null && revision.length() > 0) { map.put("revision", revision); } //通过包装类将interfaceClass进行包装然后取得方法名字,对于wapper包装器就是将不同的类统一化 //参考http://blog.csdn.net/quhongwei_zhanqiu/article/details/41597261理解Wapper String[] methods = Wrapper.getWrapper(interfaceClass).getMethodNames(); if(methods.length == 0) { logger.warn("NO method found in service interface " + interfaceClass.getName()); map.put("methods", Constants.ANY_VALUE); } else { //将所有的方法拼接成以逗号为分隔符的字符串 map.put("methods", StringUtils.join(new HashSet(Arrays.asList(methods)), ",")); } } //处理token属性 //如果配置的injvm的话就代表本地调用(本地调用还用Dubbo的话实在有点蛋疼) //所有的核心属性最后都成了URL的拼接属性,如果我们还记得map里面拼装了多少属性的话就知道这个URL内容有多丰富 URL url = new URL(name, host, port, (contextPath == null || contextPath.length() == 0 ? "" : contextPath + "/") + path, map); // 下面是核心暴露过程,将不会省略源码 String scope = url.getParameter(Constants.SCOPE_KEY); //配置为none不暴露 if (! Constants.SCOPE_NONE.toString().equalsIgnoreCase(scope)) { //配置不是remote的情况下做本地暴露 (配置为remote,则表示只暴露远程服务) if (!Constants.SCOPE_REMOTE.toString().equalsIgnoreCase(scope)) { exportLocal(url); } //如果配置不是local则暴露为远程服务.(配置为local,则表示只暴露远程服务) if (! Constants.SCOPE_LOCAL.toString().equalsIgnoreCase(scope) ){ if (logger.isInfoEnabled()) { logger.info("Export dubbo service " + interfaceClass.getName() + " to url " + url); } if (registryURLs != null && registryURLs.size() > 0 && url.getParameter("register", true)) { for (URL registryURL : registryURLs) { //dynamic表示是否需要人工管理服务的上线下线(动态管理模式) url = url.addParameterIfAbsent("dynamic", registryURL.getParameter("dynamic")); URL monitorUrl = loadMonitor(registryURL); if (monitorUrl != null) { url = url.addParameterAndEncoded(Constants.MONITOR_KEY, monitorUrl.toFullString()); } if (logger.isInfoEnabled()) { logger.info("Register dubbo service " + interfaceClass.getName() + " url " + url + " to registry " + registryURL); } ==================================================== //取到invoker对象(ref为接口实现类的引用) Invoker invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString())); //将invoker转化为exporter对象 Exporter exporter = protocol.export(invoker); exporters.add(exporter); //将exporter添加到需要暴露的列表中取 } ================================================================ } else { //如果找不到注册中心的话就自己充当自己的注册中心吧 Invoker invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, url); Exporter exporter = protocol.export(invoker); exporters.add(exporter); } } } //多个协议就有多个url与其对应,所以要一一存储。 this.urls.add(url); }
检验所需参数的合法性
将多层的参数(可能重复配置)最终整理出最终的结果(map),然后根据参数拼接成暴露服务需用到的url。
处理generic,Stub,injvm等其他需要支持的内容,补充dubbo的功能多样性,但是都不涉及核心流程。
根据对应的协议将服务进行暴露(将提供的服务推送到注册中心供服务调用者发现),默认使用Dubbo协议。
项目管理
2018-08-20 09:59:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
以电子商务系统配置管理为实例,手把手教你搭建 jeesz 模块项目
1、 创建表
1.1、 创建电子商务系统配置 jeesz_eb_global_config 表
SET FOREIGN_KEY_CHECKS=0;
-- ----------------------------
-- Table structure for `jeesz_eb_global_config`
-- ----------------------------
DROP TABLE IF EXISTS `jeesz_eb_global_config`;
CREATE TABLE `jeesz_eb_global_config` (
`id` varchar(64) NOT NULL COMMENT '编号',
`context_path` varchar(20) DEFAULT NULL COMMENT '部署路径',
`port` int(11) DEFAULT NULL COMMENT '端口号',
`treaty` longtext COMMENT '用户协议',
`activescore` int(11) NOT NULL COMMENT '激活积分',
`def_img` varchar(255) NOT NULL DEFAULT '/r/eb/u/no_picture.gif' COMMENT '图片不存
在时默认图片',
`create_by` varchar(64) NOT NULL COMMENT '创建者',
`create_date` datetime NOT NULL COMMENT '创建时间',
`update_by` varchar(64) NOT NULL COMMENT '更新者',
`update_date` datetime NOT NULL COMMENT '更新时间',
`remarks` varchar(255) DEFAULT NULL COMMENT '备注信息',
`del_flag` char(1) NOT NULL DEFAULT '0' COMMENT '删除标记',
PRIMARY KEY (`id`),
KEY `jeesz_eb_global_config` (`del_flag`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='jeesz 电子商务系统配置表';
-- ----------------------------
-- Records of jeesz_eb_global_config
-- ----------------------------
注意:
1. 表名的修改
2. `create_by`、`create_date`、`update_by`、`update_date`、`remarks`、`del_flag` 是不可缺少的,大家在创建表的时候请勿忽略这些字段。

1.2、 驱动式方案添加业务表配置
点击下一步进行业务表配置(主要针对于 sql 查询条件、页面元素进行设置)
最后进行保存
1.3、 生成方案添加
保存并生成代码(我代码生成在 D:/src 目录下)
2、 创建模块项目
2.1、 根据自己的业务创建模块项目(我以 EB 为实例)

2.2、 对 module 项目进行修改、配置
因为考虑到项目的完整和一致性,通过工具生成的 maven 项目缺少一些源文件,故需要手动创建如下:
点击 ok 后对新创建的文件目录进行顺序调整:
调整后的结果:
修改模块项目 jeesz-core-eb 的 pom.xml 文件:
具体内容如下:



com.alibaba
druid
${druid.version}



mysql
mysql-connector-java
${mysql.driver.version}
runtime


com.oracle
ojdbc14
${oracle.driver.version}
runtime


net.sourceforge.jtds
jtds
${mssql.driver.version}
runtime



org.aspectj
aspectjrt
1.7.4


org.aspectj
aspectjweaver
1.7.4


cglib
cglib
3.1




com.sml.sz
jeesz-config





com.sml.sz
jeesz-framework



2.3、 将生的代码 copy 到指定目录 com.sml.sz.eb
修改 jeesz-project 的 pom.xml 文件,添加模块依赖
代码如下


com.sml.sz
jeesz-core-eb
${project.version}

修改 jeesz-web-admin 的 pom.xml 文件,添加模块依赖
代码如下


com.sml.sz
jeesz-core-eb

将生成的 controller 文件 copy 到 web 项目中

将生成的界面文件 copy 到 web 项目中:

3、 新建菜单并配置权限
具体配置请看我的收藏配置
功能截图:

项目管理
2018-08-20 09:47:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
Spring MVC 是一个模型 - 视图 - 控制器(MVC)的Web框架建立在中央前端控制器servlet(DispatcherServlet),它负责发送每个请求到合适的处理程序,使用视图来最终返回响应结果的概念。Spring MVC 是 Spring 产品组合的一部分,它享有 Spring IoC容器紧密结合Spring松耦合等特点,因此它有Spring的所有优点。
SpringMVC架构图所下所示

Spring Web MVC框架特点
清晰的角色划分:控制器(controller)、验证器(validator)、 命令对象(command object)、表单对象(form object)、模型对象(model object)、 Servlet分发器(DispatcherServlet)、 处理器映射(handler mapping)、视图解析器(view resolver)等等。 每一个角色都可以由一个专门的对象来实现。
强大而直接的配置方式:将框架类和应用程序类都能作为JavaBean配置,支持跨多个context的引用,例如,在web控制器中对业务对象和验证器(validator)的引用。
可适配、非侵入:可以根据不同的应用场景,选择合适的控制器子类 (simple型、command型、form型、wizard型、multi-action型或者自定义),而不是从单一控制器 (比如Action/ActionForm)继承。
可重用的业务代码:可以使用现有的业务对象作为命令或表单对象,而不需要去扩展某个特定框架的基类。
可定制的绑定(binding) 和验证(validation):比如将类型不匹配作为应用级的验证错误, 这可以保存错误的值。再比如本地化的日期和数字绑定等等。
可定制的handler mapping和view resolution:Spring提供从最简单的URL映射, 到复杂的、专用的定制策略。
灵活的model转换:在Springweb框架中,使用基于Map的 键/值对来达到轻易地与各种视图技术的集成。
可定制的本地化和主题(theme)解析:支持在JSP中可选择地使用Spring标签库、支持JSTL、支持Velocity(不需要额外的中间层)等等。
简单而强大的JSP标签库(Spring Tag Library):支持包括诸如数据绑定和主题(theme) 之类的许多功能。它提供在标记方面的最大灵活性。
JSP表单标签库:在Spring2.0中引入的表单标签库,使得在JSP中编写 表单更加容易。
Spring Bean的生命周期可以被限制在当前的HTTP Request或者HTTP Session。 准确的说,这并非Spring MVC框架本身特性,而应归属于Sping MVC使用的WebApplicationContext容器。
Spring MVC大致的执行流程如下:
1、首先浏览器发送请求给前端控制器DispatcherServlet,DispatcherSerlvet根据请求信息,基于一定的原则选择合适的控制器进行处理并把 请求委托给它。
2、页面控制器接收到请求之后进行功能处理,首先需要收集、绑定请求参数到一个对象(命令对象),并进行验证,然后将该对象委托给业务对象进行处理(service层);业务对象处理之后控制器将返回一个ModelAndView(模型数据和逻辑视图名);
3、DispatcherServlet根据返回的逻辑视图名,选择合适的视图进行渲染(界面展示、资源加载),并把模型数据传入以便视图渲染。
4、前端控制器将响应返回个客户端浏览器。
项目管理
2018-08-20 09:30:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
数据库
所有的其他数据库,包括有页面,内容都存储在数据库中。如果你安装的 Confluence 是用于评估或者你选择使用的是 Embedded H2 Database 数据库。数据库有关的文件将会存储在 database/ 目录中,这个目录位于 Home 目录下面。否则数据库将会存储你 Confluence 站点所使用的所有数据。
临时目录(安装目录)
temp 目录是由 Java 运行时进行配置的,同时一些 Confluence 的组件将会写入历史文件或者锁定文件到这个目录中。
临时目录位于安装目录下的 /temp 目录中。
希望修改这个目录的位置,重启你的 Java虚拟机(Java Virtual Machine)的时候配置下面的参数:
- Djava.io .tmpdir=/path/to/your/own/temp/directory .
请注意:这个临时目录与 Confluence Home 目录中导出的临时目录是不相同的。请查看上面表格中有关 /temp 的配置来修改你临时目录的配置。
https://www.cwiki.us/display/CONF6ZH/Confluence+Home+and+other+important+directories
项目管理
2018-08-20 00:46:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
当 Confluence 第一次启动的时候,Confluence 将会读取 confluence-init.properties 文件并从这个文件中确定如何去查找 Home 目录。
希望修改 home 目录的路径,你可以在 confluence-init.properties 文件中按照下面的格式修改 confluence.home 的属性: Windows
在 Window 环境下,路径 C:\confluence\data 将会被写成:
confluence.home=C:/confluence/data
请注意需要将反斜杠(\)修改为斜杆(/) Linux / Solaris
在任何基于 Linux 环境的系统中,属性的定义遵守 Linux 定义的常规句法:
confluence.home=/var/confluence/
符号链接(symbolic links)
在 Confluence 的 home 目录中,可能会有一些符号链接。你需要必须定义为绝对路径。如果你有磁盘空间存储的问题,请修改 confluence.home 目录的配置指派到有足够空间的磁盘。生成文件所使用的绝对路径(例如导出的时候)将会使用 confluence.home 中配置的绝对路径,当在构造 URLs 的时候。当一个子目录有不同的的路径的时候,URL 的构造可能会不正常,你可能会收到页面没有找到的错误。这个信息用来避免下页面中显示目录的所有结构。
修改 Confluence 配置
Confluence 配置文件: confluence-cfg.xml 在 home 目录中可能参照你 Confluence home 目录的原始位置。你需要编辑这个文件来升级参考这些文件指向到新的位置。在这个文件中有下面 2 个属性可以进行配置: daily.backup.dir 如果你还没有配置,你的备份已经在其他地方进行存储了。 hibernate.connection.url 如果你现在正在使用嵌入的 HSQL 数据库。
https://www.cwiki.us/display/CONF6ZH/Confluence+Home+and+other+important+directories
项目管理
2018-08-20 00:41:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
Confluence Home 目录是存储 Confluence 信息,查找索引,页面附件的地方。其实 Home 目录也可以定义为数据目录。
找到 Home 目录
Confluence 的 home 目录是你在安装 Confluence 的时候定义的。存储位置定义在 confluence-init.properties 文件中,这个文件位于你 Confluence 安装目录的 confluence/WEB-INF/classes 目录下。
当 Confluence 正在运行的时候,你可以从下面的配置连接中找到 home 目录的配置路径: > 基本配置( General Configuration) > 系统信息(System Information) > Confluence 信息 - Home 目录(Confluence Information - Confluence Home) 。
如果你现在正在使用的是 Confluence 数据中心(集群服务器),同时你已经配置了 共享 home (shared home) 目录,这个目录中应该已经有一些数据了(例如附件和备份等),那么你共享目录的大小可能改变。你共享目录的路径应该在你的 /confluence.cfg.xml 文件中的 c onfluence.cluster.home 属性定义。
Home 目录中的内容
Confluence home 目录存储了 Confluence 在运行中所使用的数据。下面对 Confluence home 目录中使用的数据和文件进行一些说明:
文件或目录(File or directory) 用途(Purpose) confluence.cfg.xml 这个文件有 Confluence 能正常启动所需要的所有配置,例如有: 产品许可证(Product license) 上下文路径(Context path) 数据库配置细节,例如数据库地址,连接池配置等 重要目录的路径
attachments/ 这个目录存储了 Confluence 所使用附件的每一版本。
你可以为你的附件存储指定一个特定的其他目录,这个配置是在 confluence.cfg.xml 文件中的 attachments.dir 配置定义的。
在数据中心模式下,这个目录通常是能在 Shared Home 目录中定义的。
backups/ Confluence 将会把每天的备份文件存储在这里,同时也会存储你手动创建的备份文件。在这个备份目录中的备份文件通常的文件名是按照 daily-backup-YYYY_MM_DD.zip 格式定义的。
你可以为你的备份文件存储指定一个特定的其他目录,这个配置是在 confluence.cfg.xml 文件中的 daily.backup.dir 配置定义的。
在数据中心模式下,这个目录通常是能在 Shared Home 目录中定义的。
bundled-plugins/ Confluence 安装了一系列的插件。 bundled-plugins 目录是 Confluence 在启动的时候解压压缩的插件后存储的地方。这个目录将会在 Confluence 每一次重启的时候进行刷新,所以将这个目录中安装的插件删除的话是不会删除 Confluence 已经安装的插件的。因为这些内容将会在下一次 Confluence 重启的时候重新创建。
database/ 这个是 Confluence 运行使用 Embedded H2 Database 模式的时候数据库存储的位置。在这种情况下,这个目录存储了 Confluence 运行所需要的所有数据库数据。如果你配置 Confluence 使用外部数据库例如你使用的数据库是 MySQL 的话,这个目录是不需要的。
index/ Confluence 的索引在 Confluence 使用的频率非常高,查找 Confluence 最近更新的内容列表对 Confluence 运行来说是非常重要的。如果这个目录中的数据丢失或者损坏了,你可以通过重构完整索引的方式将这些内容进行恢复。这个过程可能要花费很差的时间,花费时间的长度与你系统运行的数据量有多少有关系。
你可以为你的索引文件存储指定一个特定的其他目录,这个配置是在 confluence.cfg.xml 文件中的 lucene.index.dir 配置定义的。
journal/ 所以添加的日志(journal )存储在这里,这个添加的日志为你对页面进行修改的的时候(例如,页面添加了评论,点赞,新页面等)。Journal 中的内容将会被处理并添加到索引中(每 5 秒钟处理一次)。
在集群环境下,这个目录中保存了各个实例需要同步的内容。keeps the indexes on each node in sync.
logs/ Confluence 的应用程序日志存储在这个目录中。
plugin-cache/ 所有的 Confluence 插件存储在数据库中。为了能够更加快速的访问插件所需要的 Jar 文件,Confluence 将会把这些存储在数据库中的插件缓存到本地上来,这个缓存目录为 plugin-cache 目录。这个目录中的内容将会在 Confluence 每一次重新启动的时候刷新,在每一次 Confluence 重新启动的时候,数据将会从 Confluence 数据库中缓存到这里,将这个目录中安装的插件删除的话是不会删除 Confluence 已经安装的插件的。
temp/
thumbnails/
temp 目录被用来使用为运行时候的功能临时存储的地方,例如导出,导入,文件上传和索引。在这个目录中存储的文件为临时存储的文件。当 Confluence 实例没有运行的时候,你可以安全的清空这个文件夹中的所有内容。一个每天运行的计划任务将会删除这个文件中不在需要的文件。
你可以为你的备份文件存储指定一个特定的其他目录,这个配置是在 confluence.cfg.xml 文件中在 webwork.multipart.saveDir 配置新的存储位置。
为图片缩略图存储的临时文件目录。这个目录只用来使用为缩略图的换成,当这个文件中的内容删除后,当图片下一次访问的时候,系统会重新将文件的缩略图存在这个文件夹中创建。
在数据中心模式下,这个目录通常是能在 Shared Home 目录中定义的。
https://www.cwiki.us/display/CONF6ZH/Confluence+Home+and+other+important+directories
项目管理
2018-08-20 00:37:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
本部分对你 Confluence 的外部设置进行描述。包括有如何配置 Web 服务器,应用服务器,目录和文件等信息—— Confluence 运行所需要的所有环境。有关在服务器内部对配置进行修改的内容请参考页面 Configuring a Confluence Environment 中的内容。
Confluence 是一个 J2EE web 应用。在客户端层面,用户主要通过浏览器来访问 Confluence。
这个部分包含有下面的一些导引: Confluence Home and other important directories Application Server Configuration Starting Confluence Automatically on System Startup
图片:一个 Confluence 安装
https://www.cwiki.us/display/CONF6ZH/Configuring+a+Confluence+Environment
项目管理
2018-08-20 00:15:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
Confluence 安装目录
Confluence 安装的目录(Confluence Installation directory)定义的是 Confluence 是在那里进行安装的。这个目录有时候也被称为 Confluence 安装目录(Confluence Install directory)。
安装目录中的一些重要文件: bin/setenv.bat or bin/setenv.sh
这个文件通常被用来编辑 CATALINA_OPTS 内存和垃圾回收的设置,同时也定义了系统的属性。 confluence/WEB-INF/classes/confluence-init.properties
这个文件中定义了 Confluence Home 目录的位置。
https://www.cwiki.us/display/CONF6ZH/Confluence+Home+and+other+important+directories
项目管理
2018-08-20 00:25:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
更多新文章,请关注博主个人网站: http://wwhl.wang (助记:万物互联.网)
【若要到岸,请摇船:开源中国 不最醉不龟归】
说到软件测试,这个职位目前在国内着实有点尴尬。外行的不知道这个是干啥子的,如果你要解释,当时又不想费那么多口水,你可以说属于it行业的。他们一般做room顿悟状;行内的又觉得低人一等,就连有些测试(主要是一些只会功能测试的,或者说只会点点点的低级功能测试,这两点有没什么区别?有什么区别?后面有时间再八一下吧)都会自觉低人一等,觉得自身就是开发或者其他职位的附庸。其实有些人有这些心理,对,也不对。为什么对,为什么不对,就涉及到下来的内容了。花开两朵,各表一枝......
这些天跟之前的一些同学交流,他们现在要么是自己开学习班,要么当老师。主要是软件测试方面的(当然也有从事区块链方面的,总感觉这方面有点高大上,这是相对低级的功能测试而言),刚好本人也是从事这方面的工作。如果单从低级的功能测试而言,我个人认为只要四肢健全,智商在90+(智商分值对应的正常登记看文末)都是可以胜任的。
细心的同学可能就发现了,上面我几次提到低级功能测试字眼。不知道专业书籍有没这方面的区分,但是个人觉得功能测试也有低高之分。低级的功能测试,就是人家给你一些测试用例,你就照着测试用例执行(业内自嘲的叫法是“点点点”,据说32瞬间赞,就可以这么练出来当然,这只是博主的玩笑话),完全没有自己思想地延伸,如果经验丰富的测试人员,在执行过程中,感觉某些地方会出问题,很大程度上就会出问题。不要觉得这是第六感,觉得很玄乎。其实不然,何为经验丰富?业务相当熟悉,后台的架构,实现逻辑等都很熟悉的测试人员就是能凭第六感提bug的经验丰富测试人员。高级的功能测试甚至可以跟开发一起讨论实现业务的逻辑,虽然真正的编码他未必能写出来,但是实现逻辑他却可以给出自己的想法。如果你能做到这一步,你还会觉得自己低人(开发)一等吗?还会觉得你就是别人的附庸,可有可无的吗?还需要在项目组里面感觉自己卑微如尘埃吗?开发甩过来的锅,只能一声不吭地接住,成为忍者神龟般地背锅侠吗?
至于成为高级的测试人员如果本身没有相关的基础的话,也不是一朝一夕就能做到的。
一般学习班的整体课程:Linux,MySQL(Oracle),环境搭建,功能测试用例的编写,编程基础,接口测试(JMeter入门),自动化测试(入门),性能测试(入门)。到最后一个星期就是复习,模拟面试,然后就是推荐简历,用人单位筛选简历,觉得可以,就安排面试。 纵观整个学习的流程,最终能否就业,也是看你个人面试的结果如何。 所谓的包就业,那也是坑人的。试想,你如果面试不上,用人单位不要你,培训机构如何包就业?
上面说了那么多,个人的观点就是如果有定力的同学(只是有定力的同学又有几个呢),无需专门报班,只需系统学习以上知识就可以面试软件测试职位。本人打算下班有空之余,写一个测试软件专题,就按照以上内容来写,当然仅限平时面试,工作用到知识,如果需要扩展知识的同学,本人也会提供几个比较靠谱的学习网站的针对课程,可以自行到上面去学习。不过不建议学习初期,知识面扩得太广,免得跟博主一样——兴趣广泛,心得全无。还是那句话,一切的学习不以实用为目的,都是浪费时间,都是跟自己的人生耍流氓。
如果这算业余工作的话,工期我可无法保障,因为本人现在某为负责自动化测试,明天又得接手未接触过的业务的API自动化。所以可能需要几个月时间吧~反正有时间,又不累的时候就写一下。后面如果能坚持下来,可能考虑承接简历包装,远程模拟面试等业务。不过这些现在言之尚早。接下来写的是《(一)软件测试专题——之Linux常用命令篇》。
更多新文章,请关注博主个人网站: http://wwhl.wang (助记:万物互联.网)
PS:智商140以上者称为天才,智商120-140为最优秀,100、110、120为优秀,90-100为常才,80-90为次正常,70-80为临界正常,60-70为轻度智力落后,50-60为愚鲁,20-25为痴鲁,25以下为白痴。
最后铺一下楼主的写博客的环境,是多么艰辛,完全是站着写的。这么辛苦的码字,如果写得不好,你忍心吐槽吗?忍心拍砖吗?换句话说,博主这么辛苦都为了你写博客,你好意思偷懒,不好好学习吗!
项目管理
2018-08-20 00:08:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
参考 《设计模式:可复用面向对象软件的基础 》5.3 Interpreter 解释器 类行为型模式 《Android源码设计模式解析与实战》第10章 化繁为简的翻译机--解释器模式
意图
给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器适用该表示来解释语言中的句子。
适用场景
当有一个语言需要解释执行,并且你可将该语言中的句子表示为一个抽象语法树时,可使用解释器模式。而当存在以下情况时该模式效果最好: 该文法简单对于复杂的文法,文法的类层次变得庞大而又无法管理。 效率不是一个关键问题最高效的解释器通常不是通过直接解释语法分析树实现的,而是首先将它们转换为另一种形式。
非终结符号与终止符号
例: S ::= abA*ef A ::= cd
符号“::=”表示推导;符号“*”表示闭包,意思就是符号A可以有0或有N个重复;S和A称为非终结符号,因为他们能推导出式子右边的表达式,同时又因为整个推导式是从S出发的,因此,这个S也称为初始符号;而abef和cd这些字符不能再被推导我们称之为终结符号。
结构
AbstractExpression 抽象表达式,声明一个抽象的解释操作父类,并定义了一个抽象的解释方法,其具体的实现在各个具体的子类解释器中完成。 TeminalExpression 终结符表达式,实现文法中与终结符有关的解释操作。 NonterminalExpression 非终结符表达式,实现文法中与非终结符有关的解释操作。 Context 上下文环境类,包含解释器之外的全局信息。 Client 客户端类,解析表达式,构建抽象语法树,执行具体的解释表达操作等。
优点
灵活的扩展性,当我们想对文法规则进行扩展延伸时,只需要增加相应的非终结符解释器,并在构建抽象语法树时,使用到新增的解释器对象进行具体的解释即可,比较方便。
缺点
每一条文法都对应一个解释器,会生成大量类,导致后期维护困难。同时,对于过于复杂的文法,构建其抽象语法树会显得异常繁琐,因此,对于复杂的文法并不推荐使用解释器模式。
例子1
描述
表达式“m+n+p”,如果我们使用解释器模式对该表达式进行解释,那么代表数字的m,n,和p三个字母我们就可以看成是终结符号,而“+”这个算术运算符号则可当作非终结符,同时我们可以先创建一个抽象解释器表示数学运算。
结构
代码实现 /** * 算术运算解释器抽象类 * @author newtrekWang * @email wangjiaxing20160101@gmail.com * @time 2018/8/19 23:22 */ public abstract class AthmeticExpression { /** * 抽象的解析方法 * @return 解析得到的值 */ abstract int interpreter(); } /** * 数字解释器,只为了解释数字 * @author newtrekWang * @email wangjiaxing20160101@gmail.com * @time 2018/8/19 23:24 */ public class NumExpression extends AthmeticExpression { private int num; public NumExpression(int num){ this.num = num; } @Override int interpreter() { return num; } } /** * 运算符解释器 * @author newtrekWang * @email wangjiaxing20160101@gmail.com * @time 2018/8/19 23:27 */ public abstract class OperatorExpression extends AthmeticExpression { /** * 运算符两边的表达式 */ protected AthmeticExpression exp1,exp2; public OperatorExpression(AthmeticExpression exp1, AthmeticExpression exp2) { this.exp1 = exp1; this.exp2 = exp2; } } /** * 加法解释器 * @author newtrekWang * @email wangjiaxing20160101@gmail.com * @time 2018/8/19 23:29 */ public class AdditionExpressoin extends OperatorExpression { public AdditionExpressoin(AthmeticExpression exp1, AthmeticExpression exp2) { super(exp1, exp2); } /** * 具体解释+符号 * @return 解释结果 */ @Override int interpreter() { return exp1.interpreter()+exp2.interpreter(); } } /** * 计算器类 * @author newtrekWang * @email wangjiaxing20160101@gmail.com * @time 2018/8/19 23:30 */ public class Calculator { /** * 用一个栈存储并操作所有相关的解释器 */ private Stack mExpStack = new Stack<>(); public Calculator(String expression){ AthmeticExpression exp1,exp2; // 以空格分开元素 String[] elements = expression.split(" "); for (int i = 0;i输出结果: 13
应用例子2 Android源码中的PackageParser
项目管理
2018-08-19 23:57:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
参考 《设计模式:可复用面向对象软件的基础 》5.1 Chain of responsibility 职责链 对象行为型模式 《Android源码设计模式解析与实战》第9章 使编程更有灵活性--责任链模式
意图
使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。
适用场景 有多个的对象可以处理一个请求,哪个对象处理该请求运行时刻自动确定。 想在不明确指定接收者的情况下,像多个对象中的一个提交一个请求。 可处理一个请求的对象集合应被动态指定。
结构
Handler 抽象处理角色,声明一个请求处理的方法,并在其中保持一个对下一个处理节点Handler对象的引用。 ConcreteHandler 具体处理者角色,对请求进行处理,如果不能处理则将该请求转发给下一个节点上的处理对象。
进一步抽象
对于请求来说,其形式是固定的,就是一个字符串,而判断一个节点上的对象是否能够处理该请求的标志,则是该字符串是否与之匹配。然而在大多数情况下,责任链中的请求和对应的处理规则是不尽相同的,在这种情况下可以将请求进行抽象,使之对请求的处理规则也进行封装作为一个独立的对象。
代码实现 /** * 抽象处理类 * @author newtrekWang * @email wangjiaxing20160101@gmail.com * @time 2018/8/19 0:18 */ public abstract class AbstractHandler { /** * 下一节点上的处理者对象 */ private AbstractHandler nextHandler; /** * 处理请求 * @param request */ public void handleRequest(AbstractRequest request){ if (getHandlerLevel() == request.getRequestLevel()){ handle(request); }else { if (nextHandler != null){ nextHandler.handleRequest(request); }else { System.out.println("所有对象都不能处理该请求"); } } } /** * 处理请求 * @param request 请求对象 */ public abstract void handle(AbstractRequest request); /** * 每个处理者具体处理请求对象的实现 * @return 处理级别 */ public abstract int getHandlerLevel(); /** * 设置下一个处理者 * @param nextHandler */ public void setNextHandler(AbstractHandler nextHandler) { this.nextHandler = nextHandler; } } /** * 抽象请求类 * @author newtrekWang * @email wangjiaxing20160101@gmail.com * @time 2018/8/19 0:20 */ public abstract class AbstractRequest { /** * 要处理的内容对象 */ private Object object; /** * 通过构造函数注入要处理的内容对象 * @param object 要处理的内容对象 */ public AbstractRequest(Object object) { this.object = object; } /** * 获取要处理的内容对象 * @return 要处理的内容对象 */ public Object getObject() { return object; } /** * 获取请求级别 * @return 请求级别 */ public abstract int getRequestLevel(); } ** * 具体处理者1 * @author newtrekWang * @email wangjiaxing20160101@gmail.com * @time 2018/8/19 0:33 */ public class ConcretHandler1 extends AbstractHandler { @Override public void handle(AbstractRequest request) { System.out.println("Handler1 handler request :"+request.getRequestLevel()); } @Override public int getHandlerLevel() { return 1; } } /** * 具体处理者2 * @author newtrekWang * @email wangjiaxing20160101@gmail.com * @time 2018/8/19 0:33 */ public class ConcretHandler2 extends AbstractHandler { @Override public void handle(AbstractRequest request) { System.out.println("Handler2 handler request :"+request.getRequestLevel()); } @Override public int getHandlerLevel() { return 2; } } /** * 具体处理者3 * @author newtrekWang * @email wangjiaxing20160101@gmail.com * @time 2018/8/19 0:33 */ public class ConcretHandler3 extends AbstractHandler { @Override public void handle(AbstractRequest request) { System.out.println("Handler3 handler request :"+request.getRequestLevel()); } @Override public int getHandlerLevel() { return 3; } } /** * 具体请求者1 * @author newtrekWang * @email wangjiaxing20160101@gmail.com * @time 2018/8/19 0:36 */ public class ConcretRequest1 extends AbstractRequest { /** * 通过构造函数注入要处理的内容对象 * * @param object 要处理的内容对象 */ public ConcretRequest1(Object object) { super(object); } @Override public int getRequestLevel() { return 1; } } ** * 具体请求者2 * @author newtrekWang * @email wangjiaxing20160101@gmail.com * @time 2018/8/19 0:36 */ public class ConcretRequest2 extends AbstractRequest { /** * 通过构造函数注入要处理的内容对象 * * @param object 要处理的内容对象 */ public ConcretRequest2(Object object) { super(object); } @Override public int getRequestLevel() { return 2; } } ** * 具体请求者3 * @author newtrekWang * @email wangjiaxing20160101@gmail.com * @time 2018/8/19 0:36 */ public class ConcretRequest3 extends AbstractRequest { /** * 通过构造函数注入要处理的内容对象 * * @param object 要处理的内容对象 */ public ConcretRequest3(Object object) { super(object); } @Override public int getRequestLevel() { return 3; } } public static void main(String[] args){ // 三个不同级别的请求 AbstractRequest request1 = new ConcretRequest1("request 1"); AbstractRequest request2 = new ConcretRequest2("request 2"); AbstractRequest request3 = new ConcretRequest3("request 3"); //三个不同级别的处理者 AbstractHandler handler1 = new ConcretHandler1(); AbstractHandler handler2 = new ConcretHandler2(); AbstractHandler handler3 = new ConcretHandler3(); // 设置处理者的链式关系 handler1.setNextHandler(handler2); handler2.setNextHandler(handler3); // 总是从链子的首端发起请求 handler1.handleRequest(request1); handler1.handleRequest(request2); handler1.handleRequest(request3); }
输出结果: Handler1 handler request :1 Handler2 handler request :2 Handler3 handler request :3
优点
对请求者和处理者关系解耦,提高代码的灵活性。
缺点
如果处理者太多,那么遍历必定会影响性能。
应用例子1 Android 的触摸事件传递与分发机制
应用例子2 利用有序广播实现责任链事件处理
Android种的BroastCast分为两种,一种时普通广播,另一种是有序广播。普通广播是异步的,发出时可以被所有的接收者收到。而有序广播是根据优先级一次传播的,直到有接收者将其终止或者所有接收者都不终止它。有序广播的这一特性与我们的责任链模式很相近,我们可以轻松地实现一种全局的责任链事件处理。
项目管理
2018-08-19 10:52:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
属性(profile)垃圾
属性垃圾的定义为,一个垃圾用户在 Confluence 创建了用户,但是这个用户在自己的属性页面中添加了垃圾 URL。
如果你有很多垃圾用户在你的系统中创建了属性,你可以使用下面的 SQL 很容易的一次性删除。
如何删除属性中的垃圾: 停止 Confluence 实例然后备份好你的数据库。
备注: 这个步骤应该在你在数据库运行下面的 SQL 之前进行操作。 找到最后的真实属性:
SELECT bodycontentid,body FROM bodycontent WHERE contentid IN
( SELECT contentid FROM content WHERE contenttype= 'USERINFO' )
ORDER BY bodycontentid DESC ;
查看属性页面中的内容,直到你找到垃圾用户的状态。你可能需要要求一系列的区间。 找到需要删除的集合:
CREATE TEMP TABLE killset AS SELECT bc.bodycontentid,c.contentid,c.username FROM
bodycontent bc JOIN content c ON bc.contentid=c.contentid WHERE
bodycontentid >= BOTTOM_OF_SPAM_RANGE AND bodycontentID <= TOP_OF_SPAM_RANGE
AND c.contenttype= 'USERINFO' ;

DELETE FROM bodycontent WHERE bodycontentid IN ( SELECT bodycontentid FROM killset);

DELETE FROM links WHERE contentid IN ( SELECT contentid FROM killset);

DELETE FROM content WHERE prevver IN ( SELECT contentid FROM killset);

DELETE FROM content WHERE pageid IN ( SELECT contentid FROM killset);

DELETE FROM content WHERE contentid IN ( SELECT contentid FROM killset);

DELETE FROM os_user_group WHERE user_id IN ( SELECT id FROM killset k JOIN os_user o ON o.username=k.username);

DELETE FROM os_user WHERE username IN ( SELECT username FROM killset);
如果你使用的是 Confluence 5.6 或者早期的版本,请使用下面的 SQL 命令:
For Confluence 5.6 and earlier... 一旦垃圾内容被删除后,重启 Confluence 然后 重构内容索引 。这操作将会从索引查找对话框中删除任何相关的垃圾内容。
https://www.cwiki.us/display/CONF6ZH/Preventing+and+Cleaning+Up+Spam
项目管理
2018-08-19 06:07:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
Confluence 提供了 3 个电子邮件策略,这些策略 Confluence 管理员可以通过管理员控制台( Administration Console )进行配置: 公开(Public) :电子邮件地址公开显示在网站中。 保护(Masked): 电子邮件地址还是在网站中显示的,但是电子邮件地址被掩盖了,这样有助于电子邮件地址不被垃圾邮件收集器收集后发送垃圾邮件。 仅站点管理员可见(Only visible to site administrators): 仅 Confluence 管理员 可以看到电子邮件地址。请注意,如果你选择了这个选项,电子邮件地址将不会在用户查找组中可见(例如,当设置 Page Restrictions 的时候)。
如何配置用户电子邮件可见: 在屏幕的右上角单击 控制台按钮 ,然后选择 基本配置(General Configuration) 链接。 选择 安全配置(Security Configuration) 。 选择 编辑( Edit )。在 安全配置(Security Configuration) 界面中的字段是可以被编辑的。 从 用户电子邮件可见(User email visibility) 下拉菜单中选择上面提到的 3 种电子策略。 选择 保存( Save )。
屏幕截图:电子邮件可见

https://www.cwiki.us/display/CONF6ZH/User+Email+Visibility
项目管理
2018-08-19 06:07:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
如果你具有 Confluence 管理员的权限,你可以限制 Confluence 登录失败的最大尝试次数。在给予最大登录失败尝试(默认为 3 次)次数后,Confluence 将会在用户进行再次尝试的时候显示验证码输入框。这个能够避免用户通过登录页面进行密码暴利破解。
同样的,在 3 次错误尝试的 XML-RPC 或者 SOAP API,一个错误的信息将会发送给用户,提示用户通过页面进行再次登录。验证码将会在用户通过 Web 页面尝试登录的时候自动显示。
验证码(Captcha)是一个测试小程序来用于自动区分用户或者机器(比如说 robot 或 spider)。当验证码被启用后,用户将会看到下面随机的图片中的文字,然后用户必须将文字输入到文本框中随着表单同时提交。这个内容能够很容易的被人类阅读,但是很难被机器识别。
屏幕截图:一个验证码测试示例
https://www.cwiki.us/display/CONF6ZH/Configuring+Captcha+for+Failed+Logins
项目管理
2018-08-19 06:07:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
添加 SSL
如果你计划在你的应用中启用 SSL ,请参考 Securing your Atlassian applications with Apache using SSL 页面中的内容,并确定你在相同的连接器中选择 HTTPS。
更多信息 mod_proxy_html 站点提供了一些示例来为你如何配置反向代理提供帮助。 Apache Week has a tutorial 这个指南提供了如何解决复杂的配置情况和 ProxyHTMLURLMap。
https://www.cwiki.us/display/CONF6ZH/Using+Apache+with+mod_proxy
项目管理
2018-08-19 06:05:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
在这些示例中,我们使用下面的信息:
http://www.example.com/confluence - 你计划使用的 URL
http://example:8090/ - Confluence 当前安装的主机名和端口
http://example:8091/ - Synchrony 当前安装的主机名和端口,这个服务被用来提供默认的协同编辑服务
/confluence - 这个是有关你 Confluence 计划使用的上下文(在主机名和端口的后面)
/synchrony - Synchrony 使用的上下文路径,这个提供了协同编辑服务
你需要将上面的 URL 替换成你自己的 URL。
1 设置上下文路径
如果你不需要使用上下文来访问 Confluence,例如你希望使用 www.example.com 来访问这个,你可以跳过这部分的内容。
设置你 Tomcat 中 Confluence 上下文的路径(在主机名和端口的后面)。在这个示例中,上下文路径被配置为 /confluence。
编辑 conf/server.xml,找到 "Context" 的定义:
然后修改为:
在这个示例中,我们已经使用了 /confluence 为上下文的路径。请注意,你不能使用 /resources 为你的上下文路径,这是因为这个上下文的路径在 Confluence 中被用来定义资源,如果你使用这个配置的话,将会在 Confluence 中导致问题。
重启 Confluence,然后你可以尝试使用 http://example:8090/confluence 进行访问,并确保你能正确访问。
2 设置 URL 重定向
下一步,设置 URL 重定向。在 conf/server.xml 文件中,使用示例的连接器为你的起始点。
注释掉默认的连接器(针对不使用代理的访问)。
显示如何进行配置..
在 HTTP - Proxying Confluence via Apache or Nginx over HTTP 头部列出的内容,取消注释连接器。
显示如何进行配置..
在上面显示内容的最下面,插入你的 proxyName 和 proxyPort :
maxThreads= "48" minSpareThreads= "10"
enableLookups= "false" acceptCount= "10" debug= "0" URIEncoding= "UTF-8"
protocol= " org.apache.coyote.http11.Http11NioProtocol "
scheme= "http" proxyName= " www.example.com " proxyPort= "80" />
如果你计划启用 HTTPS,使用 HTTPS - Proxying Confluence via Apache or Nginx over HTTPS . 下面的连接器。
3 配置 mod_proxy
使用下面的一个示例来编辑你的 http.conf 文件中有关应用服务器代理的配置。
你需要针对你的 Apache 启用下面的一些模块,如果这些模块还没有在你的 Apache 中启用的话: mod_proxy mod_proxy_http proxy_wstunnel mod_rewrite
(proxy_wstunnel 和 mod_rewrite 是针对 Confluence 6.0 需要的新的模块)
http.conf 文件的格式和模块的位置路径,针对你使用操作系统的不同可能有所不同。你们推荐 Windows 的用户在这里配置的时候使用绝对路径。
示例 1:配置上下文路径
在这个示例中,如果你已经在上面的第一步中设置了上下文路径,同时你的 Confluence 服务器也可以通过配置的上下文路径进行访问,例如这个 http://www.example.com/confluence。
在这个示例中,用户将会连接到 Synchrony,这个是针对协同编辑所使用的服务,是通过 WebSockets 直连的。
在配置文件中的配置顺序是非常重要的。
Apache HTTP server 2.4
# Put this after the other LoadModule directives
LoadModule proxy_module /usr/lib/apache2/modules/ mod_proxy.so
LoadModule proxy_http_module /usr/lib/apache2/modules/ mod_proxy_http.so
LoadModule proxy_wstunnel_module /usr/lib/apache2/modules/ mod_proxy_wstunnel.so
LoadModule rewrite_module /usr/lib/apache2/modules/ mod_rewrite.so

# Put this in the main section of your configuration (or virtual host, if using Apache virtual hosts)
ProxyRequests Off
ProxyPreserveHost On


Require all granted


ProxyPass /synchrony http: //:8091/synchrony

Require all granted
RewriteEngine on
RewriteCond %{HTTP:UPGRADE} ^WebSocket$ [NC]
RewriteCond %{HTTP:CONNECTION} Upgrade$ [NC]
RewriteRule .* ws: //:8091%{REQUEST_URI} [P]


ProxyPass /confluence http: //:8090/confluence
ProxyPassReverse /confluence http: //:8090/confluence


Require all granted
注意: 如果你使用的是 HTTP Server 2.2,但是你的 Confluence 使用的 6.0 及其后续版本,这个配置是不能成功的。如果你计划使用 SSL ,你需要使用 2.4.10 或者后续版本。
示例 2:不使用上下文的配置
在这个示例中,如果你已经跳过了第一步的配置,没有使用上下文路径进行访问,例如访问的 URL 为 http://www.example.com/。
在这个示例中,用户将会连接到 Synchrony,这个是针对协同编辑所使用的服务,是通过 WebSockets 直连的。
在配置文件中的配置顺序是非常重要的。
Apache HTTP server 2.4
# Put this after the other LoadModule directives
LoadModule proxy_module /usr/lib/apache2/modules/ mod_proxy.so
LoadModule proxy_http_module /usr/lib/apache2/modules/ mod_proxy_http.so
LoadModule proxy_wstunnel_module /usr/lib/apache2/modules/ mod_proxy_wstunnel.so
LoadModule rewrite_module /usr/lib/apache2/modules/ mod_rewrite.so

# Put this in the main section of your configuration (or virtual host, if using Apache virtual hosts)

ProxyRequests Off
ProxyPreserveHost On

RewriteEngine On
RewriteCond %{REQUEST_URI} !^/synchrony
RewriteRule ^/(.*) http: //:8090/$1 [P]


Require all granted


ProxyPass /synchrony http: //:8091/synchrony


Require all granted
RewriteEngine on
RewriteCond %{HTTP:UPGRADE} ^WebSocket$ [NC]
RewriteCond %{HTTP:CONNECTION} Upgrade$ [NC]
RewriteRule .* ws: //:8091%{REQUEST_URI} [P]


ProxyPass / http: //:8090
ProxyPassReverse / http: //:8090


Require all granted
注意: 如果你使用的是 HTTP Server 2.2,但是你的 Confluence 使用的 6.0 及其后续版本,这个配置是不能成功的。如果你计划使用 SSL ,你需要使用 2.4.10 或者后续版本。
4 重启 Apache
为了让新的配置生效,你需要运行下面的命令来让 Apache 重启后载入新的配置文件:
sudo apachectl graceful
5 禁用 HTTP 压缩
如果在代理和 Tomcat 中启用了压缩,这个将会在整合其他 Atlassian 应用的时候出现问题,例如 JIRA。请禁用 HTTP 压缩,并请参考 Compressing an HTTP Response within Confluence 页面中的内容。
6 修改 Confluence 基础 URL
最后一个步骤是配置 Base URL 的地址来指向你没有使用的代理的地址,例如 http://www.example.com/confluence。

https://www.cwiki.us/display/CONF6ZH/Using+Apache+with+mod_proxy
项目管理
2018-08-19 06:01:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
Atlassian 应用允许用户使用反向代理,但是 Atlassian 并不会为这个功能的配置提供支持。因此,Atlassian 也不能保证能够为这些配置提供任何支持 。
如果你在配置上有任何需求,请参考 Atlassian Answers 中的内容来获取帮助。
这个页面描述了一个使用 Apache HTTP Server 2.4 来方向代理标准的 Tomcat 容器的方法。你也可以在 NGINX 中找到类似的配置方法。你可能在下面的情况中需要使用这个配置: 你已经有一个存在的 Apache 网站,同时还希望添加 Confluence 到你已经存在的网站中(例如, http://www.example.com/confluence )。 你已经有多个 Java 的应用了,每一个应用都运行在自己的服务器上,并且使用不同的端口在运行,例如, http://example:8090/confluence 和 http://example:8080/jira 同时你希望你的这个 2 个应用都能运行在常用的 HTTP (80)端口(例如, http://www.example.com/confluence 和 http://www.example.com/jira )。每一个应用都能够被独立的启动,管理和编辑。
备注: 这个页面的文档是有关 Apache 的配置而不是 Confluence 的配置。Atlassian 将会为你在 Confluence 中的配置提供支持,但是我们不能保证能够帮你解决所有有关 Apache 配置的时候遇到的问题。请意识到,这个页面中的内容只是为配置向你提供相关的的信息,如果参考这个页面配置产生的任何问题,后果自负。
https://www.cwiki.us/display/CONF6ZH/Using+Apache+with+mod_proxy
项目管理
2018-08-19 05:56:00