Wetts's blog

Stay Hungry, Stay Foolish.

0%

转自:http://ifeve.com/synchronized-blocks/

Java 同步块(synchronized block)用来标记方法或者代码块是同步的。Java同步块用来避免竞争。本文介绍以下内容:

  • Java同步关键字(synchronzied)
  • 实例方法同步
  • 静态方法同步
  • 实例方法中同步块
  • 静态方法中同步块
  • Java同步示例

Java 同步关键字(synchronized)

Java中的同步块用synchronized标记。同步块在Java中是同步在某个对象上。所有同步在一个对象上的同步块在同时只能被一个线程进入并执行操作。所有其他等待进入该同步块的线程将被阻塞,直到执行该同步块中的线程退出。

有四种不同的同步块:

  1. 实例方法
  2. 静态方法
  3. 实例方法中的同步块
  4. 静态方法中的同步块

上述同步块都同步在不同对象上。实际需要那种同步块视具体情况而定。

实例方法同步

下面是一个同步的实例方法:

1
2
3
public synchronized void add(int value){
this.count += value;
}

注意在方法声明中同步(synchronized )关键字。这告诉Java该方法是同步的。

Java实例方法同步是同步在拥有该方法的对象上。这样,每个实例其方法同步都同步在不同的对象上,即该方法所属的实例。只有一个线程能够在实例方法同步块中运行。如果有多个实例存在,那么一个线程一次可以在一个实例同步块中执行操作。一个实例一个线程。

静态方法同步

静态方法同步和实例方法同步方法一样,也使用synchronized 关键字。Java静态方法同步如下示例:

1
2
3
public static synchronized void add(int value){
count += value;
}

同样,这里synchronized 关键字告诉Java这个方法是同步的。

静态方法的同步是指同步在该方法所在的类对象上。因为在Java虚拟机中一个类只能对应一个类对象,所以同时只允许一个线程执行同一个类中的静态同步方法。

对于不同类中的静态同步方法,一个线程可以执行每个类中的静态同步方法而无需等待。不管类中的那个静态同步方法被调用,一个类只能由一个线程同时执行。

实例方法中的同步块

有时你不需要同步整个方法,而是同步方法中的一部分。Java可以对方法的一部分进行同步。

在非同步的Java方法中的同步块的例子如下所示:

1
2
3
4
5
6
public void add(int value){

synchronized(this){
this.count += value;
}
}

示例使用Java同步块构造器来标记一块代码是同步的。该代码在执行时和同步方法一样。

注意Java同步块构造器用括号将对象括起来。在上例中,使用了“this”,即为调用add方法的实例本身。在同步构造器中用括号括起来的对象叫做监视器对象。上述代码使用监视器对象同步,同步实例方法使用调用方法本身的实例作为监视器对象。

一次只有一个线程能够在同步于同一个监视器对象的Java方法内执行。

下面两个例子都同步他们所调用的实例对象上,因此他们在同步的执行效果上是等效的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class MyClass {

public synchronized void log1(String msg1, String msg2){
log.writeln(msg1);
log.writeln(msg2);
}

public void log2(String msg1, String msg2){
synchronized(this){
log.writeln(msg1);
log.writeln(msg2);
}
}
}

在上例中,每次只有一个线程能够在两个同步块中任意一个方法内执行。

如果第二个同步块不是同步在this实例对象上,那么两个方法可以被线程同时执行。

静态方法中的同步块

和上面类似,下面是两个静态方法同步的例子。这些方法同步在该方法所属的类对象上。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class MyClass {

public static synchronized void log1(String msg1, String msg2){
log.writeln(msg1);
log.writeln(msg2);
}

public static void log2(String msg1, String msg2){
synchronized(MyClass.class){
log.writeln(msg1);
log.writeln(msg2);
}
}
}

这两个方法不允许同时被线程访问。

如果第二个同步块不是同步在MyClass.class这个对象上。那么这两个方法可以同时被线程访问。

Java同步实例

在下面例子中,启动了两个线程,都调用Counter类同一个实例的add方法。因为同步在该方法所属的实例上,所以同时只能有一个线程访问该方法。

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
public class Counter{

long count = 0;

public synchronized void add(long value){
this.count += value;
}
}

public class CounterThread extends Thread{

protected Counter counter = null;

public CounterThread(Counter counter){
this.counter = counter;
}

public void run() {
for(int i=0; i<10; i++){
counter.add(i);
}
}
}

public class Example {

public static void main(String[] args){
Counter counter = new Counter();
Thread threadA = new CounterThread(counter);
Thread threadB = new CounterThread(counter);

threadA.start();
threadB.start();
}
}

创建了两个线程。他们的构造器引用同一个Counter实例。Counter.add方法是同步在实例上,是因为add方法是实例方法并且被标记上synchronized关键字。因此每次只允许一个线程调用该方法。另外一个线程必须要等到第一个线程退出add()方法时,才能继续执行方法。

如果两个线程引用了两个不同的Counter实例,那么他们可以同时调用add()方法。这些方法调用了不同的对象,因此这些方法也就同步在不同的对象上。这些方法调用将不会被阻塞。如下面这个例子所示:

1
2
3
4
5
6
7
8
9
10
11
12
public class Example {

public static void main(String[] args){
Counter counterA = new Counter();
Counter counterB = new Counter();
Thread threadA = new CounterThread(counterA);
Thread threadB = new CounterThread(counterB);

threadA.start();
threadB.start();
}
}

注意这两个线程,threadA和threadB,不再引用同一个counter实例。CounterA和counterB的add方法同步在他们所属的对象上。调用counterA的add方法将不会阻塞调用counterB的add方法。

转自:http://ifeve.com/compare-and-swap/

CAS(Compare and swap)比较和替换是设计并发算法时用到的一种技术。简单来说,比较和替换是使用一个期望值和一个变量的当前值进行比较,如果当前变量的值与我们期望的值相等,就使用一个新值替换当前变量的值。这听起来可能有一点复杂但是实际上你理解之后发现很简单,接下来,让我们跟深入的了解一下这项技术。

CAS的使用场景

在程序和算法中一个经常出现的模式就是“check and act”模式。先检查后操作模式发生在代码中首先检查一个变量的值,然后再基于这个值做一些操作。下面是一个简单的示例:

1
2
3
4
5
6
7
8
9
10
11
12
class MyLock {

private boolean locked = false;

public boolean lock() {
if(!locked) {
locked = true;
return true;
}
return false;
}
}

上面这段代码,如果用在多线程的程序会出现很多错误,不过现在请忘掉它。

如你所见,lock()方法首先检查locked>成员变量是否等于false,如果等于,就将locked设为true。

如果同个线程访问同一个MyLock实例,上面的lock()将不能保证正常工作。如果一个线程检查locked的值,然后将其设置为false,与此同时,一个线程B也在检查locked的值,又或者,在线程A将locked的值设为false之前。因此,线程A和线程B可能都看到locked的值为false,然后两者都基于这个信息做一些操作。

为了在一个多线程程序中良好的工作,”check then act” 操作必须是原子的。原子就是说”check“操作和”act“被当做一个原子代码块执行。不存在多个线程同时执行原子块。

下面是一个代码示例,把之前的lock()方法用synchronized关键字重构成一个原子块。

1
2
3
4
5
6
7
8
9
10
11
12
class MyLock {

private boolean locked = false;

public synchronized boolean lock() {
if(!locked) {
locked = true;
return true;
}
return false;
}
}

现在lock()方法是同步的,所以,在某一时刻只能有一个线程在同一个MyLock实例上执行它。

原子的lock方法实际上是一个”compare and swap“的例子。

CAS用作原子操作

现在CPU内部已经执行原子的CAS操作。Java5以来,你可以使用java.util.concurrent.atomic包中的一些原子类来使用CPU中的这些功能。

下面是一个使用AtomicBoolean类实现lock()方法的例子:

1
2
3
4
5
6
7
8
public static class MyLock {
private AtomicBoolean locked = new AtomicBoolean(false);

public boolean lock() {
return locked.compareAndSet(false, true);
}

}

locked变量不再是boolean类型而是AtomicBoolean。这个类中有一个compareAndSet()方法,它使用一个期望值和AtomicBoolean实例的值比较,和两者相等,则使用一个新值替换原来的值。在这个例子中,它比较locked的值和false,如果locked的值为false,则把修改为true。

如果值被替换了,compareAndSet()返回true,否则,返回false。

使用Java5+提供的CAS特性而不是使用自己实现的的好处是Java5+中内置的CAS特性可以让你利用底层的你的程序所运行机器的CPU的CAS特性。这会使还有CAS的代码运行更快。

服务器端使用异步机制的主要目的是将“处理连接”与“处理请求”解耦。

对于服务器而言,如果处理连接的线程被一个需要较长时间才能处理完毕的任务阻塞,那么服务器处理连接的能力就会下降,而此时服务器的资源很有可能是空闲的。此时,我们考虑将处理连接和处理请求任务解耦,处理连接的线程接收请求后,将其分派给处理请求任务的线程。这样一来,即使任务需要较长时间才能完成,处理连接的线程也无需阻塞等待了,服务器因此可以重用连接线程,从而提供更高的吞吐率。处理请求的线程相对于处理连接的线程是异步执行的,当任务结束后,服务器会从上下文中找到当前连接,并将连接结束返回,作为该连接请求的响应。

  • 200(OK):服务器正常响应
  • 201(Created):创建新实体,响应头Location指定访问该实体的URL
  • 202(Accepted):服务器接受请求,处理尚未完成。可用于异步处理机制
  • 204(No Content):服务器正常响应,但响应实体为空
  • 301(Moved Permanently):请求资源的地址发生永久变动,响应头Location指定新的URL
  • 302(Found):请求资源的地址发生临时变动
  • 304(Not modified):客户端缓存资源依然有效
  • 400(Bad Request):请求信息出现语法错误
  • 401(Unauthorized):请求资源无法授权给未验证用户
  • 403(Forbidden):请求资源未授权当前用户
  • 404(Not Found):请求资源不存在
  • 405(Method Not Allowed):请求方法不匹配
  • 406(Not Acceptable):请求资源的媒体类型不匹配
  • 500(Internal Server Error):服务器内部错误,意外终止响应
  • 501(Not Implemented):服务器不支持当前请求

REST服务和RPC服务的接口定义上的区别是:REST使用HTTP协议的通用方法作为统一接口的标准词汇,REST服务所提供的方法信息都在HTTP方法里,而RPC服务所提供的方法信息在SOAP/HTTP信封里(其封装的格式通常是HTTP或SOAP),每一个RPC式的Web服务都会发布一套符合自己商业逻辑的方法词汇。

每一种HTTP请求方法都可以从安全性和幂等性两方面考虑,这对正确理解HTTP请求方法和设计统一接口具有决定性的意义。

安全性是指外系统对该接口的访问,不会使服务器端资源的状态发生改变;幂等性(idempotence)是指外系统对同一REST接口的多次访问,得到的资源状态是相同的。

REST(Representational State Transfer)翻译为表述性状态转移。

Roy Thomas Fielding博士在2000年就读加州大学欧文分校期间发表的一篇学术论文《Architectural Styles and the Design of Network-based Software Architectures》,该文中提出了REST的6个特点,分别是:客户端
-服务端的、无状态的、可缓存的、统一接口、分层系统和按需编码。

REST是一种架构风格。在这种架构风格中,对象被视为一种资源(resource),通常使用概念清晰的名词命名。

REST的资源是可寻址的,通过HTTP1.1协议(RFC 2616)定义的通用动词方法(比如GET、PUT、DELETE、POST),使用URI协议(RFC 3305)来唯一表示某个资源公布出来的接口。

解读JAX-RS标准

JAX-RS是Java领域的REST式的Web服务的标准规范,是使用Java完成REST服务的基本约定。

Java领域中的Web Service是指实现SOAP协议的JAX-WS。直到Java EE 6(发布于2008年9月)通过JCP(Java Community Process)组织定义的JSR311,才将REST在Java领域标准化。

JSR311名为The Java API for RESTful Web Service,即JAX-RS。此后,JSR311进行了一次升级(2009年9月),即JAX-RS1.1。JAX-RS诞生后,时隔5年(2013年5月)发布的Java EE 7包含了JSR339,将JAX-RS升级到JAX-RS2。JAX-RS2在前面版本的基础上增加了很多实用性的功能。比如对REST客户端API的定义,异步REST等,对REST的支持更加完善和强大。

转自:http://ifeve.com/dynamic-class-loading-reloading/

Java允许你在运行期动态加载和重载类,但是这个功能并没有像人们希望的那么简单直接。这篇文章将阐述在Java中如何加载以及重载类。

你可能会质疑为什么Java动态类加载特性是Java反射机制的一部分而不是Java核心平台的一部分。不管怎样,这篇文章被放到了Java反射系列里面而且也没有更好的系列来包含它了。

类加载器

所有Java应用中的类都是被java.lang.ClassLoader类的一系列子类加载的。因此要想动态加载类的话也必须使用java.lang.ClassLoader的子类。

一个类一旦被加载时,这个类引用的所有类也同时会被加载。类加载过程是一个递归的模式,所有相关的类都会被加载。但并不一定是一个应用里面所有类都会被加载,与这个被加载类的引用链无关的类是不会被加载的,直到有引用关系的时候它们才会被加载。

类加载体系

在Java中类加载是一个有序的体系。当你新创建一个标准的Java类加载器时你必须提供它的父加载器。当一个类加载器被调用来加载一个类的时候,首先会调用这个加载器的父加载器来加载。如果父加载器无法找到这个类,这时候这个加载器才会尝试去加载这个类。

类加载

类加载器加载类的顺序如下:

  1. 检查这个类是否已经被加载。
  2. 如果没有被加载,则首先调用父加载器加载。
  3. 如果父加载器不能加载这个类,则尝试加载这个类。

当你实现一个有重载类功能的类加载器,它的顺序与上述会有些不同。类重载不会请求的他的父加载器来进行加载。在后面的段落会进行讲解。

动态类加载

动态加载一个类十分简单。你要做的就是获取一个类加载器然后调用它的loadClass()方法。下面是个例子:

1
2
3
4
5
6
7
8
9
10
11
12
public class MainClass {

public static void main(String[] args){
ClassLoader classLoader = MainClass.class.getClassLoader();

try {
Class aClass = classLoader.loadClass("com.jenkov.MyClass");
System.out.println("aClass.getName() = " + aClass.getName());
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}

动态类重载

动态类重载有一点复杂。Java内置的类加载器在加载一个类之前会检查它是否已经被加载。因此重载一个类是无法使用Java内置的类加载器的,如果想要重载一个类你需要手动继承ClassLoader。

在你定制ClassLoader的子类之后,你还有一些事需要做。所有被加载的类都需要被链接。这个过程是通过ClassLoader.resolve()方法来完成的。由于这是一个final方法,因此这个方法在ClassLoader的子类中是无法被重写的。resolve()方法是不会允许给定的ClassLoader实例链接一个类两次。所以每当你想要重载一个类的时候你都需要使用一个新的ClassLoader的子类。你在设计类重载功能的时候这是必要的条件。

自定义类重载

在前面已经说过你不能使用已经加载过类的类加载器来重载一个类。因此你需要其他的ClassLoader实例来重载这个类。但是这又带来了一些新的挑战。

所有被加载到Java应用中的类都以类的全名(包名 + 类名)作为一个唯一标识来让ClassLoader实例来加载。这意味着,类MyObject被类加载器A加载,如果类加载器B又加载了MyObject类,那么两个加载器加载出来的类是不同的。看看下面的代码:

1
MyObject object = (MyObject)myClassReloadingFactory.newInstance("com.jenkov.MyObject");

MyObject类在上面那段代码中被引用,它的变量名是object。这就导致了MyObject这个类会被这段代码所在类的类加载器所加载。

如果myClassReloadingFactory工厂对象使用不同的类加载器重载MyObject类,你不能把重载的MyObject类的实例转换(cast)到类型为MyObject的对象变量。一旦MyObject类分别被两个类加载器加载,那么它就会被认为是两个不同的类,尽管它们的类的全名是完全一样的。你如果尝试把这两个类的实例进行转换就会报ClassCastException。

你可以解决这个限制,不过你需要从以下两个方面修改你的代码:

  1. 标记这个变量类型为一个接口,然后只重载这个接口的实现类。
  2. 标记这个变量类型为一个超类,然后只重载这个超类的子类。

请看下面这两个例子:

1
MyObjectInterface object = (MyObjectInterface)myClassReloadingFactory.newInstance("com.jenkov.MyObject");
1
MyObjectSuperclass object = (MyObjectSuperclass)myClassReloadingFactory.newInstance("com.jenkov.MyObject");

只要保证变量的类型是超类或者接口,这两个方法就可以正常运行,当它们的子类或是实现类被重载的时候超类跟接口是不会被重载的。

为了保证这种方式可以运行你需要手动实现类加载器然后使得这些接口或超类可以被它的父加载器加载。当你的类加载器加载MyObject类时,超类MyObjectSuperclass或者接口MyObjectSuperclass也会被加载,因为它们是MyObject的依赖。你的类加载器必须要代理这些类的加载到同一个类加载器,这个类加载器加载这个包括接口或者超类的类。

类加载/重载示例

光说不练假把式。让我们看看一个简单的例子。下面这个例子是一个类加载器的子类。注意在这个类不想被重载的情况下它是如何把对一个类的加载代理到它的父加载器上的。如果一个类被它的父加载器加载,这个类以后将不能被重载。记住,一个类只能被同一个ClassLoader实例加载一次。

就像我之前说的那样,这仅仅是一个简单的例子,通过这个例子会向你展示类加载器的基本行为。这并不是一个可以让你直接用于设计你项目中类加载器的模板。你自己设计的类加载器应该不仅仅只有一个,如果你想用来重载类的话你可能会设计很多加载器。并且你也不会像下面这样将需要加载的类的路径硬编码(hardcore)到你的代码中。

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
public class MyClassLoader extends ClassLoader{

public MyClassLoader(ClassLoader parent) {
super(parent);
}

public Class loadClass(String name) throws ClassNotFoundException {
if(!"reflection.MyObject".equals(name))
return super.loadClass(name);

try {
String url = "file:C:/data/projects/tutorials/web/WEB-INF/" +
"classes/reflection/MyObject.class";

URL myUrl = new URL(url);
URLConnection connection = myUrl.openConnection();
InputStream input = connection.getInputStream();
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
int data = input.read();

while(data != -1){
buffer.write(data);
data = input.read();
}

input.close();

byte[] classData = buffer.toByteArray();

return defineClass("reflection.MyObject",
classData, 0, classData.length);
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}

下面是使用MyClassLoader的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {

ClassLoader parentClassLoader = MyClassLoader.class.getClassLoader();
MyClassLoader classLoader = new MyClassLoader(parentClassLoader);
Class myObjectClass = classLoader.loadClass("reflection.MyObject");

AnInterface2 object1 = (AnInterface2) myObjectClass.newInstance();

MyObjectSuperClass object2 =(MyObjectSuperClass) myObjectClass.newInstance();

//create new class loader so classes can be reloaded.
classLoader = new MyClassLoader(parentClassLoader);
myObjectClass = classLoader.loadClass("reflection.MyObject");

object1 = (AnInterface2) myObjectClass.newInstance();
object2 = (MyObjectSuperClass) myObjectClass.newInstance();
}

下面这个就是被加载的reflection.MyObject类。注意它既继承了一个超类并且也实现了一个接口。这样做仅仅是为了通过例子演示这个特性。在你自定义的情况下你可能仅会实现一个类或者继承一两个接口。

1
2
3
4
public class MyObject extends MyObjectSuperClass implements AnInterface2{
//... body of class ... override superclass methods
// or implement interface methods
}

转自:http://ifeve.com/java-reflection-11-dynamic-proxies/

利用Java反射机制你可以在运行期动态的创建接口的实现。java.lang.reflect.Proxy类就可以实现这一功能。这个类的名字(译者注:Proxy意思为代理)就是为什么把动态接口实现叫做动态代理。动态的代理的用途十分广泛,比如数据库连接和事物管理(transaction management)还有单元测试时用到的动态mock对象以及AOP中的方法拦截功能等等都使用到了动态代理。

创建代理

你可以通过使用Proxy.newProxyInstance()方法创建动态代理。newProxyInstance()方法有三个参数:

  1. 类加载器(ClassLoader)用来加载动态代理类。
  2. 一个要实现的接口的数组。
  3. 一个InvocationHandler把所有方法的调用都转到代理上。

如下例:

1
2
3
4
5
InvocationHandler handler = new MyInvocationHandler();
MyInterface proxy = (MyInterface) Proxy.newProxyInstance(
MyInterface.class.getClassLoader(),
new Class[] { MyInterface.class },
handler);

在执行完这段代码之后,变量proxy包含一个MyInterface接口的的动态实现。所有对proxy的调用都被转向到实现了InvocationHandler接口的handler上。有关InvocationHandler的内容会在下一段介绍。

InvocationHandler接口

在前面提到了当你调用Proxy.newProxyInstance()方法时,你必须要传入一个InvocationHandler接口的实现。所有对动态代理对象的方法调用都会被转向到InvocationHandler接口的实现上,下面是InvocationHandler接口的定义:

1
2
3
4
public interface InvocationHandler{

Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}

下面是它的实现类的定义:

1
2
3
4
5
6
public class MyInvocationHandler implements InvocationHandler{

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//do something "dynamic"
}
}

传入invoke()方法中的proxy参数是实现要代理接口的动态代理对象。通常你是不需要他的。

invoke()方法中的Method对象参数代表了被动态代理的接口中要调用的方法,从这个method对象中你可以获取到这个方法名字,方法的参数,参数类型等等信息。关于这部分内容可以查阅之前有关Method的文章。

Object数组参数包含了被动态代理的方法需要的方法参数。注意:原生数据类型(如int,long等等)方法参数传入等价的包装对象(如Integer, Long等等)。

常见用例

动态代理常被应用到以下几种情况中

  • 数据库连接以及事物管理
  • 单元测试中的动态Mock对象
  • 自定义工厂与依赖注入(DI)容器之间的适配器
  • 类似AOP的方法拦截器

数据库连接以及事物管理

Spring框架中有一个事物代理可以让你提交/回滚一个事物。它的具体原理在 Advanced Connection and Transaction Demarcation and Propagation一文中有详细描述,所以在这里我就简短的描述一下,方法调用序列如下:

1
2
3
4
5
6
web controller --> proxy.execute(...);

proxy --> connection.setAutoCommit(false);
proxy --> realAction.execute();
realAction does database work
proxy --> connection.commit();

单元测试中的动态Mock对象

Butterfly Testing工具通过动态代理来动态实现桩(stub),mock和代理类来进行单元测试。在测试类A的时候如果用到了接口B,你可以传给A一个实现了B接口的mock来代替实际的B接口实现。所有对接口B的方法调用都会被记录,你可以自己来设置B的mock中方法的返回值。

而且Butterfly Testing工具可以让你在B的mock中包装真实的B接口实现,这样所有调用mock的方法都会被记录,然后把调用转向到真实的B接口实现。这样你就可以检查B中方法真实功能的调用情况。例如:你在测试DAO时你可以把真实的数据库连接包装到mock中。这样的话就与真实的情况一样,DAO可以在数据库中读写数据,mock会把对数据库的读写操作指令都传给数据库,你可以通过mock来检查DAO是不是以正确的方式来使用数据库连接,比如你可以检查是否调用了connection.close()方法。这种情况是不能简单的依靠调用DAO方法的返回值来判断的。

自定义工厂与依赖注入(DI)容器之间的适配器

依赖注入容器Butterfly Container有一个非常强大的特性可以让你把整个容器注入到这个容器生成的bean中。但是,如果你不想依赖这个容器的接口,这个容器可以适配你自己定义的工厂接口。你仅仅需要这个接口而不是接口的实现,这样这个工厂接口和你的类看起来就像这样:

1
2
3
4
5
6
public interface IMyFactory {

Bean bean1();
Person person();
...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
public class MyAction{

protected IMyFactory myFactory= null;

public MyAction(IMyFactory factory){
this.myFactory = factory;
}

public void execute(){
Bean bean = this.myFactory.bean();
Person person = this.myFactory.person();
}
}

当MyAction类调用通过容器注入到构造方法中的IMyFactory实例的方法时,这个方法调用实际先调用了IContainer.instance()方法,这个方法可以让你从容器中获取实例。这样这个对象可以把Butterfly Container容器在运行期当成一个工厂使用,比起在创建这个类的时候进行注入,这种方式显然更好。而且这种方法没有依赖到Butterfly Container中的任何接口。

类似AOP的方法拦截器

Spring框架可以拦截指定bean的方法调用,你只需提供这个bean继承的接口。Spring使用动态代理来包装bean。所有对bean中方法的调用都会被代理拦截。代理可以判断在调用实际方法之前是否需要调用其他方法或者调用其他对象的方法,还可以在bean的方法调用完毕之后再调用其他的代理方法。

转自:http://ifeve.com/java-reflection-10-arrays/

利用反射机制来处理数组会有点棘手。尤其是当你想要获得一个数组的Class对象,比如int[]等等。本节会讨论通过反射机制创建数组和如何获取数组的Class对象。

注意:在阅读Eyal Lupu的博客文章“Two Side Notes About Arrays and Reflection”之后对本文的内容做了更新。目前这个版本参考了这篇博文里面的内容。

java.lang.reflect.Array

Java反射机制通过java.lang.reflect.Array这个类来处理数组。不要把这个类与Java集合套件(Collections suite)中的java.util.Arrays混淆,java.util.Arrays是一个提供了遍历数组,将数组转化为集合等工具方法的类。

创建一个数组

Java反射机制通过java.lang.reflect.Array类来创建数组。下面是一个如何创建数组的例子:

1
int[] intArray = (int[]) Array.newInstance(int.class, 3);

这个例子创建一个int类型的数组。Array.newInstance()方法的第一个参数表示了我们要创建一个什么类型的数组。第二个参数表示了这个数组的空间是多大。

访问一个数组

通过Java反射机制同样可以访问数组中的元素。具体可以使用Array.get(…)和Array.set(…)方法来访问。下面是一个例子:

1
2
3
4
5
6
7
8
9
int[] intArray = (int[]) Array.newInstance(int.class, 3);

Array.set(intArray, 0, 123);
Array.set(intArray, 1, 456);
Array.set(intArray, 2, 789);

System.out.println("intArray[0] = " + Array.get(intArray, 0));
System.out.println("intArray[1] = " + Array.get(intArray, 1));
System.out.println("intArray[2] = " + Array.get(intArray, 2));

这个例子会输出:

1
2
3
intArray[0] = 123
intArray[1] = 456
intArray[2] = 789

获取数组的Class对象

在我编写Butterfly DI Container的脚本语言时,当我想通过反射获取数组的Class对象时遇到了一点麻烦。如果不通过反射的话你可以这样来获取数组的Class对象:

1
Class stringArrayClass = String[].class;

如果使用Class.forName()方法来获取Class对象则不是那么简单。比如你可以像这样来获得一个原生数据类型(primitive)int数组的Class对象:

1
Class intArray = Class.forName("[I");

在JVM中字母I代表int类型,左边的‘[’代表我想要的是一个int类型的数组,这个规则同样适用于其他的原生数据类型。
对于普通对象类型的数组有一点细微的不同:

1
Class stringArrayClass = Class.forName("[Ljava.lang.String;");

注意‘[L’的右边是类名,类名的右边是一个‘;’符号。这个的含义是一个指定类型的数组。

需要注意的是,你不能通过Class.forName()方法获取一个原生数据类型的Class对象。下面这两个例子都会报ClassNotFoundException:

1
2
Class intClass1 = Class.forName("I");
Class intClass2 = Class.forName("int");

我通常会用下面这个方法来获取普通对象以及原生对象的Class对象:

1
2
3
4
5
6
7
public Class getClass(String className){

if("int" .equals(className)) return int .class;
if("long".equals(className)) return long.class;
...
return Class.forName(className);
}

一旦你获取了类型的Class对象,你就有办法轻松的获取到它的数组的Class对象,你可以通过指定的类型创建一个空的数组,然后通过这个空的数组来获取数组的Class对象。这样做有点讨巧,不过很有效。如下例:

1
2
Class theClass = getClass(theClassName);
Class stringArrayClass = Array.newInstance(theClass, 0).getClass();

这是一个特别的方式来获取指定类型的指定数组的Class对象。无需使用类名或其他方式来获取这个Class对象。

为了确保Class对象是不是代表一个数组,你可以使用Class.isArray()方法来进行校验:

1
2
Class stringArrayClass = Array.newInstance(String.class, 0).getClass();
System.out.println("is array: " + stringArrayClass.isArray());

获取数组的成员类型

一旦你获取了一个数组的Class对象,你就可以通过Class.getComponentType()方法获取这个数组的成员类型。成员类型就是数组存储的数据类型。例如,数组int[]的成员类型就是一个Class对象int.class。String[]的成员类型就是java.lang.String类的Class对象。

下面是一个访问数组成员类型的例子:

1
2
3
4
String[] strings = new String[3];
Class stringArrayClass = strings.getClass();
Class stringArrayComponentType = stringArrayClass.getComponentType();
System.out.println(stringArrayComponentType);

下面这个例子会打印“java.lang.String”代表这个数组的成员类型是字符串。

转自:http://ifeve.com/java-reflection-8-annotation/

利用Java反射机制可以在运行期获取Java类的注解信息。

什么是注解

注解是Java 5的一个新特性。注解是插入你代码中的一种注释或者说是一种元数据(meta data)。这些注解信息可以在编译期使用预编译工具进行处理(pre-compiler tools),也可以在运行期使用Java反射机制进行处理。下面是一个类注解的例子:

1
2
@MyAnnotation(name="someName",  value = "Hello World")
public class TheClass {}

在TheClass类定义的上面有一个@MyAnnotation的注解。注解的定义与接口的定义相似,下面是MyAnnotation注解的定义:

1
2
3
4
5
6
7
8
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface MyAnnotation {

public String name();

public String value();
}

在interface前面的@符号表名这是一个注解,一旦你定义了一个注解之后你就可以将其应用到你的代码中,就像之前我们的那个例子那样。

在注解定义中的两个指示@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.TYPE),说明了这个注解该如何使用。

@Retention(RetentionPolicy.RUNTIME)表示这个注解可以在运行期通过反射访问。如果你没有在注解定义的时候使用这个指示那么这个注解的信息不会保留到运行期,这样反射就无法获取它的信息。

@Target(ElementType.TYPE)表示这个注解只能用在类型上面(比如类跟接口)。你同样可以把Type改为Field或者Method,或者你可以不用这个指示,这样的话你的注解在类,方法和变量上就都可以使用了。

关于Java注解更详细的讲解可以访问Java Annotations tutorial

类注解

你可以在运行期访问类,方法或者变量的注解信息,下是一个访问类注解的例子:

1
2
3
4
5
6
7
8
9
10
Class aClass = TheClass.class;
Annotation[] annotations = aClass.getAnnotations();

for(Annotation annotation : annotations){
if(annotation instanceof MyAnnotation){
MyAnnotation myAnnotation = (MyAnnotation) annotation;
System.out.println("name: " + myAnnotation.name());
System.out.println("value: " + myAnnotation.value());
}
}

你还可以像下面这样指定访问一个类的注解:

1
2
3
4
5
6
7
8
Class aClass = TheClass.class;
Annotation annotation = aClass.getAnnotation(MyAnnotation.class);

if(annotation instanceof MyAnnotation){
MyAnnotation myAnnotation = (MyAnnotation) annotation;
System.out.println("name: " + myAnnotation.name());
System.out.println("value: " + myAnnotation.value());
}

方法注解

下面是一个方法注解的例子:

1
2
3
4
5
public class TheClass {

@MyAnnotation(name="someName", value = "Hello World")
public void doSomething(){}
}

你可以像这样访问方法注解:

1
2
3
4
5
6
7
8
9
10
Method method = ... //获取方法对象
Annotation[] annotations = method.getDeclaredAnnotations();

for(Annotation annotation : annotations){
if(annotation instanceof MyAnnotation){
MyAnnotation myAnnotation = (MyAnnotation) annotation;
System.out.println("name: " + myAnnotation.name());
System.out.println("value: " + myAnnotation.value());
}
}

你可以像这样访问指定的方法注解:

1
2
3
4
5
6
7
8
Method method = ... // 获取方法对象
Annotation annotation = method.getAnnotation(MyAnnotation.class);

if(annotation instanceof MyAnnotation){
MyAnnotation myAnnotation = (MyAnnotation) annotation;
System.out.println("name: " + myAnnotation.name());
System.out.println("value: " + myAnnotation.value());
}

参数注解

方法参数也可以添加注解,就像下面这样:

1
2
3
4
5
public class TheClass {

public static void doSomethingElse(@MyAnnotation(name="aName", value="aValue") String parameter){
}
}

你可以通过Method对象来访问方法参数注解:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Method method = ... //获取方法对象
Annotation[][] parameterAnnotations = method.getParameterAnnotations();
Class[] parameterTypes = method.getParameterTypes();

int i=0;
for(Annotation[] annotations : parameterAnnotations){
Class parameterType = parameterTypes[i++];

for(Annotation annotation : annotations){
if(annotation instanceof MyAnnotation){
MyAnnotation myAnnotation = (MyAnnotation) annotation;
System.out.println("param: " + parameterType.getName());
System.out.println("name : " + myAnnotation.name());
System.out.println("value: " + myAnnotation.value());
}
}
}

需要注意的是Method.getParameterAnnotations()方法返回一个注解类型的二维数组,每一个方法的参数包含一个注解数组。

变量注解

下面是一个变量注解的例子:

1
2
3
4
5
public class TheClass {

@MyAnnotation(name="someName", value = "Hello World")
public String myField = null;
}

你可以像这样来访问变量的注解:

1
2
3
4
5
6
7
8
9
10
Field field = ... //获取方法对象</pre>
Annotation[] annotations = field.getDeclaredAnnotations();

for(Annotation annotation : annotations){
if(annotation instanceof MyAnnotation){
MyAnnotation myAnnotation = (MyAnnotation) annotation;
System.out.println("name: " + myAnnotation.name());
System.out.println("value: " + myAnnotation.value());
}
}

你可以像这样访问指定的变量注解:

1
2
3
4
5
6
7
8
Field field = ...//获取方法对象</pre>
Annotation annotation = field.getAnnotation(MyAnnotation.class);

if(annotation instanceof MyAnnotation){
MyAnnotation myAnnotation = (MyAnnotation) annotation;
System.out.println("name: " + myAnnotation.name());
System.out.println("value: " + myAnnotation.value());
}