JAVA备忘
JAVA1.7&JAVA1.8新增特性
JDK1.7部分新特性
- switch可以接受String类型(本质上是算出了一个hash值)
- 可以在catch代码块中捕获多个异常类型
catch(Exception 1 | Exception2)
- 增加了二进制字面量的表示
0b111
- 在数字中可以添加分隔符, 如
123_456
, 只能被用在数字中间, 编译时会被去掉 - 增加了类型推断机制
Map<String, String> map = new HashMap<>()
- 增加了try-with-resource语句, 确保每个资源都能在生命周期结束后被关闭, 不需要显式调用close方法
try (UnputStream fis = new FileInpustream("input.txt")) {}
- 增加了fork/join框架来增强对处理多核并行计算的支持
JDK1.8部分新特性
lambda表达式(核心)
Lambda是一个匿名函数,我们可以把Lambda表达式理解为一段可以传递的代码(将代码像数据一样传递)。可以写出更加简洁、灵活的代码。作为一种更加紧凑的代码风格,使java的语言表达能力得到了提升
1 | // 匿名内部类 -> lambda |
lambda语法
- 无参, 无返回值
1 | () -> System.out.println("无参无返回值"); |
- 一个参数, 无返回值
1 | (n) -> System.out.println("无参无返回值"); |
- 需要2个参数, 有返回值
1 | // 当lambda体中只有一条语句时, return与大括号可以省略 |
- 类型判断
上述Lambda表达式中的参数类型都是有编译器推断得出的。Lambda表达式中无序指定类型,程序依然可以编译,这是因为javac根据程序上下文,在后台推断出参数的类型,Lambda表达式的类型依赖于上下文环境,是由编译器推断出来的,这就是所谓的“类型推断”。
函数式接口
值包含一个抽象方法的接口,称为函数式接口。我们可以在任意函数式接口上使用@FunctionalInterface注解,这样做可以检查它是否是一个函数式接口
1 |
|
四大核心函数式接口
接口中默认方法和静态方法
默认方法
Java8 中允许接口中包含具有具体实现的方法,该方法称为“默认方法”,默认方法使用default关键字修饰。
1 | public interface AddFun { |
类优先原则
若一个接口中定义了一个默认方法,而另外一个父类或接口中又定义了一个同名的方法时:
- 如果子类(或实现类)继承的父类和实现的接口中声明了同名同参数的默认方法,那么子类在没重写此方法的情况下,默认调用的是父类中的同名同参数的方法
- 接口冲突。如果一个父接口提供一个默认方法,而另一个接口也提供了一个具有相同名称和参数列表的方法(不管方法是否是默认方法),那么必须覆盖该方法解决冲突。
静态方法
1 | public interface AddFun { |
方法引用与构造器引用
方法引用
当要传递给Lambda体的操作,已经有实现方法了,可以使用方法引用!(实现抽象方法的列表,必须与方法引用方法的参数列表保持一致)
方法引用:使用操作符”::”将方法名和对象或类的名字分隔开来。
1 | public static void main(String[] args) { |
方法引用三种情况:
- 对象::实例方法
- 类::静态方法
- 类::实例方法
构造器引用
格式:ClassName::new
1 | Function<Integer, MyClass> fun = (n) -> new MyClass(n); |
数组引用
1 | Function<Integer, Integer[]> fun = (n) -> new Integer[n]; |
stream类(核心)
Stream是Java8中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤映射数据等操作。使用Stream API对集合数据进行操作,就类似于使用SQL执行的数据库查询。也可以使用Stream API来并行执行操作。简而言之,Sream API提供了一种高效且易于使用的处理数据方式。
- Stream 自己不会存储元素。
- Stream 不会改变源对象。相反,他们会返回一个持有结果的新的Stream。
- Stream 操作时延迟执行的。这意味着他们会等到需要的结果的时候才会执行。
Stream操作的三个步骤
1. 创建Stream
通过一个数据源(如:集合、数组),获取一个流
有两个获取流的方法default Stream<E> stream()
: 返回一个顺序流;default Stream<E> parallelStream()
: 返回一个并行流。
1 | // 通过数组创建流 |
2. 中间操作
一个中间操作链,对数据源的数据进行处理,多个中间操作可以连接起来形成一个流水线,除非流水线上出发终止操作,否则中间操作不会执行任何的处理!而在终止操作时一次性安全的全部处理,称之为“惰性求值”;
方法 | 描述 |
---|---|
filter(Predicate p) | 接受lambda, 从流中排除一些元素 |
distinct() | 筛选, 通过流所生成的元素hashCode()和equals()取出重复元素 |
limit(long maxSize) | 截断流, 使其元素不超过给定的数量 |
skip(long n) | 跳过元素, 返回一个扔掉前n个元素的流, 若流中元素不足n个, 则返回一个空流, 与limit互补 |
map(Function f) | 接受一个函数作为参数, 该函数会被应用到每个元素上面, 并将其映射成一个新的元素 |
mapToDouble(ToDoubleFunction f) | 接受一个函数作为参数, 该函数会被应用到每个元素上, 产生一个新的DoubleStream |
mapToInt(ToIntFunction f) | 接受一个函数作为参数, 该函数会被应用到每个元素上, 产生一个新的IntStream |
mapToLong(ToLongFunction f) | 接受一个函数作为参数, 该函数会被应用到每个元素上, 产生一个新的LongStream |
flatMap(Function f) | 接受一个函数作为参数, 将流中的每个值转换成另一个流, 然后把所有流连成一个流 |
sorted() | 产生一个新流, 其中俺自然排序排序 |
sorted(Comparator comp) | 产生一个新流, 其中按比较器顺序排序 |
3. 终止操作(终端操作)
一个终止操作,执行中间操作链,并产生结果,结果可以是任何不是流的值,例如:List、Integer,甚至void
查找与匹配 | – |
---|---|
方法 | 描述 |
allMatch(Predicate p) | 检查是否匹配所有元素 |
anyMatch(Predicate p) | 检查是否至少匹配一个元素 |
noneMatch(Predicate p) | 检查是否没有匹配所有元素 |
findFirst() | 返回第一个元素 |
findAny() | 返回当前流中的任意一个元素 |
count() | 返回流中元素总数 |
max(Comparator c) | 返回流中最大值 |
min(Comparator c) | 返回流中最小值 |
forEach(Consumer c) | 内部迭代 |
归约 | – |
reduce(T iden, BinaryOperation b) | 可以将流中的元素反复结合起来, 得到一个值T |
reduce(BinaryOperator b) | 可以将流中的元素反复结合起来, 得到一个值, 返回Operation<T> |
收集 | – |
collect(Collector) | 将流转化为其他形式, 用于给Stream中元素做汇总的方法, 如toList, toSet |
并行流与串行流
并行流就是把一个内容分成多个数据块,并用不同的线程分别处理每个数据块的流。Java8中将并行进行了优化,我们可以很容易的对数据进行并行操作。Stream API可以声明性的通过parallel()与sequential()在并行流与顺序流之间进行切换。
日期新特性
中间件
redis
opsForValue:字符串
opsForList:列表
opsForSet:集合
opsForHash:散列
opsForZSet:有序集合
消息队列
- JMS(Java Message Service)
- AMQP(advanced message queuing protocol)- 兼容JMS, RabbitMQ 、 StormMQ 、 RocketMQ
- MQTT(Message Queueing Telemetry Transport) - 占用带宽小
交换机类型
直接(direct), 主题(topic), 标题(headers), 扇出(fanout)
常见消息队列对比
RocketMQ只支持java和c++
Kafka不支持消费失败重试, 吞吐量大, 分布式
RocketMQ稳定性更好
RabbitMQ社区比较活跃
绑定键规则
*表示一个单词, 相当于_, #表示任意数量单词, 相当于%
topic.*.* 可以匹配到topic.order.id
topic.# 可以匹配到topic.order
Elasticsearch
Elasticsearch 是面向文档的,这意味着索引和搜索数据的最小单位是文档, 一个文档通常是以JSON 的数据格式来表示的
field
一个Document中有很多Field,一个Field就是一个数据字段
type
类型,是文档的逻辑容器,类似于表格是行的容器。在不同的类型中,最好放入不同结构的文档, 每个类型中字段(Field)的定义称为映射(Mapping)。例如, 一个人的姓名可以映射为string,年龄可以映射为int
ES 6以前每个Index可以有多个Type,在ES 6中一个Index仅能包含一个Type,而在ES 7将完全移除Type
index
索引,是类型的容器。一个Elasticsearch 索引非常像关系型世界的数据库,是独立的大量文档集合。每个索引存储在磁盘上的同组文件中,索引存储了所有映射类型的字段,还有一些设置
Shard 分片
一个索引可以存储超出单个结点硬件限制的大量数据。比如,一个具有10亿文档的索引占据1TB的磁盘空间,而任一节点都没有这样大的磁盘空间;或者单个节点处理搜索请求,响应太慢。为了解决这个问题,ES提供了将索引划分成多份的能力,这些份就叫做分片。当创建一个索引的时候,可以指定想要的分片的数量。每个分片本身也是一个功能完善并且独立的Lucene“索引”,这个“索引”可以被放置到集群中的任何节点上。在一个网络/云的环境里,失败随时都可能发生,在某个分片/节点不知怎么的就处于离线状态,或者由于任何原因消失了。这种情况下,有一个故障转移机制是非常有用并且是强烈推荐的。
为此目的,ES允许创建分片的一份或多份拷贝。一旦有了拷贝,每个索引就有了主分片(Primary Shard)和复制分片(Replica Shard)之别
Cluster
Elasticsearch 的集群监控信息中包含了许多的统计数据,其中最为重要的一项就是集群健康, 它在 status 字段中展示为 green 、 yellow 或者 red 。
Green:所有主分片和备份分片都准备就绪(分配成功),即使有一台机器挂了(假设一台机器一个实例),数据都不会丢失,但会变成Yellow状态
Yellow::所有主分片准备就绪,但存在至少一个主分片(假设是A)对应的备份分片没有就绪,此时集群属于警告状态,意味着集群高可用和容灾能力下降,如果刚好A所在的机器挂了,并且你只设置了一个备份(已处于未就绪状态),那么A的数据就会丢失(查询结果不完整),此时集群进入Red状态
Red::至少有一个主分片没有就绪(直接原因是找不到对应的备份分片成为新的主分片),此时查询的结果会出现数据丢失(不完整)
Spring
类加载
一个java类的完整的生命周期会经历加载、连接、初始化、使用、和卸载五个阶段
当程序主动使用某个类时,如果该类还没有加载到内存中,则通过以下三个步骤对类进行加载初始化:
类的加载:将类的class文件读入内存,并为之创建一个java.lang.Class对象到方法区中,此过程由类加载器完成
类的链接:将类的二进制数据合并到JRE中
类的初始化:JVM负责对类进行初始化
加载完之后还有两步为使用,卸载,就是完整的类的生命周期。
加载
将类的文件信息加载到内存中,作为程序方法入口,分三步:
通过类的全限定名获取类的二进制字节流
将字节流的静态存储结构转化为方法去的运行时结构
在内存中生成Class对象,作为方法去入口
如果被加载的是一个数组类型,数组类型是一个比较特殊的类型,他不通过类加载器加载,而是由虚拟机去完成。但是他的引用却要靠加载器加载,加载器会加载完数组的数据类型后将该数组绑定到相应的加载器上,然后与该类加载器一起绑定标识唯一性。
链接
其中,类的链接可以分为以下几点:
- 验证:确保加载的类符合JVM规范
- 准备:正式为类变量(static)分配内存并设置类变量默认初始值的阶段,这些内存都将在方法区中进行分配,后续初始化时会实际赋值(实例变量会被分配到堆中)
- 解析:虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程)
符号引用替换为直接引用理解:
符号引用:比如一个类中引用了其它类,但是JVM不知道实际引用的其它类地址在哪,所以就会用符号引用来代表,等到解析的时候,再根据唯一符号引用去找其它类的地址。不止是其它类的代表,符号引用也可以代表方法,字段等。需要注意的是,符号引用与虚拟机布局无关,引用的目标不一定已经加载到内存中。
直接引用:直接引用与虚拟机布局有关,如果使用直接引用,那么引用的目标一定已经加载到内存中。
符号引用要转换成直接引用才有效,这也说明直接引用的效率要比符号引用高。那为什么要用符号引用呢?这是因为类加载之前,javac会将源代码编译成.class文件,这个时候javac是不知道被编译的类中所引用的类、方法或者变量他们的引用地址在哪里,所以只能用符号引用来表示,当然,符号引用是要遵循java虚拟机规范的。
初始化
执行类构造器()方法的过程。类构造器()方法是由编译器自动收集所有类变量的赋值动作和静态代码块中的语句合并产生的。
当初始化一个类的时候,会判断该类父类有没有初始化,如果没有则先触发父类的初始化
虚拟机会保证一个类的()方法在多线程环境下的被正确加锁和同步
准备阶段中已经对类变量(static)进行内存分配,初始化时对类变量和静态代码块进行赋值和执行
类加载时机
创建类的实例,也就是一个new对象
访问某个类或接口的静态变量,或者对该静态变量赋值
调用类的静态方法
反射获取类信息
初始化一个类的子类(会首先初始化子类的父类)
JVM启动时标明的启动类,即文件名和类名相同的那个类
类的加载机制
- 全盘负责:所谓全盘负责,就是当一个类加载器负责加载某个Class时,该Class所依赖和引用其他Class也将由该类加载器负责载入,除非显示使用另外一个类加载器来载入
- 双亲委派:所谓的双亲委派,则是先让父类加载器试图加载该Class,只有在父类加载器无法加载该类时才尝试从自己的类路径中加载该类。通俗的讲,就是某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父加载器,依次递归,如果父加载器可以完成类加载任务,就成功返回;只有父加载器无法完成此加载任务时,才自己去加载
- 缓存机制:缓存机制将会保证所有加载过的Class都会被缓存,当程序中需要使用某个Class时,类加载器先从缓存区中搜寻该Class,只有当缓存区中不存在该Class对象时,系统才会读取该类对应的二进制数据,并将其转换成Class对象,存入缓冲区中。这就是为很么修改了Class后,必须重新启动JVM,程序所做的修改才会生效的原因
双亲委派模型
类加载器分为:启动类加载器(BootStrap ClassLoader)、扩展类加载器(Extension ClassLoader)、应用类加载器(Application ClassLoader)、自定义类加载器。
如果一个类加载器收到了一个类加载的请求,它首先不会去加载类,而是去把这个请求委派给父加载器去加载,直到顶层启动类加载器,如果父类加载不了(不在父类加载的搜索范围内),才会自己去加载。
- 启动类加载器:加载的是lib目录中的类加载出来,包名是java.xxx(如:java.lang.Object)
- 扩展类加载器:加载的是lib/ext目录下的类,包名是javax.xxx(如:javax.swing.xxx)
- 应用程序扩展器:这个加载器就是ClassLoader的getSystemClassLoader的返回值,这个也是默认的类加载器。
双亲委派模型的意义在于不同的类加载器之间分别负责所搜索范围内的类的加载工作,这样能保证同一个类在使用中才不会出现不相等的类。
IOC
控制反转, 意味着将你设计好的对象交给容器控制,而不是传统的在你的对象内部直接控制
- 被动实例化: 无需主动new对象, 只需要描述如何创建, 让IOC容器帮我创建
- 被动接受装配: 不需要主动装配对象之间的依赖关系, 容器会帮我装配
- 主动变被动
- 迪米特法则: 不知道依赖的具体实现; 面向接口编程; 可能少的了解
1 | <!-- id要全局唯一 --> |
DI
依赖注入, 用一个单独的对象来装配对象之间的依赖关系
动态注入依赖关系
- 应用程序不主动创建对象, 只描述创建他们的方式
- 在应用程序代码中不直接进行服务的装配, 但要配置文件中描述哪一个组件需要哪一项服务, 容器负责将这些装配在一起
好莱坞法则: Dont call us, we’ll call you
1 | <!-- 数据库类型或配置变化时, 不需要改代码, 只需要改配置 --> |
AOP
AOP是一种编程范式, 将那些与业务无关, 缺为业务模块所共同调用的逻辑或责任封装起来, 它可以降低模块的耦合度, 使系统容易扩展, 设计决定的迟邦定, 更好的代码复用性
1 |
|
- AfterReturning会多一个returning参数(Object result)
@AfterReturning(value = "execution(public int com.st.demo.CalImpl.*(..))", returning="result")
- AfterThrowing会多一个throwing参数(Exception e)
@AfterThrowing(value = "execution(public int com.st.demo.CalImpl.*(..))", throwing="e")
After和After-returning的区别:
- 后置通知(After):表示在目标方法执行完成后执行的通知功能,即使在执行目标方法出现异常,也照常执行这个逻辑。
- 返回通知(After-returning)表示目标方法正常执行完成后置的通知功能,如果目标方法执行过程中出现异常,那么这个通知的逻辑就不执行。
1 | execution(public void aop.TargetImpl.run()) 表示 aop.TargetImpl类,修饰符为public,返回值、参数列表为空的run方法。 |
Spring事务管理
分布式事务, 先定义事务再打开连接 与 一般事务有所不同
1)编程式事务-使用jdbc原生的事务处理,可以将事务处理写在业务逻辑代码中,违背aop原则,不推荐;
2)声明式事务-使用事务注解 @Transactional,可以声明在方法上,也可以声明在类上;
- 默认配置下 Spring 只会回滚运行时异常(非受检查异常),即继承自 RuntimeException 的异常或者 Error
- @Transactional 注解只能应用到 public 修饰的方法
- 可以明确的配置在抛出哪些异常时回滚事务,包括checked异常。也可以明确定义哪些异常抛出时不回滚事务,也可通过setRollbackOnly()方法来指定一个事务必须回滚,在调用完setRollbackOnly()后你所能执行的唯一操作就是回滚。
Transaction属性
Propagation: 事务传播行为
代码 | 解释 |
---|---|
@Transactional(propagation=Propagation.REQUIRED) | 如果有事务则加入事务,若没有则新建(默认) |
@Transactional(propagation=Propagation.NOT_SUPPORTED) | 容器不为这个方法开启事务 |
@Transactional(propagation=Propagation.REQUIRES_NEW) | 不管是否存在事务,都创建一个新事务,原来的挂起,新事务执行完毕,继续执行挂起的事务 |
@Transactional(propagation=Propagation.MANDATORY) | 必须在一个已有的事务中执行,否则抛出异常 |
@Transactional(propagation=Propagation.NEVER) | 必须在一个没有的事务中执行,否则抛出异常(与Propagation.MANDATORY相反) |
@Transactional(propagation=Propagation.SUPPORTS) | 如果其他bean调用这个方法,在其他bean中声明事务,那就用事务。如果其他bean没有声明事务,那就不用事务 |
@Transactional(propagation=Propagation.NESTED) | 若一个活动的事务存在,则运行在一个嵌套的事务中;若没有活动事务,则按REQUIRED属性执行 |
Timeout属性
用于设置事务处理的时间长度,阻止可能出现的长时间的阻塞系统或占用系统资源。单位为秒。如果超时设置事务回滚,并抛出TransactionTimeOutException异常。
Isolation事务隔离级别属性
1、读未提交Isolation.READ_UNCOMMITTED : 读取其它事务未提交的数据,了解,基本不会使用;会出现脏读,不可重复读
2、读已提交Isolation.READ_COMMITTED : oracle的默认事务隔离级别,同一个事务处理中,只能读取其它事务提交后的数据(也就是说事务提交之前对其余事务不可见);SQL server默认, 会出现不可重复读和幻读, 解决脏读问题
3、可重复读Isolation.REPEATABLE_READ : mysql默认事务隔离级别,同一个事务处理中,多次读取同一数据是都是一样的,不受其它事务影响;会出现幻读, 解决不可重复读问题
4、串行化Isolation.SERIALIZABLE : 可以避免上面所有并发问题,但是执行效率最低,数据一致性最高;解决全部
5、Isolation.DETAULT: 使用各个数据库默认的隔离级别
ORM框架
Mybatis, Hibernate, Jpa, Jdo都是ORM框架
同一个方法里面JDBC\Mybatic\Hibernate不要同时用
SpringMVC
- 支持Restful的URL
- 可完全注解驱动
- 引入HttpMessageConverter
- 对静态资源处理提供特殊支持
- 更加灵活的控制器方法签名
- 和数据转换、格式化、验证框架无缝集成
/ 和 / 的区别
/ 不会拦截页面(比如:/xxx/login.html),只会拦截路径(比如:/xxx/login)
/ 都会拦截(即 路径+ 页面 )/ 和 / 的区别
/ 是拦截所有的文件夹,不包含子文件夹
/** 是拦截所有的文件夹及里面的子文件夹
@RequestBody && @ResponseBody
- 将HttpServletRequest的getInputStream内容绑定到入参中
- 将返回值写入到HttpServletResponse的getOutputStream中
优点是处理方法签名不受限
缺点是只能访问报文体, 不能访问报文头
HttpEntity && ResponseEntity
用的比较少, 它可以支持访问报文头
数据校验处理
JSR303, @Null, @Min(value), @AssertTrue等等
数据库
MySQL关系型数据库, 有4种数据库存储引擎: InnoDB、MyISAM、MEMORY、MERGE
InnoDB
是Mysql的默认存储引擎,用于事务处理应用程序,支持外键。如果应用对事务的完整性有比较高的要求,在并发条件下要求数据的一致性,数据操作除了插入和查询意外,还包含很多的更新、删除操作,那么InnoDB存储引擎是比较合适的选择。InnoDB存储引擎除了有效的降低由于删除和更新导致的锁定, 还可以确保事务的完整提交和回滚,对于类似于计费系统或者财务系统等对数据准确性要求比较高的系统,InnoDB是最合适的选择
MyISAM
如果应用是以读操作和插入操作为主,只有很少的更新和删除操作,并且对事务的完整性、并发性要求不是很高,那么选择这个存储引擎是非常合适的。
面向对象设计方法
网络IO
异常
Exception:属于程序的错误,包含运行时异常和检查性异常,除了运行时异常剩下的都是检查性异常,运行时异常会继承RuntimeException
常见运行时异常:
- NullPointerException
空指针异常,JAVA8中可用Optional来避免,一般是代码中出现了空对象时,抛出该异常 - IndexOutOfBoundsException
数组下标越界异常,当使用的数组下标超出数组允许范围时,抛出该异常 - ClassCastException
类型转换异常,当试图将对象强制转换为不是实例的子类时,抛出该异常 - NumberFormatException
数字格式化异常,当试图将一个String转换为指定的数字类型,而该字符串确不满足数字类型要求的格式时,抛出该异常 - JSONException
JSON异常,进行JSON格式化操作时出现异常,会抛出该异常
常见检查性异常:
- SQLException
SQL异常 - IOException
IO异常,在对流操作时有可能会出现的异常 - FileNotFoundException
找不到某个文件时,会抛出该异常 - ClassNotFoundException
找不到某个类时,会抛出该异常 - EOFException
输入过程中意外地到达文件尾或流尾,会抛出该异常,常见于对流的操作
@ExceptionHandler
可以统一处理方法抛出的异常, 但只作用于当前controller, 可以结合@ControllerAdvice进行全局处理, 当前方法的默认需要返回ModelAndView对象或void, 添加@ResponseBody之后可以返回String
1 | () |
JAVA开发规范
规范目的
全面有效地执行开发规范是保证团队代码开发质量有效的措施
开发编码规范总的原则
- 排版规范: 整齐划一
- 注释规范: 易于理解
- 命名规范: 见名知意
- 编码规范: 少犯错误
JAVA源文件
每个JAVA源文件都包含一个单一的公共类或者接口, 或私有类与接口与公共类相关联, 可以将他们放入同一个源文件. 公共类必须是这个文件中的第一个类或接口
规则:
- 开头注释
- 包和引用语句
- 类和接口声明
开头注释
所有源文件应该都在开头有一个注释, 列出类名, 版本信息, 日期和版权申明
1 | /** |
包和引用语句
在多数java源文件中, 第一个非注释行是包语句, 在它之后是引入语句
1 | package com.demo.st; |
类和接口生命
先后顺序如下:
- 类/接口文档注释:
/**...*/
- 类/接口声明
- 类/接口实现注释:
/*...*/
如果有必要的话, 包含有关类的相关信息但又不适合作为文档注释 - 类的(静态)变量: 公共变量-保护变量-包一级变量-私有变量
- 实例变量: 公共变量-保护变量-包一级变量-私有变量
- 构造器
- 方法: 方法按功能分组, 方便阅读代码
1 | package com.demo.st; |
缩进排版
尽量避免一行的长度超过80个字符, 视情况考虑换行, 遵循以下4个规则:
- 在逗号之后换行
- 在运算符之前换行
- 优先选择较高级别的断开
- 新的一行应该与上一行的开头处对齐, 或者缩进8个空格
1 | if (condition1 && condition2 |
注释
- 一般情况下, 源程序的有效注释必须在30%以上
- 编写代码编注释, 修改代码同时修改相应的注释, 保证注释和代码的一致性, 不再用时需要删除
- 注释应当浅显/明白
- 错误的注释还不如不注释
- 注释不是程序员指南
- 注释不是标准库函数参考手册
- 注释的主要任务是答疑解惑而不是增加程序行数
- 好的注释是对设计思想的精确表述和清晰展现
- 含义一目了然的不需要注释
- 一行内可以写完的请使行注释
- 一行内无法写完的使用块注释
- 为将来生成api文档而写的注释请使用javadoc注释
1 | 块注释: |
文档注释
javadoc
// TODO
空行
- 空行将逻辑相关的代码段分隔开
- 下列情况使用两个空行:
- 一个源文件的两个片段之间
- 类声明和接口声明之间
- 下列情况使用一个空行:
- 两个方法之间
- 方法内的局部变量和方法的第一条语句之间
- 块注释或单行注释之前
- 一个方法内的两个逻辑段之间, 用以提高可读性
空格
1 | // 需要空格 |
命名规范
类/接口
使用大驼峰, 每个单词的首字母大写, 使用完整单词, 避免缩写词, 需要简洁而富于描述
如: ApiController
方法
使用小驼峰, 如: getUserInfo();
变量
使用小驼峰, 尽量不适用下划线或者美元符号开头, 变量名应易于记忆, 并能指出其用途
整形: 临时变量可被取名为i,j,k,m,n
字符型: c,d,e
实例变量 -> 独立于方法之外的变量,不过没有 static 修饰
使用小驼峰, 最前面需要加下划线, 例如: String _name
常量
应该全部大写, 单词间用下划线隔开, 例如: static final int USER_ID = 4;
编码规范
避免使用魔鬼数字
1 | // 错误示例: |
Sonar检查规则
多线程
3种实现方式
1. 实现Runnable接口
1 | public class TaskA implements Runnable { |
2. 实现Callable接口
1 | // Callable接口是泛型, 定义的是返回值的类型 |
3. 集成Thread类
1 | // 本方法与实现Runnable接口基本一致, 因为Thread实现了Runnable |
使用方式
方式1
1 | // 直接创建并启动, 这样子无法获取任务的执行结果 |
方法2
使用线程池
- 直接创建ThreadPoolExecutor实例
- 使用Executors创建常见的线程池类型(不推荐使用)
常见的线程池类型:
- Executors.newCashedThreadPool(): 可缓存线程池, 创建线程数量的上限是INTEGER.MAX
- Executors.newSingleThreadExecutor(): 单线程化的线程池, 只会用唯一的工作线程来执行任务, 保证所有任务都是按照指定顺序执行的, 先进先出或者先进后出, 任务队列的长度基本是无限的, 可能有内存溢出的风险
- Executors.newFixedThreadPool(int): 创建一个指定工作线程数量的线程池, 如果数量超过设定的最大值, 则放在队列中, 也存在内存溢出的风险
1 | ExecutorService executor = Executors.newCashedThreadPool(); |
以上三种调用线程均无法获取执行结果
- Executors.newScheduledThreadPool(int): 它与上面是三种不同, 它封装的是ScheduleedThreadPoolExecutor类, 线程池的数量是固定的, 支持只执行一次或周期性执行的自动任务, 也有内存溢出的风险
1
2
3
4ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
TaskC taskC = new TaskC();
ScheduledFuture<String> future = executor.schedule(taskC, 1, TimeUint.SECONDS);
锁
Synchornize
1 | public synchronize String applyCoupon() { |
Lock
Lock比Synchronize更灵活, 可以手动加锁和释放
1 | public static final Lock lock = new ReentrantLock(); |
分布式锁
Redis分布锁
死锁
需要满足的4个条件
解决方案
固定执行顺序
指定锁账户顺序:
(1) 代发任务: 先查询内部户状态, 再查询其他账户状态
(2) 退款任务: 先查询内部户转台, 再查询要退款账户状态
执行效率差异
可以用多线程处理耗时的项目
数据库
隔离级别
从低到高的隔离级别:
- Read uncommitted
- Read committed: 解决脏读
- Repeatable read: 解决脏读, 不可重复读
- Serializable: 解决脏读, 不可重复读, 幻读
oracle数据库没有脏读的概念, 其他的数据库有
数据源
springboot默认数据源: 默认数据源是HikariDataSource
常用连接池: boneCP、DBCP、C3P0、Druid
设计模式
工厂模式
简单工厂
一个工厂负责所有类型的生产
工厂方法
有一个抽象工厂和很多个实际的工厂,比如奥迪工厂,宝马工厂
抽象工厂
抽象工厂的任务是定义一个负责创建一组产品的接口.这个接口内的每个方法都负责创建一个具体的产品
需要根据某个前提条件创建不同的类实现时, 可以使用工厂模式
代理模式
JDK动态代理
必须面向接口
建造者模式
- 需要生成的对象具有复杂的内部结构
- 需要生成的对象内部属性本身相互依赖
- 与工厂模式的区别是:建造者模式更加关注与零件装配的顺序
- JAVA 中的 StringBuilder就是建造者模式创建的,他把一个单个字符的char数组组合起来
- Spring不是建造者模式,它提供的操作应该是对于字符串本身的一些操作,而不是创建或改变一个字符串
拼凑机器人
模板方法
实现一些操作时,整体步骤很固定,但是呢。就是其中一小部分需要改变,这时候可以使用模板方法模式,将容易变的部分抽象出来,供子类实现。
例如:数据库访问的封装、Junit单元测试、servlet中关于doGet/doPost方法的调用等等
餐厅吃饭,餐厅给我们提供了一个模板就是:看菜单,点菜,吃饭,付款,走人 (这里 “点菜和付款” 是不确定的由子类来完成的,其他的则是一个模板。)
外观模式, 也叫门面模式
它向现有的系统添加一个接口,用这一个接口来隐藏实际的系统的复杂性, 他外部看起来就是一个接口,其实他的内部有很多复杂的接口已经被实现
原型模式
- 类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等。这时我们就可以通过原型拷贝避免这些消耗
- 通过new产生的一个对象需要非常繁琐的数据准备或者权限,这时可以使用原型模式
- 一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式拷贝多个对象供调用者使用,即保护性拷贝
我们Spring框架中的多例就是使用原型
策略模式
主要是为了 简化 if…else 所带来的复杂和难以维护
- 策略模式的用意是针对一组算法或逻辑,将每一个算法或逻辑封装到具有共同接口的独立的类中,从而使得它们之间可以相互替换
- 例如:我要做一个不同会员打折力度不同的三种策略,初级会员,中级会员,高级会员(三种不同的计算)
- 例如:我要一个支付模块,我要有微信支付、支付宝支付、银联支付等
观察者模式
观察者模式主要用于1对N的通知。当一个对象的状态变化时,他需要及时告知一系列对象,令他们做出相应
跨系统的消息交换场景,如消息队列、事件总线的处理机制
装饰器模式
装饰器模式(Decorator Pattern) 也称为包装模式(Wrapper Pattern) 是指在不改变原有对象的基础之上,将功能附加到对象上,提供了比继承更有弹性的替代方案(扩展原有对象的功能),属于结构型模式。装饰器模式的核心是功能扩展,使用装饰器模式可以透明且动态地扩展类的功能。
当需要给一个现有类添加附加职责,而又不能采用生成子类的方法进行扩充时。例如,该类被隐藏或者该类是终极类或者采用继承方式会产生大量的子类。
当需要通过对现有的一组基本功能进行排列组合而产生非常多的功能时,采用继承关系很难实现,而采用装饰器模式却很好实现。
当对象的功能要求可以动态地添加,也可以再动态地撤销。
比如: 点奶茶加料
适用场景:
- 当需要给一个现有类添加附加职责,而又不能采用生成子类的方法进行扩充时。例如,该类被隐藏或者该类是终极类或者采用继承方式会产生大量的子类。
- 当需要通过对现有的一组基本功能进行排列组合而产生非常多的功能时,采用继承关系很难实现,而采用装饰器模式却很好实现。
- 当对象的功能要求可以动态地添加,也可以再动态地撤销。
装饰器与适配器都有一个别名叫做 包装模式(Wrapper),它们看似都是起到包装一个类或对象的作用,但是使用它们的目的很不一样。适配器模式的意义是要将一个接口转变成另一个接口,它的目的是通过改变接口来达到重复使用的目的。
而装饰器模式不是要改变被装饰对象的接口,而是恰恰要保持原有的接口,但是增强原有对象的功能,或者改变原有对象的处理方式而提升性能。所以这两个模式设计的目的是不同的。
在jdk中,InputStreamReader是一个适配器,因为它把InputStream的API转换成Reader的API。InputStream是被适配的类,而 Reader是适配的目标类。InputStreamReader做为适配器类把InputStream类的一个实例包装起来,从而能够把InputStream的API。
而BufferReader是一个装饰器类,因为它实现Reader,并且包装了一个Reader。一些对流处理器可以对另一些流处理器起到装饰作用,形成新的、具有改善功能得流处理器。类似地,BufferedInputStream、OutputStream、Writer 各自都是它们自己的装饰类。LineNumberReader、FilterReader和 PushbackReader均是Reader的装饰类,因为它们自己是Reader类,而且包装其他的Reader类。CharArrayReader、FileReader、PipedReader和StringReader类不是装饰类,因为它们包装的是字符数值组、File、PipedWriter和String类。它们应该被看做字符数值组、File、PipedWriter 和String类的适配器类。
注解
@Autowired
作用范围: 构造器, 方法, 参数 成员变量, 注解
默认是按照byType方式装配的, 还有byName, constructor, autodetect
@Autowired注解的required参数默认是true, 表示开启自动装配, @Autowired(required=false)
spring的@Service方法不允许出现相同的类名,因为spring会将类名的第一个字母转换成小写,作为bean的名称,比如:testService1,而默认情况下bean名称必须是唯一的
byName的方法: @Qualifier(“名字”)1
2
3
4
5
6
7
public class UserService {
"user1") (
private IUser user;
}
或者用@Primary注解解决重复实现的问题。在User1上面加上@Primary注解:1
2
3
4
5
6
7
public class User1 implements IUser{
public void say() {
}
}
web应用启动的顺序是:listener->filter->servlet
众所周知,springmvc的启动是在DisptachServlet里面做的,而它是在listener和filter之后执行。如果我们想在listener和filter里面@Autowired某个bean,肯定是不行的,因为filter初始化的时候,此时bean还没有初始化,无法自动装配。
spring的bean默认是单例的,如果单例bean使用@Autowired自动装配,大多数情况,能解决循环依赖问题。但是也有特殊情况, 创建代理对象的时候还是会出现循环依赖的问题
@Autowired和@Resource的区别
- @Autowired默认按byType自动装配,而@Resource默认byName自动装配。
- @Autowired只包含一个参数:required,表示是否开启自动准入,默认是true。而@Resource包含七个参数,其中最重要的两个参数是:name 和 type。
- @Autowired如果要使用byName,需要使用@Qualifier一起配合。而@Resource如果指定了name,则用byName自动装配,如果指定了type,则用byType自动装配。
- @Autowired能够用在:构造器、方法、参数、成员变量和注解上,而@Resource能用在:类、成员变量和方法上。
- @Autowired是spring定义的注解,而@Resource是JSR-250定义的注解。
@Configuration
@Configuation等价于<Beans></Beans>
@Bean等价于<Bean></Bean>
@ComponentScan等价于<context:component-scan base-package="com.ahies.ija.management"/>
一般于@Bean方法结合
1
2
3
4
5
6
7
8
public class AppConfig {
public MyBean myBean() {
// instantiate, configure and return bean ...
}
}
@ComponentScan
@ComponentScan主要用法就是用来扫描指定的包下面的bean对象,ComponentScan可以配置多个,springboot默认就是扫描启动类所在的包的下面所有的bean,如果还需要额外指定别的package,则需要新增一个ComponentScan,并且需要手动指定springboot所在类的package的路径,要不然就不会被加载。
basePackageClasses 用于指定特定的类,就比如说需要加载某个特定的类可以使用这一的写法
可以通过设置@ComponentScan basePackages,includeFilters,excludeFilters属性来动态确定自动扫描范围
@SpringBootApplication
@SpringBootApplication = @ComponentScan+@EnableAutoConfiguration+@SpringBootConfiguration
spring四大注解: @Service,@Repository,@Component,@Controller用来定义一个bean
@SpringBootConfiguration
这个注解的作用与@Configuration作用相同,都是用来声明当前类是一个配置类
@EnableAutoConfiguration
@EnableAutoConfiguration是springboot实现自动化配置的核心注解,通过这个注解把spring应用所需的bean注入容器中.@EnableAutoConfiguration源码通过@Import注入了一个ImportSelector的实现类
AutoConfigurationImportSelector,这个ImportSelector最终实现根据我们的配置,动态加载所需的bean
自动装载
- 通过@SpringBootApplication包含@EnableAutoConfiguration实现自动装配的功能;
- @EnableAutoConfiguration通过@AutoConfigurationPackage实现对于当前项目中Bean的加载;
- @EnableAutoConfiguration通过@Import({AutoConfigurationImportSelector.class})实现对于引入的start中的XXAutoConfiguration的加载;
- AutoConfigurationImportSelector类中通过SpringFactoriesLoader读取 META-INF/spring.factories中key为org.springframework.boot.autoconfigure.EnableAutoConfiguration对应的各个XXAutoConfiguration的值,然后springboot在结合各个start中的代码完成对于XXAutoConfiguration中的Bean的加载动作;
- AutoConfigurationImportSelector类通过实现DeferredImportSelector延迟导入接口,在容器初始化的过程中在完成以上动作;
条件装配@Conditional注解
@Conditional注解需要配合Condition类一起使用
@Conditional可以作用在方法上,也可以作用在类上。
使用的时候需要传入实现Condition接口类数组。
如果是类和方法都加了@Conditional注解,最终在方法上的注解为最终的条件,如果返回true则加入容器,反之不会加入容器。
如果只是类上加了@Conditional注解,整个类的所有方法都会加入容器中。
当一个类有@Conditional注解, 例如@Conditional(DatabaseCondtional.class)
, 那么DatabaseCondtional这个类需要实现Condition, 并重写matches方法, 只有当matches方法返回true时, spring才会装配这个类
Bean的作用域
1、Bean的5种作用域
(1)singleton:单例模式,Spring IoC容器中只会存在一个共享的Bean实例,无论有多少个Bean引用它,始终指向同一对象Singleton作用域是Spring中的缺省作用域,也可以显示的将Bean定义为singleton模式,配置为:<bean id="userDao" class="com.ioc.UserDaoImpl" scope="singleton"/>
(2)prototype:原型模式,每次通过Spring容器获取prototype定义的bean时,容器都将创建一个新的Bean实例,每个Bean实例都有自己的属性和状态,而singleton全局只有一个对象。根据经验,对有状态的bean使用prototype作用域,而对无状态的bean使用singleton作用域。
(3)request:在一次Http请求中,容器会返回该Bean的同一实例。而对不同的Http请求则会产生新的Bean,而且该bean仅在当前Http Request内有效。<bean id="loginAction" class="com.cnblogs.Login" scope="request"/>
,针对每一次Http请求,Spring容器根据该bean的定义创建一个全新的实例,且该实例仅在当前Http请求内有效,而其它请求无法看到当前请求中状态的变化,当当前Http请求结束,该bean实例也将会被销毁。
(4)session:在一次Http Session中,容器会返回该Bean的同一实例。而对不同的Session请求则会创建新的实例,该bean实例仅在当前Session内有效。<bean id="userPreference" class="com.ioc.UserPreference" scope="session"/>
,同Http请求相同,每一次session请求创建新的实例,而不同的实例之间不共享属性,且实例仅在自己的session请求内有效,请求结束,则实例将被销毁。
(5)global Session:在一个全局的Http Session中,容器会返回该Bean的同一个实例,仅在使用portlet context时有效。该作用域仅在基于Web的Spring上下文(例如SpringMVC)中才有效
Hystrix
服务端降级
首先在业务类中加入新的配置标签@HystrixCommand,相当于在fallbackMethod中添加一个方法,如下value设置为3s,当超过3秒时,就会调用备用的方法,或者是方法中运行错误,比如说是以下的10/0报错
然后在主启动类中加入@EnableCircuitBreaker,这样就可以在服务提供端测试自己的服务降级了
1 | "降级方法名", commandProperties={ (name="execution.isolatiion.thread.timeoutInMilliseconds", value="3000")}) (fallbackMethod= |
客户端降级(常用)
首先在yml加上hystrix和feign的配置
1
2
3feign:
hystrix:
enabled: true在主启动类中添加注解@EnableHystrix
- 在业务类中利用Hystrix实现服务降级就行了,跟服务提供端类似
全局通用降级
使用@DefaultProperties(defaultFallback=”方法名”)
Ribbon
静态负载均衡算法: 随机(Random), 轮询(Round Robin), 加权轮询(Weighted Round Robin)
动态负载均衡算法: 源IP哈希算法, 最少连接数算法, 服务调用时延算法
BestAvailableRule策略
选择一个并发量最小的服务器, 逐个考察服务器然后选择其中活跃请求数最小的服务器
WeightedResponseTimeRule策略
与请求响应时间有关, 如果响应时间越长, 就代表这个服务的响应能力越有限, 分配给该服务的权重就应该越小
AvailabilityFilteringRule策略
通过检查LoadBalanceStats中, 记录的各个服务器的运行状态, 过滤掉一直连接失败或处于高并发状态的后端服务器
@LoadBalanced
通过@LoadBalanced注解修饰的resttemplate可以实现负载均衡的调用, 原理是 LoadBalanceAutoConfiguration类里面对restTemplate进行了特殊处理, 最终其实是使用loadBalanceClient进行调用的
1 |
|
@RibbonClient
可自定义负载均衡策略
1 |
|
OpenFegin(FeignClient)
1 | // 客户端 |
ribbon的默认超时时间是1s, 如果服务端处理时间大于1s, 可修改以下配置1
2
3ribbon: #设置feign客户端连接所用的超时时间,适用于网络状况正常情况下,两端连接所用时间
ReadTimeout: 1000 #指建立连接后从服务读取到可用资源所用时间
ConnectTimeout: 1000 #指的是建立连接所用时间
请求日志
1 | /** |
Feign的重试机制
可以通过实现Retryer接口进行重试机制的自定义, 包括attempt重试次数、maxPeriod最大等待时长、period时长
有两种方法可以进行配置:
- 通过配置文件feign.client.config.[feignName].retryer=Retryer实现类([feignName]为Client Id名称) 方式配置
- 通过指定 configuration进行配置。
1
2"test-service", contextId = "testClient", (name =
fallbackFactory = FmsBaseClientFallbackFactory.class, configuration = CommonFeignRetryConfig.class)
设置超时时间
配置默认超时时间
1 | feign.client.config.default.connectTimeout=2000 |
为单个服务设置超时时间
1 | #对单个服务设置超时,会覆盖默认的超时 |
重置小节:
- Feign默认配置是不走重试策略的,当发生
RetryableException
异常时直接抛出异常。 - 并非所有的异常都会触发重试策略,只有
RetryableException
异常才会触发异常策略。 - 在默认Feign配置情况下,只有在网络调用时发生
IOException
异常时,才会抛出RetryableException
,也是就是说链接超时、读超时等不不会触发此异常。
Gateway
gateway和zuul相比, gateway使用的是非阻塞的Netty
路由断言:
1 | # path断言 |
gateway过滤器
1 | # 常见过滤器 |
自定义过滤器
继承 AbstractGatewayFilterFactory 类,重写 apply 方法的逻辑。命名需要以 GatewayFilterFactory 结尾,比如 CheckAuthGatewayFilterFactory
如果你的配置是 Key、Value 这种形式的,那么可以不用自己定义配置类,直接继承 AbstractNameValueGatewayFilterFactory 类即可。直接使用getName和getValue获取值即可
全局过滤器
1 | // order来指定执行的顺序, 数字越小优先级越高 |