本篇以及后面的系列文章都是使用以下环境:
JDK 版本:1.8
IDA:Eclipse 4.6.0
Maven 版本:3.5.0
SpringBoot 版本:2.1.8.RELEASE
SpringCloud 版本:Greenwich.RELEASE
创建一个父工程,命名为 spring-cloud-pom, 所有的子工程都继承这个父工程,用以管理子工程的版本依赖。
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<!-- Spring Boot -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.8.RELEASE</version>
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>spring-cloud-pom</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>pom</packaging>
<name>spring-cloud-pom</name>
<properties>
<java.version>1.8</java.version>
<maven-jar-plugin.version>3.1.1</maven-jar-plugin.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring-cloud.version>Greenwich.RELEASE</spring-cloud.version>
</properties>
<!-- Spring Cloud -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
父工程只留一个 pom.xml 文件就可以了,其他的文件目录都可以不要。
在父工程下新建一个子工程 spring-cloud-provider,作为服务提供者。
pom.xml
<?xml version="1.0"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.example</groupId>
<artifactId>spring-cloud-pom</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>spring-cloud-provider</artifactId>
<name>spring-cloud-provider</name>
<url>http://maven.apache.org</url>
<dependencies>
<!-- 引入 web 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
</project>
application.properties
# 服务名字
spring.application.name=spring-cloud-provider
# 在 8071 端口启动
server.port=8071
controller
创建一个 Controller,提供一个接口给其他服务查询,简单的输出一些信息即可。
@RestController
public class UserController {
@GetMapping("/users/{name}")
public String users(@PathVariable("name") String name) {
return "hello " + name;
}
}
启动服务,访问 http://localhost:8071/user/zhangsan ,如果能看到我们返回的 “hello zhangsan” 字符串,就证明接口提供成功了。
在父工程下新建一个子工程 spring-cloud-consumer,作为服务消费者。
pom.xml
<?xml version="1.0"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.example</groupId>
<artifactId>spring-cloud-pom</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>spring-cloud-consumer</artifactId>
<name>spring-cloud-consumer</name>
<url>http://maven.apache.org</url>
<dependencies>
<!-- 引入 web 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
</project>
application.properties
# 服务名字
spring.application.name=spring-cloud-consumer
# 在 8072 端口启动
server.port=8072
RestTemplate
RestTemplate 是 Spring 提供的用于访问 Rest 服务的客户端,RestTemplate 提供了多种便捷访问远程 Http 服务的方法,能够大大提高客户端的编写效率。我们通过配置 RestTemplate 来调用接口。
@Configuration
public class BeanConfiguration {
@Bean
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
}
controller
创建 controller,利用 RestTemplate 访问服务提供者的接口。
@RestController
public class UserController {
@Autowired
private RestTemplate restTemplate;
@GetMapping("/users/{name}")
public String getUser(@PathVariable("name") String name) {
return restTemplate.getForObject("http://localhost:8071/users/" + name, String.class);
}
}
服务消费者的 getUser() 方法没有自己实现,而是调用服务提供者的接口。
启动服务消费者,访问 http://localhost:8072/users/zhangsan ,如果返回 “hello zhangsan” 字符串就证明调用成功了。
这样一个简单的伪微服务项目的服务提供者和消费者就已经完成了。
之所以说是伪微服务,是因为服务提供者和服务消费者其实是两个独立的应用,它们之间只是简单的通过 http 的方式进行资源访问和操作。而真正微服务架构不仅仅是将单体应用划分为小型的服务单元这么简单,还需要一套的基础组件来管理各个服务,如服务注册、服务发现、配置中心、消息总线、负载均衡、断路器、数据监控等。Spring Cloud 就是这一系列微服务基础组件框架的集合,利用 Spring Boot 的开发便利性,巧妙地简化了微服务系统基础设施的开发。
通俗地讲,Spring Cloud 就是用于构建微服务开发和治理的框架集合(它并不是具体的一个框架,而是框架集合)
Spring Cloud 提供的微服务基础组件有:
所以接下来,我们利用 Spring Cloud 提供的组件,一步步的构建出一个真正的微服务系统(Demo)。
对于基本类型,== 比较的是字面量。
对于引用类型,== 比较的是地址值。
String a = new String("abc");
String b = new String("abc");
String aa = "abc";
String bb = "abc";
// false,因为用 new 关键字创建 String 对象,每次都是新对象,地址值都不同
System.out.println(a == b);
// true,因为用字符串字面量创建 String 对象,都指向位于 Srping pool 中的同一个对象,地址值相同
System.out.println(aa == bb);
// true,基本类型比较的是字面量
System.out.println(42 == 42.0);
分两种情况
1.没有重写 equals(),则比较的是地址值,相当于 == 比较。
public class Test03 {
public static void main(String[] args) {
User user1 = new User("张三",15);
User user2 = new User("张三",15);
// false,因为 User 类没有重写 equals(),实际还是通过 == 比较
System.out.printf("user1.equals(user2): %s \n",user1.equals(user2));
}
public static class User{
String name;
int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
}
}
看一下 Object 中的 equals()
public boolean equals(Object obj) {
return (this == obj);
}
可以看到用的是 == 比较
2.重写了 equals()
我们可以在 equals() 里面先比较两个对象是否相等,如果不相等,再去比较内容是否相等。
public class Test03 {
public static void main(String[] args) {
User user1 = new User("张三",15);
User user2 = new User("张三",15);
// true,因为重写了 equals()
System.out.printf("user1.equals(user2): %s \n",user1.equals(user2));
}
public static class User{
String name;
int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
User other = (User) obj;
if (age != other.age)
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
}
}
String 类也重写了 equals(),所以我们可以直接拿来比较。
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String) anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
hashCode() 的作用是获取哈希码,也称为散列码;它实际上是返回一个 int 整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。
通俗的将,在 Java 中,你要在本质是散列表的集合中用到该对象,如:HashMap,Hashtable,HashSet,那么 hashCode() 才起作用,其他情况下没用。
1.一般情况,只重写 equals(),没有重写 hashCode()
public class Test03 {
public static void main(String[] args) {
User user1 = new User("张三",15);
User user2 = new User("张三",15);
System.out.printf("user1.equals(user2): %s, user1 hashCode: %d, user2 hashCode: %d \n",user1.equals(user2),user1.hashCode(),user2.hashCode());
}
public static class User{
String name;
int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
User other = (User) obj;
if (age != other.age)
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
}
}
输出:
user1.equals(user2): true, user1 hashCode: 382503496, user2 hashCode: 721139189
可以看到 hashCode 不相等,并不影响 equals() 比较。
2.往 HashSet 中添加该对象
User user1 = new User("张三",15);
User user2 = new User("张三",15);
System.out.printf("user1.equals(user2): %s, user1 hashCode: %d, user2 hashCode: %d \n",user1.equals(user2),user1.hashCode(),user2.hashCode());
HashSet<User> set = new HashSet<>();
set.add(user1);
set.add(user2);
System.out.println(set);
输入:
user1.equals(user2): true, user1 hashCode: 1936129502, user2 hashCode: 365113514
[User [name=张三, age=15], User [name=张三, age=15]]
可以看到这两个对象的内容是重复的,因为往 HashSet 中添加该对象时,会计算对象的 hashCode 值来判断对象加入的位置是否相同,如果不相同,那么就认为这两个对象不相等。
所以虽然 user1 和 user2 的内容相等,但是它们的 hashCode 不相等。所以 HashSet 在添加它们时,会认为它们不相等。
3.重写 HashCode()
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + age;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
输出:
user1.equals(user2): true, user1 hashCode: 776315, user2 hashCode: 776315
[User [name=张三, age=15]]
可以看到 user1 和 user2 的 hashCode 相等,往 HashSet 中添加它们时,首先会根据 hashCode 判断对象加入的位置是否相同,如果相同,那么就再去判断它们的内容是否一样,如果一样,那么会认为它们是相等的。
1.对于基本类型,== 比较的是字面量
2.对于引用类型,== 比较的是地址值
3.没有重写 equals(),则比较的是地址值,相当于 == 比较
4.重写 equals(),则比较内容是否相等
5.对象相等,hashCode() 一定相等
6.hashCode() 相等,对象不一定相等
7.对象相等,equals() 一定相等
8.equals() 相等,对象不一定相等
9.equals() 和 hashCode() 没有关系
10.在本质是散列表的集合中使用某个对象,则必须重写该对象的 equals() 和 hashCode() 方法
一个简简单单的 User 类
public class User {
private String name;
private int age;
public User() {
}
public User(String name, int age) {
this.name = name;
this.age = age;
}
public void print() {
System.out.println("name: " + this.name + ", age: " + this.age);
}
public void print(String sex, String addr) {
System.out.println("name: " + this.name + ", age: " + this.age + ", sex: " + sex + ", addr: " + addr);
}
private void privateMethod() {
System.out.println("private 方法");
}
protected void protectedMethod() {
System.out.println("protected 方法");
}
void defaultMethod() {
System.out.println("default 方法");
}
public static void staticMethod() {
System.out.println("static 方法");
}
public final void finalMethod() {
System.out.println("final 方法");
}
}
测试类
public class UserTest {
public static void main(String[] args) throws Exception {
// 1.先获取 User 类的 Class 对象
Class userClass = User.class;
// 2.通过 Class 对象获取 Method 对象,参数是方法名(String methodName)和参数类型(Class<?>... parameterTypes)
// public 修饰的空参方法
Method print1 = userClass.getMethod("print");
// public 修饰的带参方法
Method print2 = userClass.getMethod("print", String.class, String.class);
// private 修饰的方法,抛出异常 java.lang.NoSuchMethodException
// Method privateMethod = userClass.getMethod("privateMethod", null);
// protected 修饰的方法,抛出异常 java.lang.NoSuchMethodException
// Method protectedMethod = userClass.getMethod("protectedMethod", null);
// default 修饰的方法,抛出异常 java.lang.NoSuchMethodException
// Method defaultMethod = userClass.getMethod("defaultMethod", null);
// static 方法
Method staticMethod = userClass.getMethod("staticMethod", null);
// final 方法
Method finalMethod = userClass.getMethod("finalMethod", null);
// 3.调用 Method 对象的 invoke 方法,将 User 类的实例对象和方法的参数传进去
User user1 = new User("ZhangSan",10);
User user2 = new User("LiSi",12);
print1.invoke(user1);
print2.invoke(user2,"男","中国");
staticMethod.invoke(user2);
finalMethod.invoke(user2);
// 一般的方法调用
System.out.println("========= 一般的方法调用 ========");
user1.print();
user2.print("男","中国");
user2.staticMethod();
user2.finalMethod();
}
}
控制台输出
name: ZhangSan, age: 10
name: LiSi, age: 12, sex: 男, addr: 中国
static 方法
final 方法
========= 一般的方法调用 ========
name: ZhangSan, age: 10
name: LiSi, age: 12, sex: 男, addr: 中国
static 方法
final 方法
可以看到通过 Method 对象调用的 invoke() 方法跟对象.方法名的调用的结果是一样的。
结论
终于明白 JDK 的动态代理为什么能执行原对象的方法了,原来是将 Method 对象和方法参数 args 传进了 InvocationHandler 接口的 invoke(Object proxy, Method method, Object[] args) 方法里,然后通过调用 method.invoke(原对象实例,args) 方法就可以达到调用原对象方法的效果。
public class ObjInterceptor implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return method.invoke(obj, args);
}
}
Elasticsearch 是一个 nosql 数据库,它底层基于开源库 Lucene,Elasticsearch 是 Lucene 的封装,隐藏了复杂的操作,提供了 RESTful 风格的 API 和 Java 客户端(其它语言的客户端也有),开箱即用。
RandomAccessFile 类是专门处理文件的字节流,与其他文件类不同、也是此类最大的特点是:该类支持对文件的随机访问。
RandomAccessFile 维护着一个偏移量,也叫文件指针。
当你用 RandomAccessFile 打开一个文件时,指针的值为 0
读取文件时,指针就会自动向前移动,每读取一个字节,指针就 +1
写入文件时也一样,每写入一个字节,指针就 +1
可以通过 getFilePointer 方法 获取指针,通过 seek 方法设置指针
标注当前类是一个控制类,类名不能和注解名一样
@Controller // 标注这是一个控制类,类名不能和注解名一样
@RequestMapping("/book") // 访问父路径
public class BookController {
......
}