AQS

学习AQS的思路

学习AQS的目的主要是理解原理、提高技术以及应对面试

先从应用层面理解为什么需要它、如何使用它,然后再看一看Java代码的设计者是如何使用它的,了解它的应用场景

为什么学习AQS

锁和协作类有共同的特点就是闸门

事实上,不仅是ReentrantLock和Semaphore,包括CountDownLatch、ReentrantReadWriteLock都有这样类似“协作”(或者叫“同步”)的功能,其实他们底层都用了一个共同的基类,这就是AQS

为什么需要AQS

因为上面的那些协作类,它们有很多工作都是类似的,所以如果能提取出一个工具类,那么就可以直接用,对于ReentrantLock和Semaphore而言就可以屏蔽很多的细节,只关注它们自己的“业务逻辑”就可以

Semaphore和AQS的关系

Semaphore内部有一个Sync类,Sync类继承了AQS

image-20200225201434705

AQS的比喻

比喻:群面、单面

安排就坐、叫号、先来后到等HR的工作就是AQS做的工作

面试官不会去关心两个面试者是不是号码相同冲突了,也不想去管面试者是不是需要一个地方坐着休息,这些都交给HR处理

Semaphore:一个人面试完以后,后一个人才能进来继续面试

CountDownLatch:群面,等待10人到齐

Semaphore、CountDownLatch这些同步工具要做的就只是写下自己的招聘规则,比如是出一个进一个,或者说凑齐10人,一起面试

如果没有AQS

就需要每个协作工具自己实现

  • 同步状态的原子性管理

  • 线程的阻塞与解除阻塞

  • 队列的管理

在并发场景下,自己正确且高效地实现这些内容,是相当有难度的,所以使用AQS来帮我们把这些脏活累活都搞定,我们只关心业务逻辑就够了

AQS的作用

AQS是一个用于构建锁、同步器、协作工具类的工具类(框架)。有了AQS以后,更多的协作工具类就可以很方便的被写出来

一句话总结:有了AQS,构建线程协作类就容易多了

AQS的重要性

AbstractQueueSynchronizer是Doug Lea写的,从JDK5

加入的一个基于FIFO等待队列实现的一个用于实现同步器的基础框架,用IDE看AQS的实现类,可以发现实现类有以下这些

image-20200226092323742

AQS内部原理解析

AQS最核心的就是三大部分

  • state
  • 控制线程抢锁和配合的FIFO队列
  • 期望协作工具类去实现的获取、释放等重要方法

state

1
2
3
4
/**
* The synchronization state.
*/
private volatile int state;

这里的state的具体含义,会根据具体实现类的不同而不同,比如在Semaphore里,它表示剩余的许可证的数量,而在CountDownLatch里,它表示还需要倒数的数量

state是volatile修饰的,会并发的修改,所以所有修改state的方法都需要保证线程安全,比如getState、setState以及compareAndSetState操作来读取和更新这个状态,这些方法都依赖于juc.atomic包的支持

1
2
3
4
protected final boolean compareAndSetState(int expect, int update) {
// See below for intrinsics setup to support this
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}

在ReentrantLock中,state用来表示“锁”的占有情况,包括可重入计数

当state的值为0的时候,表示该Lock不被任何线程所占有,加一表示有一个线程占用,减一表示有一个线程释放锁

控制线程抢锁和配合的FIFO队列

这个队列用来存放等待的线程,AQS就是排队管理器,当多个线程争用同一把锁时,必须有排队机制将那些没有拿到锁的线程串起来,当锁释放的时候,锁管理器就会挑选一个合适的线程来占用刚被释放的锁

AQS会维护一个等待的线程队列,把线程都放到这个队列里,这是一个双向形式的队列

image-20200226094956287

期望协作工具类去实现的获取/释放等重要方法

这里的获取和释放方法,是利用AQS的协作工具类里最重要的方法,是由协作类自己去实现的,并且含义各不相同

获取方法

获取操作会依赖state变量,经常会阻塞,比如获取不到锁的时候

在Semphore中,获取就是acquire方法,作用是获取一个许可证,而在CountDownLatch中,获取就是await方法,作用是等待,直到倒数结束

释放方法

释放操作不会阻塞

在Semaphore中,释放就是release方法,作用是释放一个许可证

在CountDownLatch中,释放就是countDown方法,作用是倒数1个数

需要重新tryAcquire和tryRelease等方法

应用实例与源码解析

AQS的用法

第一步:写一个类,想好协作的逻辑,实现获取/释放方法

第二步:内部写一个Sync类继承

第三步:根据是否独占来重写tryAcquire/tryRelease或tryAcquireShared(int acquires)和tryReleaseShared(int releases)等方法,在之前写的获取/释放方法中调用AQS的acquire/release或者Shared方法

AQS在CountDownLatch的应用

内部类Sync继承AQS

构造函数

CountDownLatch中

1
2
3
4
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}

private static final class Sync extends AbstractQueuedSynchronizer中有如下定义

1
2
3
Sync(int count) {
setState(count);
}

在setState()方法中有如下定义

1
2
3
protected final void setState(int newState) {
state = newState;
}

getCount()

CountDownLatch中

1
2
3
public long getCount() {
return sync.getCount();
}
1
2
3
int getCount() {
return getState();
}

public abstract class AbstractQueuedSynchronizer类中

1
2
3
protected final int getState() {
return state;
}

countDown()

CountDownLatch中

1
2
3
public void countDown() {
sync.releaseShared(1);
}

public abstract class AbstractQueuedSynchronizer类中

1
2
3
4
5
6
7
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}

CountDownLatch中

1
2
3
4
5
6
7
8
9
10
11
protected boolean tryReleaseShared(int releases) {
// Decrement count; signal when transition to zero
for (;;) {
int c = getState();
if (c == 0)
return false;
int nextc = c-1;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}

await()

CountDownLatch中

1
2
3
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}

public abstract class AbstractQueuedSynchronizer类中

1
2
3
4
5
6
7
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}

AQS在CountDownLatch的总结

调用CountDownLatch的await方法,便会尝试获取共享锁,不过一开始是获取不到该锁的,于是线程被阻塞

而共享锁可以获得到的条件,就是锁计数器的值为0,而锁计数器的初始值为count,每当一个线程调用该CountDownLatch对象的countDown()方法时,才将锁计数器减1

count个线程调用countDown()之后,锁计数器才为0,而前面提到的等待获取共享锁的线程才能继续运行

AQS在Semaphore的应用

在Semaphore中,state表示许可证的剩余数量

1
2
3
4
public void acquire(int permits) throws InterruptedException {
if (permits < 0) throw new IllegalArgumentException();
sync.acquireSharedInterruptibly(permits);
}

看tryAcquire方法,判断nonfairTryAcquireShared,若大于等于0的则代表成功

这里会先检查剩余许可证数量够不够这次需要,用减法来计算,如果直接不够,则返回负数,表示失败。如果够了,就用自旋加compareAndSetState来改变state状态,直到改变成功就返回正数

如果在这期间被其他人修改了导致剩余数量不够了,那也返回负数代表获取失败

AQS在ReentrantLock的应用

分析释放锁的方法tryRelease

由于是可重入的,所以state代表重入的次数,每次释放锁先判断当前持有锁的线程是不是都是十方的,如果不是就抛出异常,如果是的话,重入次数就减一,当减到0时说明完全释放了,于是free就为true,并把state设置为0

加锁的方法

利用AQS实现一个自己的Latch门闩

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
/**
* 描述: 自己用AQS实现一个简单的线程协作器
*/
public class OneShotLatch {

private final Sync sync = new Sync();

public void signal() {
sync.releaseShared(0);
}
public void await() {
sync.acquireShared(0);
}

private class Sync extends AbstractQueuedSynchronizer {

@Override
protected int tryAcquireShared(int arg) {
return (getState() == 1) ? 1 : -1;
}

@Override
protected boolean tryReleaseShared(int arg) {
setState(1);

return true;
}
}


public static void main(String[] args) throws InterruptedException {
OneShotLatch oneShotLatch = new OneShotLatch();
for (int i = 0; i < 10; i++) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"尝试获取latch,获取失败那就等待");
oneShotLatch.await();
System.out.println("开闸放行"+Thread.currentThread().getName()+"继续运行");
}
}).start();
}
Thread.sleep(5000);
oneShotLatch.signal();

new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"尝试获取latch,获取失败那就等待");
oneShotLatch.await();
System.out.println("开闸放行"+Thread.currentThread().getName()+"继续运行");
}
}).start();
}
}

输出结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Thread-0尝试获取latch,获取失败那就等待
Thread-2尝试获取latch,获取失败那就等待
Thread-1尝试获取latch,获取失败那就等待
Thread-3尝试获取latch,获取失败那就等待
Thread-4尝试获取latch,获取失败那就等待
Thread-5尝试获取latch,获取失败那就等待
Thread-6尝试获取latch,获取失败那就等待
Thread-7尝试获取latch,获取失败那就等待
Thread-8尝试获取latch,获取失败那就等待
Thread-9尝试获取latch,获取失败那就等待
开闸放行Thread-0继续运行
开闸放行Thread-2继续运行
开闸放行Thread-1继续运行
开闸放行Thread-3继续运行
开闸放行Thread-4继续运行
开闸放行Thread-5继续运行
开闸放行Thread-6继续运行
开闸放行Thread-7继续运行
开闸放行Thread-8继续运行
开闸放行Thread-9继续运行
Thread-10尝试获取latch,获取失败那就等待
开闸放行Thread-10继续运行

Java反射机制

Java反射机制

Java反射机制概述

关于反射的理解

Reflection(反射)是被视为动态语言的关键,反射机制允许程序在执行期借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法

加载完类之后,在堆内存的方法区中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息。我们可以通过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构,所以,我们形象的称之为:反射

框架 = 反射 + 注解 + 设计模式

image-20200219141442814

体会反射机制的“动态性”

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
//体会反射的动态性
@Test
public void test2(){

for(int i = 0;i < 100;i++){
int num = new Random().nextInt(3);//0,1,2
String classPath = "";
switch(num){
case 0:
classPath = "java.util.Date";
break;
case 1:
classPath = "java.lang.Object";
break;
case 2:
classPath = "com.atguigu.java.Person";
break;
}

try {
Object obj = getInstance(classPath);
System.out.println(obj);
} catch (Exception e) {
e.printStackTrace();
}
}

}

/*
创建一个指定类的对象。
classPath:指定类的全类名
*/
public Object getInstance(String classPath) throws Exception {
Class clazz = Class.forName(classPath);
return clazz.newInstance();
}

反射机制能提供的功能

在运行时判断任意一个对象所属的类

在运行时构造任意一个类的对象

在运行时判断任意一个类所具有的成员变量和方法

在运行时获取泛型信息

在运行时调用任意一个对象的成员变量和方法

在运行时处理注解

生成动态代理

相关API

java.lang.Class:反射的源头

java.lang.reflect.Method

java.lang.reflect.Field

java.lang.reflect.Constructor

….

Class类的理解与获取Class的实例

Class类的理解

类的加载过程
程序经过javac.exe命令以后,会生成一个或多个字节码文件(.class结尾)。接着我们使用java.exe命令对某个字节码文件进行解释运行。相当于将某个字节码文件加载到内存中。此过程就称为类的加载。加载到内存中的类,我们就称为运行时类,此运行时类,就作为Class的一个实例

换句话说,Class的实例就对应着一个运行时类

加载到内存中的运行时类,会缓存一定的时间。在此时间之内,我们可以通过不同的方式来获取此运行时类

在Object类中定义了以下的方法,此方法将被所有子类继承:public final Class getClass()

以上的方法返回值的类型是一个Class类,此类是Java反射的源头,实际上所谓反射从程序的运行结果来看也很好理解,即:可以通过对象反射求出类的名称

获取Class实例的几种方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//方式一:调用运行时类的属性:.class
Class clazz1 = Person.class;
System.out.println(clazz1);
//方式二:通过运行时类的对象,调用getClass()
Person p1 = new Person();
Class clazz2 = p1.getClass();
System.out.println(clazz2);

//方式三:调用Class的静态方法:forName(String classPath)
Class clazz3 = Class.forName("com.atguigu.java.Person");
//clazz3 = Class.forName("java.lang.String");
System.out.println(clazz3);

System.out.println(clazz1 == clazz2);
System.out.println(clazz1 == clazz3);

//方式四:使用类的加载器:ClassLoader (了解)
ClassLoader classLoader = ReflectionTest.class.getClassLoader();
Class clazz4 = classLoader.loadClass("com.atguigu.java.Person");
System.out.println(clazz4);

System.out.println(clazz1 == clazz4);

Class类的常用方法

image-20200219180654962

总结

创建类的对象的方式

方式一:new + 构造器

方式二:要创建Xxx类的对象,可以考虑:Xxx、Xxxs、XxxFactory、XxxBuilder类中查看是否有静态方法的存在。可以调用其静态方法,创建Xxx对象

方式三:通过反射

哪些类型可以有Class对象

class:外部类,成员(成员内部类,静态内部类),局部内部类,匿名内部类
interface:接口
[]:数组
enum:枚举
annotation:注解@interface
primitive type:基本数据类型
void

ClassLoader

类的加载过程

img

类的加载器的作用

img

什么时候会发生类初始化

类的主动引用(一定会发生类的初始化)

  • 当虚拟机启动,先初始化main方法所在的类

  • new一个类的对象

  • 调用类的静态成员(除了final常量)和静态方法

  • 使用java.lang.reflect包的方法对类进行反射调用

  • 当初始化一个类,如果其父类没有被初始化,则先会初始化它的父类

类的被动引用(不会发生类的初始化)

  • 当访问一个静态域时,只有真正声明这个域的类才会被初始化,当通过子类引用父类的静态变量,不会导致子类初始化

  • 通过数组定义类引用,不会触发此类的初始化

  • 引用常量不会触发此类的初始化(常量在链接阶段就存入调用类的常量池中了)

类的加载器的分类

img

Java类编译运行的执行的流程

img

使用Classloader加载src目录下的配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Test
public void test2() throws Exception {

Properties pros = new Properties();
//此时的文件默认在当前的module下。
//读取配置文件的方式一:
//FileInputStream fis = new FileInputStream("jdbc.properties");
//FileInputStream fis = new FileInputStream("src\\jdbc1.properties");
//pros.load(fis);

//读取配置文件的方式二:使用ClassLoader
//配置文件默认识别为:当前module的src下
//这里使用系统类加载器
ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
InputStream is = classLoader.getResourceAsStream("jdbc1.properties");
pros.load(is);

String user = pros.getProperty("user");
String password = pros.getProperty("password");
System.out.println("user = " + user + ",password = " + password);
}

反射应用一

创建运行时类的对象

代码举例

1
2
3
4
5
6
7
8
9
public class NewInstanceTest {

@Test
public void test1() throws IllegalAccessException, InstantiationException {
Class<Person> personClass = Person.class;
Person person = personClass.newInstance();
System.out.println(person);
}
}

说明

newInstance()

调用此方法,创建对应的运行时类的对象。内部调用了运行时类的空参的构造器

要想此方法正常的创建运行时类的对象,要求:

  1. 运行时类必须提供空参的构造器

  2. 空参的构造器的访问权限得够。通常,设置为public

在javabean中要求提供一个public的空参构造器。原因:

  1. 便于通过反射,创建运行时类的对象

  2. 便于子类继承此运行时类时,默认调用super()时,保证父类此构造器

反射应用二

获取运行时类的完整结构

我们可以通过反射,获取对应的运行时类中所有的属性、方法、构造器、父类、接口、父类的泛型、包、注解、异常等

典型代码

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
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
@Test
public void test1(){

Class clazz = Person.class;

//获取属性结构
//getFields():获取当前运行时类及其父类中声明为public访问权限的属性
Field[] fields = clazz.getFields();
for(Field f : fields){
System.out.println(f);
}
System.out.println();

//getDeclaredFields():获取当前运行时类中声明的所属性。(不包含父类中声明的属性)
Field[] declaredFields = clazz.getDeclaredFields();
for(Field f : declaredFields){
System.out.println(f);
}
}

@Test
public void test1(){

Class clazz = Person.class;

//getMethods():获取当前运行时类及其所父类中声明为public权限的方法
Method[] methods = clazz.getMethods();
for(Method m : methods){
System.out.println(m);
}
System.out.println();
//getDeclaredMethods():获取当前运行时类中声明的所方法。(不包含父类中声明的方法)
Method[] declaredMethods = clazz.getDeclaredMethods();
for(Method m : declaredMethods){
System.out.println(m);
}
}

/*
获取构造器结构
*/
@Test
public void test1(){

Class clazz = Person.class;
//getConstructors():获取当前运行时类中声明为public的构造器
Constructor[] constructors = clazz.getConstructors();
for(Constructor c : constructors){
System.out.println(c);
}

System.out.println();
//getDeclaredConstructors():获取当前运行时类中声明的所的构造器
Constructor[] declaredConstructors = clazz.getDeclaredConstructors();
for(Constructor c : declaredConstructors){
System.out.println(c);
}

}

/*
获取运行时类的父类
*/
@Test
public void test2(){
Class clazz = Person.class;

Class superclass = clazz.getSuperclass();
System.out.println(superclass);
}

/*
获取运行时类的带泛型的父类
*/
@Test
public void test3(){
Class clazz = Person.class;

Type genericSuperclass = clazz.getGenericSuperclass();
System.out.println(genericSuperclass);
}

/*
获取运行时类的带泛型的父类的泛型
代码:逻辑性代码 vs 功能性代码
*/
@Test
public void test4(){
Class clazz = Person.class;

Type genericSuperclass = clazz.getGenericSuperclass();
ParameterizedType paramType = (ParameterizedType) genericSuperclass;
//获取泛型类型
Type[] actualTypeArguments = paramType.getActualTypeArguments();
//System.out.println(actualTypeArguments[0].getTypeName());
//或者如下方法
System.out.println(((Class)actualTypeArguments[0]).getName());
}

/*
获取运行时类实现的接口
*/
@Test
public void test5(){
Class clazz = Person.class;

Class[] interfaces = clazz.getInterfaces();
for(Class c : interfaces){
System.out.println(c);
}

System.out.println();
//获取运行时类的父类实现的接口
Class[] interfaces1 = clazz.getSuperclass().getInterfaces();
for(Class c : interfaces1){
System.out.println(c);
}

}
/*
获取运行时类所在的包
*/
@Test
public void test6(){
Class clazz = Person.class;

Package pack = clazz.getPackage();
System.out.println(pack);
}

/*
获取运行时类声明的注解
*/
@Test
public void test7(){
Class clazz = Person.class;

Annotation[] annotations = clazz.getAnnotations();
for(Annotation annos : annotations){
System.out.println(annos);
}
}

反射应用三

调用指定的属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Test
public void testField1() throws Exception {
Class clazz = Person.class;

//创建运行时类的对象
Person p = (Person) clazz.newInstance();

//1. getDeclaredField(String fieldName):获取运行时类中指定变量名的属性
Field name = clazz.getDeclaredField("name");

//2.保证当前属性是可访问的
name.setAccessible(true);
//3.获取、设置指定对象的此属性值
name.set(p,"Tom");

System.out.println(name.get(p));
}

调用指定的方法

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
 @Test
public void testMethod() throws Exception {

Class clazz = Person.class;

//创建运行时类的对象
Person p = (Person) clazz.newInstance();

/*
1.获取指定的某个方法
getDeclaredMethod():参数1 :指明获取的方法的名称 参数2:指明获取的方法的形参列表
*/
Method show = clazz.getDeclaredMethod("show", String.class);
//2.保证当前方法是可访问的
show.setAccessible(true);

/*
3. 调用方法的invoke():参数1:方法的调用者 参数2:给方法形参赋值的实参
invoke()的返回值即为对应类中调用的方法的返回值。
*/
Object returnValue = show.invoke(p,"CHN"); //String nation = p.show("CHN");
System.out.println(returnValue);

System.out.println("*************如何调用静态方法*****************");

// private static void showDesc()

Method showDesc = clazz.getDeclaredMethod("showDesc");
showDesc.setAccessible(true);
//如果调用的运行时类中的方法没返回值,则此invoke()返回null
//Object returnVal = showDesc.invoke(null);
Object returnVal = showDesc.invoke(Person.class);
System.out.println(returnVal);//null
}

调用指定的构造器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Test
public void testConstructor() throws Exception {
Class clazz = Person.class;

//private Person(String name)
/*
1.获取指定的构造器
getDeclaredConstructor():参数:指明构造器的参数列表
*/

Constructor constructor = clazz.getDeclaredConstructor(String.class);

//2.保证此构造器是可访问的
constructor.setAccessible(true);

//3.调用此构造器创建运行时类的对象
Person per = (Person) constructor.newInstance("Tom");
System.out.println(per);

}

反射应用四

动态代理

代理模式的原理

使用一个代理将对象包装起来, 然后用该代理对象取代原始对象。任何对原始对象的调用都要通过代理。代理对象决定是否以及何时将方法调用转到原始对象上

静态代理

1
2
3
4
5
6
7
8
//实现Runnable接口的方法创建多线程
Class MyThread implements Runnable{} //相当于被代理类
Class Thread implements Runnable{} //相当于代理类
main(){
MyThread t = new MyThread();
Thread thread = new Thread(t);
thread.start();//启动线程;调用线程的run()
}

静态代理的缺点

① 代理类和目标对象的类都是在编译期间确定下来,不利于程序的扩展。

② 每一个代理类只能为一个接口服务,这样一来程序开发中必然产生过多的代理。

动态代理的特点

动态代理是指客户通过代理类来调用其它对象的方法,并且是在程序运行时根据需要动态创建目标类的代理对

动态代理的实现

需要解决的两个主要问题

问题一:如何根据加载到内存中的被代理类,动态的创建一个代理类及其对象。 (通过

Proxy.newProxyInstance()实现)

问题二:当通过代理类的对象调用方法a时,如何动态的去调用被代理类中的同名方法a。(通过

InvocationHandler接口的实现类及其方法invoke())

代码实现

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
91
92
93
94
95
96
97
98
99
100
101
102
103
/**
*
* 动态代理的举例
*
*/

interface Human{

String getBelief();

void eat(String food);

}
//被代理类
class SuperMan implements Human{


@Override
public String getBelief() {
return "I believe I can fly!";
}

@Override
public void eat(String food) {
System.out.println("我喜欢吃" + food);
}
}

class HumanUtil{

public void method1(){
System.out.println("====================通用方法一====================");

}

public void method2(){
System.out.println("====================通用方法二====================");
}

}


class ProxyFactory{
//调用此方法,返回一个代理类的对象。解决问题一
public static Object getProxyInstance(Object obj){//obj:被代理类的对象
MyInvocationHandler handler = new MyInvocationHandler();

handler.bind(obj);

return Proxy.newProxyInstance(obj.getClass().getClassLoader(),obj.getClass().getInterfaces(),handler);
}

}

class MyInvocationHandler implements InvocationHandler{

private Object obj;//需要使用被代理类的对象进行赋值

public void bind(Object obj){
this.obj = obj;
}

//当我们通过代理类的对象,调用方法a时,就会自动的调用如下的方法:invoke()
//将被代理类要执行的方法a的功能就声明在invoke()中
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

HumanUtil util = new HumanUtil();
util.method1();

//method:即为代理类对象调用的方法,此方法也就作为了被代理类对象要调用的方法
//obj:被代理类的对象
Object returnValue = method.invoke(obj,args);

util.method2();

//上述方法的返回值就作为当前类中的invoke()的返回值。
return returnValue;

}
}

public class ProxyTest {

public static void main(String[] args) {
SuperMan superMan = new SuperMan();
//proxyInstance:代理类的对象
Human proxyInstance = (Human) ProxyFactory.getProxyInstance(superMan);
//当通过代理类对象调用方法时,会自动的调用被代理类中同名的方法
String belief = proxyInstance.getBelief();
System.out.println(belief);
proxyInstance.eat("四川麻辣烫");

System.out.println("*****************************");

NikeClothFactory nikeClothFactory = new NikeClothFactory();

ClothFactory proxyClothFactory = (ClothFactory) ProxyFactory.getProxyInstance(nikeClothFactory);

proxyClothFactory.produceCloth();

}
}

体会:反射的动态性

反射

反射机制是 Java 语言提供的一种基础功能,赋予程序在运行时自省(introspect)的能力。简单来说就是通过反射,可以在运行期间获取、检测和调用对象的属性和方法。

反射的使用场景

在现实中反射的使用场景有很多,比如以下几个。

使用场景一:编程工具 IDEA 或 Eclipse 等,在写代码时会有代码(属性或方法名)提示,就是因为使用了反射。

使用场景二:很多知名的框架,为了让程序更优雅更简洁,也会使用到反射。

例如,Spring 可以通过配置来加载不同的类,调用不同的方法,代码如下所示:

1
2
<bean id="person" class="com.spring.beans.Person" init-method="initPerson">
</bean>

例如,MyBatis 在 Mapper 使用外部类的 SQL 构建查询时,代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
@SelectProvider(type = PersonSql.class, method = "getListSql")
List<Person> getList();
class PersonSql {
public String getListSql() {
String sql = new SQL() {{
SELECT("*");
FROM("person");
}}.toString();
return sql;
}
}

使用场景三:数据库连接池,也会使用反射调用不同类型的数据库驱动,代码如下所示:

1
2
3
4
5
String url = "jdbc:mysql://127.0.0.1:3306/mydb";
String username = "root";
String password = "root";
Class.forName("com.mysql.jdbc.Driver");
Connection connection = DriverManager.getConnection(url, username, password);

当然反射还有其他很多类似的使用场景,这里就不一一列举,读者可以举一反三,想想在平常的开发中,还有哪些使用了反射功能的场景。

反射的基本使用

下来我们通过反射调用类中的某个方法,来学习反射的基本使用。

使用反射调用类中的方法,分为三种情况:

  • 调用静态方法
  • 调用公共方法
  • 调用私有方法

假设有一个实体类 MyReflect 包含了以上三种方法,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.interview.chapter4;
class MyReflect {
// 静态方法
public static void staticMd() {
System.out.println("Static Method");
}
// 公共方法
public void publicMd() {
System.out.println("Public Method");
}
// 私有方法
private void privateMd() {
System.out.println("Private Method");
}
}

下面分别来看,使用反射如何调用以上三种类型的方法。

反射调用静态方法
1
2
3
Class myClass = Class.forName("com.interview.chapter4.MyReflect");
Method method = myClass.getMethod("staticMd");
method.invoke(myClass);
反射调用公共方法
1
2
3
4
5
Class myClass = Class.forName("com.interview.chapter4.MyReflect");
// 创建实例对象(相当于 new )
Object instance = myClass.newInstance();
Method method2 = myClass.getMethod("publicMd");
method2.invoke(instance);
反射调用私有方法
1
2
3
4
5
6
Class myClass = Class.forName("com.interview.chapter4.MyReflect");
// 创建实例对象(相当于 new )
Object object = myClass.newInstance();
Method method3 = myClass.getDeclaredMethod("privateMd");
method3.setAccessible(true);
method3.invoke(object);

反射使用总结

反射获取调用类可以通过 Class.forName(),反射获取类实例要通过 newInstance(),相当于 new 一个新对象,反射获取方法要通过 getMethod(),获取到类方法之后使用 invoke() 对类方法进行调用。如果是类方法为私有方法的话,则需要通过 setAccessible(true) 来修改方法的访问限制,以上的这些操作就是反射的基本使用。

动态代理

动态代理可以理解为,本来应该自己做的事情,却交给别人代为处理,这个过程就叫做动态代理。

动态代理的使用场景

动态代理被广为人知的使用场景是 Spring 中的面向切面编程(AOP)。例如,依赖注入 @Autowired 和事务注解 @Transactional 等,都是利用动态代理实现的。

动态代理还可以封装一些 RPC 调用,也可以通过代理实现一个全局拦截器等。

动态代理和反射的关系

JDK 原生提供的动态代理就是通过反射实现的,但动态代理的实现方式还可以是 ASM(一个短小精悍的字节码操作框架)、cglib(基于 ASM)等,并不局限于反射。

下面我们分别来看:JDK 原生动态代理和 cglib 的实现。

1)JDK 原生动态代理

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
interface Animal {
void eat();
}
class Dog implements Animal {
@Override
public void eat() {
System.out.println("The dog is eating");
}
}
class Cat implements Animal {
@Override
public void eat() {
System.out.println("The cat is eating");
}
}

// JDK 代理类
class AnimalProxy implements InvocationHandler {
private Object target; // 代理对象
public Object getInstance(Object target) {
this.target = target;
// 取得代理对象
return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("调用前");
Object result = method.invoke(target, args); // 方法调用
System.out.println("调用后");
return result;
}
}

public static void main(String[] args) {
// JDK 动态代理调用
AnimalProxy proxy = new AnimalProxy();
Animal dogProxy = (Animal) proxy.getInstance(new Dog());
dogProxy.eat();
}

以上代码,我们实现了通过动态代理,在所有请求前、后都打印了一个简单的信息。

注意: JDK Proxy 只能代理实现接口的类(即使是 extends 继承类也是不可以代理的)。

2)cglib 动态代理

要是用 cglib 实现要添加对 cglib 的引用,如果是 maven 项目的话,直接添加以下代码:

1
2
3
4
5
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.12</version>
</dependency>

cglib 的具体实现,请参考以下代码:

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
class Panda {
public void eat() {
System.out.println("The panda is eating");
}
}
class CglibProxy implements MethodInterceptor {
private Object target; // 代理对象
public Object getInstance(Object target) {
this.target = target;
Enhancer enhancer = new Enhancer();
// 设置父类为实例类
enhancer.setSuperclass(this.target.getClass());
// 回调方法
enhancer.setCallback(this);
// 创建代理对象
return enhancer.create();
}
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("调用前");
Object result = methodProxy.invokeSuper(o, objects); // 执行方法调用
System.out.println("调用后");
return result;
}
}
public static void main(String[] args) {
// cglib 动态代理调用
CglibProxy proxy = new CglibProxy();
Panda panda = (Panda)proxy.getInstance(new Panda());
panda.eat();
}

以上程序执行的结果:

调用前

The panda is eating

调用后

由以上代码可以知道,cglib 的调用通过实现 MethodInterceptor 接口的 intercept 方法,调用 invokeSuper 进行动态代理的。它可以直接对普通类进行动态代理,并不需要像 JDK 代理那样,需要通过接口来完成,值得一提的是 Spring 的动态代理也是通过 cglib 实现的。

注意:cglib 底层是通过子类继承被代理对象的方式实现动态代理的,因此代理类不能是最终类(final),否则就会报错 java.lang.IllegalArgumentException: Cannot subclass final class xxx。

相关面试题

1.动态代理解决了什么问题?

答:首先它是一个代理机制,如果熟悉设计模式中的代理模式,我们会知道,代理可以看作是对调用目标的一个包装,这样我们对目标代码的调用不是直接发生的,而是通过代理完成,通过代理可以让调用者与实现者之间解耦。比如进行 RPC 调用,通过代理,可以提供更加友善的界面;还可以通过代理,做一个全局的拦截器。

2.动态代理和反射的关系是什么?

答:反射可以用来实现动态代理,但动态代理还有其他的实现方式,比如 ASM(一个短小精悍的字节码操作框架)、cglib 等。

3.以下描述错误的是?

A:cglib 的性能更高
B:Spring 中有使用 cglib 来实现动态代理
C:Spring 中有使用 JDK 原生的动态代理
D:JDK 原生动态代理性能更高

答:D

题目解析:Spring 动态代理的实现方式有两种:cglib 和 JDK 原生动态代理。

4.请补全以下代码?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class MyReflect {
// 私有方法
private void privateMd() {
System.out.println("Private Method");
}
}
class ReflectTest {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException {
Class myClass = Class.forName("MyReflect");
Object object = myClass.newInstance();
// 补充此行代码
method.setAccessible(true);
method.invoke(object);
}
}

答:Method method = myClass.getDeclaredMethod(“privateMd”);

题目解析:此题主要考的是私有方法的获取,私有方法的获取并不是通过 getMethod() 方式,而是通过 getDeclaredMethod() 获取的。

5.cglib 可以代理任何类这句话对吗?为什么?

答:这句话不完全对,因为 cglib 只能代理可以有子类的普通类,对于像最终类(final),cglib 是不能实现动态代理的,因为 cglib 的底层是通过继承代理类的子类来实现动态代理的,所以不能被继承类无法使用 cglib。

6.JDK 原生动态代理和 cglib 有什么区别?

答:JDK 原生动态代理和 cglib 区别如下:

  • JDK 原生动态代理是基于接口实现的,不需要添加任何依赖,可以平滑的支持 JDK 版本的升级;
  • cglib 不需要实现接口,可以直接代理普通类,需要添加依赖包,性能更高。

7.为什么 JDK 原生的动态代理必须要通过接口来完成?

答:这是由于 JDK 原生设计的原因,来看动态代理的实现方法 newProxyInstance() 的源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* ......
* @param loader the class loader to define the proxy class
* @param interfaces the list of interfaces for the proxy class to implement
* ......
*/
@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
// 省略其他代码

来看前两个参数的声明:

  • loader:为类加载器,也就是 target.getClass().getClassLoader()
  • interfaces:接口代理类的接口实现列表

看了上面的参数说明,我们就明白了,要使用 JDK 原生的动态只能通过实现接口来完成。

总结

通过本文可以知道 JDK 原生动态代理是使用反射实现的,但动态代理的实现方式不止有反射,还可以是 ASM(一个短小精悍的字节码操作框架)、cglib(基于 ASM)等。其中 JDK 原生的动态代理是通过接口实现的,而 cglib 是通过子类实现的,因此 cglib 不能代理最终类(final)。而反射不但可以反射调用静态方法,还可以反射调用普通方法和私有方法,其中调用私有方法时要设置 setAccessible 为 true。

Java8新特性

Java8新特性

Java8新特性简介

Lambda表达式

Lambda 是一个匿名函数,我们可以把 Lambda 表达式理解为是一段可以传递的代码(将代码像数据一样进行传递)。使用它可以写出更简洁、更灵活的代码。作为一种更紧凑的代码风格,使Java的语言表达能力得到了提升

IO流

IO流

File类的使用

File类的理解

  1. File类的一个对象,代表一个文件或一个文件目录(俗称:文件夹)
  2. File类声明在java.io包下
  3. File类中涉及到关于文件或文件目录的创建、删除、重命名、修改时间、文件大小等方法
    并未涉及到写入或读取文件内容的操作。如果需要读取或写入文件内容,必须使用IO流来完成
  4. 后续File类的对象常会作为参数传递到流的构造器中,指明读取或写入的”终点”

File的实例化

常用构造器

File(String filePath)

File(String parentPath,String childPath)

File(File parentFile,String childPath)

路径的分类

相对路径:相较于某个路径下,指明的路径

绝对路径:包含盘符在内的文件或文件目录的路径

说明

IDEA中:

如果大家开发使用JUnit中的单元测试方法测试,相对路径即为当前Module下。

如果大家使用main()测试,相对路径即为当前的Project下。

Eclipse中:

不管使用单元测试方法还是使用main()测试,相对路径都是当前的Project下。

路径分隔符

windows和DOS系统默认使用“\”来表示

UNIX和URL使用“/”来表示

File类的常用方法

IO流概述

Java IO原理

I/O是Input/Output的缩写, I/O技术是非常实用的技术,用于处理设备之间的数据传输。如读/写文件,网络通讯等

Java程序中,对于数据的输入/输出操作以“流(stream)” 的方式进行

java.io包下提供了各种“流”类和接口,用以获取不同种类的数据,并通过标准的方法输入或输出数据

流的分类

  1. 操作数据单位:字节流、字符流

  2. 数据的流向:输入流、输出流

  3. 流的角色:节点流、处理流

图示

img

流的体系结构

img

说明:红框对应的是IO流中的4个抽象基类,蓝框的流需要大家重点关注。

重点说明的几个流结构

img

输入输出的标准化过程

输入过程

① 创建File类的对象,指明读取的数据的来源。(要求此文件一定要存在)

② 创建相应的输入流,将File类的对象作为参数,传入流的构造器中

③ 具体的读入过程:

创建相应的byte[] 或 char[]。

④ 关闭流资源

说明:程序中出现的异常需要使用try-catch-finally处理。

输出过程

① 创建File类的对象,指明写出的数据的位置。(不要求此文件一定要存在)

② 创建相应的输出流,将File类的对象作为参数,传入流的构造器中

③ 具体的写出过程:

write(char[]/byte[] buffer,0,len)

④ 关闭流资源

说明:程序中出现的异常需要使用try-catch-finally处理

StringBuffer与StringBuilder

StringBuffer与StringBuilder

  • java.lang.StringBuffer代表可变的字符序列,JDK1.0中声明,可以对字符串内容进行增删,此时不会产生新的对象
  • StringBuffer很多方法与String相同
  • 作为参数传递时,方法内部可以改变值

String详解

String介绍

String:字符串,使用一对””引起来表示

Java程序中的所有字符串字面值(如”abc”)都作为此类的实例实现

  1. String声明为final的,不可被继承,代表不可变的字符序列

  2. String实现了Serializable接口:表示字符串是支持序列化的

    实现了Comparable接口:表示String可以比较大小

  3. String内部定义了final char[] value用于存储字符串数据

  4. 4.String:代表不可变的字符序列。简称:不可变性

面向对象二

面向对象二

继承性(inheritance)

为描述和处理个人信息,定义类Person:

1
2
3
4
5
6
7
8
9
class Person{
public String name;
public int age;
public Date birthDate;

public String getInfo(){

}
}

为描述和处理学生信息,定义Student:

1
2
3
4
5
6
7
8
9
10
class Student{
public String name;
public int age;
public Date birthDate;
public String school;

public String getInfo(){

}
}

面向对象三

面向对象三

关键字:static

static:静态的

类属性、类方法的设计思想

  • 类属性作为该类各个对象之间共享的变量。在设计类时,分析哪些属性不因对象的不同而改变,将这些属性设置为类属性。相应的方法设置为类方法
  • 如果方法与调用者无关,则这样的方法通常被声明为类方法,由于不需要创建对象就可以调用类方法,从而简化了方法的调用

可以用来修饰的结构

主要用来修饰类的内部结构,属性、方法、代码块、内部类

static修饰属性

静态变量(或类变量)

面向对象一

类与对象

面向对象学习的三条主线

1.Java类及类的成员:属性、方法、构造器、代码块、内部类

2.面向对象的大特征:封装性、继承性、多态性、(抽象性)

3.其它关键字:this、super、static、final、abstract、interface、package、import等

|