0%

我们在生活中会遇到这样一些例子,比如你订阅了某人的博客,那么这个人发布博客的时候会将消息推送给你,而且不是只推送你自己一人,只要订阅了该人的博客,那么订阅者都会收到通知,像这样的例子生活中实在是太多了。其实这种操作可以抽象一下,A对象(观察者)对B对象(被观察者)的某种变化高度敏感,需要在B变化的一瞬间做出反应,同时在B对象中维护着所有A的集合。我们在实际编程中称这种模式为观察者模式,有时也称为发布/订阅(Publish/Subscribe)模型。

在JDK的util包中已经帮我们实现了观察者模式,不过我们还是先通过自己写代码来看看观察者到底是怎么回事,自己该如何简单的实现,相信通过自己的简单实现,来理解JDK的观察者模式的实现是十分容易的。

在观察者模式中,首先要有两个角色,观察者与被观察者,这两者拥有的功能是不同的。对于观察者,需要有一个方法来接收被观察者发出的信息(update),而对于被观察者而言,需要在其内部维护一个观察者的列表,用来记录需要通知的观察者(list),所以需要一个添加观察者的方法(addWatcher),同时还要有一个方法可以用来移除观察者(removeWatcher), 最后我们需要一个用来通知所有观察者的方法(notifyWatchers), 一切准备就绪,那么我们来看代码吧。

先定义两个接口,观察者(Watcher)和被观察者(Watched),代码如下:

首先是观察者接口,定义了update方法用来接收通知

1
2
3
4
5
6
7
8
9
10
11
12
/**
* 观察者
* @author mingshan
*
*/
public interface Watcher {

/**
* 用来接收通知
*/
void update();
}
Read more »

我们在项目中会遇到这样一些情况,比如一个类,我们想让这个类在系统中有且仅有一个对象,不能够重复创建类的实例,因为这种类是无状态的,我们只需要有过一个类的实例就行了。这时我们需要用到设计模式的单例模式。

单例模式分为饿汉式和懒汉式,下面对这两种模式简单介绍:

  1. 饿汉式是指当系统启动或者类被加载时就已经创建了类的实例
  2. 懒汉式是指当该类第一次被调用的时候才会去创建该类的实例

单例模式有很多实现,不同实现有优有劣,下面谈谈单例的具体的一些实现

单例模式 - 饿汉式

如果不实现懒加载的话,那么就用饿汉式实现单例就比较简单,首先让无参构造函数私有化,我们可以直接对该类进行实例化,然后将其赋值给类的成员变量instance,然后提供一个外部可以访问的方法来获取类的实例。下面是代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 单例模式 - 饿汉式
* 线程安全,但未实现懒加载。
* @author mingshan
*
*/
public class SingletonDemo1 {
private SingletonDemo1() {}
private static final SingletonDemo1 instance = new SingletonDemo1();

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

单例模式 - 懒汉式

如果想实现懒加载,那么就要用到懒汉式了。懒汉式是当类第一次被调用的时候才被实例化,但这个时候就会出现线程安全问题,所以我们需要对进行类实例化的部分进行加锁,来保证类的实例只有一个,由于加锁的问题,性能就会降低。虽然做到了线程安全,并且解决了多实例的问题,但是它并不高效。因为在任何时候只能有一个线程调用 getInstance()方法。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* 单例模式 - 懒汉模式
* 实现延迟加载 ,所以 getInstance() 方法必须同步
*
* 此方法实现单例模式 性能比饿汉式低
* @author mingshan
*
*/
public class SingletonDemo2 {
private static SingletonDemo2 instance = null;

private SingletonDemo2() {}

public static synchronized SingletonDemo2 getInstance() {
if (instance == null) {
instance = new SingletonDemo2();
}

return instance;
}
}
Read more »

分布式锁可以基于以下几种方式实现:

  • 基于数据库的乐观锁,用于分布式锁
  • 基于缓存(Redis, memcached)实现分布式锁
  • 基于ZooKeeper实现分布式锁

在这篇文章中,主要讲讲ZooKeeper以及分布式锁的实现,通过了解基于ZooKeeper分布式锁实现的原理,我们会对ZooKeeper有一个基本的了解。

ZooKeeper介绍

首先谈谈ZooKeeper,ZooKeeper是一种为分布式应用所设计的高可用、高性能且一致的开源协调服务,它提供了一项基本服务:分布式锁服务。由于ZooKeeper的开源特性,后来我们的开发者在分布式锁的基础上,摸索了出了其他的使用方法:配置维护、组服务、分布式消息队列、分布式通知/协调等。

在ZooKeeper中,有一个被称为ZNode的节点,在该节点可以存储同步相关的数据,并且多个ZNode节点可以形成类似下图的结构。

image

基本命令:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
1. 查看节点
ls /
2. 创建节点
create /zk myData
3. 查看节点
get /zk
4. 设置节点
set /zk myData2
5. 删除节点
delete /zk
6. 创建临时节点
create -e /han data
7. 创建顺序节点
create -s /han/ data
8. 创建顺序临时节点
create -s -e /han/ data

ZNode

客户端可以在一个ZNode上设置一个监视器(Watch),如果该ZNode数据发生变更,ZooKeeper会通知客户端,从而触发监视器中实现的逻辑的执行。其中ZNode有以下几种类型:

  • PERSISTENT
  • PERSISTENT_SEQUENTIAL
  • EPHEMERAL
  • EPHEMERAL_SEQUENTIAL

下面分别解释一下:

  1. PERSISTENT为持久节点,持久节点是指在节点创建后,就一直存在,直到有删除操作来主动清除这个节点——不会因为创建该节点的客户端会话失效而消失。
    ZooKeeper命令:
1
create /zk myData
  1. PERSISTENT_SEQUENTIAL为持久顺序节点,基本特性与持久节点一致,但每个父节点会为他的第一级子节点维护一份时序,会记录每个子节点创建的先后顺序。命令:
1
create -s /han/ data

用这条命令的话,需要先创建/han节点,节点类型为PERSISTENT。

  1. EPHEMERAL为临时节点,客户端会话失效或连接关闭后,该节点会被自动删除,且不能在临时节点下面创建子节点,命令:
1
create -e /han

如果在临时节点下面还要创建子节点,那么zk就会提示:Ephemerals cannot have children

  1. EPHEMERAL_SEQUENTIAL为临时顺序节点,该节点的除了不是持久性节点,其他特性与持久顺序节点一致。命令:
1
create -s -e /han/ data
Read more »

认识RESTful API

RESTful API是目前比较成熟的API设计理论,它通过统一的API接口来对外提供服务,这样对其他调用者来说比较友好,更加容易实现前后端分离。那么如果要使用RESTful API来写我们的代码,那么就需要先知道RESTful API规范。

参考RESTful API规范

下面是两篇文章讲解RESTful API的,推荐:

  1. RESTful API 设计指南
  2. RESTful API 设计最佳实践

SpringMVC实现RESTful API

SpringMVC提供了一些注解来实现RESTful API, 例如@RestController,同时我们用Swagger来生成API文档,这样更加利于测试API。

常见swagger注解一览与使用

最常用的5个注解

@Api:修饰整个类,描述Controller的作用
@ApiOperation:描述一个类的一个方法,或者说一个接口
@ApiParam:单个参数描述
@ApiModel:用对象来接收参数
@ApiProperty:用对象接收参数时,描述对象的一个字段

其它若干

@ApiResponse:HTTP响应其中1个描述
@ApiResponses:HTTP响应整体描述
@ApiClass
@ApiError
@ApiErrors
@ApiParamImplicit
@ApiParamsImplicit

其中@ApiOperation和@ApiParam参数说明

@ApiOperation和@ApiParam为添加的API相关注解,参数说明如下:
@ApiOperation(value = “接口说明”, httpMethod = “接口请求方式”, response = “接口返回参数类型”, notes = “接口发布说明”;其他参数可参考源码;
@ApiParam(required = “是否必须参数”, name = “参数名称”, value = “参数具体描述”

添加依赖

首先在pom.xml文件中添加swagger依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<!-- swagger -->
<dependency>
<groupId>com.mangofactory</groupId>
<artifactId>swagger-springmvc</artifactId>
<version>1.0.2</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.5.1</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.5.1</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.5.1</version>
</dependency>

Swagger-UI配置

首先从Swagger-UI下载地址下载Swagger-UI文件,然后将其拷贝到webapp目录下,我这里新建了一个swagger文件夹,然后解压后的文件拷贝到这个文件夹里面了。

修改swagger/index.html文件,默认是从连接http://petstore.swagger.io/v2/swagger.json获取 API 的JSON,这里需要将url值修改为http://{ip}:{port}/{projectName}/api-docs的形式,{}中的值根据自身情况填写。比如我的url值为:http://localhost:8080/lightblog/api-docs

编写swagger配置文件

配置完Swagger-UI后,我们需要配置Swagger,并将其交给Spring进行管理。

Read more »

利用接口回调实现JDBCTemplate

  1. 设计一个回调接口JDBCCallback, 用来设置参数和获取结果集, 代码如下:

    1
    2
    3
    4
    5
    6
    public interface JDBCCallback<T> {

    T rsToObject(ResultSet rs) throws SQLException;

    void setParams(PreparedStatement pstmt) throws SQLException;
    }
  2. 设计一个抽象类JDBCAbstractCallBack,该类实现JDBCCallback接口,重写接口中的两个方法,
    不需要具体实现,只需要重写一下就可以了,这样在DAO层用的时候不用这两个方法全部都要实现,代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    /**
    * JDBC abstract callback class, implements {@link JDBCCallback} interface.
    * @author Mingshan
    *
    * @param <T>
    */
    public abstract class JDBCAbstractCallBack<T> implements JDBCCallback<T> {

    @Override
    public T rsToObject(ResultSet rs) throws SQLException {
    return null;
    }

    @Override
    public void setParams(PreparedStatement pstmt) throws SQLException {
    // NOOP
    }
    }
  3. 设计一个JDBCTemplate类,该类实现增删改查的基本方法,把公共的代码抽取出来,以便DAO层去调用JDBCTemplate来实现具体的业务,部分代码如下:

    Read more »

在Hibernate框架中,一个实体类映射为一个数据库表,在进行多表查询时,如何将不同表中的数据整合起来,并且映射为一个实体类是利用Hibernate进行多表查询的关键,根据我的理解,先将代码整理一下:

实体类

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
52
53
@Entity
@Table(name = "ps_trends")
public class Trends implements Serializable {
private static final long serialVersionUID = -2228382525594394975L;

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;

@Column(name = "item_title")
private String itemTitle;

@Column(name = "item_content")
private String itemContent;

@Column(name = "type_id")
private int typeId;

@Column(name = "add_time")
private String addTime;

@Column(name = "view_count")
private int viewCount;

@Column(name = "is_image")
private int isImage;

@Column(name = "is_publish")
private int isPublish;

//临时属性
@Transient
private String itemTypeFlag;

@Transient
private String itemTypeName;

public Trends() {}

public Trends(int id, String itemTitle, String itemContent, String addTime, int viewCount,
String itemTypeName,String itemTypeFlag) {
super();
this.id = id;
this.itemTitle = itemTitle;
this.itemContent = itemContent;
this.addTime = addTime;
this.viewCount = viewCount;
this.itemTypeName = itemTypeName;
this.itemTypeFlag = itemTypeFlag;

}
setter ,getter方法
}

Read more »

Linux目录结构:

image

/: 根目录,一般根目录下只存放目录,不要存放文件,/etc、/bin、/dev、/lib、/sbin应该和根目录放置在一个分区中

/bin:/usr/bin: 可执行二进制文件的目录,如常用的命令ls、tar、mv、cat等。

/boot: 放置linux系统启动时用到的一些文件。/boot/vmlinuz为linux的内核文件,以及/boot/gurb。建议单独分区,分区大小100M即可

/dev:存放linux系统下的设备文件,访问该目录下某个文件,相当于访问某个设备,常用的是挂载光驱mount /dev/cdrom /mnt。

/etc:系统配置文件存放的目录,不建议在此目录下存放可执行文件,重要的配置文件有/etc/inittab、/etc/fstab、/etc/init.d、/etc/X11、/etc/sysconfig、/etc/xinetd.d修改配置文件之前记得备份。
注:/etc/X11存放与x windows有关的设置。

/home:系统默认的用户家目录,新增用户账号时,用户的家目录都存放在此目录下,~表示当前用户的家目录,~test表示用户test的家目录。建议单独分区,并设置较大的磁盘空间,方便用户存放数据

/lib:/usr/lib:/usr/local/lib:系统使用的函数库的目录,程序在执行过程中,需要调用一些额外的参数时需要函数库的协助,比较重要的目录为/lib/modules。

Read more »

目录操作:

  • 创建目录:
1
mkdir $HOME/testFolder
  • 切换目录:

使用 cd 命令切换目录

1
cd $HOME/testFolder

使用 cd ../ 命令切换到上一级目录

1
cd ../
  • 移动目录

使用 mv 命令移动目录

1
mv $HOME/testFolder /var/tmp
  • 删除目录

使用 rm -rf 命令删除目录

1
rm -rf /var/tmp/testFolder
  • 查看目录下的文件

使用 ls 命令查看 /etc
目录下所有文件和文件夹

1
ls /etc
Read more »

在jdk1.5中引入枚举这个小功能,这个功能虽然用的不多,但是却给我们的开发带来很多便利,我们
今天来看看java的枚举是个什么样子。

枚举的主要操作方法

1
2
3
4
5
6
7
8
protected Enum(String name,int ordinal)  //接受枚举的名称和枚举的常量创建枚举对象  
protected final Object clone()throws CloneNotSupportedException //克隆枚举对象
public final int compareTo(E o) //比较枚举与指定对象的顺序
public final boolean equals(Object other) //比较两个枚举对象
public final int hashCode() //返回枚举常量的哈希码
public final String name() //返回枚举类的名称
public final int ordinal() //返回枚举常量的序号
public static <T extends Enum<T>> T valueOf(Class<T> enum Type,String name) //返回带指定名称的指定枚举类型的枚举常量

先定义一个枚举,用enum关键字

1
2
3
4
5
6
7
8
/**
* 定义枚举
* @author mingshan
*
*/
public enum EnumTest {
MON, TUE, WED, THU, FRI, SAT, SUN;
}

这里将星期定义为枚举类型,但没有赋值,既然已经定义好了,那么就先测试一下吧。

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
/**
* 枚举测试
* @author mingshan
*
*/
public class Test {

public static void main(String[] args) {

//遍历枚举
for(EnumTest e : EnumTest.values()) {
System.out.println(e.toString());
}

System.out.println("我是分割线------");


//switch 操作
EnumTest fri = EnumTest.FRI;

switch(fri){
case MON :
System.out.println("今天是星期一"); break;
case FRI :
System.out.println("今天是星期五"); break;
default :
System.out.println("-----"); break;
}

//返回
System.out.println(fri.getDeclaringClass());

//利用compareTo进行比较
switch (fri.compareTo(EnumTest.SAT)) {
case -1:
System.out.println("之前");
break;
case 1:
System.out.println("之后");
break;
default:
break;
}
}
}

我们可以遍历枚举,用java的foreach进行遍历,调用枚举的values方法获取定义的枚举列表,但当
我们编写自定义enum时,却不包含values这个方法,这个方法是当我门编译文件时,编译器自动帮我
们加上的。枚举还可以进行switch操作,可以对获取的枚举进行判断。利用compareTo函数进行比较两个
枚举的顺序

Read more »

在java多态中,引用与对象可以是不同的类型,如:

1
A b=new B();

运用多态时,引用类型可以是实际对象类型的父类,即实际对象类型已经是一个比较具体的类,而引用类型则是一个比较抽象的类,任何extends过声明引用类型的对象都可以赋值给这个引用变量,这样就可以做出类似动态数组的东西,如下:

1
2
3
4
5
6
Animal[] a=new Animal[2];
a[0]=new Dog();
a[1]=new Cat();
for(int i=0;i<a.length;i++){
a[i].eat();
}

a数组里面可以放任何Animal的子类对象,调用的时候可以把子类都当作Animal来操作,实际上调用的是子类的方法,是不是很好玩呢→_→

当然,多态的应用很广泛呢,参数和返回类型也可以多态,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Vet{
public void giveShot(Anmial a){

a.makeNoise();
}
}

class Pet{
public void a(){
Vet v=new Vet();
Dog dog=new Dog();
Cat cat=new Cat();
v.giveShot(dog);
v.giveShot(cat);
}

}

giveShot会接受任何Animal的子类的对象实例,根据传入的参数不同,会调用不同对象的方法。