0%

如果有误,欢迎批评指正。友情提示,本文有点长。
SpringBoot 版本 2.0.5.RELEASE。

在学习 SpringBoot 时,会在启动项里面看到在类名上面有一个注解 @SpringBootApplication。前几天在一个公众号中看到关于一道面试题,题目类似于:知道 @SpringBootApplication 的原理吗?

突然发现自己对于 SpringBoot 还是处在‘知其然而不知所以然’的状态。于是就去官网和网上查看一些资料,于是有了这篇文章。

SpringBoot 启动项

1
2
3
4
5
6
7
@SpringBootApplication
public class ApplicationStartup {

public static void main(String[] args) {
SpringApplication.run(ApplicationStartup.class, args);
}
}

@SpringBootApplication

看下 @SpringBootApplication 注解的详情

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
excludeFilters = {@Filter(
type = FilterType.CUSTOM,
classes = {TypeExcludeFilter.class}
), @Filter(
type = FilterType.CUSTOM,
classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {
//...
}

从注解的源码中可以看到它被 @SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan 等注解修饰。

@SpringBootConfiguration

Read more »

如果有错,希望指出

动态代理在Java中有着广泛的应用,比如 Spring Aop、Hibernate 数据查询、RPC、Java 注解对象的获取。静态代理的代理关系在编译时确定,而动态代理的代理关系是在编译期确定的。主要了解:JDK Proxy 和 cglib 动态代理。

动态代理可以提供另一个对象的访问,同时可以隐藏实际对象的具体实例。

静态代理

再说动态代理之前,先了解下静态代理。

静态代理是代理模式实现方式之一,在程序运行前,由程序员创建或者特定工具自动生成源代码并对其编译生成 .class 文件。静态代理的实现需要三步:

  • 定义业务接口
  • 实现业务接口
  • 定义代理类并实现业务接口

最后就可以直接通过客户顿进行调用静态代理了。

Read more »

如果有问题,希望指出

Reflection(反射)是 Java 被视为动态语言的关键,反射机制允许程序在执行期借用 Reflection API 获取任何内部信息,并能操作任何对象的内部属性级方法。

Java 反射机制主要提供一下功能:

  • 在运行时构造任意一个类的对象
  • 在运行时获取任意一个类所具有的成员变量和方法
  • 在运行时调用任意一个对象的方法
  • 生成动态代理

Java 反射提供三种获取 Class 对象的方法:

1
2
3
4
5
6
7
8
Person person = new Person(1L, "反射");
//1、通过对象名
Class personClass = person.getClass();
//2、通过类名
Class baseClass = Person.class;
//3、通过全类名
Class baseClass2 = Class.forName("com.example.basejava.basics.reflect.Person");

Class 是一个类,一个描述类的类。它封装了描述方法的 Method,描述字段的 Filed,描述构造器的 Constructor 等属性。

代码

Person 类

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
public class Person implements Serializable {

private Long id;
private String name;

static {
System.out.println("测试反射静态代码块");
}

Person(Person person) {
System.out.println("(默认)反射测试类构造了" + person);
}

public Person() {
System.out.println("调用了公有、无参构造方法执行了。。。");
}

Person(Long id, String name) {
this.id = id;
this.name = name;
}

Person(String name) {
this.name = name;
System.out.println("name+" + name);
}

public Long getId() {
return id;
}

public void setId(Long id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

@Override
public String toString() {
return "Person{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
Read more »

如果本文有错,希望在下面的留言区指正。

在之前的一篇文章中,写了一篇 Spring 事务管理

本地事务管理主要考虑的是单台服务器上面的事务回滚。那么,如果在分布式服务之间如何管理事务呢?

如上图所示的分布式调用,客户端 client 调用服务A,而服务A 除了本身的对数据库的修改,还需要调用服务B和服务C。如果在调用服务B成功后在调用服务C,但是这时候服务C出错了,这时候如何解决事务的回滚?

CAP

CAP 理论是分布式系统的一个基础理论,描述任何一个分布式系统最多只能满足下面三种特性中的两个:

  • 一致性(Consistency):对某个指定的客户端来说,读操作保证能够返回最新的写操作结果。
  • 可用性(Availability):非故障的节点在合理的时间内返回合理的响应(不是错误和超时的响应)。
  • 分区容忍性(Partition tolerance):当出现网络分区后,系统能够继续“履行职责”。

CAP 的选择

虽然 CAP 理论定义是三个要素中只能取两个,但放到分布式环境下来思考,对于一个分布式系统而言,分区容错性可以说是一个最基本的要求。分布式系统的组件必然是需要部署到不同节点,否则也就无所谓分布式系统了,因此必然出现子网络。而对于分布式系统而言,网络问题有时一个必定会出现的异常情况,因此分区容错性也就成为一个分布式系统必然需要面对和解决的问题了。

Read more »

类被加载到虚拟机的内存中开始,到卸载出内存为止,它的整个生命周期包括:加载、验证、准备、解析、初始化、使用和卸载。

类加载的过程

加载

在加载阶段,需要完成以下3件事情:

  • 通过一个类的全限定名来获取定义此类的二进制字节流
  • 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
  • 在内存中生成一个代表这个类的 java.lang.Class 对象,作为方法区这个类的各种数据的访问入口。

加载阶段结束后,Java 虚拟机外部的二进制字节流就按照虚拟机所设定的格式存储在方法区之中,方法区中的数据存储格式完全由虚拟机实现自行定义。

验证

验证是连接阶段的第一步,这一阶段的目的是为了确保 Class 文件的字节流包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。

Read more »

声明:本文使用JDK1.8,如果有错希望指出

在Java面试的时候,HashMap 和 Hashtable 经常被问,就想仔细分析下两者。

Java集合框架 —— HashMap

HashMap 和 Hashtable 的区别

HashMap 和 Hashtable 都是实现 Map 接口,两者主要的区别是线程安全性,同步,以及速度。

HashMap Hashtable
键值接受null 键值对不能为null
非synchronized Hashtable是synchronized,线程安全
单线程下HashMap的速度比Hashtable快 sychronized意味着在一次仅有一个线程能够更改Hashtable
迭代器(Iterator)是fail-fast迭代器 Hashtable的enumerator迭代器不是fail-fast的

因此仅在需要线程安全的时候使用Hashtable,而如果Java5以上,还是使用 ConcurrentHashMap。

Java并发容器 ——— ConcurrentHashMap

源码分析

Read more »

声明:本文使用JDK1.8,如果有错希望指出

本文将简单介绍 JAVA 集合框架——TreeMap。TreeMap 集合是基于红黑树的 NavigableMap 实现。该集合最重要的特性就是可排序,该映射可以根据 Map 的键的自然顺序排序,或者根据创建时提供的 Comparator 进行排序,具体取决于使用的构造方法。

TreeMap 继承关系

源码分析

数据结构

TreeMap采用红黑树的数据结构来实现。树节点Entry实现了Map.Entry,采用内部类的方式实现:

1
2
3
4
5
6
7
static final class Entry<K,V> implements Map.Entry<K,V> {
K key;
V value;
Entry<K,V> left;
Entry<K,V> right;
Entry<K,V> parent;
boolean color = BLACK;

接下来看下 TreeMap 中支持红黑树的数据成员:

1
2
3
4
5
6
7
8
9
10
11
public class TreeMap<K,V>
extends AbstractMap<K,V>
implements NavigableMap<K,V>, Cloneable, java.io.Serializable{
//用于接收自定义比较器,插入时用于比对元素的大小
private final Comparator<? super K> comparator;
//红黑树的根节点
private transient Entry<K,V> root;
//树中元素个数
private transient int size = 0;
//
private transient int modCount = 0;
Read more »

欢迎指正。

最近打算重温一下排序算法。

插入排序

原理

插入排序主要是通过构建有序序列,对未排列的数据,从后往前扫描,插入到前面已经排好序的有序序列中去,直到插完所有元素为止。

代码

1
2
3
4
5
6
7
8
9
10
11
12
public static void InsertSort(int[] array) {

for (int i = 1; i < array.length; i++) {
int j = i;
int target = array[i];
while (j > 0 && target < array[j - 1]) {
array[j] = array[j - 1];
j--;
}
array[j] = target;
}
}

选择排序

原理

第一次从下标为0的开始下标为0的这个数与后面的n-1个进行比较;找出最小或者最大的放在下标为0的这个位置。

Read more »

在 Java 集合中,有一种集合框架叫 Set,应该很多人在日常开发中用到它。
先了解下 Set 的一些特性,然后看下源码:

  • 不重复存储元素
  • 元素排列无序

因为其是一个抽象的接口,所以不能直接实例化一个 set 对象。(Set s = new Set() 错误 )。该接口主要继承于Collections接口,所以具有Collection的一些常见的方法。

源码分析

1
2
3
4
5
6
7
//Set是无序的,不是按照插入的顺序排列的,不允许重复
Set<String> set = new HashSet<>();
set.add("a");
set.add("c");
set.add("b");
set.add("a");
System.out.println(set);

关于 HashSet,如果看下源码,就可以知道,其实实际使用的是 HashMap。而如果使用的是 TreeSet,通过源码,可以知道实际使用的是 TreeMap。

如何判断元素是否重复

HashSet 使用 hashCode 和 equals 方法。
注意:HashSet集合在判断元素是否相同先判断hashCode方法,如果相同才会判断equals。如果不相同,是不会调用equals方法的。

Read more »

如果有什么错误的地方,希望指出。

之前的文章中介绍了 Dockerfile,知道了 Dockerfile 模板文件可以定义一个单独的应用容器,如果需要定义多个容器就需要服务编排。服务编排有很多种技术方案,今天给大家介绍 Docker 官方产品 Docker Compose 。

Docker Compose 是 Docker 官方编排(Orchestration)项目之一,负责快速在集群中部署分布式应用。Compose 定位是 「定义和运行多个 Docker 容器的应用(Defining and running multicontainer Docker applications)」,其前身是开源项目 Fig。

Docker-Compose 是用来管理你的容器的,有点像一个容器的管家,想象一下当你的 Docker 中有成百上千的容器需要启动,如果一个一个的启动那得多费时间。有了 Docker-Compose 只需要编写一个文件,在这个文件里面声明好要启动的容器,配置一些参数,执行一下这个文件,Docker就会按照你声明的配置去把所有的容器启动起来,但是Docker-Compose只能管理当前主机上的Docker,也就是说不能去启动其他主机上的Docker容器。恰好满足了这样的需求。它允许用户通过一个单独的 docker-compose.yml 模板文件 (YAML 格式)来定义一组相关联的应用容器为一个项目(project)。

Compose 中有两个重要的概念:

  • 服务 ( service ):一个应用的容器,实际上可以包括若干运行相同镜像的容器实例。
  • 项目 ( project ):由一组关联的应用容器组成的一个完整业务单元,在 dockercompose.yml 文件中定义。

使用

创建 Python 服务

1、创建项目文件夹:

1
2
$ mkdir composetest
$ cd composetest
Read more »