利他才能利己


  • 首页

  • 标签

  • 归档

  • 搜索

还有你不知道的Java枚举特性(下篇)

发表于 2019-08-11 | 分类于 Java |

简介

这篇是博文 还有你不知道的Java枚举特性(上篇) 的下篇,可以点击下面的链接前往。

本篇主要内容:

  • Java 枚举是一个特殊的类,聊聊其方法的重写
  • 如何使用接口来组织 Java 枚举?
  • 如何使用枚举实现 Java 的单例模式
  • JDK 数据结构中关于枚举的集合 EnumSet 和字典 EnumMap

重写枚举的方法

所有的枚举类都继承自 Enum,在这个父类当中 toString、equals 和hashCode 的三个方法,可以看一下,源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
public String toString() {
return name;
}

public final boolean equals(Object other) {
return this == other;
}

public final int hashCode() {
return super.hashCode();
}

可以看出在这三个方法当中,我们只能重写 toString 方法,另外两个方法都是 final 修饰的方法,不可以被子类重写。

我们在自定义的枚举中,可以重写 toString 方法的,示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public enum Color {
RED("red color"),
GREEN("green color"),
BLUE("blue color"),
YELLOW("yellow color");

Color(String name) {
_name = name;
}

private String _name;

@Override
public String toString() {
return "this.name: " + _name;
}
}

关于 Enum 的源码,可以在博文 Java 枚举的本质 中的文末翻阅。

对于 Java 中所有枚举都是继承自 Enum,大家可以去使用 javap 命令反编译看看,如下代码是 javap 后的简单示例:

1
2
3
4
5
6
7
8
9
public final class com.veryitman.Color extends java.lang.Enum<com.veryitman.Color> {
public static final com.veryitman.Color RED;
public static final com.veryitman.Color GREEN;
public static final com.veryitman.Color BLUE;
public static final com.veryitman.Color YELLOW;
public static com.veryitman.Color[] values();
public static com.veryitman.Color valueOf(java.lang.String);
static {};
}

可以看到 Color 是继承自 java.lang.Enum 的,Enum 是一个抽象类并实现了 Comparable 和 Serializable 这两个接口,如下:

1
public abstract class Enum<E extends Enum<E>> implements Comparable<E>, Serializable 

使用接口组织枚举

当我们定义的枚举过多且又有很多嵌套,可以使用接口来组织这些枚举,将其归类,这样一来不仅代码看起来很规范,并且也很好管理代码。

如下示例,使用接口 MobileTool 来组织两个枚举。

1
2
3
4
5
6
7
8
9
public interface MobileTool {
enum Phone implements MobileTool {
HUAWEI, iPhone, OPPO, XIAOMI
}

enum Pad implements MobileTool {
iPad, WEPad, sPad
}
}

简单的可以这样使用,示例如下:

1
2
3
4
5
MobileTool mphone = MobileTool.Phone.HUAWEI;
mphone = MobileTool.Phone.iPhone;

MobileTool mpad = MobileTool.Pad.iPad;
mpad = MobileTool.Pad.sPad;

枚举实现单例模式

在 Effective Java 这本书籍中,作者有个这样下面的描述:

1
"This approach is functionally equivalent to the public field approach, except that it is more concise, provides the serialization machinery for free, and provides an ironclad guarantee against multiple instantiation, even in the face of sophisticated serialization or reflection attacks. While this approach has yet to be widely adopted, a single-element enum type is the best way to implement a singleton."

核心的意思是

1
使用枚举实现单例的方法虽然还没有广泛采用,但单元素的枚举类型已经成为实现 Java 单例模式的最佳方法。

我们用枚举实现一下单例模式,示例代码如下:

1
2
3
4
5
6
7
public enum Foo {
INSTANCE;

public void printFoo() {
System.out.println("Foo here.");
}
}

相比 Java 中其他的单例实现方式,此时此刻你会发现,枚举实现单例的代码会精简很多。

那么,枚举实现单例模式到底有哪些优势呢?或者换句话说,就这样实现单例靠谱吗?

经过大量例子和 Java 编程专家的讲解,枚举实现单例模式相当靠谱,它具有以下一些特点:

1、枚举实现的单例模式是线程安全的

本质上面来讲,枚举实现的单例之所以是线程安全的,这个跟 Java 的类加载机制有关。从上面反编译的代码来看,枚举是 final class 并且每个枚举值都是 static 的,这里牵扯到 ClassLoader 的相关知识,如果有兴趣建议大家去研究一下。

总之,对于我们任何一个枚举在第一次被真正用到之时,会被 Java 虚拟机加载并且完成初始化,而这个初始化过程是线程安全的,所以你需要记住枚举实现的单例模式是多线程安全的就可以了。

2、枚举可解决反射/反序列化问题

我们知道,一般的单例模式都存在两个问题,一个是可以通过反射调用,另一个就是可以通过序列化和反序列化来破坏单例。

一般解决反射调用可以通过私有构造方法中做处理,示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
private static boolean flag = false;

private Foo() {
synchronized (this) {
if (false == flag) {
flag = true;
} else {
throw new RuntimeException("不能反复创建");
}
}
};

了解序列化原理的同学,可以通过在单例类中实现 readResolve 方法就可以避免反序列化攻击这个问题了。示例代码如下:

1
2
3
private Object readResolve() {
return INSTANCE;
}

Java 的枚举的反序列化实现并不是通过反射实现的,也就是说枚举的序列化和反序列化是有经过特殊定制和处理的,这就可以避免反序列化过程中由于反射而导致的单例被破坏问题。

总之,枚举实现的单例模式不仅可以防止反射破坏,还可以防止序列化破坏单例。

除枚举实现单这种方式以外,我一般使用下面两种方式来实现单例模式。

饿汉式的单例模式写法

1
2
3
4
5
6
7
8
9
10
11
public class Foo {
private static Foo instance = new Foo();

private Foo () {

}

public static Foo getInstance() {
return instance;
}
}

静态内部类实现单例模式

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Foo {
private static class FooHolder {
private static final Foo INSTANCE = new Foo();
}

private Foo () {

}

public static final Foo getInstance() {
return FooHolder.INSTANCE;
}
}

枚举集合

EnumSet 是一个专为枚举设计的集合类,EnumSet 中的所有元素都必须是指定枚举类型的枚举值。

EnumSet 类结构图如下:

1

EnumSet 是一个抽象类,无法被实例化,但是可以通过静态方法获取该类的实例。

1
2
public abstract class EnumSet<E extends Enum<E>> extends AbstractSet<E>
implements Cloneable, java.io.Serializable

EnumMap 类结构图如下:

1

EnumMap 定义如下:

1
2
public class EnumMap<K extends Enum<K>, V> extends AbstractMap<K, V>
implements java.io.Serializable, Cloneable

EnumSet 保证集合中的元素不重复;EnumMap 中的 key 是 enum 类型,而 value 则可以是任意类型。

关于这两个数据结构的使用方法,大家可以参考 JDK 手册。


赠人玫瑰,手留余香~

还有你不知道的Java枚举特性(上篇)

发表于 2019-08-09 | 分类于 Java |

简介

博文 Java 枚举的本质 跟大家一起学习了 Java 语言中枚举到底是什么,探索其本质和原理。用一句话来概括其本质就是:“Java 中的枚举是一个特殊的 Java 类”,有兴趣的同学可以点击了解上面那篇文章。

今天跟大家一起学习和总结一下 Java 中的枚举的其他特性和用途,由于内容较多,这次的分享分为两篇文章来写,欢迎大家拍砖、指正和交流!

本篇主要内容:

  • Java switch 语句支持枚举类型以及注意事项
  • Java 枚举的常量使用
  • 如何在自定义枚举中自定义成员变量和成员方法、静态方法
  • Java 枚举实现接口

下篇主要内容:

  • Java 枚举是一个特殊的类,聊聊其方法的重写
  • 如何使用接口来组织 Java 枚举?
  • JDK 数据结构中关于枚举的集合 EnumSet 和字典 EnumMap

switch 语句支持枚举

JDK1.5 才开始有枚举类型,同时可爱的 Java 组织丰富了 switch 语句,即支持枚举。

我们还是动手写个例子~

定义一个枚举 Color,示例如下:

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
public enum Color {
RED("red color", 0),
GREEN("green color", 1),
BLUE("blue color", 2),
YELLOW("yellow color", 3);

// 构造方法
Color(String name, int id) {
_name = name;
_id = id;
}

private String _name;
private int _id;

public String getName() {
return _name;
}

public int getId() {
return _id;
}

public static Color getColor(int max) {
Random random = new Random(System.currentTimeMillis());
int num = random.nextInt(max);
switch (num) {
case 0:
return Color.RED;
case 1:
return Color.GREEN;
case 2:
return Color.BLUE;
case 3:
return Color.YELLOW;
default:
return Color.BLUE;
}
}
}

为枚举 Color 提供了一个静态方法 getColor,可以获取随机的颜色值。

那么,可以结合 switch 语句这样使用,示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public static void main(String[] args) {
int len = Color.values().length;
Color color = Color.getColor(len);
switch (color) {
case RED:
System.out.println("select " + "RED");
break;
case GREEN:
System.out.println("select " + "GREEN");
break;
case BLUE:
System.out.println("select " + "BLUE");
break;
case YELLOW:
System.out.println("select " + "YELLOW");
break;
default:
System.out.println("select " + "unknow!!");
break;
}
}

可以看出,switch 语句中条件值是 Color 对象。

这里提醒大家要注意一个问题,如果 case 的条件带上类的名字,代码是无法通过编译的。如下面的代码(错误示例):

1
2
3
4
// Error
case Color.RED:
System.out.println("select " + "RED");
break;

编译的错误信息:

1
Error: An enum switch case label must be the unqualified name of an enumeration constant

即 switch case 语句结合枚举使用时,只能写枚举类定义的变量名称,不能加类名。

当常量使用

一般在 Java 中我们定义常量最常用的方式就是使用下面的方式,如下:

1
public static final int MAX_NAME_LENGTH = 0x1112;

有了枚举之后,也可以使用枚举来定义常量。

1
2
3
public enum Color {
YELLOW, BLANK, RED, GREEN
}

这样就可以把一些相关的常量放到定义的枚举当中了。

自定义方法、成员变量

上面的例子中自定义的一个枚举 Color,大家可以看到可以给它添加成员变量 _id、_name,构造方法和其他方法 getColor(静态方法)。

如果这部分很难理解,建议去阅读 Java 枚举的本质 这篇文章。

实现接口

所有的枚举类都继承自 Enum,因为 Java 不支持多继承,所以枚举无法继承其它类。

枚举既然是和 Java 的普通类基本一样,那么,枚举当然也可以实现接口。

下面是枚举 Color 实现接口 Paint 的例子,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
interface Paint {
void setColor();
}

public enum Color implements Paint {
RED("red color"),
GREEN("green color"),
BLUE("blue color"),
YELLOW("yellow color");

Color(String name) {
_name = name;
}

private String _name;

@Override
public void setColor() {
System.out.println("Current paint color: " + _name);
}
}

还可以采用匿名内部类的方式,让每个枚举值实现接口 Paint 的方法,示例如下:

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
public enum Color implements Paint {
RED("red color") {
@Override
public void setColor() {
System.out.println("Current paint color: " + RED._name);
}
},
GREEN("green color") {
@Override
public void setColor() {
System.out.println("Current paint color: " + GREEN._name);
}
},
BLUE("blue color") {
@Override
public void setColor() {
System.out.println("Current paint color: " + BLUE._name);
}
},
YELLOW("yellow color") {
@Override
public void setColor() {
System.out.println("Current paint color: " + YELLOW._name);
}
};

Color(String name) {
_name = name;
}

private String _name;

@Override
public void setColor() {
System.out.println("Current paint color: " + _name);
}
}

可以写个方法测试一下,如下:

1
2
3
4
5
public static void main(String[] args) {
for (Color color : Color.values()) {
color.setColor();
}
}

输出结果,如下:

1
2
3
4
Current paint color: red color
Current paint color: green color
Current paint color: blue color
Current paint color: yellow color

人生没有永远的赢家,切勿大喜大悲,要以平常心对待一切~

微服务: 学习几个容易混淆的URL注解

发表于 2019-08-04 | 分类于 Server |

了解 URL

首先,大家需要知道下面这三个东西的定义:

URI,Universal Resource Identifier,统一资源标志符

URL,Universal Resource Locator,统一资源定位符

URN,Universal Resource Name,统一资源名称

在 WWW 上,每一信息资源都有统一的且在网上唯一的地址,该地址就叫 URL,它是 WWW 的统一资源定位标志,就是指网络地址。

URL 是 URI 的子集,所有的 URL 都是 URI,但不是每个 URI 都是 URL,还有可能是 URN,他们的关系可以用下面的图来表示:

换句话说,URI 分为三种,URL 或者 URN 或者是 URL 和 URI 的组合。

不管怎么说,大家平时使用HTTP请求的地址基本都是称之为 URL,所以暂时不必要纠结于三者的区别之中而无法自拔,搞懂 URL 的组成部分和规范才是重点。

URL 由三部分组成即资源类型、存放资源的主机域名和资源文件名。

URL 的一般语法格式为:

1
scheme:[//[user[:password]@]host[:port]][/path][?query][#fragment]

具体参数释义如下:

  • scheme:传送协议有些地方也写作protocol,如http、https、ftp等

  • user:password:访问资源需要的凭证信息,可省略

  • host:服务器地址,通常为域名,有时为IP地址

  • port:端口号,以数字方式表示,一般使用端口默认值80,可省略

  • path:路径,以“/”字符区别路径中的每一个目录名称

  • query:查询,GET模式的窗体参数,以“?”字符为起点,每个参数以“&”隔开,再以“=”分开参数名称与数据,通常以UTF8的URL编码,避开字符冲突的问题

  • fragment:片段,以“#”字符为起点

常用的 URL 如下:

1
2
3
http://www.veryitman.com/article/name=itman&pwd=123

http://www.veryitman.com/article/1/2

大部分的编程语言都会有针对 URL 处理的系统函数库,最常用的如 URLEncode、URLDecode 针对 URL 编解码的类。

下面跟大家分享一下在 SpringBoot 中经常使用的 RequestParam、QueryParam、PathVariable 和 PathParam 这三个注解。

@RequestParam

注解 @RequestParam 可以从每个 HTTP Request 中获取参数,该注解支持下面四种参数:

  • defaultValue 如果本次请求没有携带这个参数,或者参数为空,那么就会启用默认值
  • name 绑定本次参数的名称,要跟 URL 上面的一样,否则会请求失败
  • required 用来标示对应的参数是不是必须的,默认是 true,如果对某个参数做可选值可以设置该值为 false
  • value 是 name 属性的一个别名,value 和 name 只能出现一个否则会请求报错

下面是注解 @RequestParam 的源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package org.springframework.web.bind.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;

@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestParam {
@AliasFor("name")
String value() default "";

@AliasFor("value")
String name() default "";

boolean required() default true;

String defaultValue() default "\n\t\t\n\t\t\n\ue000\ue001\ue002\n\t\t\t\t\n";
}

这里重点说一下 value 这个值,举个例子。

1
2
3
4
@RequestMapping(value = "/friends", method = RequestMethod.GET)
public String getFrieds(@RequestParam(value = "page") String page, @RequestParam(value = "pageSize") String pageSize) {
return "Get friend list. " + "page: " + page + " - pagesize: " + pageSize;
}

该方法有两个参数,分别是 page 和 pageSize,注意这里的参数 pageSize 是大写的,在注解 @RequestParam 中对应的 value 值也是大写的 pageSize,现在启动工程后,请求一下下面的地址。

1
http://localhost:8080/friends?page=1&pageSize=2

这个时候可以正常访问且能返回正确的结果,假如我们将 @RequestParam 中对应的 value 值改为小写的 pagesize 再来使用上面的地址访问呢?

就会报下面的错误,如图所示:

所以需要修改访问地址,即将 pageSize 改为 pagesize:

1
http://localhost:8080/friends?page=1&pagesize=2

那我们再思考另外一个问题,是否可以修改注解 @RequestParam 中对应的value值的 page 和 pageSize 为其他的比如 xx、yy?

动手试试就知道了!

1
2
3
4
@RequestMapping(value = "/friends", method = RequestMethod.GET)
public String getFrieds(@RequestParam(value = "xx") String page, @RequestParam(value = "yy") String pageSize) {
return "Get friend list. " + "page: " + page + " - pagesize: " + pageSize;
}

函数本身的参数名称 page 和 pageSize 没有做任何修改,只是将注解中的修改为 xx 和 yy 了,再次访问下面的URL:

1
http://localhost:8080/friends?xx=1&yy=2

一如既往的正常~ 所以,对于注解 @RequestParam 的参数,URL中对应的参数一定要和value对应。

@QueryParam

这个跟 @RequestParam 基本一致,都是以键值对的方式来或者参数。

只不过,该注解需要在 pom 文件中导入依赖,如下:

1
2
3
4
5
<dependency>
<groupId>javax.ws.rs</groupId>
<artifactId>jsr311-api</artifactId>
<version>1.1.1</version>
</dependency>

然后在工程类中需要导入包,如下:

1
import javax.ws.rs.QueryParam;

注解 @QueryParam 源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
@Target({ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface QueryParam {
/**
* Defines the name of the HTTP query parameter whose value will be used
* to initialize the value of the annotated method argument, class field or
* bean property. The name is specified in decoded form, any percent encoded
* literals within the value will not be decoded and will instead be
* treated as literal text. E.g. if the parameter name is "a b" then the
* value of the annotation is "a b", <i>not</i> "a+b" or "a%20b".
*/
String value();
}

可以看到 @QueryParam 只有 value 一个属性,value 对应的值要和函数参数保持一致,否则可能请求会得不到预期的响应结果,这个跟注解 @RequestParam 是不一样的。下面给个示例,如下:

1
2
3
4
@RequestMapping(value = "/friends", method = RequestMethod.GET)
public String getFrieds(@QueryParam(value = "page") String page, @QueryParam(value = "pageSize") String pageSize) {
return "Get friend list. " + "page: " + page + " - pageSize: " + pageSize;
}

在浏览器请求下面的地址即可。

1
http://localhost:8080/friends?page=1&pageSize=2

@PathVariable

注解 @PathVariable 与上面的刚说的注解 @RequestParam 是不同的,@PathVariable 需要从 URI 中获取参数,比如下面的例子,如下:

1
2
3
4
@RequestMapping(value = "/friends/{page}/{pagesize}", method = RequestMethod.GET)
public String getFrieds(@PathVariable(value = "page") String page, @PathVariable(value = "pagesize") String pageSize) {
return "Get friend list. " + "page: " + page + " - pagesize: " + pageSize;
}

在 @RequestMapping 中写了 {page}/{pagesize},二者带上了 {} 这样 @PathVariable 才能识别。

请求下面的地址,可以正常访问。

1
http://localhost:8080/friends/1/2

相当于 page=1,pagesize=2,所以注解 @PathVariable 是从URI 中匹配参数的,他不是以键值对方式获取对应的值。

@PathVariable 源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package org.springframework.web.bind.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;

@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface PathVariable {
@AliasFor("name")
String value() default "";

@AliasFor("value")
String name() default "";

boolean required() default true;
}

有三个属性 value、name 和 required,其中 value 也是 name 的别名。

@PathParam

注解 @PathParam 源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
package javax.websocket.server;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.PARAMETER})
public @interface PathParam {
String value();
}

可以看出,@PathVariable 和 @RequestParam 是 org.springframework.x 包下面的即属于spring框架,而 @PathParam 属于 javax.websocket.x 包下面的。

注解 @PathParam 只有一个属性 value,没有其他属性了。@PathParam 里面的value对应的值一定要和函数参数名称一致(包括大小写),示例:

1
2
3
4
@RequestMapping(value = "/friends", method = RequestMethod.GET)
public String getFrieds(@PathParam(value = "page") String page, @PathParam(value = "pageSize") String pageSize) {
return "Get friend list. " + "page: " + page + " - pageSize: " + pageSize;
}

在浏览器中请求下面的地址,请求和响应都是正常的。

1
http://localhost:8080/friends?page=1&pageSize=2

如果 @PathParam 中 value 对应的值和函数参数不一致,请求得不到预期的响应。现在修改 value 的 pageSize 为 pagesize,如下:

1
2
3
4
@RequestMapping(value = "/friends", method = RequestMethod.GET)
public String getFrieds(@PathParam(value = "page") String page, @PathParam(value = "pagesize") String pageSize) {
return "Get friend list. " + "page: " + page + " - pageSize: " + pageSize;
}

在浏览器中请求下面的地址:

1
http://localhost:8080/friends?page=1&pagesize=2

就会得到下面的结果,如下图所示:

但是你使用 http://localhost:8080/friends?page=1&pageSize=2 对于上面的代码请求和响应也是正常的,这就说明注解 @PathParam 中请求的 URL 参数是以函数参数为主的,他和注解 @QueryParam 、@RequestParam 都是以键值对方式获取对应的值。

所以,注解 @PathParam 中 value 对应的值和函数参数尽量保持一致。


学海无涯苦作舟~

微服务: 立志做个伟大的项目

发表于 2019-07-03 | 分类于 Server |

简介

在开篇 微服务: 步步为营 里面我们已经定下一个小目标(以终为始的策略),即使用 Spring Boot 写 API 提供给 iOS、Android 客户端或者网页端来调用。

接下来我们开始完成我们这个超级梦想吧!在启航之前,我们还是给这个 App 起个名字吧,该 App 就是给用户展示 Blog 的,所以暂且称之为 MSBlog 吧!


朋友做了一个关于 人工智能的教程,教程不仅是零基础,通俗易懂,而且非常风趣幽默,像看小说一样!点 这里 可以跳转到教程,对人工智能感兴趣的同学可以了解一下。

原型图

项目 MSBlog 的原型图是用 Balsamiq Mockups 3 画的,这个画图工具使用起来非常简单,画出来的效果也相当凑合!

1、登录、注册页面

登录和注册页面相对比较简单,第一个版本我们只支持用户名登录,后面再支持其他登录方式。

下图左边是登录页面,右边是注册页面,如图所示:

2、四个Tab页面

这四个主页面,分别是首页、文章分类、好友、个人页面,如下图所示:

上面的原型图只做了登录、注册和 Tab 主页面,从这几个页面已经可以看出这个 App 的构造了。

1、首页(Home):主要展示推荐的文章,以及广告展示页(Banner)。

2、分类(Category):文章分类,按照评论数、发布时间、热度对所有文章进行分类。

3、好友(Friends):App 中所有的好友,需要支持本地搜索。

4、个人(Profile):个人中心,基本是任何一个 App 的标配。

项目计划

从上面的原型图中,我们基本了解接下来要做什么了。

首先,把登录、注册页面搞定,接着再把四个 Tab 页面搞起来,对于 App 初学者来说,这个压力着实不小了,没关系慢慢来,一步一个脚印的走下去。

无论是哪个页面,都需要服务端提供相关的接口(API)和数据,简单描述一下吧,如下:

1、登录(Signin):需要服务端提供登录接口,即支持用户名和密码登录的API能力。

2、注册(Signup):需要服务端提供注册的接口,即支持用户名和密码注册的API能力。

3、首页(Home):需要提供查询推荐文章的API,后期需要考虑智能推荐。

4、分类(Category):需要提供查询所有文章分类的API。

5、好友(Friends):需要提供查询每个人所有好友的API。

6、个人(Profile):需要提供当前登录用户的所有信息,如用户名、头像、昵称等信息。

第一步,先把这登录注册做好,服务端把登录、注册的 API 准备好;

第二步,然后再做 Tab 页面,服务端把其他 API 准备好;

第三步,客户端和服务端联调数据,确保我们能正常使用客户端。

客户端先从 iOS 开始,Android 版本的 MSBlog 后续再补上,iOS 客户端的项目采用 MVC 的架构,开发语言采用 Swift(Swift版本为5.0),IDE 采用高大上的 Xcode,项目代码使用 Git 来管理,用 Github 作为代码仓库。

到这里,是不是觉得自己还有很多东西需要学习比如 Swift 编程语言。当然了,如果你会 Flutter 或者 Javascript 也可以写客户端代码。别担心,我从容的告诉你,编程语言这个东西没有你想象中的那么难,多动手写就可以了,遇到不会就去查手册。世上本没有路,走的多了就有了,对于编程也一样,写的多了就能融会贯通,举一反三。真正难的是你不动手总是在那里埋怨!

后期有可能会使用 Flutter 来写这个 App,待定。


祝好,加油!是时候动手写起来了!


改变,从你我相识开始~

微服务:本地热部署

发表于 2019-06-30 | 分类于 Server |

简介

在项目开发中,难免会经常修改代码来验证一些问题,如果每次修改都需要进行重新关闭、启动这样的操作会让人有点不爽,如果能实现代码的热部署就好了。

所谓热部署,就是在应用正在运行的时候升级软件,却不需要重新启动应用。通俗来讲,热部署就是在修改代码后,不需要重新启动服务器,就可以加载修改后的代码,看到修改后的效果。对于 IDEA 来说,热部署就是不需要反复的通过开始、停止来进行项目的启动,而是修改代码保存后自动加载修改后的代码。

Spring Boot 提供了热部署能力。可以使用 Spring Boot Loader 的方式,也可以使用 Spring Boot Devtools 的方式来进行,二者实现方式是不同的,大致总结如下:

Spring Boot Loader:真正的热部署。

Spring Boot Devtools:重新部署来实现热部署。

这两种热部署也是本地热部署,真正的远程服务器热部署方案另有方案,这个不是今天分享的点后续牵涉到部署方案再说!今天只分享如何使用 devtools 实现 Spring Boot 项目的热部署。


朋友做了一个关于 人工智能的教程,教程不仅是零基础,通俗易懂,而且非常风趣幽默,像看小说一样!点 这里 可以跳转到教程,对人工智能感兴趣的同学可以了解一下。

spring-boot-devtools

在 IDEA 中使用 devtools 需要修改 IDEA 的设置和工程的 pom 文件,具体操作步骤如下。

1、修改pom.xml文件,增加devtools

在 pom.xml 文件中的 dependency 下增加 devtools,如下:

1
2
3
4
5
6
7
<!--Devtools-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
<scope>true</scope>
</dependency>

在 build 标签下增加 configuration/fork 配置,如果没有该项配置,devtools 不会起作用,即应用不会重启。

1
2
3
4
5
6
7
8
9
10
11
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<fork>true</fork>
</configuration>
</plugin>
</plugins>
</build>

2、修改IDEA编译选项

在IDEA中打开 File/Settings,如下图所示:

1555860471904

3、修改IDEA中的Registry

在 IDEA 中 Help/Find Action...,打开下面的对话框,输入registry 进行搜索,如下图:

1555860471904

双击 Registry,出现下面弹框,如图操作,选中 Compiler autoMake allow when app running 即可。

1555860471904

也可以使用快捷键 ctrl + shift + alt + / 打开 Registry,如图:

1555860471904

4、重新启动项目

重新运行项目进行验证。

1
2
3
4
@RequestMapping(value = "/user")
public String user() {
return "Greate user!";
}

浏览器打开:http://localhost:8080/user 可以看到效果。

修改原来的 user 方法和增加一个 user2 方法,再来验证。

1
2
3
4
5
6
7
8
9
@RequestMapping(value = "/user")
public String user() {
return "Greate user, welcome!";
}

@RequestMapping(value = "/user2")
public String user2() {
return "Greate user!";
}

分别在浏览器打开:http://localhost:8080/user 和 http://localhost:8080/user2

1555860471904

1555860471904

现在不需要在 IDEA 中重新启动、关闭这种麻烦操作了。

如果你足够细心就会发现,使用 devtools 后,每次修改控制台自动的重新启动了 Spring Boot 项目,所以 devtools 是使用了重新部署的方式从而实现了热部署的功能。

注意几个问题

1、代码修改不会立即生效

随着工程越来越大,修改代码或者配置后需要稍等几秒钟(一般3s以内)就会自动重新启动工程,控制台就会看到日志的输出。

2、代码修改后控制台没有自动的重新启动工程

这个就需要按照这篇文章说的,重新检查一下如下设置:

  • pom 文件,检查一下是否把必要的配置被修改了。
  • IDE 的设置是否和本文一致。

改变,从你我相识开始~

认知

发表于 2019-06-23 | 分类于 随笔 |

任何时候都不要轻易的开口说:“这不可能”,就跟在任何情况下都不要轻易说放弃一样重要。

在很多场合下,你这么一说不仅暴露了你的学识,而且还会暴露你的心态。特别是程序员,在产品说要实现xxx功能或者UE说要实现yyy效果的时候,很容易用“这个无法实现”这样的语句来“抗拒”需求。

我记得有这样一件小事。

程序员A说:“这个框架已经限制了,无法实现这个功能,别白费力气了!”。

程序员B说:“我们可以再试试,或许还有其他办法呢?”。

A显得很不耐烦,说到:“你要是不相信我,你就自己搞吧,我不管了!”。

B没有和A继续争论而是默默地转去想办法解决问题。功夫不负有心人,下午B就找到了解决方案,这个方案不仅简单而且高效,只需要在框架的基础上简单封装一下就好。

这样的场景,我相信参加过工作的开发大大们经常遇到。之所以我们说出一些类似“不可能”的华语,要么是心态不对,要么是认知不够。很多人在工作当中面对复杂事情的时候会说“这不可能”或者“这绝不可能”的话,可能你都没有思考过就随口说出来了,一方面显得自己知识不足,另一方面会拒别人于千里之外。

刚入职场的程序员和工作已久且爱思考的程序员有着很不一样的认知。刚入职场的程序员或者工程师,面对产品经理提出的需求基本都是一一顺从,无论需求是否合理,他们都会老老实实的把需求转化为一行行的代码,稍微有点想法的估计因为“胆小”而不敢发声。随着职场的磨练,业务能力的增强他们开始有了自己的想法和思考,敢于产品经理“抗衡”了,对于不合理的需求会与产品大大们针锋相对,有时候你会发现产品需求会议俨然一场分崩离析的内战。项目做的多了,编程能力、业务能力和沟通能力又上了一层楼,优秀的程序员们不仅能挑出不合理的需求(伪需求)而且还能提出更合理的需求帮助产品更上一层楼,提升产品的逼格和实用性,不仅减少了开发成本而且还提升了团队作战的士气,更重要的是产品经理以后不敢小瞧你了,你自己也会获得无行的成就感。经历事情(如项目经历),思考和总结事情的发展、经过、结局,从而不断的提高自己的业务水平是提升自我认知的一个有效途径。

年轻时候的嬴政敢爱敢恨,个性很强,下面是吕不韦和嬴政的一段对话。

吕不韦:我为了王上的千秋大业,有些事情,王上一时不可理解。
嬴政:不可理解?有些事情,还要使劲才能理解呀?
吕不韦:我只能告诉你,当一个人还不够成熟的时候,他所看到的事情,未必是真实的。
嬴政:未必真实?那我看到的是什么?
吕不韦:你看到的,只是真实的影子。
嬴政:那就请仲父告诉我,如何才能理解你的真实?
吕不韦:王上现在还没这个能力!

统一六国之后,嬴政面对李斯感慨:“如今天下已经一统,那么大秦现如今的仲父又是谁呢?”可见吕不韦在嬴政心中的地位举足轻重,非同一般。

我从 “深入理解Linux内核” 这本书中,看到了这样一段话:

Linux 是非抢占式(nonpreemptive)内核。

自从接触Linux以来,在个人的认知中Linux都是抢占式的。那么为什么这里却说它是非抢占式的呢?

这本书又很权威,难道是自己错了?我就硬着头皮又去找相关的资料,结果算是搞清楚了来龙去脉。

Linux分为内核抢占和用户抢占,Linux内核版本2.4支持内核抢占但不支持用户抢占,一直到Linux内核版本2.6才都支持内核抢占和用户抢占,”深入理解Linux内核” 这本书是基于Linux内核版本2.4之前讲解的,所以说法并没有错误。

活到老学到老,讲的是人无论在那个阶段都不应该放弃学习,因为只有不断的学习才能提高自己的认知。对于新鲜事物,我们一定要保持敬畏之心,因为你没有这方面的经验,虚心请教和借鉴才能避免一定的损失。我记得有一次参加一个技术培训,老师提问:“谁能简单解释一下什么是 墨菲定律 ?”,我看了一下全场,半分钟没有人回答,于是自己就斗胆站起来说了:“你总是担心的事情,它总会发生的。”,老师满意的点了点头,我相信当时会场中有能比我解释更好的人,可能是他不敢也有可能是他不屑于回答。说这个事情并不是要彰显我自己有多么厉害,而是要告诉大家多读书是提高自我认知能力的一种方式。

墨菲定律主要内容有四个方面:

一、任何事都没有表面看起来那么简单;

二、所有的事都会比你预计的时间长;

三、会出错的事总会出错;

四、如果你担心某种情况发生,那么它就更有可能发生。

“墨菲定律”的根本内容是“凡是可能出错的事有很大几率会出错”,指的是任何一个事件,只要具有大于零的机率,就不能够假设它不会发生。

对于人的认知大概可以分为四个阶段,如下图所示:

1559469918269

以后工作中或者生活中遇到一些自己不知道的事情,首先要摆正心态,然后去找比你更强的人来请教。这样做的话,你即结交了朋友又学到了知识,两全其美,何乐而不为呢?


知之为知之,不知为不知~

微服务: Git入门

发表于 2019-06-09 | 分类于 Server |

代码管理

如果你还没有Github账号,赶紧点击 这里 去创建一个,很简单,然后下载 Git GUI 安装即可,如果你还不会这些操作,请行动起来去搜索对应的方法,这里不再赘述。

在项目开发过程中,除了写代码之外,还需要对代码进行管理。在说如何管理代码之前,先跟大家分享几个实际开发中关于代码管理的场景。

案例1:入门的小旺

小旺是名入门不久开发之路的编程狂魔,对代码有颗执着的心。平时编写的代码全部放在自己的那台Macbook Pro上面,有一天小旺不小心把自己编写的重要代码给删除了,万恶的 rm -fr code 呀!心中一万头草泥马奔驰而过。这让小旺伤透了脑筋,只能熬夜硬着头皮再把它给码起来,自己犯的错只能默默地承受着。

案例2:新手小李和小刚

小李和小刚是一对编程搭档(公司只有他们两个Android开发,相依为命吧),两位都是开发的新手。每次小李和小刚分别提交代码到仓库,都要经历一场惨目忍睹的博弈。所谓的博弈指的是两位要花很长时间来解决代码冲突的问题,哎,除了写代码还要解决这些无聊的问题,烦!

如果小旺知道使用Git管理代码,也可以避免误删的风险。

小李和小刚要比小旺机智,至少知道代码要提交到仓库,当然小李和小刚工作经验丰富,公司也要求代码必须提交。但是二位却没有用好代码管理工具,从而带来了一些烦恼。

是的,管理代码很重要,无论使用SVN还是Git,至少你要熟练掌握其中一个,我建议还是直接学习Git吧,毕竟现在大多数公司都在用它。

在 Spring Boot 系列的文章中,使用 Github 作为我们的代码管理仓库,使用Git工具进行代码的提交、修改、合并和删除以及对分支的管理和使用。

Git 提交

把在 微服务: 想办法让项目运行起来 写好的代码提交到代码仓库中,这样即使本地代码被误删也可以从远程找回来,安全。假如你更换一台电脑进行编程也可以从远程仓库拉下代码进行编程,随时随地撸代码。

提交代码,很简单,windows上面安装git工具即可。我使用的是 Git GUI,它自带 Git Bash,有种Unix终端的风范,贼亲切!

我比较喜欢使用命令行来进行操作,打开工程目录,如下:

1555860042560

然后在空白地方右键,会显示 Git Bash,选他就会打开命令终端工具,如下图所示:

1555860285254

在终端操作命令如下:

1
2
3
4
5
6
# 添加所有文件
git add -A *
# 提交到本地仓库并加上注释
git commit -m "Create a greate project"
# 推送到远程master分支
git push origin master

如果你之前没有进行任何git提交的话,首次提交会提示你输入用户名和密码,此时你输入自己的github账号和密码即可。

三步操作完成后,工程也就提交成功了。为了验证提交是否成功,你也可以到远程仓库(使用浏览器打开你的仓库地址)刷新看看有没有刚才提交的内容,如果网络不好就等会在瞅瞅。

创建分支

为了不影响主干(master)上面的代码,我们可以创建一个个人分支来进行开发,例如我的个人分支 veryitman-feature 分支。

1
git chekcout -b veryitman-feature

上面的git命令意思是在本地创建一个名为 veryitman-feature 的分支。

注意:git chekcout -b xxx 相当于进行下面两步操作

1
2
3
git branch xxx

git checkout xxx

主干 master 本身也是一个分支,只是开发者为了协助和代码管理方便创建了各自的分支,等代码测试或者验收通过再从自己的分支合并代码到主干 master 上面,所以一般 master 分支上面的代码都会认为是相对理性、安全的代码。

分支的存在是为了方便代码的管理,类似于SVN的trunk、tag、branch文件夹管理代码一样。

创建完成后,提交分支到远程仓库即可,终端操作命令如下:

1
git push origin veryitman-feature

去远程代码仓库中,刷新页面后,在Branch可以看到刚才创建的 veryitman-feature 分支,如下图所示:

1555860471904

也可以使用下面的方式进行创建和推送分支代码,操作步骤如下:

1
2
3
4
5
6
# 在本地创建新的veryitman-feature分支 
git branch veryitman-feature
# 切换到新的veryitman-feature分支
git checkout veryitman-feature
# 将新的分支推送到仓库
git push origin veryitman-feature

切换分支

在上面的操作中,大家已经看到我们创建了 veryitman-feature 分支,加上原来默认的 master 分支,一共是两个分支了。

一般来说,master 分支上面的代码应该是经过测试并且稳定的版本,保证其稳定不会出错。而其他分支如上面的 veryitman-feature 是我们需要进行开发的个人分支,不保证其稳定性(因为在开发嘛)。等 veryitman-feature 开发完成再合并到 master 分支上面,关于分支的合并操作,后续再说吧!

Git 支持在分支之间自由切换,比如你在 veryitman-feature 分支,提交完成代码后,可以切换到 master 分支。切换分支的命令如下:

1
git checkout master

如果再想从 master 分支切换到 veryitman-feature 也可以,如下:

1
git chekcout veryitman-feature

切换分支一般用在下面几个场景中:

  • 合并代码
  • 多分支开发
  • 修复问题

删除分支

Git 提供了可以删除分支的方法和命令。

删除分支分为两类,一个是仅仅删除本地分支,另一个是删除远程分支。

删除本地分支意思是删除已经存储在你本地计算机上面的文件,如下:

1
2
# 在本地删除veryitman-feature分支
git branch -d veryitman-feature

删除远程分支意思是删除存在 GitHub 上面的代码文件,如下:

1
2
# 在github远程端删除veryitman-feature分支
git push origin --delete veryitman-feature

关于Git的使用还有很多,今天先说这么多,用到的时候再补充。现在最重要的是需要你去注册一个 Github 账号折腾起来,熟能生巧,动手实践才是王道!


改变,从你我相识开始~

调侃C中的define

发表于 2019-06-08 | 分类于 C/C++ |

二狗子

二狗子这个名字,在大街小巷,在电视剧中几乎都能听到。我也不知道老一辈的父母为什么这么喜欢给自己的孩子取这样的名字,唯一能让我信服的理由是:顺口!大叔大伯们之所以叫这个名字还有一个理由,之前孩子多,希望孩子像小狗儿一样好喂养。

正好我们村有个孩子也叫二狗子,大名叫张力万,无论是叫二狗子还是叫张力万,都指的是同一个人。我们大多数情况下还是叫他二狗子,他也习惯了倒也觉得亲切,叫张力万一般都是在正式场合。

有些企业文化中规定:“不允许在公司直接喊同事的名字,每个人必须有个英文名”。比如二狗子入职到这样的企业,大家不允许喊他二狗子或者张力万,于是二狗子又有了一个英文名:“Jack”,你看 “Jack” 这个名字既符合企业文化又听起来高大尚。

无论是 “二狗子“ 还是 “Jack“ 都是张力万的别名。

在 C 语言中,关键字 define 和 typedef 就可以用来取别名,但是二者又有不同点,今天主要分享一下 define 的用法。

下面是使用 define 来模拟别名,示例如下:

1
2
3
4
5
6
7
8
9
10
11
#define Zhangliwan1 "二狗子"
#define Zhangliwan2 "Jack"

#define Zhangliwan "张力万"

int main(int argc, const char *argv[])
{
printf("我们村的%s可以叫他%s也可以叫他%s\n", Zhangliwan, Zhangliwan1, Zhangliwan2);

return 0;
}

输出结果:

1
我们村的张力万可以叫他二狗子也可以叫他Jack

虚构一下

丹尼斯·里奇 一个伟大而低调的牛人,是Unix之父、C语言之父。

丹尼斯·里奇还将Unix的设计原则定为 KISS 原则 即 Keep it simple stupid,保持简单和直接,所以Unix一直都是经典中的经典。这也说明丹尼斯·里奇不仅是一个优秀的工程师,还是一个优秀的产品经理。

我在想,当初丹尼斯·里奇和肯·汤普逊在实验室里没事也会讨论C语言的事情。

丹尼斯·里奇:“老兄,你看我们现在的语言是不是过于复杂了?”

肯·汤普逊:“的确有点复杂,我有个大胆的想法,不知当讲不当讲?”

丹尼斯·里奇:“你还跟我墨迹啥,有话直接说呗,呵呵!”

肯·汤普逊:“嗯,我们可以开发一门新的语言,他要足够的简单、高效。”

丹尼斯·里奇:“想法是挺好的,那就开干吧!”

于是C语言诞生了。

肯·汤普逊:“你有没有觉得我们在定义常量的时候不太方便?”

丹尼斯·里奇:“是呀,这样你看行不行,弄个预处理器可以让我们任意定义常量,暂时称他为 ‘宏’ 吧!”

肯·汤普逊:“我觉得完全没有问题,来,徒手写一个。”

丹尼斯·里奇:“哈哈,给力!”

于是 define 就有了。

以上纯属个人猜想,并不是冒犯两位大师,本故事纯属虚构,如有雷同,纯属巧合。Unix和C语言的大道至简,对后代科学的发展奠定了不可磨灭的贡献和影响。

基本用法

关键字 define 是 C 语言中的预处理命令,它用于宏定义,在大多数定义下可以提高代码的可读性,为编程提供方便。

在 C 语言中预处理命令以 “#” 号开头,如 #include、#ifdef、#endif 和宏定义命令 #define 等。

关键字 define 的用法如下:

1
#define 新类型名 原类型名
1
2
3
4
5
6
7
8
#define INTEGER int
INTEGER a = 100;

#define PI 3.1415927

#define UserName "user_name"

#define MAX(x, y) (x)>(y)?(x):(y);

在 C 语言中,关键字 define 的定义的常量都会在预处理阶段将用到的别名直接被原样替换掉。例如在编写源程序时,所有用到 3.1415927 的地方都可用 PI 代替,而编译时,将先由预处理程序进行宏代换即用 3.1415927 去置换所有的宏名 PI,然后再进行编译。

关键字 define 还可以结合 “#”、“##”、“#@” 使用。

  • 符号 “#”,表示将其字符串化。

  • 符号 “##”,表示连接变量。

  • 符号 “#@”,表示将其字符化。

1
2
3
4
5
6
7
8
#define M(x) x##x
#define L(x) #x

int main(int argc, const char *argv[])
{
// M(1): 11, L(1): "1"
printf("M(1): %i, L(1): %s\n", M(1), L(1));
}

我使用 “#@” 定义,无论是GCC编译器还是Clang编译器都无法通过编译,错误信息:“ ‘#’ is not followed by a macro parameter ”,但是在 Visual Studio 中编译就没有问题。示例如下:

1
#define K(x) #@x

关键字 define 给我们写代码带来了一定的便利,但是如果过多的乱用它也会代码不小的麻烦,比如下面的例子:

1
2
3
4
5
6
7
8
9
10
#define square(x) x*x

int main()
{
int i;
i = 64/square(4);
printf("i = %d\n", i);

return 0;
}

定义宏 square(x) 本来是求某个数的平方,按理说 64/16 结果应该是 4,但是运行程序你会发现结果是 64.

我们把上面的例子展开,因为 define 是直接原样替换,如下:

1
2
3
i = 64/4*4;
i = 16*4;
i = 64;

修改一下程序中 define 的定义,结果就对了。

1
2
3
4
5
6
7
8
9
10
11
#define square(x) (x*x)

int main()
{
int i;
i = 64/square(4);
// 4
printf("i = %d\n", i);

return 0;
}

其实最安全的做法是这样定义,如下:

1
2
3
4
#define square(x) ({    \
typeof(x) y = (x); \
y*y; \
})

作用域

可以在C文件的开头,也可以在方法体里面,还可以在方法的声明前都可以使用 define 关键字。

定义在文件开头:

1
2
3
4
5
6
7
#define NAME "name"

int main()
{
printf("Define NAME: %s\n", NAME);
return 0;
}

定义在方法中:

1
2
3
4
5
void play()
{
#define NAME "name"
printf("Define NAME: %s\n", NAME);
}

这里要注意,定义在方法中,并不是指该宏定义 NAME 只能用在该方法里面,其他地方照样可以使用。

1
2
3
4
5
6
7
8
9
10
11
void play()
{
#define NAME "name"
printf("Define NAME: %s\n", NAME);
}

/* 注意:该方法一定是在define定义之后才能使用NAME */
void eat()
{
printf("Define NAME: %s\n", NAME);
}

条件编译

在C语言中或者在类C语言中如Objective-C和C++中,我们会经常用到条件编译语句,如下:

1
2
3
4
5
#ifdef NAME

#else

#endif

大家在做一些跨平台开发工作的时候,也会用到条件编译语句。

1
2
3
4
5
#ifdef ANDROID
#define PLAYFORM 1
#else
#define PLAYFORM 2
#endif

还有就是类似防止重复包含(重复定义)头文件,也会用到条件编译,如下:

1
2
3
#ifndef __Header_Person_H__
#define __Header_Person_H__
#endif

下面是来自Linux Kernel里面的代码片段:

1
2
3
4
5
6
7
#if defined(CONFIG_ALPHA_GENERIC)
#define GAMMA_BIAS alpha_mv.sys.t2.gamma_bias
#elif defined(CONFIG_ALPHA_GAMMA)
#define GAMMA_BIAS _GAMMA_BIAS
#else
#define GAMMA_BIAS 0
#endif

既然我们可以定义宏,那么是否可以取消宏定义呢?答案是当然可以。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void play()
{
#define NAME "name"
printf("Define NAME: %s\n", NAME);
}

#ifdef NAME
// 取消宏定义
#undef NAME
#endif

void eat()
{
// Compile errror: Use of undeclared identifier 'NAME'
printf("Define NAME: %s\n", NAME);
}

这份 GNU Macors 在线文档介绍了很多关于宏定义的知识,可以点击前往学习。


时间可以改变一切,但你得做点什么!

回忆高考

发表于 2019-06-07 | 分类于 随笔 |

1

楔子

转眼间,我人生中的高考距今已十几年有余了,那时候意气风发,踌躇满志,随时迎接高考的灵魂拷问。幸运的是自己跌跌撞撞的考上了大学,阴差阳错的变成了一名程序猿。

今天是2019年高考的第一天,作为一名 “资深” 的程序猿我还是想用编程的方式来记录这一天,算是对学子们的祝福和对自己的高中生活的一点回忆吧。

祝所有高考中的学者们都能如愿以偿的考上自己心中的那所大学,你们的人生即将辉煌,加油!

不期而遇

在我们那个年代特别是在我们老家人多且穷,谁能上大学简直就是 “光宗耀祖” 的事情,然而很多人对学习的欲望并不强,他们大部分都向往着外面的世界。其实,我也很渴望外面的世界,有过逃离那种压抑的高中生活的想法。但是回过头仔细想想出去后环境难道就没有障碍吗?生活就能如日中天吗?不可能的,尤其是对农村出来的孩子来说没有学历想在大城市中好好的活下去犹如天方夜谭。出来工作这么多年,生活的艰辛印证了我当初的想法。

凡事不忘初心,每当你想放弃的时候就好好想想当初自己为什么这么做。

我记得高考结束的那天下午,父母一起来学校准备接我回家,这是上高中三年以来第一次父母一起来学校。我知道父母是担心我,他们没有问我考的怎么样,让我把被子收拾一下跟他们回家。我怕他们担心就告诉他们:“别担心,我感觉自己考的还可以”,我永远忘不了父母那一刻展露出的欣慰的笑容。

高考结束的那一夜我没有和父母一起回家,而是跟同学们在网吧守了一夜等着高考试题的答案出来,想给自己和父母一个交代,那个晚上陪受煎熬。

这是我人生中第一次真正的接触电脑,也是第一次感觉到要上大学的喜悦。

基础很重要

大家应该听说过这样的话题:“兄弟,把数学学那么好干什么,你上街买菜会用对数(log)跟大妈算账吗,买鸡蛋需要开根号吗?”。

听上去好像很有道理的样子,但是这里不完全合理。

首先,说话者是抱着一种嫉妒的心态而不是学习的心态,很消极的一种说法。

其次,说话者把数学知识的使用的范畴建立在买菜买鸡蛋的基础上了,假如你要搞算法扎实的数学基础和思维必须要具备,否则寸步难行,随着机器学习、AI 和大数据分析的发展,数学知识成为了一个必备学科。

再者,随着时代和科技的发展,说不定卖菜的大爷大妈们用的数学知识让你应接不暇。

所以,好好学习并没有错。基本学科能让你扎实自己的基础,拓展自己的思维。

学点编程

几年前国外、国内的很多人都在说:“以后编程是人人都应该具备的基本技能,就像会骑车子一样那么简单!”。

对于这个 “以后” 谁也没有给出具体期限,就好比我小时候羡慕别人家有彩色电视一样,没想到过了几年这个东西居然也出现在了自己的家中,如获至宝,欣喜若狂。

现在很多教育机构已经提供了针对儿童编程的课程,大多是可视化编程。有孩子的家长们也许都发现了现在的孩子接受面和接受能力都远超我们那代人,这跟社会环境、家庭环境和营养饮食都有一定的关系,再加上兴趣班的催化让现在大多数孩子变得有点 “鬼机灵”。比如你的电脑莫名其妙的被打开并且还能自动被播放动画片,还有你明明设置的 iPad 密码很复杂竟然也能让他们给 “破解”,最气愤的是他居然可以趁你不注意用你的零钱给对方发了红包~

如果有条件的话,家长们可以孩子接触编程知识,培养他们思考问题的习惯。培养学习编程知识,不一定非要买个高端的 PC 或者昂贵的 iPad,可以从搭积木和一些简单的数学知识开始,也可以让他参加一些例如机器人课的兴趣班,参观参观科技博物馆之类的。

高考的学子们,无论高考结果如何,不要刻意的去强迫自己,因为人生的路还有很长,及时的调整心态后面比高考更难的 ”考试试题“ 还等着你们呢!

高考结束了,如果你是理科的学生建议大家可以去看看编程方面的知识,懂点编程的知识对你以后的工作还是很有好处的。把编程当做一种乐趣而不是工作,这样才有可能创造无限的可能。

祝大家金榜题名,端午安康!


时间可以改变一切,但你得做点什么!

微服务: 想办法让项目运行起来

发表于 2019-06-02 | 分类于 Server |

必备工具

最新的 Spring Boot 发布版本是 2.1.4,需要JDK版本最小为8即 JDK8,构建工程的工具 Maven 最小版本为3.3,Gradle 最小版本为4.4,当然了 Maven 和 Gradle 任选一个即可,接下来分享的文章中基本都使用 Maven 来构建工程。

1、Spring Boot 2.1.4

2、JDK8

3、IntelliJ IDEA2019 收费版

有稳定可用的网络,保证工程构建和一些依赖的下载。

具体 Spring Boot 对其他工具的版本要求,可以参考这里的 getting-started-system-requirements 文章内容。

建议大家使用Chrome作为默认浏览器,测试、调试API数据和安装对应的插件很方便。

认识 Github

如果你还没有 Github 账号,就去注册一个,打开 这个地址 即可。

注册完成后,可以新建一个 Repo(可以存放代码的仓库),本文涉及的所有代码都放在 Github 上面。

Spring Boot 系列文章也会跟大家一起学习 Git 的使用。

创建工程

新建工程,打开IDEA,File/New/Project,下一步即可

填写工程信息,然后下一步

这里注意:Artifact 要求是全是小写字母,否则无法创建工程。

选择依赖,Core 中选择 Lombok,这个使用起来比较方便,建议大家选择,不选也可以的。

紧接着在 Web 中选择 Web 即可,下一步

保存工程,启动后在右小角提示下面内容,直接 Enable Auto-Import 和 Enable plgins 即可,如果你之前安装过 Lombok 插件,这里也不会提示 Enable plgins 了。

然后安装 Lombok 插件,点击 ok 即可。

等待IDEA构建完成,工程显示如下:

如果你的工程加载其他库加载失败,可以重新导入,操作如下:

Run起来

直接运行已经构建成功的工程。

打开浏览器,输入 http://localhost:8080/ 能看到下面的内容:

说明工程已经成功运行起来了,你距离伟大的目标越来越近了。

为了让浏览器显示一个正常的语句,我们需要增加点东西。

新建一个 MSUserController,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.veryitman.springboot.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class MSUserController {

@RequestMapping(value = "/user")
public String user() {
return "Greate user";
}
}

打开浏览器,输入 http://localhost:8080/user 能看到下面的内容:

说点神奇的

1、创建工程还有其他方式吗?

除了使用IDEA提供的方法来创建 Spring Boot 工程,也可以使用官方提供的 Spring Initializr 来创建工程。

本质上,IDEA是集成了 Spring Initializr 这个工具,所以二者同宗同源。

2、为什么点个按钮就Run起来了?

Spring Boot 已经内置了 Tomcat 服务器作为 Web Server,所以可以直接跑起来。

Spring Boot 就是这么简单,约定大于配置的思想贯彻其始终。


改变,从你我相识开始~

<1…678…20>

193 日志
16 分类
163 标签
© 2024 veryitman