编程最害怕的是出现 bug,满屏的报错让我们的大脑嗡嗡直响,不好的编码习惯也会让其他人无从下手,所以良好的编码习惯不仅可以让我们尽量少的出错,也可以让他人清明的看懂自己写的代码。本篇主要分为三个部分:基础的编码规范、spring boot后端编码注意事项、前端编码注意事项

一、基础的编码规范

1.1命名风格

1.【强制】代码中的命名均不能以下划线或美元符号开始,也不能以下划线或美元符号结束。
反例:

_name / __name / $name / name_ / name$ / name__
  • 1

我的理解:Oracle 官网建议不要使用$或者_开始变量命名,并且建议在命名中完全不要使用"$"字符,且放在开始和结束容易避免冲突和不易分辨。

2.【强制】类名使用 UpperCamelCase 风格,但以下情形例外:DO / BO / DTO / VO / AO /
PO / UID 等。
正例:ForceCode / UserDO / HtmlDTO / XmlService / TcpUdpDeal / TaPromotion
反例:forcecode / UserDo / HTMLDto / XMLService / TCPUDPDeal / TAPromotion
我的理解:利用驼峰命名法且首字母大写方便查看类名,看起来也比较规范。

3.【强制】方法名、参数名、成员变量、局部变量都统一使用 lowerCamelCase 风格。
正例: localValue / getHttpMessage() / inputUserId
我的理解:方法名等使用驼峰命名法且首字母小写,首先是为了规范,让所有人都能看明白这是一个方法等,还有就是跟类的命名做一个区分。

4.【强制】抽象类命名使用 Abstract 或 Base 开头;异常类命名使用 Exception 结尾;测试类
命名以它要测试的类的名称开始,以 Test 结尾。
我的理解:Stackoverflow 上对于这个问题的解释是,由于这些类不会被使用,一定会由其他的类继承并实现内部细节,所以需要明白地告诉读者这是一个抽象类,那以 Abstract 开头比较合适。

1.2常量定义

1.避免魔法值的使用
阿里强制规定不允许任何魔法值(未经定义的常量)直接出现在代码中
反例:

String key = "Id#taobao_" + tradeId;
cache.put(key,value);
  • 1
  • 2
  • 3

我的理解:
魔法值确实让你很疑惑,比如你看下面这个例子:

int priceTable[] = new int[16];//这样定义错误;这个 16 究竟代表什么?

正确的定义方式是这样的:

static final int PRICE_TABLE_MAX = 16; //这样定义正确,通过使用完整英语单词的常量名明确定义

int price Table[] = new int[PRICE_TABLE_MAX];

魔法值会让代码的可读性大大降低,而且如果同样的数值多次出现时,容易出现不清楚这些数值是否代表同样的含义。另一方面,如果本来应该使用相同的数值,一旦用错,也难以发现。因此可以采用以下两点,极力避免使用魔法数值。

(1) 不适用魔法数值,使用带名字的 Static final 或者 enum 值;

(2)原则上 0 不用于魔法值,这是因为 0 经常被用作数组的最小下标或者变量初始化的缺省值。

2.变量值范围
阿里推荐如果变量值仅在一个范围内变化,且带有名称之外的延伸属性,定义为枚举类。下面这个正例中的数字就是延伸信息,表示星期几。
正例:

public Enum {MONDAY(1),TUESDAY(2),WEDNESDAY(3),THURSDAY(4),FRIDAY(5),SATURDAY(6),SUNDAY(7);}
  • 1

我的理解:对于固定并且编译时对象,如 Status、Type 等,应该采用 enum 而非自定义常量实现,enum 的好处是类型更清楚,不会再编译时混淆。这是一个建议性的试用推荐,枚举可以让开发者在 IDE 下使用更方便,也更安全。另外就是枚举类型是一种具有特殊约束的类类型,这些约束的存在使得枚举类本身更加简洁、安全、便捷。

1.3代码格式

1.大括号的使用约定
阿里强制规定如果是大括号为空,则简洁地写成{}即可,不需要换行;如果是非空代码块则:

(1) 左大括号前不换行

(2) 左大括号后换行

(3) 右大括号前换行

(4)右大括号后还有 else 等代码则不换行表示终止的右大括号后必须换行

我的理解: 阿里的这条规定应该是参照了 SUN 公司 1997 年发布的代码规范(SUN 公司是 JAVA 的创始者),Google 也有类似的规定,大家都是遵循 K&R 风格(Kernighan 和 Ritchie),Kernighan 和 Ritchie 在《The C Programming Language》一书中推荐这种风格,JAVA 语言的大括号风格就是受到了 C 语言的编码风格影响。

注意,SUN 公司认为方法名和大括号之间不应该有空格。

2.单行字符数限制
阿里强制规定单行字符数限制不超过 120 个,超出需要换行,换行时遵循如下原则:

(1)第二行相对第一行缩进 4 个空格,从第三行开始,不再继续缩进,参考示例。

(2)运算符与下文一起换行。

(3)方法调用的点符号与下文一起换行。

(4)方法调用时,多个参数,需要换行时,在逗号后进行。

(5) 在括号前不要换行,见反例。

StringBuffer sb = new StringBuffer();
//超过 120 个字符的情况下,换行缩进 4 个空格,点号和方法名称一起换行
sb.append("zi").append("xin")…
.append("huang")…
.append("huang")…
.append("huang")…
反例:
StringBuffer sb = new StringBuffer();
//超过 120 个字符的情况下,不要在括号前换行
sb.append("zi").append("xin").append
("huang");
//参数很多的方法调用可能超过 120 个字符,不要在逗号前换行
method(args1,args2,args3,….,argsX);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

我的理解: SUN 公司 1997 年的规范中指出单行不要超过 80 个字符,对于文档里面的代码行,规定不要超过 70 个字符单行。当表达式不能在一行内显示的时候,genuine 以下原则进行切分:

(1)在逗号后换行;

(2)在操作符号前换行;

(3)倾向于高级别的分割;

(4)尽量以描述完整作为换行标准;

(5) 如果以下标准造成代码阅读困难,直接采用 8 个空格方式对第二行代码留出空白。

function(longExpression1, longExpression2, longExpression3,
longExpression4, longExpression5);
var = function(longExpression1,
function2(longExpression2,
longExpression3));
longName1 = longName2 * (longName3 + longName4 – longName5)
+ 4 * longName6;//做法正确
longName1 = longName2 * (longName3 + longName4
– longName5) + 4 * longName6;//做法错误
if ((condition1 && condition2)|| (condition3 && condition4)|| !(condition5 && condition6) {doSomethingAboutIt();
}//这种做法错误
if ((condition1 && condition2)
|| (condition3 && condition4)|| !(condition5 && condition6) {doSomethingAboutIt();}//这种做法正确
if ((condition1 && condition2) || (condition3 && condition4)
|| !(condition5 && condition6) {doSomethingAboutIt();
}//这种做法正确
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

1.4 OOP规约

1.静态变量及方法调用
阿里强制规定代码中避免通过一个类的对象引用访问此类的静态变量或静态方法,暂时无谓增加编译器解析成本,直接用类名来访问即可。

我的理解: 谷歌公司在代码规范中指出必须直接使用类名对静态成员进行引用,并同时举例说明。

Foo aFoo = …;
Foo.aStaticMethod();//good
aFoo.aStaticMethod();//bad
somethingThatYieldsAFoo().aStaticMethod();//very bad
  • 1
  • 2
  • 3
  • 4

SUN 公司 1997 年发布的代码规范也做了类似的要求。

为什么需要这样做呢?因为被 static 修饰过的变量或者方法都是随着类的初始化产生的,在堆内存中有一块专门的区域用来存放,后续直接用类名访问即可,避免编译成本的增加和实例对象存放空间的浪费。

StackOverflow 上也有人提出了相同的疑问,网友较为精辟的回复是"这是由于生命周期决定的,静态方法或者静态变量不是以实例为基准的,而是以类为基准,所以直接用类访问,否则违背了设计初衷"。那为什么还保留了实例的访问方式呢?可能是因为允许应用方无污染修改吧。

2.可变参数编程
阿里强制规定相同参数类型、相同业务类型,才可以使用 Java 的可变参数,避免使用 Object,并且要求可变参数必须放置在参数列表的最后(提倡同学们尽量不用可变参数编程)。

我的理解:
我们先来了解可变参数的使用方式:
(1)在方法中定义可变参数后,我们可以像操作数组一样操作该参数。
(2)如果该方法除了可变参数还有其他的参数,可变参数必须放到最后。
(3)拥有可变参数的方法可以被重载,在被调用时,如果能匹配到参数定长的方法则优先调用参数定长的方法。
(4)可变参数可以兼容数组参数,但数组参数暂时无法兼容可变参数。

至于为什么可变参数需要被放在最后一个,这是因为参数个数不定,所以当其后还有相同类型参数时,编译器无法区分传入的参数属于前一个可变参数还是后边的参数,所以只能让可变参数位于最后一项。

可变参数编程有一些好处,例如反射、过程建设、格式化等。对于阿里同学提出的尽量不使用可变参数编程,我猜测的原因是不太可控,比如 Java8 推出 Lambda 表达式之后,可变参数编程遇到了实际的实现困难。

3.并发处理
单例模式需要保证线程安全
阿里强制要求获取单例对象需要保证线程安全,其中的方法也要保证线程安全,并进一步说明资源驱动类、工具类、单例工厂类都需要注意。

我的理解:
对于这一条规范是通识化规定,我这里进一步讲讲如何做好针对单例对象的线程安全,主要有以下几种方式:

(1)方法中申明 synchronized 关键字
出现非线程安全问题,是由于多个线程可以同时进入 getInstance()方法,那么只需要对该方法进行 synchronized 锁同步即可。
(2)同步方法块实现
(3) 针对某些重要的代码来进行单独的同步
针对某些重要的代码进行单独的同步,而不是全部进行同步,可以极大的提高执行效率

二、spring boot后端编码注意事项

Spring Boot是由Pivotal团队提供的全新框架,其设计目的是用来简化新Spring应用的初始搭建以及开发过程。该框架使用了特定的方式来进行配置,从而使开发人员不再需要定义样板化的配置。

通过这种方式,Spring Boot致力于在蓬勃发展的快速应用开发领域(rapid application development)成为领导者。

2.1ResTful API 风格

RESTful架构是对MVC架构改进后所形成的一种架构,通过使用事先定义好的接口与不同的服务联系起来。在RESTful架构中,浏览器使用POST,DELETE,PUT和GET四种请求方式分别对指定的URL资源进行增删改查操作。因此,RESTful是通过URI实现对资源的管理及访问,具有扩展性强、结构清晰的特点。

RESTful架构将服务器分成前端服务器和后端服务器两部分,前端服务器为用户提供无模型的视图;后端服务器为前端服务器提供接口。浏览器向前端服务器请求视图,通过视图中包含的AJAX函数发起接口请求获取模型。

项目开发引入RESTful架构,利于团队并行开发。在RESTful架构中,将多数HTTP请求转移到前端服务器上,降低服务器的负荷,使视图获取后端模型失败也能呈现。但RESTful架构却不适用于所有的项目,当项目比较小时无需使用RESTful架构,项目变得更加复杂。

HTTP方法

GET(SELECT):从服务器取出资源(一项或多项)。
POST(CREATE):在服务器新建一个资源。
PUT(UPDATE):在服务器更新全部资源(客户端提供改变后的完整资源)。
PATCH(UPDATE):在服务器更新部分资源(客户端提供改变的属性)。
DELETE(DELETE):从服务器删除资源。

特别注意几个常用的状态码:

200 请求已成功,请求所希望的响应头或数据体将随此响应返回。

400 1、语义有误,当前请求无法被服务器理解。除非进行修改,否则客户端不应该重复提交这个请求。   
2、请求参数有误。

500 服务器遇到了一个未曾预料的状况,导致了它无法完成对请求的处理。一般来说,这个问题都会在服务器的程序码出错时出现。

2.2 常见的文本、邮件、不能为空等,spring boot自带注解可以快速实现:@Valid注解和BindingResult验证请求参数的合法性并处理校验结果。(此块内容在开发中应用较为广泛,认真学习)

例子:创建用户时,接口参数中用户名不能为空。@NotBlank(message = “用户名不能为空”)


提示:在请求方法的字段上加上@valid注解时,以上的注解将生效。如果请求接口的参数无法通过校验,将返回状态码为400(此处认真理解学习)

使用postman测试结果如下:

常见的注解如下图:


已经能够按照RestFulApi的方式返回400的错误信息,但是返回的异常信息不是很友好(有很多乱七八糟的字段,与我们自定义的400错误信息json结构不一致,那么前端接受到此json时还需特殊处理),并且错误信息也没有进行统一的维护。所以 我们重构注解的异常方法,让其错误信息按着我们统一格式返回。

第一步:定义错误消息枚举

第二步:在dto中加入相应注解并在message中传入定义的枚举

第三步: Postman测试结果

第四步: 前端接收到后端返回的json对象,做统一的拦截处理,判断 status==400弹出统一的提示(提示框为警告黄色状态,提示语为返回json对象中“error”信息)

总结:使用此方式,代码精简、错误码与消息能够统一维护、返回客户端的状态更加明确,是符合RestFulApi的最佳实践。

2.3 默认的校验注解只能够满足一小部分校验要求。例如需要校验一个字段是否唯一,就无法通过默认的注解完成。还有可能有更多的复杂校验逻辑,比如西仓项目需要校验一个询价单中所有的物料是否已经报价等(此处的开发很关键,每个项目都有大量这样的代码)

常见的实现思路有二种,①重构校验注解(自定义注解);②在service层【特别注意代码逻辑不允许写在controller层】代码逻辑判断后做异常抛出。我们采用后者,所以重点介绍与掌握第二点的写法。
如:根据一个用户id查找此用户,如果用户不存在返回此用户不存在。
Service层代码实现:

public Object getUser(String id){User currentInstance = userRepository.findOne(id);//判断获取的用户为空
if (currentInstance == null){Map<String, Object> map;map.put("error", "user " + id + "is exit!");
map.put("code", "0001");
        <span class="token keyword">return</span>   map<span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token keyword">return</span> userRepository<span class="token punctuation">.</span><span class="token function">findOne</span><span class="token punctuation">(</span>id<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

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

使用postman测试结果

返回结果是我们之前项目经常使用的方式,但这种方式维护起来很烦,错误信息与错误状态码不能集中管理,HTTP的响应码也是200,应该是400(这是由客户端传参不正确—用户id传输错误导致的错误根据RestfulApi的规范此处应该返回400)。所以,我们需要使用更加严谨的规范。

第一步:定义错误返回消息的实体类

第二步:在dto中加入相应注解并在message中传入定义的枚举

第三步: Postman测试结果

第四步: 前端接收到后端返回的json对象,做统一的拦截处理,判断 status==400弹出统一的提示(提示框为警告黄色状态,提示语为返回json对象中“error”信息)

总结:使用此方式,代码精简、错误码与消息能够统一维护、返回客户端的状态更加明确,是符合RestFulApi的最佳实践。

2.4 默认的校验注解只能够满足一小部分校验要求。例如需要校验一个字段是否唯一,就无法通过默认的注解完成。还有可能有更多的复杂校验逻辑,比如西仓项目需要校验一个询价单中所有的物料是否已经报价等(此处的开发很关键,每个项目都有大量这样的代码)

常见的实现思路有二种,①重构校验注解(自定义注解);②在service层【特别注意代码逻辑不允许写在controller层】代码逻辑判断后做异常抛出。我们采用后者,所以重点介绍与掌握第二点的写法。
如:根据一个用户id查找此用户,如果用户不存在返回此用户不存在。
Service层代码实现:

public Object getUser(String id){User currentInstance = userRepository.findOne(id);//判断获取的用户为空
if (currentInstance == null){Map<String, Object> map;map.put("error", "user " + id + "is exit!");
map.put("code", "0001");
        <span class="token keyword">return</span>   map<span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token keyword">return</span> userRepository<span class="token punctuation">.</span><span class="token function">findOne</span><span class="token punctuation">(</span>id<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

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

使用postman测试结果

返回结果是我们之前项目经常使用的方式,但这种方式维护起来很烦,错误信息与错误状态码不能集中管理,HTTP的响应码也是200,应该是400(这是由客户端传参不正确—用户id传输错误导致的错误根据RestfulApi的规范此处应该返回400)。所以,我们需要使用更加严谨的规范。

第一步:定义错误返回消息的实体类

第二步:定义错误消息枚举(方便统一维护错误消息,书写错误信息与错误码在此处维护)

第三步 :定义全局错误消息处理类,重构400错误异常方法


通过使用注解@ControllerAdvice,类RestExceptionHandler就可以实现全局异常的拦截处理功能。自定义的方handleResourceBadRequestException旨在拦截BadRequestException异常,一旦拦截成功后,我们可以进行各种处理操作,并且返回自己想要的结果。

第四步:Service代码实现
① 在枚举中加入此错误消息

② Service层中直接抛出自己重构过的400方法

public Object getUser(String id) throws NotFoundException{User currentInstance = userRepository.findOne(id);//判断获取的用户为空
if (currentInstance == null){throw new BadRequestException(ResultErrorEnum.USER_ID_NULL);
    <span class="token punctuation">}</span><span class="token keyword">return</span> userRepository<span class="token punctuation">.</span><span class="token function">findOne</span><span class="token punctuation">(</span>id<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

第五步:Postman测试结果

第六步: 前端接收到后端返回的json对象,做统一的拦截处理,判断 status==400弹出统一的提示(提示框为警告黄色状态,提示语为返回json对象中“error”信息)

2.5 定义全局的异常处理方法,避免异常未经处理直接暴露给前端,前端无法处理未知错误的异常,经常给客户造成系统bug。(此处很关键,在日常开发中不需要程序员在此处编写逻辑代码,但是需要理解实现原理,以及重视对异常的处理,方便在一些大型项目中重构我在此封装的方法。)

解决方案:我们比较传统的解决办法是用try catch去捕获异常并按照相应的逻辑状态输出json错误码,但是初级程序员,经常遗漏或者不重视此块的代码编写,往往造成大量的程序漏洞。所以 采用spring boot 全局异常重构的方法处理此问题。

提示:基于前后端分离,重构ErrorController方法实现全局异常的捕获,所以 下面的方法对于不是前后端分离的项目不起作用,需要重新封装.特别注意----可以捕获到Controller层的异常,前提是Controller层没有对异常进行catch处理,如果Controller层对异常进行了catch处理,那么在这里就不会捕获到Controller层的异常了,所以这一点要特别注意(不是特殊业务需求,不用书写try catch)。

第一步 在yml配置文件中定义异常后跳转路由

第二步 重构ErrorController方法,此方法最大的作用是将所有http错误状态 加入code错误码方便前端获取此类状态,所有未定义的错误 前端code都将收到500的错误状态码,但是也将系统异常信息返回给前端,方便程序员日常维护与定位错误.

第三步:Postman测试结果:

第四步: 前端接收到后端返回的json对象,做统一的拦截处理,判断 status==500弹出统一的提示(提示框为危险红色状态,提示语为“系统错误,请联系管理员处理!”,在浏览器中console.log出json对象信息)

总结:如postman测试结果找不到此路由本来系统返回的是status 404状态,但通过重构全局的异常方法,将所有http错误状态统一返回status为500服务器内部错误状态(返回的status同样保留系统原始异常抛出的状态方便程序员定位错误原因),方便前端获取此状态后做统一的判断处理。避免由于系统开发造成的未知bug,引起前端做无状态的处理或者处理起来容易遗漏。

2.6开发过程中需要注意如数据库密码、用户名等可配置信息请存放在配置文件中,读取配置文件参数,配置类在包config/DemoConfig.java(根据需求新增新的配置类)下面书写。配置文件 统一使用yml的方式,并且书写四个yml配置文件,用于不同环境下面的参数书写。

2.7 注解@JsonView的使用,有时候我们希望在不同的请求中隐藏一些字段。可以用@JsonView控制输出内容。

例子:条件查询时候不返回用户的密码,查看详情时候返回用户的密码。

2.8 使用前后端分离开发的项目,需要定义统一的前置路由规范

例子:http://localhost:8080/api/v1/user/1 api–代表后端接口,v1–代表版本
Yml中的实现:
server:
port: 8080
servlet:
context-path: “/api/v1”

2.9 代码逻辑必须写在service层,除非极为特殊情况,可以将少量逻辑放在controller

2.10 SpringBoot Web项目的参数绑定:URL传参及默认参数设置

第一种情况:(参数类型必须书写包装类型)

@RequestMapping(value = "/hello1.htm")
public String hello1(ModelMap modelMap,Integer param1, int param2) {modelMap.addAttribute("param1", param1);modelMap.addAttribute("param2", param2);return "hello";
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

这里前面的参数时包装型(Integer),后面的参数时原始类型(int)
如果不传param2:  
http://localhost:8080/hello1.htm?param1=1
直接就报错了
因为无法将null转换为原始类型
所以:所有的参数都用包装类型,别用原始类型
提示: JAVA原始数据类型与包装类类型(参考博文地址:https://blog.csdn.net/u013201439/article/details/79781845)
Java语言中默认定义了8个原始数据类型,大致可以分为4类:

整型:包含byte、short、int、long

浮点型:float、double

布尔型:boolean

字符:char

这8个基本的数据类型如果在声明的时候没有初始化,编译器会赋予默认值,引用类型的对象(如String)默认值为null。但如果是局部变量(Local Variables,方法体内申明),编译器不会自动赋予初始值,会报编译错误。

第二种情况:

如果页面上表单里的参数和代码里的参数名不一样怎么办,这时候就可以用注解了:@RequestParam(value = “paramTest”)

@RequestMapping(value = "/hello1.htm")public String hello1(ModelMap modelMap, @RequestParam(value = "paramTest") Integer param1, Integer param2) {modelMap.addAttribute("param1", param1);modelMap.addAttribute("param2", param2);return "hello";}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

第三种情况:

有时候页面上的表单客户不填任何值,但是在控制器里希望它有默认值
可以这样:@RequestParam(defaultValue = “5”)

@RequestMapping(value = "/hello1.htm")
public String hello1(ModelMap modelMap, @RequestParam(defaultValue = "5") Integer param1, Integer param2) {modelMap.addAttribute("param1", param1);modelMap.addAttribute("param2", param2);return "hello";
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

第四种情况:

RequestParam还有个属性:required(必填)

@RequestMapping(value = "/hello1.htm")
public String hello1(ModelMap modelMap, @RequestParam(required = true) Integer param1, Integer param2) {modelMap.addAttribute("param1", param1);modelMap.addAttribute("param2", param2);return "hello";
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

但是当required=true,和defaultValue= 同时出现时,required失效,可传可不传
此处注意:需要拦截异常处理方法,让返回的错误码是我们的格式。
第一步:定义错误消息枚举

第二步:拦截异常

第三步:postman测试结果

第四步 前端处理(由于在校验方法中定义过400的错误拦截,此处不需要特殊处理)前端判断400,弹出危险黄色提示框并将错误消息显示给用户。

2.11 理解spring boot 自带tomcat

(1)明白是哪一个依赖让我们使用了springboot自带的tomcat


接下来我们把自带的容器去除:找到spring-boot-starter-web节点,修改:

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><!-- 移除嵌入式tomcat插件 --><exclusions><exclusion><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-tomcat</artifactId></exclusion></exclusions>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

(2)然后再做常规的war项目配置:

(2.1)给war项目做一个声明:

在pom.xml里设置 war

(2.2)添加servlet-api依赖:

<dependency><groupId>javax.servlet</groupId><artifactId>javax.servlet-api</artifactId><version>3.1.0</version><scope>provided</scope>
</dependency>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

(2.3)修改启动类,并重写启动方法

我们平常用main方法启动的方式,都有一个App的启动类,代码如下:

我们需要类似于web.xml的配置方式来启动spring上下文了,在Application类的同级添加一个SpringBootStartApplication类,其代码如下:

(3)测试操作是否成功

配置我们自己的tomcat,因为我们使用的JDK1.8,所以下载最新的tomcat9版本,把项目部署上去启动测试是否能够正常访问。

2.12 AOP切面的理解与应用

例子:① 若是需要一个记录日志的功能,首先想到的是在方法中通过log4j或其他框架来进行记录日志,但写下来发现一个问题,在每个方法都需要写log日志的输出代码。②判断用户是否有登录过,登录后才能让其访问我们的controller层,也需要在每个controller方法前写统一的判断方法。所以 我们使用AOP切面进行操做。

使用AOP实现 http请求中验证http签名

实现思路:

if (webAppConfing.isAutoGraph()) {//开启签名认证//1.前端在Header中加入请求时间参数String timestamp = request.getHeader("timestamp");//2.前端与后端使用同一种加密算法加密出sign 并在Header中加入String sign = request.getHeader("sign");//3.后端使用同样的算法计算signString _sign = MD5Util.MD5string(URLEncoder.encode(webAppConfing.getAppKey() + timestamp, "UTF-8"));if (_sign.equals(sign) == false) {// log.error("【签名错误】{}", sign);//4.如果计算出来的sign不一致,则抛出异常,不让其访问接口throw new RuntimeException("【签名错误】" + sign);}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

第一步:在yml文件中加入配置参数,并实现参数的调用(参考上面内容如何写配置文件)


第二步:新建AOP切面,并写切面逻辑

第三步:postman测试

总结: 使用切面可以轻松完成签名认证,防止我们后端接口没有任何安全策略的情况下暴露给任何人,从而提高系统的健壮性。

使用AOP完成sql注入的筛选

实现方法较为简单,自己查看源码包中的实现方式。

2.13 统一日志管理

如何做基本日志文件配置以及使用spring boot 自带的日志模块有什么弊端,参考博文(https://www.jianshu.com/p/780a1bf46a1f)

开发过程中我们需要调试项目,希望打印项目输入与输出内容,监控异常消息,接下来我们实现统一日志打印。
第一步 配置yml文件控制打印的范围

第二步 编写日志输出公用类

/*** @Author: 黄明* @Date: 2019/4/18 9:09* @Description:  日志输出公用类*/
@Component
public class LogUtil {private  final Logger logger = LogManager.getLogger(this.getClass());
<span class="token keyword">private</span> <span class="token keyword">static</span>  LogUtil logUtil<span class="token punctuation">;</span>
<span class="token comment">/*** 读取webapp参数配置  //类不是controller在使用autowired注入的类显示为空*/</span>
<span class="token annotation punctuation">@Autowired</span>
<span class="token keyword">private</span> WebAppConfing webAppConfing<span class="token punctuation">;</span><span class="token comment">/*** 在类初始化时主动注入被Autowired的类*/</span>
<span class="token annotation punctuation">@PostConstruct</span>
<span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">init</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{<!-- --></span>logUtil<span class="token operator">=</span><span class="token keyword">this</span><span class="token punctuation">;</span>logUtil<span class="token punctuation">.</span>webAppConfing<span class="token operator">=</span><span class="token keyword">this</span><span class="token punctuation">.</span>webAppConfing<span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token comment">/*** 正常日志信息输出* @param msg*/</span>
<span class="token keyword">public</span>  <span class="token keyword">void</span>  <span class="token function">LogInfo</span><span class="token punctuation">(</span>String msg<span class="token punctuation">)</span><span class="token punctuation">{<!-- --></span><span class="token keyword">if</span> <span class="token punctuation">(</span>logUtil<span class="token punctuation">.</span>webAppConfing<span class="token punctuation">.</span><span class="token function">isLogInfo</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>logger<span class="token punctuation">.</span><span class="token function">info</span><span class="token punctuation">(</span>msg<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token comment">/*** 警告信息打印* @param msg*/</span>
<span class="token keyword">public</span>  <span class="token keyword">void</span>  <span class="token function">LogWarn</span><span class="token punctuation">(</span>String msg<span class="token punctuation">)</span><span class="token punctuation">{<!-- --></span><span class="token keyword">if</span> <span class="token punctuation">(</span>logUtil<span class="token punctuation">.</span>webAppConfing<span class="token punctuation">.</span><span class="token function">isLogWarn</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>logger<span class="token punctuation">.</span><span class="token function">warn</span><span class="token punctuation">(</span>msg<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token comment">/*** 错误信息打印* @param msg*/</span>
<span class="token keyword">public</span>  <span class="token keyword">void</span>  <span class="token function">LogError</span><span class="token punctuation">(</span>String msg<span class="token punctuation">)</span><span class="token punctuation">{<!-- --></span><span class="token keyword">if</span> <span class="token punctuation">(</span>logUtil<span class="token punctuation">.</span>webAppConfing<span class="token punctuation">.</span><span class="token function">isLogError</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>logger<span class="token punctuation">.</span><span class="token function">error</span><span class="token punctuation">(</span>msg<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52

第三步: 使用AOP切面将访问接口信息打印出来

第四步 :打印正常返回信息

第五步: 打印异常信息


第六步 postman测试结果

总结:可以从控制台看到整个接口调用周期产生的日志信息,清晰的看到系统产生的异常信息、校验信息、正常的输入输出信息,实现了统一的日志输出,不需要在每个方法中书写日志打印,这样的日志文件对于后续运维是有很大帮助的。

三、前端编码注意事项

3.1 一般规范

以下列举了一些可应用在 HTML, JavaScript 和 CSS/SCSS 上的通用规则。

文件/资源命名

在 web 项目中,所有的文件名应该都遵循同一命名约定。以可读性而言,减号(-)是用来分隔文件名的不二之选。同时它也是常见的 URL 分隔符(i.e. //example.com/blog/my-blog-entry or //s.example.com/images/big-black-background.jpg),所以理所当然的,减号应该也是用来分隔资源名称的好选择。

请确保文件命名总是以字母开头而不是数字。而以特殊字符开头命名的文件,一般都有特殊的含义与用处(比如 compass[1] 中的下划线就是用来标记跳过直接编译的文件用的)。

资源的字母名称必须全为小写,这是因为在某些对大小写字母敏感的操作系统中,当文件通过工具压缩混淆后,或者人为修改过后,大小写不同而导致引用文件不同的错误,很难被发现。

还有一些情况下,需要对文件增加前后缀或特定的扩展名(比如 .min.js, .min.css),抑或一串前缀(比如 3fa89b.main.min.css)。这种情况下,建议使用点分隔符来区分这些在文件名中带有清晰意义的元数据。

不推荐

1. MyScript.js
2. myCamelCaseName.css
3. i_love_underscores.html
4. 1001-scripts.js
5. my-file-min.css
  • 1
  • 2
  • 3
  • 4
  • 5

推荐

1. my-script.js
2. my-camel-case-name.css
3. i-love-underscores.html
4. thousand-and-one-scripts.js
5. my-file.min.css
  • 1
  • 2
  • 3
  • 4
  • 5

协议

不要指定引入资源所带的具体协议。

当引入图片或其他媒体文件,还有样式和脚本时,URLs 所指向的具体路径,不要指定协议部分(http:, https:),除非这两者协议都不可用。

不指定协议使得 URL 从绝对的获取路径转变为相对的,在请求资源协议无法确定时非常好用,而且还能为文件大小节省几个字节。

不推荐

1. <script src="http://cdn.com/foundation.min.js"></script>
  • 1

推荐

1. <script src="//cdn.com/foundation.min.js"></script>
  • 1

不推荐

1. .example {
2.   background: url(http://static.example.com/images/bg.jpg);
3. }
  • 1
  • 2
  • 3

推荐

1. .example {
2.   background: url(//static.example.com/images/bg.jpg);
3. }
  • 1
  • 2
  • 3

文本缩进

一次缩进两个空格。

1. <ul>
2.  <li>Fantastic</li>
3.  <li>Great</li>
4.  <li>
5.    <a href="#">Test</a>
6.  </li>
7. </ul>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
@media screen and (min-width: 1100px) {body {font-size: 100%;}
}
  • 1
  • 2
  • 3
  • 4
  • 5
(function(){var x = 10;

function y(a, b) {
return {
result: (a + b) * x
}

}
}());

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

注释

注释是你自己与你的小伙伴们了解代码写法和目的的唯一途径。特别是在写一些看似琐碎的无关紧要的代码时,由于记忆点不深刻,注释就变得尤为重要了。

编写自解释代码只是一个传说,没有任何代码是可以完全自解释的。而代码注释,则是永远也不嫌多。

当你写注释时一定要注意:不要写你的代码都干了些什么,而要写你的代码为什么要这么写,背后的考量是什么。当然也可以加入所思考问题或是解决方案的链接地址。

不推荐

1. var offset = 0;
2.
3. if(includeLabels) {
4.   // Add offset of 20
5.   offset = 20;
6. }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

推荐

1. var offset = 0;
2.
3. if(includeLabels) {
4.   // If the labels are included we need to have a minimum offset of 20 pixels
5.   // We need to set it explicitly because of the following bug: http://somebrowservendor.com/issue-tracker/ISSUE-1
6.   offset = 20;
7. }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

一些注释工具可以帮助你写出更好的注释。JSDoc 或 YUIDoc 就是用来写 JavaScript 注释用的。你甚至可以使用工具来为这些注释生成文档,这也是激励开发者们写注释的一个好方法,因为一旦有了这样方便的生成文档的工具,他们通常会开始花更多时间在注释细节上。

代码检查

对于比较宽松自由的编程语言来说,严格遵循编码规范和格式化风格指南就显得极为重要。遵循规范固然很好,但是有自动化流程来确保其执行情况,岂不更佳。Trust is good, control is better.

对于 JavaScript,建议使用 JSLint 或 JSHint。

[1]: Compass 是一个基于 Sass 开源的 CSS 框架,而 Sass 是一个非常流行的 CSS 预编译器。

3.2 HTML 规范

文档类型

推荐使用 HTML5 的文档类型申明: <!DOCTYPE html>.

(建议使用 text/html 格式的 HTML。避免使用 XHTML。XHTML 以及它的属性,比如 application/xhtml+xml 在浏览器中的应用支持与优化空间都十分有限)。

HTML 中最好不要将无内容元素[1] 的标签闭合,例如:使用 <br> 而非 <br />.

HTML 验证

一般情况下,建议使用能通过标准规范验证的 HTML 代码,除非在性能优化和控制文件大小上不得不做出让步。

使用诸如 W3C HTML validator 这样的工具来进行检测。

规范化的 HTML 是显现技术要求与局限的显著质量基线,它促进了 HTML 被更好地运用。

不推荐

1.<title>Test</title>
2.<article>This is only a test.
  • 1
  • 2

推荐

1.<!DOCTYPE html>
2.<meta charset="utf-8">
3.<title>Test</title>
4.<article>This is only a test.</article>
  • 1
  • 2
  • 3
  • 4

省略可选标签

HTML5 规范中规定了 HTML 标签是可以省略的。但从可读性来说,在开发的源文件中最好不要这样做,因为省略标签可能会导致一些问题。

省略一些可选的标签确实使得页面大小减少,这很有用,尤其是对于一些大型网站来说。为了达到这一目的,我们可以在开发后期对页面进行压缩处理,在这个环节中这些可选的标签完全就可以省略掉了。

脚本加载

出于性能考虑,脚本异步加载很关键。一段脚本放置在 <head> 内,比如 <script src="main.js"></script>,其加载会一直阻塞 DOM 解析,直至它完全地加载和执行完毕。这会造成页面显示的延迟。特别是一些重量级的脚本,对用户体验来说那真是一个巨大的影响。

异步加载脚本可缓解这种性能影响。如果只需兼容 IE10+,可将 HTML5 的 async 属性加至脚本中,它可防止阻塞 DOM 的解析,甚至你可以将脚本引用写在 <head> 里也没有影响。

如需兼容老旧的浏览器,实践表明可使用用来动态注入脚本的脚本加载器。你可以考虑 yepnope 或 labjs。注入脚本的一个问题是:一直要等到 CSS 对象文档已就绪,它们才开始加载(短暂地在 CSS 加载完毕之后),这就对需要及时触发的 JS 造成了一定的延迟,这多多少少也影响了用户体验吧。

终上所述,兼容老旧浏览器(IE9-)时,应该遵循以下最佳实践。

脚本引用写在 body 结束标签之前,并带上 async 属性。这虽然在老旧浏览器中不会异步加载脚本,但它只阻塞了 body 结束标签之前的 DOM 解析,这就大大降低了其阻塞影响。而在现代浏览器中,脚本将在 DOM 解析器发现 body 尾部的 script 标签才进行加载,此时加载属于异步加载,不会阻塞 CSSOM(但其执行仍发生在 CSSOM 之后)。

多媒体回溯

对页面上的媒体而言,像图片、视频、canvas 动画等,要确保其有可替代的接入接口。图片文件我们可采用有意义的备选文本(alt),视频和音频文件我们可以为其加上说明文字或字幕。

提供可替代内容对可用性来说十分重要。试想,一位盲人用户如何能知晓一张图片是什么,要是没有 @alt 的话。
(图片的 alt 属性是可不填写内容的,纯装饰性的图片就可用这么做:alt="")。

不推荐

1. <img src="luke-skywalker.jpg">
  • 1

推荐

1. <img src="luke-skywalker.jpg" alt="Luke skywalker riding an alien horse">
  • 1

尽量用 alt 标签去描述图片,设想你需要对于那些只能通过语音或者看不见图片的用户表达图片到底是什么。

不推荐

<img src="huge-spaceship-approaching-earth.jpg" alt="Header image">
  • 1

推荐

<img src="huge-spaceship-approaching-earth.jpg" alt="A huge spaceship that is approaching the earth">
  • 1

Type 属性

省略样式表与脚本上的 type 属性。鉴于 HTML5 中以上两者默认的 type 值就是 text/css 和 text/javascript,所以 type 属性一般是可以忽略掉的。甚至在老旧版本的浏览器中这么做也是安全可靠的。

不推荐

1. <link rel="stylesheet" href="main.css" type="text/css">
2. <script src="main.js" type="text/javascript"></script>
  • 1
  • 2

推荐

1. <link rel="stylesheet" href="main.css">
2. <script src="main.js"></script>
  • 1
  • 2

3.3 JavaScript 规范

全局命名空间污染与 IIFE

总是将代码包裹成一个 IIFE(Immediately-Invoked Function Expression),用以创建独立隔绝的定义域。这一举措可防止全局命名空间被污染。

IIFE 还可确保你的代码不会轻易被其它全局命名空间里的代码所修改(i.e. 第三方库,window 引用,被覆盖的未定义的关键字等等)。

不推荐

1. var x = 10,
2.     y = 100;
3.
4. // Declaring variables in the global scope is resulting in global scope pollution. All variables declared like this
5. // will be stored in the window object. This is very unclean and needs to be avoided.
6. console.log(window.x + ' ' + window.y);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

推荐

1. // We declare a IIFE and pass parameters into the function that we will use from the global space
2. (function(log, w, undefined){
3.  'use strict';
4.
5.  var x = 10,
6.      y = 100;
7.
8.  // Will output 'true true'
9.  log((w.x === undefined) + ' ' + (w.y === undefined));
10.
11. }(window.console.log, window));
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

IIFE(立即执行的函数表达式)

无论何时,想要创建一个新的封闭的定义域,那就用 IIFE。它不仅避免了干扰,也使得内存在执行完后立即释放。

所有脚本文件建议都从 IIFE 开始。

立即执行的函数表达式的执行括号应该写在外包括号内。虽然写在内还是写在外都是有效的,但写在内使得整个表达式看起来更像一个整体,因此推荐这么做。

不推荐

(function(){})();
  • 1

推荐

(function(){}());
  • 1

so,用下列写法来格式化你的 IIFE 代码:

(function(){'use strict';

// Code goes here

}());

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

如果你想引用全局变量或者是外层 IIFE 的变量,可以通过下列方式传参:

(function($, w, d){'use strict';

$(function() {
w.alert(d.querySelectorAll(‘div’).length);
});
}(jQuery, window, document));

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

变量声明

总是使用 var 来声明变量。如不指定 var,变量将被隐式地声明为全局变量,这将对变量难以控制。如果没有声明,变量处于什么定义域就变得不清(可以是在 Document 或 Window 中,也可以很容易地进入本地定义域)。所以,请总是使用 var 来声明变量。

采用严格模式带来的好处是,当你手误输入错误的变量名时,它可以通过报错信息来帮助你定位错误出处。

不推荐

x = 10;
y = 100;
  • 1
  • 2

推荐

var x = 10,y = 100;
  • 1
  • 2

变量赋值时的逻辑操作

逻辑操作符 || 和 && 也可被用来返回布尔值。如果操作对象为非布尔对象,那每个表达式将会被自左向右地做真假判断。基于此操作,最终总有一个表达式被返回回来。这在变量赋值时,是可以用来简化你的代码的。

不推荐

if(!x) {if(!y) {x = 1;} else {x = y;}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

推荐

x = x || y || 1;
  • 1

这一小技巧经常用来给方法设定默认的参数。

(function(log){'use strict';

function multiply(a, b) {
a = a || 1;
b = b || 1;

log('Result ' + a * b);

}

multiply(); // Result 1
multiply(10); // Result 10
multiply(3, NaN); // Result 3
multiply(9, 5); // Result 45

}(window.console.log));

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

嵌套函数

嵌套函数是非常有用的,比如用在持续创建和隐藏辅助函数的任务中。你可以非常自由随意地使用它们。

语句块内的函数声明

切勿在语句块内声明函数,在 ECMAScript 5 的严格模式下,这是不合法的。函数声明应该在定义域的顶层。但在语句块内可将函数申明转化为函数表达式赋值给变量。

不推荐

if (x) {function foo() {}
}
  • 1
  • 2
  • 3

推荐

if (x) {var foo = function() {};
}
  • 1
  • 2
  • 3

浅谈Java编码规范,养成良好习惯!相关推荐

  1. java变量命名规则_浅谈JAVA开发规范与开发细节(上)

    开发团队在开发过程中,由于每个人的开发习惯,以及对于技术的理解深浅程度不一,往往一个项目在开发过程中,代码的质量,代码的风格都不尽相似,所以有一份适合团队的代码规范是非常有必要的,而一个团队的代码规范 ...

  2. 传智播客Java基础入门,浅谈JAVA开发规范与开发细节(上

    **** ***/ //通过用户名获取userAccount userAccount = AccountManager.getUserAccount(userName); if(userAccount ...

  3. Java基础学习总结(92)——Java编码规范之排版、注释及命名

    为使开发人员养成良好的开发习惯,编写可读性强.易维护的程序,结合以往资料,现整理Java编码规范,将之作为开发人员的参照依据. 一.排版 1.相对独立的程序块之间必须加空行

  4. java编码ppt_[2018年最新整理]Java编码规范.ppt

    [2018年最新整理]Java编码规范 制定编码规范的最主要的目的是为了对产出代码的长期维护.通常负责 维护代码的人大多都不是开发者本人,如果有一个统一的代码格式以及 说明就可以减少混淆提高理解速度. ...

  5. java中修饰常量的事_浅谈java中的声明常量为什么要用static修饰

    今天定义一个类常量,想着也只有这个类可以用到,就没用static关键字修饰.结果sonar代码检查提示: Rename this field "PERSON_TYPE_USER" ...

  6. java的throw_浅谈Java的throw与throws

    浅谈Java异常 以前虽然知道一些异常的处理,也用过一些,但是对throw和throws区别还是有不太清楚.今天用实例测试一下 异常处理机制 异常处理是对可能出现的异常进行处理,以防止程序遇到异常时被 ...

  7. java方法区对象类型_浅谈Java内存区域与对象创建过程

    一.java内存区域 Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域.这些区域都有各自的用途,以及创建和销毁的时间,有的区域随着虚拟机进程的启动而存在,有的区域则 ...

  8. java虚拟机类加载机制浅谈_浅谈Java虚拟机(三)之类加载机制

    在<浅谈Java虚拟机>这篇文章中,我们提到了JVM从操作系统方面来说,可以将其看做是一个进程,分别有类加载器子系统,执行引擎子系统和垃圾收集子系统.这一篇文章就简单的来谈一下类加载器子系 ...

  9. 公司让我编写一套自己的【Java 编码规范】作为员工季度考核标准?!参照Alibaba

    目录 一.编码规范 (一)命名风格 (二)常量定义 (三)代码格式 (四)OOP规范 (五)集合处理 (六)并发处理 (七)控制语句 (八)注释规范 (九)其它 二.SVN操作规范 三.异常日志 (一 ...

最新文章

  1. Android 支付宝H5 没有回调
  2. js解决EasyUI页面渲染速度慢问题(Mask遮罩)
  3. java filewriter_Java基础篇:什么是FileWriter
  4. 解决plt.savefig存的图是空白的
  5. 开的什么源?——第三篇:草根创业
  6. LwIP应用开发笔记之三:LwIP无操作系统UDP客户端
  7. python的urllib四大模块_Python常用的内建模块4:urllib
  8. 前端每日值得花时间看的博客
  9. 让人兴奋的视差滚动(Parallax Scrolling)效果网站分享
  10. InfoPath: Passing Command Line parameters to a new form
  11. 联想微型计算机怎么开盖,联想C4030一体机怎么拆后盖加内存?
  12. win8恢复我的计算机较早时间点,Win8系统的重置和刷新功能 -电脑资料
  13. Spring AOP动态代理原理与实现方式
  14. autocad不能画图_学了这些CAD技巧,画图速度迅速提高
  15. 怎么将图片镜面对称_怎么把镜面对称
  16. 物联网共享单车有什么物联技术?
  17. 1959年第一台电子计算机,1、 一般认为,世界上第一台电子数字计算机诞生于______。 A.1946年 B.1952年 C.1959年 D.1962年...
  18. MATLAB中神经网络工具箱的使用
  19. 离散数学总复习精华版(最全 最简单易懂)已完结
  20. SSH-keygen linux教程

热门文章

  1. ubuntu恢复默认快捷键
  2. android TextView中ClickableSpan与文本自由复制(TextIsSelectable)冲突问题
  3. oracle连接 ORA-27102: out of memory
  4. Lucene6.1学习案例
  5. Java 根据Excel模板 导出Excel报表
  6. 【汤鸿鑫 3D太极】5年目标规划(基本功、套路、实战搏击)
  7. 邮箱投递简历注意哪些礼仪?
  8. Nginx安装rtmp模块及配置
  9. 【EMC电磁兼容】01.06——标准测试类目之EMS
  10. 你的微信消息是怎么发出去的?