一、Spring Cloud-服务提供者与服务消费者

2019-09-20   SpringCloud   SpringCloud  

本篇以及后面的系列文章都是使用以下环境:

  • 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 提供的微服务基础组件有:

  • Eureka:服务注册中心,用于服务管理
  • Ribbon:基于客户端的负载均衡组件
  • Hystrix:容错框架,能够防止服务的雪崩效应
  • Feign:Web 服务客户端,能够简化 HTTP 接口的调用
  • Zuul:API 网关,提供路由转发、请求过滤等功能
  • Config:分布式配置管理
  • Sleuth:服务跟踪
  • Stream:构建消息驱动的微服务应用程序的框架
  • Bus:消息代理的集群消息总线

所以接下来,我们利用 Spring Cloud 提供的组件,一步步的构建出一个真正的微服务系统(Demo)。

源码下载:https://github.com/miansen/SpringCloud-Learn

Java ==、equals() 和 hashCode()

2019-09-11   Java   equals hashCode  

==

对于基本类型,== 比较的是字面量。

对于引用类型,== 比较的是地址值。

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);

equals()

分两种情况

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()

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() 方法

Java反射-Method的Invoke方法

2019-09-05   Java   反射 Method  

一个简简单单的 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-集群

2019-08-25   Elasticsearch   Elasticsearch  
环境准备,请确保你已经有了以下环境,(1)3台虚拟机,我是用 VMware 搭建的虚拟机,每台虚拟机的地址如下:192.168.8.4、192.168.8.6、192.168.8.8(2)每台虚拟机都确保安装好了 Elasticsearch

Elasticsearch-Java客户端

2019-08-02   Elasticsearch   Elasticsearch  
Elasticsearch 官方提供了3种客户端:1.TransportClient,这个客户端在7.0.0弃用,将在8.0版本中删除。2.elasticsearch-rest-client,低级的REST客户端,基于Apache HTTP,支持所有Elasticsearch版本,请求编码和响应解码留给用户实现(自己写JSON)。3.elasticsearch-rest-high-level-client,高级的REST客户端,基于低级客户端,对增删改查进行了封装,提供特定方法的API,不需要用户处理编解码,类似之前的TransportClient,但是兼容性较差,对客户端和集群版本要求较高。

Elasticsearch-基础

2019-07-20   Elasticsearch   Elasticsearch  

Elasticsearch 是一个 nosql 数据库,它底层基于开源库 Lucene,Elasticsearch 是 Lucene 的封装,隐藏了复杂的操作,提供了 RESTful 风格的 API 和 Java 客户端(其它语言的客户端也有),开箱即用。

数据结构-单向链表

2019-07-08   数据结构   数据结构 单向链表  

链表的定义

链表是线性表的链式存储结构,由 n 个节点组成,每个节点包含两个元素:数据域和指针域

SpringMVC 集成 Shiro

2019-07-01   SpringMVC   SpringMVC Shiro  

简介

  • Shiro 是 Apache 的一个安全(权限)框架

Java IO - RandomAccessFile

2019-06-27   Java   Java  

RandomAccessFile 类是专门处理文件的字节流,与其他文件类不同、也是此类最大的特点是:该类支持对文件的随机访问。

RandomAccessFile 维护着一个偏移量,也叫文件指针

当你用 RandomAccessFile 打开一个文件时,指针的值为 0

读取文件时,指针就会自动向前移动,每读取一个字节,指针就 +1

写入文件时也一样,每写入一个字节,指针就 +1

可以通过 getFilePointer 方法 获取指针,通过 seek 方法设置指针

SpringMVC 常用的注解

2019-06-24   SpringMVC   SpringMVC 注解  

@Controller

  • 作用

标注当前类是一个控制类,类名不能和注解名一样

  • 例子
@Controller // 标注这是一个控制类,类名不能和注解名一样
@RequestMapping("/book") // 访问父路径
public class BookController {
    ......
}