spring源码解析-IOC容器(二)-读取配置文件

在Java中,将不同来源的资源抽象成URL,通过注册不同的handler(URLStreamHandler)来处理不同来源间的资源读取逻辑。而URL中却没有提供一些基本方法来实现自己的抽象结构。因而Spring提出了一套基于
org.springframework.core.io.Resource和org.springframework.core.io.ResourceLoader接口的资源抽象和加载策略。

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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
package org.springframework.core.io;

import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URL;

/**
* Resource接口抽象了所有Spring内部使用到的底层资源:File、URL、Classpath等。
* 同时,对于来源不同的资源文件,Resource也有不同实现:文件(FileSystemResource)、Classpath资源(ClassPathResource)、
* URL资源(UrlResource)、InputStream资源(InputStreamResource)、Byte数组(ByteArrayResource)等等。
*
* @author Juergen Hoeller
* @since 28.12.2003
*/

public interface Resource extends InputStreamSource {

/**
* 判断资源是否存在
* @return boolean 是否存在
*/

boolean exists();

/**
* 判断资源是否可读
* @return boolean 是否可读
*/

boolean isReadable();

/**
* 是否处于开启状态
* @return boolean 是否开启
*/

boolean isOpen();

/**
* 得到URL类型资源,用于资源转换
* @return URL 得到URL类型
* @throws IOException 如果资源不能打开则抛出异常
*/

URL getURL() throws IOException;

/**
* 得到URI类型资源,用于资源转换
* @return URI 得到URI类型
* @throws IOException 如果资源不能打开则抛出异常
*/

URI getURI() throws IOException;

/**
* 得到File类型资源,用于资源转换
* @return File 得到File类型
* @throws IOException 如果资源不能打开则抛出异常
*/

File getFile() throws IOException;

/**
* 获取资源长度
* @return long 资源长度
* @throws IOException 如果资源不能打开则抛出异常
*/

long contentLength() throws IOException;

/**
* 获取lastModified属性
* @return long 获取lastModified
* @throws IOException 如果资源不能打开则抛出异常
*/

long lastModified() throws IOException;

/**
* 创建一个相对的资源方法
* @param relativePath 相对路径
* @return Resource 返回一个新的资源
* @throws IOException 如果资源不能打开则抛出异常
*/

Resource createRelative(String relativePath) throws IOException;

/**
* 获取文件名称
* @return String 文件名称或者null
*/

String getFilename();

/**
* 得到错误处理信息,主要用于错误处理的信息打印
* @return String 错误资源信息
*/

String getDescription();
}

nginx基本介绍

本文主要介绍一些Nginx的最基本功能以及简单配置。

1.静态HTTP服务器

首先,Nginx是一个HTTP服务器,可以将服务器上的静态文件(如HTML、图片)通过HTTP协议展现给客户端。
配置:

1
2
3
4
5
6
server {
listen 80; # 端口号
location / {
root /usr/share/nginx/html; # 静态文件路径
}
}

2、反向代理服务器

什么是反向代理?

客户端本来可以直接通过HTTP协议访问某网站应用服务器,如果网站管理员在中间加上一个Nginx,客户端请求Nginx,Nginx请求应用服务器,然后将结果返回给客户端,此时Nginx就是反向代理服务器。

配置:

1
2
3
4
5
6
server {
listen 80;
location / {
proxy_pass http://192.168.20.1:8080; # 应用服务器HTTP地址
}
}

既然服务器可以直接HTTP访问,为什么要在中间加上一个反向代理,不是多此一举吗?反向代理有什么作用?继续往下看,下面的负载均衡、虚拟主机,都基于反向代理实现,当然反向代理的功能也不仅仅是这些。

3、负载均衡

当网站访问量非常大,网站站长开心赚钱的同时,也摊上事儿了。因为网站越来越慢,一台服务器已经不够用了。于是将相同的应用部署在多台服务器上,将大量用户的请求分配给多台机器处理。同时带来的好处是,其中一台服务器万一挂了,只要还有其他服务器正常运行,就不会影响用户使用。

Nginx可以通过反向代理来实现负载均衡。

配置:

1
2
3
4
5
6
7
8
9
10
upstream myapp {
server 192.168.20.1:8080; # 应用服务器1
server 192.168.20.2:8080; # 应用服务器2
}
server {
listen 80;
location / {
proxy_pass http://myapp;
}
}

4、虚拟主机

网站访问量大,需要负载均衡。然而并不是所有网站都如此出色,有的网站,由于访问量太小,需要节省成本,将多个网站部署在同一台服务器上。
例如将www.aaa.com和www.bbb.com两个网站部署在同一台服务器上,两个域名解析到同一个IP地址,但是用户通过两个域名却可以打开两个完全不同的网站,互相不影响,就像访问两个服务器一样,所以叫两个虚拟主机。

配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
server {
listen 80 default_server;
server_name _;
return 444; # 过滤其他域名的请求,返回444状态码
}
server {
listen 80;
server_name www.aaa.com; # www.aaa.com域名
location / {
proxy_pass http://localhost:8080; # 对应端口号8080
}
}
server {
listen 80;
server_name www.bbb.com; # www.bbb.com域名
location / {
proxy_pass http://localhost:8081; # 对应端口号8081
}
}

在服务器8080和8081分别开了一个应用,客户端通过不同的域名访问,根据server_name可以反向代理到对应的应用服务器。

原文地址:http://xxgblog.com/2015/05/17/nginx-start/

spring源码解析-IOC容器(一)

Spring的核心是IOC和AOP,IOC的本质是将资源文件(如applicationContext.xml)配置的bean(java对象)信息解析出来,然后放到BeanFactory(Spring容器)的Map中(这一步就是所谓的注册),这样以后程序就可以直接从BeanFactory中拿Bean的信息

为完成IOC容器初始化,Spring设计了层次化的类并使用一些设计模式来组织这样一个过程。在分析源码过程之前要对Spring的类图有一个大概的了解:

  • 图一:IOC容器主要类图

  • 图二:定义从外面加载资源的接口

  • 图三: bean的相关定义

以上3幅图来源于:链接,感谢^_^

Spring的IOC相关的源码解析基本上围绕上面3幅图的主要接口和类。Spring的IOC本质上就做了以下3件事:

  • 加载配置文件

  • 解析配置文件并注册Bean

  • 实例化Bean

nio入门

从JDK1.4开始,JDK提供了一套专门的类库支持非阻塞I/O,可以在java.nio包及其子包中找到相关的类和接口。由于这套API是新提供的I/O API,因此也叫New I/O,这就是JAVA NIO的由来。非阻塞IO API由3个主要部分组成:缓冲区(Buffers)、通道(Channels)和Selector

NIO服务端创建过程

  1. 打开ServerSocketChannel,用于监听客户端的连接,它是所有客户端连接的父管道

    1
    ServerSocketChannel acceptorSrv = ServerSocketChannel.open();
  2. 绑定监听端口,设置连接为非阻塞模式

    1
    2
    3
    acceptorSrv.socket().bind(new InetSocketAddress(InetAddress.getByName("IP"), port));

    acceptorSrv.configureBlocking(false);
  3. 创建Reactor线程,创建多路复用器并启动线程

    1
    2
    3
    Selector selector = Selector.open();

    new Thread(new ReactorTask()).start();
  4. 将ServerSocketChannel注册到Reactor线程的多路复用器Selector上,监听ACCEPT事件

    1
    SelectionKey key = acceptorSvr.register(selector, SelectionKey.OP_ACCEPT, ioHandler);
  5. 多路复用器在线程run方法的无限循环体内轮询准备就绪的Key

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    int num = selector.select();

    Set selectedKeys = selector.selectKeys();

    Iterator it = selectedKeys.iterator();

    while(it.hasNext()) {

    SelectionKey key = (SelectionKey) in.next();

    //...deal with I/O event...

    }
  6. 多路复用器监听到有新的客户端接入,处理新的接入请求,完成TCP三次握手,建立物理链路

    1
    SocketChannel channel = svrChannel.accept();
  7. 设置客户端链路的TCP参数

    1
    2
    3
    channel.configureBlocking(false);

    channel.socket().setReuseAddress(true);
  8. 将新接入的客户端连接注册到Reactor线程的多路复用器上,监听读操作

    1
    SelectionKey key = socketChannel.register(selector, SelectionKey.OP_READ, ioHandler);
  9. 异步读取客户端请求消息到缓冲区

    1
    int readNum = channel.read(receiveeBuffer);
  10. 对ByteBuffer进行编解码,如果有半包消息指针Reset,继续读取后续的报文,将解码成功的消息封装成Task,投递到业务线程池中,进行业务逻辑操作

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20

    Object message = null;

    while(buffer.hasRemain()) {

    byteBuffer.remark();

    message = decode(byteBuffer);

    if(message == null) {

    byteBuffer.reset();

    break;

    }

    messageLisk.add(message);

    }
  11. 将POJO对象encode成ByteBuffer,调用SocketChannel的异步write接口,将消息异步发送给客服端

    1
    socketChannel.write(buffer);

第48条:如果要精确的结果,请避免使用float和double

float和double类型主要是为了科学计算和工程计算而设计的。她们执行二进制浮点运算,这是为了在广泛的数值范围上提供较为精确的快速近似计算而精心设计的。然而,它们并没有提供完全精确的结构,所以不应该被用于需要精确结果的场合。float和double类型尤其不适用于货币计算,因为要让一个float或者double精确地表示0.1(或者10的任何其它负数次方值)是不可能的。

例子:你现在有1元钱

货架上有一排糖果,标价为: 10分、20分、30分、40分、50分、60分…

你要从标价为10分的糖果开始,每种买1颗,一直到不能支付货架上下一种价格的糖果为止。傻子都看得出能买到4种糖果^_^(10+20+30+40),但是编程计算处理不当就会出问题!

错误的示范:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

public static void main(String[] args) {

double funds = 1.00;

int itemBought = 0;

for(double price = 0.10; funds >= price; price += 0.10) {

funds -= price;

itemBought++;

}

System.out.println("能买到" + itemBought + "种糖果!");

System.out.println("剩余金额 ¥" + funds);

}

运行上面这个程序你会发现你只能买到3中糖果,还剩余0.399999… GG! 出现这种情况的原因是float的计算精度造成的~

解决方案

1.使用BigDecimal

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

public static void main(String[] args) {

final BigDecimal TEN_CENTS = new BigDecimal("0.10");
int itemBought = 0;

BigDecimal funds = new BigDecimal("1.00");

for(BigDecimal price = TEN_CENTS; funds.compareTo(price) >= 0; price = price.add(TEN_CENTS)) {
itemBought++;

funds = funds.substract(price);

}
System.out.println("能买到" + itemBought + "种糖果!");
System.out.println("剩余金额 ¥" + funds);
}

2.使用int或long

除了使用BigDecimal之外,还有一种方法是使用int或者long,到底使用int或者龙要取决于所涉及数值的大小,同时要自己处理十进制小数点。在这个例子中,最明显的做法是以分为单位进行计算,而不是以元为单位。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

public static void main(String[] args) {

int itemBought = 0;

int funds = 100;

for( int price = 10; funds >= price; price += 10) {

itemBought++;

funds -= price;

}

System. out.println( "能买到" + itemBought + "种糖果!" );

System. out.println( "剩余金额 ¥" + funds);

}

总结

  • 对于需要精确答案的计算任务,不要使用float或者double

  • 数值范围没有超过9位十进制数字,就可以使用int,如果不超过18位数字,就可以使用long,如果数值范围可能超过18位数字,就要使用BigDecimal

  • 使用BigDecimal有两个缺点,与使用基本运算类型相比,这样子很不方便,而且很慢。但是计算结果精确,允许你完全控制舍入(8种舍入)