简

人生短暂,学海无边,而大道至简。


  • 首页

  • 归档

  • 分类

  • 标签

windows上安装mysql5.7问题和查询日志设置

发表于 2017-08-01 | 分类于 数据库

Windows上安装MySQL5.7,没有msi安装包,直接下载zip解压版。

解压到自己需要的安装目录,该目录创建my.ini并配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
[client]
port=3306
default-character-set=utf8
[mysqld]
# 设置为自己MYSQL的安装目录
basedir=E:\mysql\mysql-5.7.20-winx64
# 设置为MYSQL的数据目录
datadir=G:\mysql\Data
port=3306
character_set_server=utf8
sql_mode=NO_ENGINE_SUBSTITUTION,NO_AUTO_CREATE_USER
#开启查询缓存
explicit_defaults_for_timestamp=true
# 允许最大连接数
max_connections=200
# 服务端使用的字符集默认为8比特编码的latin1字符集
character-set-server=utf8
# 创建新表时将使用的默认存储引擎
default-storage-engine=INNODB
#底下代码开启,是数据库每次重启之后不要密码就可以连接数据库,适用于管理员忘记密码时的操作
#skip-grant-tables

进入bin目录,执行命令:mysqld -install进行安装

安装后查看data目录有没有生成相关文件,如果没有,再次执行下面命令。

1
2
输入mysqld --initialize-insecure自动生成无密码的root用户,产生data文件夹
输入mysqld --initialize自动生成带随机密码的root用户。(注:data文件夹存在时不能执行这个命令。可以先删除data目录下的所有文件夹)

这样就安装完成了,输入net start mysql启动服务,然后就可以进行连接数据库登录等操作了。

设置查询日志参考:http://www.cnblogs.com/kerrycode/p/7130403.html

CentOS升级,IP,安装软件等

发表于 2017-06-21 | 分类于 linux

将CentOS升级至最新版本

1. 检查你的CentOS版本。

# cat /etc/redhat-release
CentOS Linux release 7.1.1503 (Core)
2. 备份重要数据和目录(例如:/ etc,/ var,/ opt)

我建议,对于VMware虚拟机,请采用一个好的VMware快照或运行操作系统和数据的完整备份。(MySQL,Apache,NGINX,DNS等),可以查看本站如何备份数据的教程.

3. 用yum更新升级。

# yum clean all
# yum update
4. 用下面的命令重新启动服务器。

# reboot
5. 确认您的系统已成功升级

# cat /etc/redhat-release
CentOS Linux release 7.4.1611 (Core)

IP

在Centos7中,要查看IP地址,不再使用ifconfig命令,而是使用了IP命令。

查看虚拟机IP地址:ip addr

Centos的IP地址是网卡的inet 的值,第一个是本地服务地址,选择第二个。

接下来配置网卡,上面ens33。使用vi编辑器打开配置文件:

vi /etc/sysconfig/network-scripts/ifcfg-ens33

发现默认是不启动网卡的(ONBOOT=no)。J键将光标移动到最后一行,L键将光标移动到最后一列,再按i键进入到插入模式下,把这一项改为yes(ONBOOT=yes),保存退出。

然后重启网络服务:service network restart

再通过ip addr即可看到IP

安装基础软件

1、修改yum源
wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo
yum clean all
yum makecache
2、安装vim
um -y install vim*
3、安装wget命令
yum -y install wget
4、安装java
yum -y install java-1.8.0-openjd

Java动态性、反射机制、类加载、动态编译、脚本引擎、字节码操作......md

发表于 2017-05-11 | 分类于 java

动态性

Java动态性有:反射机制,动态编译/代理,字节码操作。常见的是反射和字节码操作。

Java让我们在运行时识别对象和类的信息,主要有2种方式:一种是传统的RTTI,它假定我们在编译时已经知道了所有的类型信息;另一种是反射机制,它允许我们在运行时发现和使用类的信息。

类的生命周期

20200412222135

类加载初始化阶段,必须对类进行初始化的情况:

  • 1、使用new关键字实例化对象时、读取或者设置一个类的静态字段(除final常量)以及调用静态方法的时候。

  • 2、使用反射包(java.lang.reflect)的方法对类进行反射调用时,如果类还没有被初始化,则需先进行初始化,这点对反射很重要。

  • 3、当初始化一个类的时候,如果其父类还没进行初始化则需先触发其父类的初始化。

  • 4、当Java虚拟机启动时,用户需要指定一个要执行的主类(包含main方法的类),虚拟机会先初始化这个主类

  • 5、当使用JDK 1.7 的动态语言支持时,如果一个java.lang.invoke.MethodHandle 实例最后解析结果为REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个方法句柄对应类没有初始化时。

不会被初始化的情况:

当访问静态域,只有真正声明这个域的类菜会被初始化。

    通过子类引用父类的静态变量,不会导致子类初始化。

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

引用常量不会触发此类的初始化。

Class对象

Class类的对象作用是运行时提供或获得某个对象的类型信息。

  理解RTTI在Java中的工作原理,首先需要知道类型信息在运行时是如何表示的,这是由Class对象来完成的,它包含了与类有关的信息。Class对象就是用来创建所有“常规”对象的,Java使用Class对象来执行RTTI,即使你正在执行的是类似类型转换这样的操作。

  每个类都会产生一个对应的Class对象,也就是保存在.class文件。

比如创建一个Shapes类,编译Shapes类后就会创建其包含Shapes类相关类型信息的Class对象,并保存在Shapes.class字节码文件中。

所有类都是在对其第一次使用时,动态加载到JVM的,当程序创建一个对类的静态成员的引用时,就会加载这个类。Class对象仅在需要的时候才会加载,static初始化是在类加载时进行的。

类加载器首先会检查这个类的Class对象是否已被加载过,如果尚未加载,默认的类加载器就会根据类名查找对应的.class文件。

  想在运行时使用类型信息,必须获取对象的Class对象的引用,使用功能Class.forName(“Base”)可以实现该目的,或者使用base.class。

注:使用.class来创建Class对象的引用时,不会自动初始化该Class对象,使用forName()会自动初始化该Class对象。为了使用类而做的准备工作一般有以下3个步骤:

加载:由类加载器完成,找到对应的字节码,创建一个Class对象

链接:验证类中的字节码,为静态域分配空间

初始化:如果该类有超类,则对其初始化,执行静态初始化器和静态初始化块

获取Class类的四种方式

1.调用运行时类本身的.class属性

1
Class clazz = String.class;

2.通过运行时类的对象获取

1
2
Person p = new Person();
Class clazz = p.getClass();

3.通过Class的静态方法获取:体现反射的动态性

1
Class clazz = Class.forName("com.util.xxx");

4.通过类的加载器

1
2
ClassLoader classLoader = this.getClass().getClassLoader();
Class clazz = classLoader.loadClass("com.util.xxx");

这是一个实例方法,需要一个ClassLoader对象来调用该方法,该方法将Class文件加载到内存时,并不会执行类的初始化,直到这个类第一次使用时才进行初始化.该方法因为需要得到一个ClassLoader对象,所以可以根据需要指定使用哪个类加载器.

反射

Class类和java.lang.reflect类库一起对反射进行了支持,该类库包含Field、Method和Constructor类,这些类的对象由JVM在启动时创建,以表示未知类里对应的成员。这就就可以使用Contructor创建新的对象,用get()和set()方法获取和修改类中与Field对象关联的字段,用invoke()方法调用与Method对象关联的方法。另外,还可以调用getFields()、getMethods()和getConstructors()等许多便利的方法,以返回表示字段、方法、以及构造器对象的数组,这样,对象信息可以在运行时被完全确定下来,而在编译时不需要知道关于类的任何事情。

  通过反射与一个未知类型的对象打交道时,JVM只是简单地检查这个对象,看它属于哪个特定的类。因此,那个类的.class对于JVM来说必须是可获取的,要么在本地机器上,要么从网络获取。所以对于RTTI和反射之间的真正区别只在于:

RTTI:编译器在编译时打开和检查.class文件

反射:运行时打开和检查.class文件

instanceof 关键字与isInstance方法

1
2
3
4
5
6
7
if(obj instanceof Animal){
Animal animal= (Animal) obj;
}
//isInstance方法则是Class类中的一个Native方法
if(Animal.class.isInstance(obj)){
Animal animal= (Animal) obj;
}

参考:https://blog.csdn.net/javazejian/article/details/70768369

动态编译

就是运行时动态生成java代码, JAVA 6.0引入了动态编译机制。

动态编译的应用场景

比如浏览器端编写java代码,上传服务器编译和运行。

服务器动态加载某些类文件进行编译。

动态编译的两种做法:

  • 通过Runtime调用javac,启动新的进程去操作,这是一种模拟操作。

Runtime run = Runtime.getRuntime();

Process process = run.exec(“javac -cp d:/myjava/ HelloWorld.java”);

  • 通过JavaCompiler动态编译
    1
    2
    3
    4
    5
    6
    7
    public static int compileFile(String sourceFile){
    // 动态编译
    JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
    int result = compiler.run(null, null, null,sourceFile);
    System.out.println(result==0?" 编译成功 ":" 编译失败 ");
    return result;
    }
    第一个参数: 为java编译器提供参数
    第二个参数: 得到 Java 编译器的输出信息
    第三个参数: 接收编译器的 错误信息
    第四个参数: 可变参数(是一个String数组)能传入一个或多个 Java 源文件
    返回值: 0表示编译成功,非0表示编译失败

动态编译运行好的类

1、通过Runtime.getRuntime() 运行启动新的进程运行

1
2
3
Runtime run = Runtime.getRuntime();
Process process = run.exec("java -cp d:/myjava HelloWorld");
// Process process = run.exec("java -cp "+dir+" "+classFile);

2、通过反射运行编译好的类

1
2
3
4
5
6
7
8
9
10
11
public static void runJavaClassByReflect(String dir,String classFile) throws Exception{
try {
URL[] urls = new URL[] {new URL("file:/"+dir)};
URLClassLoader loader = new URLClassLoader(urls);
Class c = loader.loadClass(classFile);
//调用加载类的main方法
c.getMethod("main",String[].class).invoke(null, (Object)new String[]{});
} catch (Exception e) {
e.printStackTrace();
}
}

脚本引擎

JAVA脚本引擎是从JDK6.0之后添加的新功能。

脚本引擎介绍:使得 Java 应用程序可以通过一套固定的接口与各种脚本引擎交互,从而达到在 Java 平台上调用各种脚本语言的目的。

Java 脚本 API 是连通 Java 平台和脚本语言的桥梁。可以把一些复杂异变的业务逻辑交给脚本语言处理。

获得脚本引擎对象

1
2
ScriptEngineManager sem = new ScriptEngineManager();
ScriptEngine engine = sem.getEngineByName("javascript");

Java 脚本 API 为开发者提供了如下功能:

获取脚本程序输入,通过脚本引擎运行脚本并返回运行结果,这是最核心的接口。
Java可以使用各种不同的实现,从而通用的调用js、python等脚本。

– 使用Js脚本引擎:Rhino

https://developer.mozilla.org/en-US/docs/Mozilla/Projects/Rhino

Rhino 是一种使用 Java 语言编写的 JavaScript 的开源实现,现在被集成进入JDK 6.0。

– 通过脚本引擎的运行上下文在脚本和 Java 平台间交换数据。

– 通过 Java 应用程序调用脚本函数。

字节码操作

就是在运行时对字节码操作,可以让我们实现如下功能:

动态生成新的类

动态改变某个类的结构(添加、删除、修改  新的属性和方法)

字节码操作的优势:比反射开销小,性能高

常见的字节码类库如下;

-BECL :是java classworking广泛使用的一种框架,可以深入理解JVM汇编语言进行类的操作细节。

基于JVM底层指令操作,比较难学。哈哈

-ASM :轻量级的java字节码操作框架类库,直接涉及JVM底层操作和指令

-CGLIB :是基于ASM的的实现,强大性能高

-Javassist :开源框架,较简单。

支持类库和bytecode来操作字节码,性能相对BECL,ASM性能低下,跟CGLIB差不多,很多开源框架都在使用它,常见。

Java动态编程——Javassist

发表于 2017-05-11 | 分类于 java

动态编程是相对于静态编程而言的,平时我们讨论比较多的就是静态编程语言,例如Java,与动态编程语言,例如JavaScript。

在静态编程中,类型检查是在编译时完成的,而动态编程中类型检查是在运行时完成的。所谓动态编程就是绕过编译过程在运行时进行操作的技术,在Java中有如下几种方式:

  • 反射:就是通过在运行时获得类型信息然后做相应的操作。

  • 动态编译:动态编译是从Java 6开始支持的,主要是通过一个JavaCompiler接口来完成的。通过这种方式我们可以直接编译一个已经存在的java文件,也可以在内存中动态生成Java代码,动态编译执行。

  • 调用JavaScript引擎:Java 6加入了对Script(JSR223)的支持。这是一个脚本框架,提供了让脚本语言来访问Java内部的方法。你可以在运行的时候找到脚本引擎,然后调用这个引擎去执行脚本。这个脚本API允许你为脚本语言提供Java支持。

  • 动态生成字节码:这种技术通过操作Java字节码的方式在JVM中生成新类或者对已经加载的类动态添加元素。

动态编程解决什么问题

在静态语言中引入动态特性,主要是为了解决一些使用场景的问题。

完全使用静态编程也办的到,只是付出的代价比较高,没有动态编程来的优雅。例如依赖注入框架Spring使用了反射,而Dagger2 却使用了代码生成的方式(APT)。

如

1、在那些依赖关系需要动态确认的场景:

2、需要在运行时动态插入代码的场景,比如动态代理的实现。

3、通过配置文件来实现相关功能的场景

Javassist

操作java字节码的工具BECL/ASM/CGLIB/Javassit

其中有两个比较流行,一个是ASM,一个是Javassit。

ASM 直接操作字节码指令,执行效率高,要是使用者掌握Java类字节码文件格式及指令,对使用者的要求比较高。

Javassit 提供了更高级的API,执行效率相对较差,但无需掌握字节码指令的知识,对使用者要求较低。

Javassit 是一个开源的分析、编辑和创建Java字节码的类库。是由东京工业大学的数学和计算机科学系的 Shigeru Chiba (千叶 滋)所创建的。它已加入了开放源代码JBoss应用服务器项目,通过使用Javassist对字节码操作为JBoss实现动态AOP框架。javassist是jboss的一个子项目,其主要的优点,在于简单,而且快速。直接使用java编码的形式,而不需要了解虚拟机指令,就能动态改变类的结构,或者动态生成类。

Javassist中最为重要的是ClassPool,CtClass ,CtMethod 以及 CtField这几个类。

ClassPool:一个基于HashMap实现的CtClass对象容器,其中键是类名称,值是表示该类的CtClass对象。默认的ClassPool使用与底层JVM相同的类路径,因此在某些情况下,可能需要向ClassPool添加类路径或类字节。

CtClass:表示一个类,这些CtClass对象可以从ClassPool获得。

CtMethods:表示类中的方法。

CtFields :表示类中的字段。

例子:

创建Class,Field,Method,Constructor

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public static void main(String[] args) throws Exception {
ClassPool pool = ClassPool.getDefault();
CtClass person = pool.makeClass("com.temp.bytecodeop.Person");

//创建属性
CtField name = CtField.make("private String name;", person);
CtField age = CtField.make("private int age;", person);
person.addField(name);
person.addField(age);

//创建方法
CtMethod getName = CtMethod.make("public String getName(){return name;}", person);
CtMethod setName = CtMethod.make("public void setName(String name){this.name=name;}", person);
person.addMethod(getName);
person.addMethod(setName);

//创建构造器
CtConstructor constructor = new CtConstructor(new CtClass[]{pool.get("java.lang.String"),CtClass.intType},person);
constructor.setBody("{this.name=name; this.age=age;}");
person.addConstructor(constructor);

person.writeFile("c:/Person");
System.out.println("success");
}

参考文献:https://blog.csdn.net/ShuSheng0007/article/details/81269295

https://www.cnblogs.com/sunfie/p/5154246.html

Java多线程并发

发表于 2017-05-11 | 分类于 java

锁的种类

Java 中锁的种类大致分为偏向锁,自旋锁,轻量级锁,重量级锁。锁的使用方式为:先提供偏向锁,如果不满足的时候,升级为轻量级锁,再不满足,升
级为重量级锁。自旋锁是一个过渡的锁状态,不是一种实际的锁类型。锁只能升级,不能降级。

锁的底层实现

Java 虚拟机中的同步(Synchronization)基于进入和退出管程(Monitor)对象实现。同步方法由方法调用指令读取运行时常量池中方法的 ACC_SYNCHRONIZED 标志来隐式实现的。

20200414091222

  • 对象头:存储对象的 hashCode、锁信息或分代年龄或 GC 标志,类型指针指向对象的类元数据,JVM 通过这个指针确定该对象是哪个类的实例等信息。
  • 实例变量:存放类的属性数据信息,包括父类的属性信息。
  • 填充数据:由于虚拟机要求对象起始地址必须是 8 字节的整数倍。填充数据不是必须存在的,仅仅是为了字节对齐。

在 Java 虚拟机(HotSpot)中,monitor 是由 ObjectMonitor 实现的。ObjectMonitor 中有两个队列,_WaitSet 和 _EntryList,以及_Owner 标记。其中_WaitSet是用于管理等待队列(wait)线程的,_EntryList 是用于管理锁池阻塞线程的,_Owner 标记用于记录当前执行线程。

当在对象上加锁时,加锁信息的数据是记录在对象头中。会在对象头中记录锁标记,锁标记指向的是 monitor 对象(也称为管程或监视器锁)的起始地址。每个对象都存在着一个 monitor 与之关联,对象与其 monitor 之间的关系有存在多种实现方式,如 monitor 可以与对象一起创建销毁或当线程试图获取对象锁时自动生
成,但当一个 monitor 被某个线程持有后,它便处于锁定状态。

当线程访问同步代码块时,首先会进入_EntryList,当线程获取锁标记后,monitor 中的_Owner 记录此线程,并在 monitor 中的计数器执行递增计算(+1)代表锁定,其他线程在_EntryList 中继续阻塞。若执行线程调用 wait 方法,则 monitor 中的计数器执行赋值为 0 计算,并将_Owner 标记赋值为 null,代表放弃锁,执行线程进如_WaitSet 中阻塞。若执行线程调用 notify/notifyAll 方法,_WaitSet 中的线程被唤醒,进入_EntryList 中阻塞,等待获取锁标记。若执行线程的同步代码执行结束,同样会释放锁标记,monitor 中的_Owner标记赋值为 null,且计数器赋值为 0 计算。

synchronized

synchronized 的锁对象包括: this, 临界资源对象,Class 类对象

this对象:

synchronized(this)和synchronized方法都是锁当前对象。

临界资源对象:

1
2
private Object o = new Object();
synchronized(o){ ... }

Class类对象: public static synchronized void testSync4(){} 或 synchronized(Test_02.class){}

synchronized 加锁的目的就是为了保证操作的原子性。

同步方法:synchronized T methodName(){}

同步方法锁定的是当前对象。当多线程通过同一个对象引用多次调用当前同步方法时,需同步执行。
同步方法只影响锁定同一个锁对象的同步方法。不影响其他线程调用非同步方法,或调用其他锁资源的同步方法。
同步方法只影响锁定同一个锁对象的同步方法。不影响其他线程调用非同步方法,或调用其他锁资源的同步方法。
同步方法只能保证当前方法的原子性,不能保证多个业务方法之间的互相访问的原子性。
同一个线程,多次调用同步代码,锁定同一个锁对象,可重入。
子类同步方法覆盖父类同步方法。可以指定调用父类的同步方法。
当同步方法中发生异常的时候,自动释放锁资源。不会影响其他线程的执行。如果异常需要对业务数据处理。

同步代码块:同步代码块的同步粒度更加细致,是商业开发中推荐的编程方式。可以定位到具体的同步位置,而不是简单的将方法整体实现同步逻辑。在效率上,相对更高。

volatile

变量的线程可见性。

在 CPU 计算过程中,会将计算过程需要的数据加载到 CPU 计算缓存中,当 CPU 计算中断时,有可能刷新缓存,重新读取内存中的数据。

在线程运行的过程中,如果某变量被其他线程修改,可能造成数据不一致的情况,从而导致结果错误。而 volatile修饰的变量是线程可见的,

当 JVM 解释 volatile 修饰的变量时,会通知 CPU,在计算过程中,每次使用变量参与计算时,都会检查内存中的数据是否发生变化,而不是一直使用 CPU 缓存中的数据,可以保证计算结果的正确。

volatile 只是通知底层计算时,CPU 检查内存数据,而不是让一个变量在多个线程中同步。

volatile只能保证可见性,不能保证原子性。

基础类型除了float和double之外,默认具有可见性。java.util.concurrent.atomic.AtomicXXX类都是具有原子性的。

1. 基本类型: AtomicInteger, AtomicLong, AtomicBoolean ;
2. 数组类型: AtomicIntegerArray, AtomicLongArray, AtomicReferenceArray ;
3. 引用类型: AtomicReference, AtomicStampedRerence, AtomicMarkableReference ;
4. 对象的属性修改类型: AtomicIntegerFieldUpdater, AtomicLongFieldUpdater, AtomicReferenceFieldUpdater 。

这些类存在的目的是对相应的数据进行原子操作。所谓原子操作,是指操作过程不会被中断,保证数据操作是以原子方式进行的。

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
public class Test_01 {
public static void main(String[] args) {
final Test_01_Container t = new Test_01_Container();
new Thread(new Runnable() {
@Override
public void run() {
for(int i = 0; i < 10; i++){
System.out.println("add Object to Container " + i);
t.add(new Object());
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();

new Thread(new Runnable(){
@Override
public void run() {
while(true){
if(t.size() == 5){
System.out.println("size = 5");
break;
}
}
}
}).start();
}
}
class Test_01_Container{
volatile List<Object> container = new ArrayList<>();

public void add(Object o){
this.container.add(o);
}
public int size(){
return this.container.size();
}
}

执行结果

1
2
3
4
5
6
7
8
9
10
11
add Object to Container 0
add Object to Container 1
add Object to Container 2
add Object to Container 3
add Object to Container 4
size = 5
add Object to Container 5
add Object to Container 6
add Object to Container 7
add Object to Container 8
add Object to Container 9

如果不设置container的可见性,另外一个进程就一直循环下去。

wait&notify

在Object.java中,定义了wait(), notify()和notifyAll()等方法。wait()的作用是让当前线程进入等待状态,同时,wait()也会让当前线程释放它所持有的锁。而notify()和notifyAll()的作用,则是唤醒当前对象上的等待线程;notify()是唤醒单个线程,而notifyAll()是唤醒所有的线程。

Object类中关于等待/唤醒的API详细信息如下:

1
2
3
4
5
notify()            唤醒在此对象监视器上等待的单个线程。
notifyAll() 唤醒在此对象监视器上等待的所有线程。
wait() 让当前线程处于"等待(阻塞)状态","直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法",当前线程被唤醒(进入"就绪状态")。
wait(long timeout) 让当前线程处于"等待(阻塞)状态","直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者超过指定的时间量",当前线程被唤醒(进入"就绪状态")。
wait(long timeout, int nanos) 让当前线程处于"等待(阻塞)状态","直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者其他某个线程中断当前线程,或者已超过某个实际时间量",当前线程被唤醒(进入"就绪状态")。
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
public class Test_02 {
public static void main(String[] args) {
final Test_02_Container t = new Test_02_Container();
final Object lock = new Object();

new Thread(new Runnable(){
@Override
public void run() {
synchronized (lock) {
if(t.size() != 5){
try {
lock.wait(); // 线程进入等待队列。
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("size = 5");
lock.notifyAll(); // 唤醒其他等待线程
}
}
}).start();

new Thread(new Runnable() {
@Override
public void run() {
synchronized (lock) {
for(int i = 0; i < 10; i++){
System.out.println("add Object to Container " + i);
t.add(new Object());
if(t.size() == 5){
lock.notifyAll();
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}).start();
}
}

class Test_02_Container{
List<Object> container = new ArrayList<>();

public void add(Object o){
this.container.add(o);
}

public int size(){
return this.container.size();
}
}


运行结果:
add Object to Container 0
add Object to Container 1
add Object to Container 2
add Object to Container 3
add Object to Container 4
size = 5
add Object to Container 5
add Object to Container 6
add Object to Container 7
add Object to Container 8
add Object to Container 9

yield

yield()的作用是让步。它能让当前线程由”运行状态”进入到”就绪状态”,从而让其它具有相同优先级的等待线程获取执行权;但是,并不能保证在当前线程调用yield()之后,其它具有相同优先级的线程就一定能获得执行权;也有可能是当前线程又进入到”运行状态”继续运行!

yield() 与 wait()的比较

wait()的作用是让当前线程由”运行状态”进入”等待(阻塞)状态”的同时,也会释放同步锁。而yield()的作用是让步,它也会让当前线程离开”运行状态”。它们的区别是:

(1) wait()是让线程由"运行状态"进入到"等待(阻塞)状态",而不yield()是让线程由"运行状态"进入到"就绪状态"。
(2) wait()是会线程释放它所持有对象的同步锁,而yield()方法不会释放锁。

sleep

sleep() 定义在Thread.java中。

sleep() 的作用是让当前线程休眠,即当前线程会从”运行状态”进入到”休眠(阻塞)状态”。sleep()会指定休眠时间,线程休眠的时间会大于/等于该休眠时间;在线程重新被唤醒时,它会由”阻塞状态”变成”就绪状态”,从而等待cpu的调度执行。

sleep() 与 wait()的比较

wait()的作用是让当前线程由”运行状态”进入”等待(阻塞)状态”的同时,也会释放同步锁。而sleep()的作用是也是让当前线程由”运行状态”进入到”休眠(阻塞)状态”。但是,wait()会释放对象的同步锁,而sleep()则不会释放锁。

join

join() 定义在Thread.java中。

join() 的作用:让”主线程”等待”子线程”结束之后才能继续运行。

终止线程

interrupt()的作用是中断本线程

本线程中断自己是被允许的;其它线程调用本线程的interrupt()方法时,会通过checkAccess()检查权限。这有可能抛出SecurityException异常。

如果本线程是处于阻塞状态:调用线程的wait(), wait(long)或wait(long, int)会让它进入等待(阻塞)状态,或者调用线程的join(), join(long), join(long, int), sleep(long), sleep(long, int)也会让它进入阻塞状态。

若线程在阻塞状态时,调用了它的interrupt()方法,那么它的”中断状态”会被清除并且会收到一个InterruptedException异常。例如,线程通过wait()进入阻塞状态,此时通过interrupt()中断该线程;调用interrupt()会立即将线程的中断标记设为”true”,但是由于线程处于阻塞状态,所以该”中断标记”会立即被清除为”false”,同时,会产生一个InterruptedException的异常。

如果线程被阻塞在一个Selector选择器中,那么通过interrupt()中断它时;线程的中断标记会被设置为true,并且它会立即从选择操作中返回。

如果不属于前面所说的情况,那么通过interrupt()中断线程时,它的中断标记会被设置为”true”。中断一个”已终止的线程”不会产生任何操作。

Thread中的stop()和suspend()方法,由于固有的不安全性,已经建议不再使用!

终止处于”阻塞状态”的线程: 在while(true)中不断的执行任务,当线程处于阻塞状态时,调用线程的interrupt()产生InterruptedException中断。中断的捕获在while(true)之外,这样就退出了while(true)循环!

注意:对InterruptedException的捕获务一般放在while(true)循环体的外面,这样,在产生异常时就退出了while(true)循环。否则,InterruptedException在while(true)循环体之内,就需要额外的添加退出处理。

终止处于”运行状态”的线程

通过”中断标记”终止线程。

1
2
3
4
5
6
@Override
public void run() {
while (!isInterrupted()) {
// 执行任务...
}
}

isInterrupted()是判断线程的中断标记是不是为true。当线程处于运行状态,并且我们需要终止它时;可以调用线程的interrupt()方法,使用线程的中断标记为true,即isInterrupted()会返回true。此时,就会退出while循环。

注意:interrupt()并不会终止处于”运行状态”的线程!它会将线程的中断标记设为true。

通过”额外添加标记”。

1
2
3
4
5
6
7
8
9
10
11
private volatile boolean flag= true;
protected void stopTask() {
flag = false;
}

@Override
public void run() {
while (flag) {
// 执行任务...
}
}

线程中有一个flag标记,它的默认值是true;并且我们提供stopTask()来设置flag标记。当我们需要终止该线程时,调用该线程的stopTask()方法就可以让线程退出while循环。

注意:将flag定义为volatile类型,是为了保证flag的可见性。即其它线程通过stopTask()修改了flag之后,本线程能看到修改后的flag的值。

interrupted() 和 isInterrupted()都能够用于检测对象的”中断标记”。

区别是,interrupted()除了返回中断标记之外,它还会清除中断标记(即将中断标记设为false);而isInterrupted()仅仅返回中断标记。

优先级和守护线程

每个线程都有一个优先级。”高优先级线程”会优先于”低优先级线程”执行。每个线程都可以被标记为一个守护进程或非守护进程。在一些运行的主线程中创建新的子线程时,子线程的优先级被设置为等于”创建它的主线程的优先级”,当且仅当”创建它的主线程是守护线程”时”子线程才会是守护线程”。

当Java虚拟机启动时,通常有一个单一的非守护线程(该线程通过是通过main()方法启动)。JVM会一直运行直到下面的任意一个条件发生,JVM就会终止运行:

1
2
(01) 调用了exit()方法,并且exit()有权限被正常执行。
(02) 所有的"非守护线程"都死了(即JVM中仅仅只有"守护线程")。

每一个线程都被标记为”守护线程”或”用户线程”。当只有守护线程运行时,JVM会自动退出。

生产消费者问题

生产/消费者问题是个非常典型的多线程问题,涉及到的对象包括”生产者”、”消费者”、”仓库”和”产品”。他们之间的关系如下:

(01) 生产者仅仅在仓储未满时候生产,仓满则停止生产。
(02) 消费者仅仅在仓储有产品时候才能消费,仓空则等待。
(03) 当消费者发现仓储没产品可消费时候会通知生产者生产。
(04) 生产者在生产出可消费产品时候,应该通知等待的消费者去消费。
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
public class TestContainer01<E> {

private final LinkedList<E> list = new LinkedList<>();
private final int MAX = 6;//最大数量
private int count = 0;//已有数量
public synchronized void put(E e){//生产
while(list.size() == MAX){
try {
this.wait();
} catch (InterruptedException e1) {
e1.printStackTrace();
}
}

list.add(e);
System.out.println("已生产"+e);
count++;
this.notifyAll();
}

public synchronized E get(){//消费
E e = null;
while(list.size() == 0){
try{
this.wait();
} catch (InterruptedException e1) {
e1.printStackTrace();
}
}
e = list.removeFirst();
count--;
System.out.println("已消费"+e);
this.notifyAll();
return e;
}

public static void main(String[] args) {
final TestContainer01<String> c = new TestContainer01<>();
int num = 10;//需要消费的数量
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < num; i++) {
System.out.println("消费者获得--"+c.get());
}
}
}, "consumer").start();
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < num; i++) {
c.put("container value "+i);
}
}
}, "producer").start();
}
}

面向对象写法:

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

public static void main(String[] args) throws InterruptedException {
Container<Object> container = new Container<>();
Producer producer = new Producer(container);
Customer customer = new Customer(container);
producer.produce();// 启动生产
// 需要消费多少
customer.consume(2);
Thread.sleep(2000);
customer.consume(3);
}
}

// 生产者
class Producer {
private Container<Object> container;

public Producer(Container<Object> container) {
this.container = container;
}

// 生产产品:新建一个线程向仓库中生产产品。
public void produce() {
new Thread() {
public void run() {
while (true) {
// 生产线程生产东西
container.put("produce----" + new Random().nextInt(100));
}
}
}.start();
}
}

// 消费者
class Customer {
private Container<Object> container;

public Customer(Container<Object> container) {
this.container = container;
}

// 消费产品:新建一个线程从仓库中消费产品
public void consume(int count) {// 消费数量
new Thread() {
public void run() {
int c = count;
while (c > 0) {
container.get();
c--;
}
}
}.start();
}
}

// 仓库
class Container<E> {
private LinkedList<E> list = new LinkedList<>();
private final int MAX = 6;// 最大数量
private int count = 0;// 已有数量

public int getCount() {
return count;
}

public synchronized void put(E e) {// 生产
while (list.size() == MAX) {
try {
this.wait();
} catch (InterruptedException e1) {
e1.printStackTrace();
}
}

list.add(e);
System.out.println("已生产" + e);
count++;
this.notifyAll();
}

public synchronized E get() {// 消费
E e = null;
while (list.size() == 0) {
try {
this.wait();
} catch (InterruptedException e1) {
e1.printStackTrace();
}
}
e = list.removeFirst();
count--;
System.out.println("已消费" + e);
this.notifyAll();
return e;
}
}

concurrent包锁

同步锁

即通过synchronized关键字来进行同步,实现对竞争资源的互斥访问的锁。Java 1.0版本中就已经支持同步锁了。

同步锁的原理是,对于每一个对象,有且仅有一个同步锁;不同的线程能共同访问该同步锁。但是,在同一个时间点,该同步锁能且只能被一个线程获取到。这样,获取到同步锁的线程就能进行CPU调度,从而在CPU上执行;而没有获取到同步锁的线程,必须进行等待,直到获取到同步锁之后才能继续运行。这就是,多线程通过同步锁进行同步的原理!

java.util.concurrent中的锁

相比同步锁,java.util.concurrent包中的锁的功能更加强大,它为锁提供了一个框架,该框架允许更灵活地使用锁,只是它的用法更难罢了。

包括:Lock接口,ReadWriteLock接口,LockSupport阻塞原语,Condition条件,AbstractOwnableSynchronizer/AbstractQueuedSynchronizer/AbstractQueuedLongSynchronizer三个抽象类,ReentrantLock独占锁,ReentrantReadWriteLock读写锁。由于CountDownLatch,CyclicBarrier和Semaphore也是通过AQS来实现的。

20200414093915

1. Lock接口

Lock 接口支持那些语义不同(重入、公平等)的锁规则。所谓语义不同,是指锁可是有”公平机制的锁”、”非公平机制的锁”、”可重入的锁”等等。”公平机制”是指”不同线程获取锁的机制是公平的”,而”非公平机制”则是指”不同线程获取锁的机制是非公平的”,”可重入的锁”是指同一个锁能够被一个线程多次获取。

2. ReadWriteLock

ReadWriteLock 接口以和Lock类似的方式定义了一些读取者可以共享而写入者独占的锁。JUC包只有一个类实现了该接口,即 ReentrantReadWriteLock,因为它适用于大部分的标准用法上下文。但程序员可以创建自己的、适用于非标准要求的实现。

3. AbstractOwnableSynchronizer/AbstractQueuedSynchronizer/AbstractQueuedLongSynchronizer

AbstractQueuedSynchronizer就是被称之为AQS的类,它是一个非常有用的超类,可用来定义锁以及依赖于排队阻塞线程的其他同步器;ReentrantLock,ReentrantReadWriteLock,CountDownLatch,CyclicBarrier和Semaphore等这些类都是基于AQS类实现的。AbstractQueuedLongSynchronizer 类提供相同的功能但扩展了对同步状态的 64 位的支持。两者都扩展了类 AbstractOwnableSynchronizer(一个帮助记录当前保持独占同步的线程的简单类)。

4. LockSupport

LockSupport提供”创建锁”和”其他同步类的基本线程阻塞原语”。

LockSupport的功能和”Thread中的Thread.suspend()和Thread.resume()有点类似”,LockSupport中的park() 和 unpark() 的作用分别是阻塞线程和解除阻塞线程。但是park()和unpark()不会遇到”Thread.suspend 和 Thread.resume所可能引发的死锁”问题。

5、Condition

Condition需要和Lock联合使用,它的作用是代替Object监视器方法,可以通过await(),signal()来休眠/唤醒线程。
Condition 接口描述了可能会与锁有关联的条件变量。这些变量在用法上与使用 Object.wait 访问的隐式监视器类似,但提供了更强大的功能。需要特别指出的是,单个 Lock 可能与多个 Condition 对象关联。为了避免兼容性问题,Condition 方法的名称与对应的 Object 版本中的不同。

6. ReentrantLock

ReentrantLock是独占锁。所谓独占锁,是指只能被独自占领,即同一个时间点只能被一个线程锁获取到的锁。ReentrantLock锁包括”公平的ReentrantLock”和”非公平的ReentrantLock”。”公平的ReentrantLock”是指”不同线程获取锁的机制是公平的”,而”非公平的  ReentrantLock”则是指”不同线程获取锁的机制是非公平的”,ReentrantLock是”可重入的锁”。

  (01) ReentrantLock实现了Lock接口。
  (02) ReentrantLock中有一个成员变量sync,sync是Sync类型;Sync是一个抽象类,而且它继承于AQS。
  (03) ReentrantLock中有”公平锁类”FairSync和”非公平锁类”NonfairSync,它们都是Sync的子类。ReentrantReadWriteLock中sync对象,是FairSync与NonfairSync中的一种,这也意味着ReentrantLock是”公平锁”或”非公平锁”中的一种,ReentrantLock默认是非公平锁。

7. ReentrantReadWriteLock

ReentrantReadWriteLock是读写锁接口ReadWriteLock的实现类,它包括子类ReadLock和WriteLock。ReentrantLock是共享锁,而WriteLock是独占锁。

  (01) ReentrantReadWriteLock实现了ReadWriteLock接口。
  (02) ReentrantReadWriteLock中包含sync对象,读锁readerLock和写锁writerLock。读锁ReadLock和写锁WriteLock都实现了Lock接口。
  (03) 和”ReentrantLock”一样,sync是Sync类型;而且,Sync也是一个继承于AQS的抽象类。Sync也包括”公平锁”FairSync和”非公平锁”NonfairSync。

8. CountDownLatch

  CountDownLatch是一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。
  CountDownLatch包含了sync对象,sync是Sync类型。CountDownLatch的Sync是实例类,它继承于AQS。

9. CyclicBarrier

CyclicBarrier是一个同步辅助类,允许一组线程互相等待,直到到达某个公共屏障点 (common barrier point)。因为该 barrier 在释放等待线程后可以重用,所以称它为循环 的 barrier。

CyclicBarrier是包含了”ReentrantLock对象lock”和”Condition对象trip”,它是通过独占锁实现的。

  CyclicBarrier和CountDownLatch的区别是:
  (01) CountDownLatch的作用是允许1或N个线程等待其他线程完成执行;而CyclicBarrier则是允许N个线程相互等待。
  (02) CountDownLatch的计数器无法被重置;CyclicBarrier的计数器可以被重置后使用,因此它被称为是循环的barrier。

10. Semaphore

Semaphore是一个计数信号量,它的本质是一个”共享锁”。

信号量维护了一个信号量许可集。线程可以通过调用acquire()来获取信号量的许可;当信号量中有可用的许可时,线程能获取该许可;否则线程必须等待,直到有可用的许可为止。 线程可以通过release()来释放它所持有的信号量许可。

和”ReentrantLock”一样,Semaphore包含了sync对象,sync是Sync类型;而且,Sync也是一个继承于AQS的抽象类。Sync也包括”公平信号量”FairSync和”非公平信号量”NonfairSync。

ReentrantLock

ReentrantLock是一个可重入的互斥锁,又被称为”独占锁”。

顾名思义,ReentrantLock锁在同一个时间点只能被一个线程锁持有;而可重入的意思是,ReentrantLock锁,可以被单个线程多次获取。

ReentrantLock分为”公平锁”和”非公平锁”。它们的区别体现在获取锁的机制上是否公平。”锁”是为了保护竞争资源,防止多个线程同时操作线程而出错,ReentrantLock在同一个时间点只能被一个线程获取(当某线程获取到”锁”时,其它线程就必须等待);ReentraantLock是通过一个FIFO的等待队列来管理获取该锁所有线程的。在”公平锁”的机制下,线程依次排队获取锁;而”非公平锁”在锁是可获取状态时,不管自己是不是在队列的开头都会获取锁。

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
// 创建一个 ReentrantLock ,默认是"非公平锁"。
ReentrantLock()
// 创建策略是fair的 ReentrantLock。fair为true表示是公平锁,fair为false表示是非公平锁。
ReentrantLock(boolean fair)
// 查询当前线程保持此锁的次数。
int getHoldCount()
// 返回目前拥有此锁的线程,如果此锁不被任何线程拥有,则返回 null。
protected Thread getOwner()
// 返回一个 collection,它包含可能正等待获取此锁的线程。
protected Collection<Thread> getQueuedThreads()
// 返回正等待获取此锁的线程估计数。
int getQueueLength()
// 返回一个 collection,它包含可能正在等待与此锁相关给定条件的那些线程。
protected Collection<Thread> getWaitingThreads(Condition condition)
// 返回等待与此锁相关的给定条件的线程估计数。
int getWaitQueueLength(Condition condition)
// 查询给定线程是否正在等待获取此锁。
boolean hasQueuedThread(Thread thread)
// 查询是否有些线程正在等待获取此锁。
boolean hasQueuedThreads()
// 查询是否有些线程正在等待与此锁有关的给定条件。
boolean hasWaiters(Condition condition)
// 如果是"公平锁"返回true,否则返回false。
boolean isFair()
// 查询当前线程是否保持此锁。
boolean isHeldByCurrentThread()
// 查询此锁是否由任意线程保持。
boolean isLocked()
// 获取锁。
void lock()
// 如果当前线程未被中断,则获取锁。
void lockInterruptibly()
// 返回用来与此 Lock 实例一起使用的 Condition 实例。
Condition newCondition()
// 仅在调用时锁未被另一个线程保持的情况下,才获取该锁。
boolean tryLock()
// 如果锁在给定等待时间内没有被另一个线程保持,且当前线程未被中断,则获取该锁。
boolean tryLock(long timeout, TimeUnit unit)
// 试图释放此锁。
void unlock()

实例:生产者消费者问题

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
public class Test2 {
public static void main(String[] args) throws InterruptedException {
Container<Object> container = new Container<>();
Producer producer = new Producer(container);
Customer customer = new Customer(container);
producer.produce();// 启动生产
// 需要消费多少
customer.consume(10);
Thread.sleep(2000);
System.out.println("-----------");
customer.consume(10);
}

// 生产者
static class Producer {
private Container<Object> container;

public Producer(Container<Object> container) {
this.container = container;
}

// 生产产品:新建一个线程向仓库中生产产品。
public void produce() {
new Thread() {
public void run() {
while (true) {
// 生产线程生产东西
container.put("produce----" + new Random().nextInt(100));
}
}
}.start();
}
}

// 消费者
static class Customer {
private Container<Object> container;

public Customer(Container<Object> container) {
this.container = container;
}

// 消费产品:新建一个线程从仓库中消费产品
public void consume(int count) {// 消费数量
new Thread() {
public void run() {
int c = count;
while (c > 0) {
if (container.getCount()>0) {
container.get();
c--;
}
}
}
}.start();
}
}

// 仓库
static class Container<E> {
private LinkedList<E> list = new LinkedList<>();
private final int MAX = 6;// 最大数量
private int count;// 已有数量
private Lock lock; // 独占锁

public int getCount() {
return count;
}

public Container() {
this.count = 0;
this.lock = new ReentrantLock();
}

public void put(E e) {// 生产
lock.lock();
try {
if (list.size()<MAX) {
list.add(e);
System.out.println("已生产" + e);
count++;
}
} finally {
lock.unlock();
}
}

public E get() {// 消费
E e = null;
lock.lock();
try {
e = list.removeFirst();
count--;
System.out.println("已消费" + e);
} finally {
lock.unlock();
}
return e;
}
}
}

Condition

Condition是需要和Lock联合使用的:通过Condition中的await()方法,能让线程阻塞[类似于wait()];通过Condition的signal()方法,能让唤醒线程[类似于notify()]。

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

public static void main(String[] args) throws InterruptedException {
Container<Object> container = new Container<>();
Producer producer = new Producer(container);
Customer customer = new Customer(container);
producer.produce();// 启动生产
// 需要消费多少
customer.consume(10);
}

// 生产者
static class Producer {
private Container<Object> container;

public Producer(Container<Object> container) {
this.container = container;
}

// 生产产品:新建一个线程向仓库中生产产品。
public void produce() {
new Thread() {
public void run() {
while (container.isPro()) {
// 生产线程生产东西
container.put("produce----" + new Random().nextInt(100));
}
}
}.start();
}
}

// 消费者
static class Customer {
private Container<Object> container;

public Customer(Container<Object> container) {
this.container = container;
}

// 消费产品:新建一个线程从仓库中消费产品
public void consume(int count) {// 消费数量
new Thread() {
public void run() {
int c = count;
while (c > 0) {
if (container.getCount()>0) {
container.get();
c--;
}
}
container.setPro(false);
}
}.start();
}
}

// 仓库
static class Container<E> {
private LinkedList<E> list = new LinkedList<>();
private final int MAX = 6;// 最大数量
private int count;// 已有数量
private boolean pro;//是否需要生产
private Lock lock; // 独占锁
//条件 - Condition, 为Lock增加条件。当条件满足时,做什么事情,如加锁或解锁。如等待或唤醒
private Condition fullCondtion;
private Condition emptyCondtion;

public int getCount() {
return count;
}


public boolean isPro() {
return pro;
}


public void setPro(boolean pro) {
this.pro = pro;
}


public Container() {
this.pro = true;
this.count = 0;
this.lock = new ReentrantLock();
this.fullCondtion = lock.newCondition();
this.emptyCondtion = lock.newCondition();
}

public void put(E e) {// 生产
lock.lock();
try {
while (list.size()==MAX) {//进入等待状态
System.out.println("等待消费");
fullCondtion.await();
}
list.add(e);
System.out.println("已生产" + e);
count++;
emptyCondtion.signal();//通知消费者可以消费了

} catch (InterruptedException e1) {
e1.printStackTrace();
} finally {
lock.unlock();
}
}

public E get() {// 消费
E e = null;
lock.lock();
try {
while (list.size()==0) {//进入等待
System.out.println("等待生产");
emptyCondtion.await();
}
e = list.removeFirst();
count--;
System.out.println("已消费" + e);
fullCondtion.signal();
} catch (InterruptedException e1) {
e1.printStackTrace();
} finally {
lock.unlock();
}
return e;
}
}
}

公平锁

CAS(Compare And Swap),即比较并交换。是解决多线程并行情况下使用锁造成性能损耗的一种机制,CAS操作包含三个操作数–内存位置(V)、预期原值(A)和新值(B)。如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值。否则,处理器不做任何操作。无论哪种情况,它都会在CAS指令之前返回该位置的值。CAS有效地说明了”我认为位置V应该包含值A;如果包含该值,则将B放到这个位置;否则,不要更改该位置,只告诉我这个位置现在的值即可。

AQS(AbstractQueuedSynchronizer),AQS是JDK下提供的一套用于实现基于FIFO等待队列的阻塞锁和相关的同步器的一个同步框架。这个抽象类被设计为作为一些可用原子int值来表示状态的同步器的基类。如果你有看过类似 CountDownLatch 类的源码实现,会发现其内部有一个继承了 AbstractQueuedSynchronizer 的内部类 Sync。可见 CountDownLatch 是基于AQS框架来实现的一个同步器.类似的同步器在JUC下还有不少。(如:Semaphore)

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
lock.lockInterruptibly(); // 可尝试打断,阻塞等待锁。可以被其他的线程打断阻塞状态

public class Test_04 {

public static void main(String[] args) {
TestReentrantlock t = new TestReentrantlock();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
t1.start();
t2.start();
}
}

class TestReentrantlock extends Thread{
// 定义一个公平锁
private static ReentrantLock lock = new ReentrantLock(true);
public void run(){
for(int i = 0; i < 5; i++){
lock.lock();
try{
System.out.println(Thread.currentThread().getName() + " get lock");
}finally{
lock.unlock();
}
}
}
}

尝试锁

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
public class Test_02 {
Lock lock = new ReentrantLock();

void m1(){
try{
lock.lock();
for(int i = 0; i < 10; i++){
TimeUnit.SECONDS.sleep(1);
System.out.println("m1() method " + i);
}
}catch(InterruptedException e){
e.printStackTrace();
}finally{
lock.unlock();
}
}

void m2(){
boolean isLocked = false;
try{
// 尝试锁, 如果有锁,无法获取锁标记,返回false;如果获取锁标记,返回true
// isLocked = lock.tryLock();

// 阻塞尝试锁,阻塞参数代表的时长,尝试获取锁标记。
// 如果超时,不等待。直接返回。
isLocked = lock.tryLock(5, TimeUnit.SECONDS);
if(isLocked){
System.out.println("m2() method synchronized");
}else{
System.out.println("m2() method unsynchronized");
}
}catch(Exception e){
e.printStackTrace();
}finally{
if(isLocked){
// 尝试锁在解除锁标记的时候,一定要判断是否获取到锁标记。
// 如果当前线程没有获取到锁标记,会抛出异常。
lock.unlock();
}
}
}

public static void main(String[] args) {
final Test_02 t = new Test_02();
new Thread(new Runnable() {
@Override
public void run() {
t.m1();
}
}).start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
new Thread(new Runnable() {
@Override
public void run() {
t.m2();
}
}).start();
}
}
1
2
3
4
5
6
7
8
9
10
11
m1() method 0
m1() method 1
m1() method 2
m1() method 3
m1() method 4
m2() method unsynchronized
m1() method 5
m1() method 6
m1() method 7
m1() method 8
m1() method 9

非公平锁

非公平锁和公平锁在获取锁的方法上,流程是一样的;它们的区别主要表现在 尝试获取锁的机制不同。

公平锁在每次尝试获取锁时,都是采用公平策略(根据等待队列依次排序等待);而非公平锁在每次尝试获取锁时,都是采用的非公平策略(无视等待队列,直接尝试获取锁,如果锁是空闲的,即可获取状态,则获取锁)。

  1. lock()

公平锁 – 公平锁的lock()函数,会直接调用acquire(1)。

非公平锁 – 非公平锁会先判断当前锁的状态是不是空闲,是的话,就不排队,而是直接获取锁。

2、”公平锁”和”非公平锁”关于tryAcquire()的对比

公平锁和非公平锁,它们尝试获取锁的方式不同。

公平锁在尝试获取锁时,即使”锁”没有被任何线程锁持有,它也会判断自己是不是CLH等待队列的表头;是的话,才获取锁。

而非公平锁在尝试获取锁时,如果”锁”没有被任何线程持有,则不管它在CLH队列的何处,它都直接获取锁。

Condition

Condition的作用是对锁进行更精确的控制。Condition中的await()方法相当于Object的wait()方法,Condition中的signal()方法相当于Object的notify()方法,Condition中的signalAll()相当于Object的notifyAll()方法。不同的是,Object中的wait(),notify(),notifyAll()方法是和”同步锁”(synchronized关键字)捆绑使用的;而Condition是需要与”互斥锁”/“共享锁”捆绑使用的。

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
public class TestContainer02<E> {

private final LinkedList<E> list = new LinkedList<>();
private final int MAX = 10;
private int count = 0;

private Lock lock = new ReentrantLock();
private Condition producer = lock.newCondition();
private Condition consumer = lock.newCondition();

public int getCount(){
return count;
}

public void put(E e){
lock.lock();
try {
while(list.size() == MAX){
System.out.println(Thread.currentThread().getName() + " 等待。。。");
// 进入等待队列。释放锁标记。
// 借助条件,进入的等待队列。
producer.await();
}
System.out.println(Thread.currentThread().getName() + " put 。。。");
list.add(e);
count++;
// 借助条件,唤醒所有的消费者。
consumer.signalAll();
} catch (InterruptedException e1) {
e1.printStackTrace();
} finally {
lock.unlock();
}
}

public E get(){
E e = null;

lock.lock();
try {
while(list.size() == 0){
System.out.println(Thread.currentThread().getName() + " 等待。。。");
// 借助条件,消费者进入等待队列
consumer.await();
}
System.out.println(Thread.currentThread().getName() + " get 。。。");
e = list.removeFirst();
count--;
// 借助条件,唤醒所有的生产者
producer.signalAll();
} catch (InterruptedException e1) {
e1.printStackTrace();
} finally {
lock.unlock();
}

return e;
}
}

LockSupport

LockSupport是用来创建锁和其他同步类的基本线程阻塞原语。

LockSupport中的park() 和 unpark() 的作用分别是阻塞线程和解除阻塞线程,而且park()和unpark()不会遇到”Thread.suspend 和 Thread.resume所可能引发的死锁”问题。

因为park() 和 unpark()有许可的存在;调用 park() 的线程和另一个试图将其 unpark() 的线程之间的竞争将保持活性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 返回提供给最近一次尚未解除阻塞的 park 方法调用的 blocker 对象,如果该调用不受阻塞,则返回 null。
static Object getBlocker(Thread t)
// 为了线程调度,禁用当前线程,除非许可可用。
static void park()
// 为了线程调度,在许可可用之前禁用当前线程。
static void park(Object blocker)
// 为了线程调度禁用当前线程,最多等待指定的等待时间,除非许可可用。
static void parkNanos(long nanos)
// 为了线程调度,在许可可用前禁用当前线程,并最多等待指定的等待时间。
static void parkNanos(Object blocker, long nanos)
// 为了线程调度,在指定的时限前禁用当前线程,除非许可可用。
static void parkUntil(long deadline)
// 为了线程调度,在指定的时限前禁用当前线程,除非许可可用。
static void parkUntil(Object blocker, long deadline)
// 如果给定线程的许可尚不可用,则使其可用。
static void unpark(Thread thread)
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
public class LockSupportTest1 {

private static Thread mainThread;

public static void main(String[] args) {

ThreadA ta = new ThreadA("ta");
// 获取主线程
mainThread = Thread.currentThread();

System.out.println(Thread.currentThread().getName()+" start ta");
ta.start();

System.out.println(Thread.currentThread().getName()+" block");
// 主线程阻塞
LockSupport.park(mainThread);

System.out.println(Thread.currentThread().getName()+" continue");
}

static class ThreadA extends Thread{

public ThreadA(String name) {
super(name);
}

public void run() {
System.out.println(Thread.currentThread().getName()+" wakup others");
// 唤醒"主线程"
LockSupport.unpark(mainThread);
}
}
}

共享锁

CountDownLatchJUC(java.util.concurrent)中的共享锁有CountDownLatch, CyclicBarrier, Semaphore, ReentrantReadWriteLock等

ReadWriteLock,顾名思义,是读写锁。它维护了一对相关的锁 - - “读取锁”和”写入锁”,一个用于读取操作,另一个用于写入操作。

"读取锁"用于只读操作,它是"共享锁",能同时被多个线程获取。
"写入锁"用于写入操作,它是"独占锁",写入锁只能被一个线程锁获取。

注意:不能同时存在读取锁和写入锁!

ReadWriteLock是一个接口。ReentrantReadWriteLock是它的实现类,ReentrantReadWriteLock包括子类ReadLock和WriteLock。

CountDownLatch

CountDownLatch是一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。

CountDownLatch和CyclicBarrier的区别

(01) CountDownLatch的作用是允许1或N个线程等待其他线程完成执行;而CyclicBarrier则是允许N个线程相互等待。
(02) CountDownLatch的计数器无法被重置;CyclicBarrier的计数器可以被重置后使用,因此它被称为是循环的barrier。
1
2
3
4
5
6
7
8
9
10
11
12
13
CountDownLatch(int count)
//构造一个用给定计数初始化的 CountDownLatch。

// 使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断。
void await()
// 使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断或超出了指定的等待时间。
boolean await(long timeout, TimeUnit unit)
// 递减锁存器的计数,如果计数到达零,则释放所有等待的线程。
void countDown()
// 返回当前计数。
long getCount()
// 返回标识此锁存器及其状态的字符串。
String toString()
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
public class Test_03 {
public static void main(String[] args) {
final Test_03_Container t = new Test_03_Container();
final CountDownLatch latch = new CountDownLatch(1);

new Thread(new Runnable(){
@Override
public void run() {
if(t.size() != 5){
try {
latch.await(); // 等待门闩的开放。 不是进入等待队列
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("size = 5");
}
}).start();

new Thread(new Runnable() {
@Override
public void run() {
for(int i = 0; i < 10; i++){
System.out.println("add Object to Container " + i);
t.add(new Object());
if(t.size() == 5){
latch.countDown(); // 门闩-1
}
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
}

class Test_03_Container{
List<Object> container = new ArrayList<>();

public void add(Object o){
this.container.add(o);
}

public int size(){
return this.container.size();
}
}


结果:
add Object to Container 0
add Object to Container 1
add Object to Container 2
add Object to Container 3
add Object to Container 4
size = 5
add Object to Container 5
add Object to Container 6
add Object to Container 7
add Object to Container 8
add Object to Container 9

CyclicBarrier

CyclicBarrier是一个同步辅助类,允许一组线程互相等待,直到到达某个公共屏障点 (common barrier point)。因为该 barrier 在释放等待线程后可以重用,所以称它为循环 的 barrier。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
CyclicBarrier(int parties)
//创建一个新的 CyclicBarrier,它将在给定数量的参与者(线程)处于等待状态时启动,但它不会在启动 barrier 时执行预定义的操作。
CyclicBarrier(int parties, Runnable barrierAction)
//创建一个新的 CyclicBarrier,它将在给定数量的参与者(线程)处于等待状态时启动,并在启动 barrier 时执行给定的屏障操作,该操作由最后一个进入 barrier 的线程执行。

int await()
//在所有参与者都已经在此 barrier 上调用 await 方法之前,将一直等待。
int await(long timeout, TimeUnit unit)
//在所有参与者都已经在此屏障上调用 await 方法之前将一直等待,或者超出了指定的等待时间。
int getNumberWaiting()
//返回当前在屏障处等待的参与者数目。
int getParties()
//返回要求启动此 barrier 的参与者数目。
boolean isBroken()
//查询此屏障是否处于损坏状态。
void reset()
//将屏障重置为其初始状态。

Semaphore

Semaphore是一个计数信号量,它的本质是一个”共享锁”。

信号量维护了一个信号量许可集。线程可以通过调用acquire()来获取信号量的许可;当信号量中有可用的许可时,线程能获取该许可;否则线程必须等待,直到有可用的许可为止。 线程可以通过release()来释放它所持有的信号量许可。

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
// 创建具有给定的许可数和非公平的公平设置的 Semaphore。
Semaphore(int permits)
// 创建具有给定的许可数和给定的公平设置的 Semaphore。
Semaphore(int permits, boolean fair)

// 从此信号量获取一个许可,在提供一个许可前一直将线程阻塞,否则线程被中断。
void acquire()
// 从此信号量获取给定数目的许可,在提供这些许可前一直将线程阻塞,或者线程已被中断。
void acquire(int permits)
// 从此信号量中获取许可,在有可用的许可前将其阻塞。
void acquireUninterruptibly()
// 从此信号量获取给定数目的许可,在提供这些许可前一直将线程阻塞。
void acquireUninterruptibly(int permits)
// 返回此信号量中当前可用的许可数。
int availablePermits()
// 获取并返回立即可用的所有许可。
int drainPermits()
// 返回一个 collection,包含可能等待获取的线程。
protected Collection<Thread> getQueuedThreads()
// 返回正在等待获取的线程的估计数目。
int getQueueLength()
// 查询是否有线程正在等待获取。
boolean hasQueuedThreads()
// 如果此信号量的公平设置为 true,则返回 true。
boolean isFair()
// 根据指定的缩减量减小可用许可的数目。
protected void reducePermits(int reduction)
// 释放一个许可,将其返回给信号量。
void release()
// 释放给定数目的许可,将其返回到信号量。
void release(int permits)
// 返回标识此信号量的字符串,以及信号量的状态。
String toString()
// 仅在调用时此信号量存在一个可用许可,才从信号量获取许可。
boolean tryAcquire()
// 仅在调用时此信号量中有给定数目的许可时,才从此信号量中获取这些许可。
boolean tryAcquire(int permits)
// 如果在给定的等待时间内此信号量有可用的所有许可,并且当前线程未被中断,则从此信号量获取给定数目的许可。
boolean tryAcquire(int permits, long timeout, TimeUnit unit)
// 如果在给定的等待时间内,此信号量有可用的许可并且当前线程未被中断,则从此信号量获取一个许可。
boolean tryAcquire(long timeout, TimeUnit unit)

ThreadLocal

ThreadLocal线程本地副本

  • 就是一个Map,key -> Thread.getCurrentThread(). value ->线程需要保存的变量。
  • ThreadLocal.set(value) -> map.put(Thread.getCurrentThread(), value);
  • ThreadLocal.get() -> map.get(Thread.getCurrentThread());
  • 内存问题 : 在并发量高的时候,可能有内存溢出。
  • 使用ThreadLocal的时候,一定注意回收资源问题,每个线程结束之前,将当前线程保存的线程变量一定要删除 。
  • ThreadLocal.remove();

同步容器

1、Map和Set

ConcurrentHashMap/ConcurrentHashSet

底层哈希实现的同步 Map(Set)。效率高,线程安全。使用系统底层技术实现线程安全。量级较 synchronized 低。key 和 value 不能为 null。

ConcurrentSkipListMap/ConcurrentSkipListSet

底层跳表(SkipList)实现的同步 Map(Set)。有序,效率比 ConcurrentHashMap 稍低。

2、List

CopyOnWriteArrayList/CopyOnWriteSet

写时复制集合,每次写入数据,都会创建一个新的底层数组。写入效率低,读取效率高。

3、 Queue

  • ConcurrentLinkedQueue 链表同步队列。
  • LinkedBlockingQueue 阻塞队列,队列容量不足自动阻塞,队列容量为 0 自动阻塞。
  • ArrayBlockingQueue 底层数组实现的有界队列。自动阻塞。

根据调用 API(add/put/offer)不同,有不同特性。

当容量不足的时候,有阻塞能力。

add 方法在容量不足的时候,抛出异常。
put 方法在容量不足的时候,阻塞等待。

offer 方法

单参数 offer 方法,不阻塞。容量不足的时候,返回 false。当前新增数据操作放弃。

三参数 offer 方法(offer(value,times,timeunit)),容量不足的时候,阻塞 times 时长(单位为 timeunit),如果在阻塞时长内,

有容量空闲,新增数据返回 true。如果阻塞时长范围内,无容量空闲,放弃新增数据,返回 false。

  • DelayQueue 延时队列。根据比较机制,实现自定义处理顺序的队列。常用于定时任务。如:定时关机。

  • LinkedTransferQueue 转移队列,使用 transfer 方法,实现数据的即时处理。没有消费者,就阻塞。

  • SynchronusQueue 同步队列,是一个容量为 0 的队列。是一个特殊的 TransferQueue。必须现有消费线程等待,才能使用的队列。

    add 方法,无阻塞。若没有消费线程阻塞等待数据,则抛出异常。
    put 方法,有阻塞。若没有消费线程阻塞等待数据,则阻塞

List的实现类主要有: LinkedList, ArrayList, Vector, Stack。

(01) LinkedList是双向链表实现的双端队列;它不是线程安全的,只适用于单线程。
(02) ArrayList是数组实现的队列,它是一个动态数组;它也不是线程安全的,只适用于单线程。
(03) Vector是数组实现的矢量队列,它也一个动态数组;不过和ArrayList不同的是,Vector是线程安全的,它支持并发。
(04) Stack是Vector实现的栈;和Vector一样,它也是线程安全的。

Set的实现类主要有: HastSet和TreeSet。

(01) HashSet是一个没有重复元素的集合,它通过HashMap实现的;HashSet不是线程安全的,只适用于单线程。
(02) TreeSet也是一个没有重复元素的集合,不过和HashSet不同的是,TreeSet中的元素是有序的;它是通过TreeMap实现的;TreeSet也不是线程安全的,只适用于单线程。

Map的实现类主要有: HashMap,WeakHashMap, Hashtable和TreeMap。

(01) HashMap是存储"键-值对"的哈希表;它不是线程安全的,只适用于单线程。
(02) WeakHashMap是也是哈希表;和HashMap不同的是,HashMap的"键"是强引用类型,而WeakHashMap的"键"是弱引用类型,也就是说当WeakHashMap 中的某个键不再正常使用时,会被从WeakHashMap中被自动移除。WeakHashMap也不是线程安全的,只适用于单线程。
(03) Hashtable也是哈希表;和HashMap不同的是,Hashtable是线程安全的,支持并发。
(04) TreeMap也是哈希表,不过TreeMap中的"键-值对"是有序的,它是通过R-B Tree(红黑树)实现的;TreeMap不是线程安全的,只适用于单线程。

线程池

20200414113139

1. Executor

它是执行者接口,它是来执行任务的。Executor提供了execute()接口来执行已提交的 Runnable 任务的对象。Executor存在的目的是提供一种将任务提交与任务如何运行分离开来的机制。
它只包含一个函数接口:

void execute(Runnable command)//启动线程任务

2. ExecutorService

ExecutorService继承于Executor。它是执行者服务接口,它是为执行者接口Executor服务而存在的;ExecutorService提供了将任务提交给执行者的接口(submit方法),让执行者执行任务(invokeAll, invokeAny方法)的接口等等。
submit,有返回值(Future 类型),submit 方法提供了 overload 方法。其中有参数类型为 Runnable 的,不需要提供返回值的;

有参数类型为 Callable,可以提供线程执行后的返回值。Future,是 submit 方法的返回值。代表未来,也就是线程执行结束后的一种结果。如返回值。

常见方法 - void execute(Runnable), Future submit(Callable), Future submit(Runnable)

线程池状态: Running, ShuttingDown, Termitnaed

Running - 线程池正在执行中。活动状态。
ShuttingDown - 线程池正在关闭过程中。优雅关闭。一旦进入这个状态,线程池不再接收新的任务,处理所有已接收的任务,处理完毕后,关闭线程池。
Terminated - 线程池已经关闭。

3 Future

代表线程任务执行结束后的结果。获取线程执行结果的方式是通过 get 方法获取的。get 无参,阻塞等待线程执行结束,并得到结果。get 有参,阻塞固定时长,等待线程执行结束后的结果,如果在阻塞时长范围内,线程未执行结束,抛出异常。
常用方法: T get() T get(long, TimeUnit)

4. AbstractExecutorService

AbstractExecutorService是一个抽象类,它实现了ExecutorService接口。

AbstractExecutorService存在的目的是为ExecutorService中的函数接口提供了默认实现。函数列表和ExecutorService一样。

5. ScheduledExecutorService

ScheduledExecutorService是一个接口,它继承于于ExecutorService。它相当于提供了延时和周期执行功能的ExecutorService。

ScheduledExecutorService提供了相应的函数接口,可以安排任务在给定的延迟后执行,也可以让任务周期的执行。

6. Executors

Executors是个静态工厂类。它通过静态工厂方法返回ExecutorService、ScheduledExecutorService、ThreadFactory 和 Callable 等类的对象。

7. FixedThreadPool

容量固定的线程池。活动状态和线程池容量是有上限的线程池。所有的线程池中,都有一个任务队列。使用的是 BlockingQueue<Runnable>作为任务的载体。

当任务数量大于线程池容量的时候,没有运行的任务保存在任务队列中,当线程有空闲的,自动从队列中取出任务执行。

使用场景: 大多数情况下,使用的线程池,首选推荐 FixedThreadPool。OS 系统和硬件是有线程支持上限。不能随意的无限制提供线程池。线程池默认的容量上限是 Integer.MAX_VALUE。

常见的线程池容量: PC - 200。 服务器 - 1000~10000

queued tasks- 任务队列
completed tasks - 结束任务队列

8. CachedThreadPool

缓存的线程池。容量不限(Integer.MAX_VALUE)。

自动扩容。容量管理策略:如果线程池中的线程数量不满足任务执行,创建新的线程。每次有新任务无法即时处理的时候,都会
创建新的线程。当线程池中的线程空闲时长达到一定的临界值(默认 60 秒),自动释放线程。默认线程空闲 60 秒,自动销毁。

应用场景: 内部应用或测试应用。 内部应用,有条件的内部数据瞬间处理时应用,如:平台夜间执行数据整理, 测试应用,在测试的时候,尝试得到硬件或软件的最高负载量,用于提供
FixedThreadPool 容量的指导。

9. ScheduledThreadPool

计划任务线程池。可以根据计划自动执行任务的线程池。scheduleAtFixedRate(Runnable, start_limit, limit, timeunit)

runnable - 要执行的任务。
start_limit - 第一次任务执行的间隔。
limit - 多次任务执行的间隔。
timeunit - 多次任务执行间隔的时间单位。

使用场景: 计划任务时选用(DelaydQueue),如:电信行业中的数据整理,没分钟整理,没消失整理,每天整理等。

10. SingleThreadExceutor

单一容量的线程池。

使用场景: 保证任务顺序时使用。如: 游戏大厅中的公共频道聊天。秒杀。

11. ForkJoinPool

分支合并线程池(mapduce 类似的设计思想)。适合用于处理复杂任务。初始化线程容量与 CPU 核心数相关。
线程池中运行的内容必须是 ForkJoinTask 的子类型(RecursiveTask,RecursiveAction)。

ForkJoinPool - 分支合并线程池。 可以递归完成复杂任务。要求可分支合并的任务必须是 ForkJoinTask 类型的子类型。其中提供了分支和合并的能力。ForkJoinTask 类型提供了两个
抽象子类型,RecursiveTask 有返回结果的分支合并任务,RecursiveAction 无返回结果的分支合并任务。(Callable/Runnable)compute 方法:就是任务的执行逻辑。

ForkJoinPool 没有所谓的容量。默认都是 1 个线程。根据任务自动的分支新的子线程。当子线程任务结束后,自动合并。所谓自动是根据 fork 和 join 两个方法实现的。

应用: 主要是做科学计算或天文计算的。数据分析的。

12. ThreadPoolExecutor

ThreadPoolExecutor就是大名鼎鼎的”线程池”。它继承于AbstractExecutorService抽象类。线程池底层实现。除 ForkJoinPool 外,其他常用线程池底层都是使用 ThreadPoolExecutor实现的。

public ThreadPoolExecutor(int corePoolSize, // 核心容量,创建线程池的时候,默认有多少线程。也是线程池保持的最少线程数

int maximumPoolSize, // 最大容量,线程池最多有多少线程
long keepAliveTime, // 生命周期,0 为永久。当线程空闲多久后,自动回收。
TimeUnit unit, // 生命周期单位,为生命周期提供单位,如:秒,毫秒
BlockingQueue<Runnable> workQueue // 任务队列,阻塞队列。注意,泛型必须是Runnable).

使用场景: 默认提供的线程池不满足条件时使用。如:初始线程数据 4,最大线程数200,线程空闲周期 30

线程池的拒绝策略 :是指当任务添加到线程池中被拒绝,而采取的处理措施。

当任务添加到线程池中之所以被拒绝,可能是由于:第一,线程池异常关闭。第二,任务数量超过线程池的最大限制。

线程池共包括4种拒绝策略,它们分别是:AbortPolicy, CallerRunsPolicy, DiscardOldestPolicy和DiscardPolicy。

AbortPolicy         -- 当任务添加到线程池中被拒绝时,它将抛出 RejectedExecutionException 异常。
CallerRunsPolicy    -- 当任务添加到线程池中被拒绝时,会在线程池当前正在运行的Thread线程池中处理被拒绝的任务。
DiscardOldestPolicy -- 当任务添加到线程池中被拒绝时,线程池会放弃等待队列中最旧的未处理任务,然后将被拒绝的任务添加到等待队列中。
DiscardPolicy       -- 当任务添加到线程池中被拒绝时,线程池将丢弃被拒绝的任务。

线程池默认的处理策略是AbortPolicy!

深入理解java类加载器

发表于 2017-05-11 | 分类于 java

类加载器原理

类加载器是一个用来加载类文件的类。Java源代码通过javac编译器编译成类文件。然后JVM来执行类文件中的字节码来执行程序。类加载器负责加载文件系统、网络或其他来源的类文件。

Java类加载器的作用就是在运行时加载类。Java类加载器基于三个机制:委托、可见性和单一性。

委托机制: 将加载一个类的请求交给父类加载器,如果这个父类加载器不能够找到或者加载这个类,那么再加载它。

可见性: 子类的加载器可以看见所有的父类加载器加载的类,而父类加载器看不到子类加载器加载的类。

单一性: 仅加载一个类一次,这是由委托机制确保子类加载器不会再次加载父类加载器加载过的类。

正确理解类加载器能够帮你解决NoClassDefFoundError和java.lang.ClassNotFoundException,因为它们和类的加载相关。

加载器树状结构

Java 中的类加载器大致可以分成两类,一类是系统提供的,另外一类则是自定义。

三种默认使用的类加载器:Bootstrap类加载器、Extension类加载器和System类加载器(或者叫作Application类加载器)。

引导类加载器(bootstrap class loader): 它用来加载 Java 的核心库(jre/lib/rt.jar)或-Xbootclasspath参数指定路径的目录,是用原生C++代码来实现的,并不继承自java.lang.ClassLoader。(出于安全考虑,Bootstrap启动类加载器只加载包名为java、javax、sun等开头的类)

扩展类加载器(extensions class loader): 它用来加载 Java 的扩展库Java 虚拟机的实现会提供一个扩展库目录JRE/lib/ext或者java.ext.dirs指向的目录。该类加载器在此目录里面查找并加载 Java 类。

系统类加载器(system/Application class loader): 它根据 Java 应用的类路径(CLASSPATH)来加载 Java 类。一般来说,Java 应用的类都是由它来完成加载的。可以通过 ClassLoader.getSystemClassLoader()来获取它。

自定义类加载器(custom class loader): 除了系统提供的类加载器以外,开发人员可以通过继承 java.lang.ClassLoader类的方式实现自己的类加载器,以满足一些特殊的需求。

1
2
3
4
5
6
7
8
public static void main(String[] args) {
//application class loader
System.out.println(ClassLoader.getSystemClassLoader());
//extensions class loader
System.out.println(ClassLoader.getSystemClassLoader().getParent());
//bootstrap class loader
System.out.println(ClassLoader.getSystemClassLoader().getParent().getParent());
}

20200412223705

将class文件字节码内容加载到内存中,并将这些静态数据转换成方法去中的运行时数据结构,在怼中生成一个代表这个类的Class对象 ,作为方法去类数据的访问入口。

类缓存

标准的类加载器可以按要求查找类,但一旦某个类被加载到类加载器中,它将维持加载(缓存)一段时间,不过,JVM垃圾回收器可以回收这些Class。

双亲委托或代理机制

代理模式:交给其他加载器来加载指定类

双亲委托机制: 某个特定的类加载器在接到加载类的请求时,首先将加载任务委托交给父类加载器,父类加载器又将加载任务向上委托,直到最父类加载器,如果最父类加载器可以完成类加载任务,就成功返回,如果不行就向下传递委托任务,由其子类加载器进行加载。

双亲委托机制是为了保证Java核心库的类型安全。保证了不会出现用户自定义java.lang.Object类的情况。

类加载器除了用于加载类,也是安全的最基本的屏障

双亲委托机制是代理模式的一种。

并不是所有的类加载都是双亲模式,比如tomcat服务器也是使用代理模式,不同的是它首先尝试去加载类,如果找不到在代理给父类加载器,这与一般加载器是相反的。

Class.forname()是一个静态方法,最常用的是Class.forname(String className);根据传入的类的全限定名返回一个Class对象.该方法在将Class文件加载到内存的同时,会执行类的初始化.

ClassLoader.loadClass():这是一个实例方法,需要一个ClassLoader对象来调用该方法,该方法将Class文件加载到内存时,并不会执行类的初始化,直到这个类第一次使用时才进行初始化,该方法因为需要得到一个ClassLoader对象,所以可以根据需要指定使用哪个类加载器.

自定义类加载器

1、继承java.lang.ClassLoader
2、首先检查请求的类型是否已经被这个类装载器装载到命名空间中,如果已经装载,直接返回。
3、委派类加载请求给父类加载器,如果父类加载器能够完成,则返回父类加载器加载的Class实例。
4、重写本类加载器的findClass(...)方法,试图获取对应字节码,如果获取得到,则调用defineClass(...)导入类型到方法区,如果获取不到对应的字节码或者其他原因失败,则终止加载过程。

注意:被两个类加载器加载的同一个类,JVM不认为是相同的类。

自定义文件系统类加载器

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
/**
* 自定义文件类加载器
*/
public class FileSysLoader extends ClassLoader {
private String rootDir;

public FileSysLoader(String rootDir) {
this.rootDir = rootDir;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
//检查有没有加载过这个类,如果已经加载直接返回这个类。
Class<?> c = findLoadedClass(name);
if (c!=null) {
return c;
}else{
ClassLoader parent = this.getParent();
try {
//委派给父类加载器
c = parent.loadClass(name);
} catch (Exception e) {
}

if(c!=null){
return c;
}else{
//如果父类也没加载,则自己加载,读取文件 进行加载
byte[] classData = getClassData(name);
if(classData==null){
//没有读取到文件,抛出异常
throw new ClassNotFoundException();
}else{
//生成Class对象
c = defineClass(name, classData, 0,classData.length);
}
}
}
return c;
}
/**
* 文件内容转为字节数组
*/
private byte[] getClassData(String classname){
String path = rootDir + File.separatorChar + classname.replace('.', File.separatorChar)+".class";
InputStream is = null;
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try{
is = new FileInputStream(path);

byte[] buffer = new byte[1024];
int temp=0;
while((temp=is.read(buffer))!=-1){
baos.write(buffer, 0, temp);
}

return baos.toByteArray();
}catch(Exception e){
e.printStackTrace();
return null;
}finally{
try {
if(is!=null){
is.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if(baos!=null){
baos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

//使用
public class TestFileLoader {
public static void main(String[] args) throws Exception {
FileSysLoader loader = new FileSysLoader("C:/Person");
FileSysLoader loader1 = new FileSysLoader("C:/Person");

Class<?> c1 = loader.loadClass("com.temp.bytecodeop.Person");
Class<?> c2 = loader1.loadClass("com.temp.bytecodeop.Person");
Class<?> c3 = loader1.loadClass("com.temp.bytecodeop.Person");

Class<?> c4 = loader.loadClass("java.lang.String");


System.out.println(c1.hashCode());
System.out.println(c2.hashCode());
System.out.println(c3.hashCode());
System.out.println(c4.hashCode());
System.out.println(c3.getClassLoader());
System.out.println(c1.getClassLoader().getParent());
System.out.println(c4.getClassLoader());
}
}

自定义网络类加载器

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
public class NetClassLoader extends ClassLoader {    
private String rootUrl;

public NetClassLoader(String rootUrl){
this.rootUrl = rootUrl;
}

@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {

Class<?> c = findLoadedClass(name);

if(c!=null){
return c;
}else{
ClassLoader parent = this.getParent();
try {
c = parent.loadClass(name);
} catch (Exception e) {
}

if(c!=null){
return c;
}else{
byte[] classData = getClassData(name);
if(classData==null){
throw new ClassNotFoundException();
}else{
c = defineClass(name, classData, 0,classData.length);
}
}
}
return c;
}

private byte[] getClassData(String classname){
String path = rootUrl +"/"+ classname.replace('.', '/')+".class";
InputStream is = null;
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try{
URL url = new URL(path);
//通过URL获取流
is = url.openStream();
byte[] buffer = new byte[1024];
int temp=0;
while((temp=is.read(buffer))!=-1){
baos.write(buffer, 0, temp);
}

return baos.toByteArray();
}catch(Exception e){
e.printStackTrace();
return null;
}finally{
try {
if(is!=null){
is.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if(baos!=null){
baos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

热部署类加载器

热部署就是利用同一个class文件不同的类加载器在内存创建出两个不同的class对象由于JVM在加载类之前会检测请求的类是否已加载过,如果被加载过,则直接从缓存获取,不会重新加载。

同一个加载器只能加载一次,多次加载将报错,因此实现的热部署必须让同一个class文件可以根据不同的类加载器重复加载,

实现所谓的热部署。自定义加载器后,直接调用findClass()方法,而不是调用loadClass()方法,因为ClassLoader中loadClass()方法体回调用findLoadedClass()方法进行了检测是否已被加载,因此我们直接调用findClass()方法就可以绕过这个问题。

前面的文件加载,使用了父类委托方式,我们这里直接写自己加载。

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
/**
* 热部署类加载器
*/
public class FileDeployLoader extends ClassLoader {
private String rootDir;

public FileDeployLoader(String rootDir) {
this.rootDir = rootDir;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
//检查有没有加载过这个类,如果已经加载直接返回这个类。
byte[] classData = getClassData(name);
Class<?> c = null;
if(classData==null){
//没有读取到文件,抛出异常
throw new ClassNotFoundException();
}else{
//生成Class对象
c = defineClass(name, classData, 0,classData.length);
}
return c;
}
/**
* 文件内容转为字节数组
*/
private byte[] getClassData(String classname){
String path = rootDir + File.separatorChar + classname.replace('.', File.separatorChar)+".class";
InputStream is = null;
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try{
is = new FileInputStream(path);

byte[] buffer = new byte[1024];
int temp=0;
while((temp=is.read(buffer))!=-1){
baos.write(buffer, 0, temp);
}

return baos.toByteArray();
}catch(Exception e){
e.printStackTrace();
return null;
}finally{
try {
if(is!=null){
is.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if(baos!=null){
baos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws ClassNotFoundException {
FileSysLoader loader = new FileSysLoader("C:/Person");
//直接调用findClass()方法,而不是调用loadClass()方法
//因为ClassLoader中loadClass()方法体回调用findLoadedClass()方法进行了检测是否已被加载
Class<?> c1 = loader.findClass("com.temp.bytecodeop.Person");
}
}

加密解密类加载器

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
/**
* 简单加密类
* 字节取反加密
*/
public class EncrptUtil {
public static void main(String[] args) {
//调用加密方法加密一个*.class文件
}

public static void encrpt(String src, String dest){
FileInputStream fis = null;
FileOutputStream fos = null;

try {
fis = new FileInputStream(src);
fos = new FileOutputStream(dest);

int temp = -1;
while((temp=fis.read())!=-1){
fos.write(temp^0xff); //加密
}

} catch (Exception e) {
e.printStackTrace();
}finally{
try {
if(fis!=null){
fis.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if(fos!=null){
fos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* 加密解密类加载器
*/
class DecrptClassLoader extends ClassLoader {

private String rootDir;

public DecrptClassLoader(String rootDir){
this.rootDir = rootDir;
}

@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {

Class<?> c = findLoadedClass(name);

if(c!=null){
return c;
}else{
ClassLoader parent = this.getParent();
try {
c = parent.loadClass(name);
} catch (Exception e) {
}

if(c!=null){
return c;
}else{
byte[] classData = getClassData(name);
if(classData==null){
throw new ClassNotFoundException();
}else{
c = defineClass(name, classData, 0,classData.length);
}
}
}
return c;
}

private byte[] getClassData(String classname){
String path = rootDir +File.separatorChar+ classname.replace('.', File.separatorChar)+".class";
InputStream is = null;
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try{
is = new FileInputStream(path);
int temp = -1;
while((temp=is.read())!=-1){
baos.write(temp^0xff); //解密,再取反
}

return baos.toByteArray();
}catch(Exception e){
e.printStackTrace();
return null;
}finally{
try {
if(is!=null){
is.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if(baos!=null){
baos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

线程上下文类加载器

在Java中存在着很多服务提供者接口(Service Provider Interface,SPI),SPI 的接口属于 Java 核心库,一般存在rt.jar包中,由Bootstrap类加载器加载。这些接口允许第三方为它们提供实现,如常见的 SPI 有 JDBC、JCE、JAXP、JBI、JNDI等,而这些SPI 的第三方实现代码则是作为Java应用所依赖的 jar 包被存放在classpath路径下,由系统加载器来加载,但SPI的核心接口类是由引导类加载器来加载的,而Bootstrap类加载器无法直接加载SPI的实现类,同时由于双亲委派模式的存在,Bootstrap类加载器也无法反向委托AppClassLoader加载器SPI的实现类。在这种情况下,我们就需要一种特殊的类加载器来加载第三方的类库。

因此,线程类加载器是很好的选择,它为了抛弃双亲委派加载链模式。

每个线程都有一个关联的上下文加载器,如果new一个新的线程,如果没有手动设置上下文类加载器,线程将继承其父线程的上下文类加载器,通过java.lang.Thread类中的getContextClassLoader()和 setContextClassLoader(ClassLoader cl)方法来获取和设置线程的上下文类加载器。

20200412224649

rt.jar核心包是有Bootstrap类加载器加载的,其内包含SPI核心接口类,由于SPI中的类经常需要调用外部实现类的方法,而jdbc.jar包含外部实现类(jdbc.jar存在于classpath路径)无法通过Bootstrap类加载器加载,因此只能委派线程上下文类加载器把jdbc.jar中的实现类加载到内存以便SPI相关类使用。它在执行过程中抛弃双亲委派加载链模式,使程序可以逆向使用类加载器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class LoaderTest {

public static void main(String[] args) throws ClassNotFoundException {
ClassLoader loader = LoaderTest.class.getClassLoader();
System.out.println(loader);

ClassLoader loader2 = Thread.currentThread().getContextClassLoader();
System.out.println(loader2);

Thread.currentThread().setContextClassLoader(new FileSysLoader("C:/Person"));
System.out.println(Thread.currentThread().getContextClassLoader());

Class<LoaderTest> c = (Class<LoaderTest>) Thread.currentThread().getContextClassLoader().loadClass("com.temp.bytecodeop.Person");
System.out.println(c);
System.out.println(c.getClassLoader());
}

}

服务器类加载器原理和OSGI介绍

Tomcat服务器的类加载机制

Tomcat不能使用系统默认的类加载器

如果使用默认类加载器,可以直接操作系统目录,不安全。对于web应用服务器,类加载器的实现方式和一般的Java运用不同。

每个web应用都有一个对应类加载器实例。该类加载器也使用代理模式(不同于双亲模式)所不同的是它首先尝试去加载某个类,如果找不到再代理给父类加载器。这与一般类加载器的顺序是相反的,也是为了保证安全,这样核心库就不在查询范围内。

Tomcat为了安全需要实现自己的类加载器。为每个webapp提供自己的加载器。

20200412225008

OSGI

(Open Service Gateway Initative)是面向Java的动态模块系统,它为开发人员提供了面向服务和基于组件的运行环境,并提供标准的方式用来管理软件的生命周期。

OSGI已经被实现和部署在很多产品上,在开源社区也得到广泛的支持,Eclipse也是基于OSGI技术来构建的。

OSGi 中的每个模块(bundle)都包含 Java 包和类。模块可以声明它所依赖的需要导入(import)的其它模块的 Java 包和类(通过 Import-Package),也可以声明导出(export)自己的包和类,供其它模块使用(通过 Export-Package)。也就是说需要能够隐藏和共享一个模块中的某些 Java 包和类。这是通过 OSGi 特有的类加载器机制来实现的。

OSGi 中的每个模块都有对应的一个类加载器。它负责加载模块自己包含的 Java 包和类。当它需要加载 Java 核心库的类时(以 java开头的包和类),它会代理给父类加载器(通常是启动类加载器)来完成。当它需要加载所导入的 Java 类时,它会代理给导出此 Java 类的模块来完成加载。

模块也可以显式的声明某些 Java 包和类,必须由父类加载器来加载。只需要设置系统属性 org.osgi.framework.bootdelegation的值即可。

OSGi 模块的这种类加载器结构,使得一个类的不同版本可以共存在 Java 虚拟机中,带来了很大的灵活性。

Equinox是OSGI的一个实现框架

java注解介绍

发表于 2017-05-11 | 分类于 java

Java注解同 classs 和 interface 一样,注解也属于一种类型。它是在 Java SE 5.0 版本中开始引入的概念。

注解的定义

通过 @interface 关键字进行定义。

1
2
public @interface TestAnnotation {
}

内置注解

@Deprecated、@Override、@SuppressWarnings、@SafeVarargs、@FunctionalInterface

1
2
3
4
5
@Deprecated 这个元素是用来标记过时的元素,编译器在编译阶段遇到这个注解时会发出提醒警告,告诉开发者正在调用一个过时的元素比如过时的方法、过时的类、过时的成员变量。
@Override 提示子类要复写父类中被 @Override 修饰的方法
@SuppressWarnings阻止警告的意思,把警告取消。
@SafeVarargs参数安全类型注解。它的目的是提醒开发者不要用参数做一些不安全的操作,它的存在会阻止编译器产生 unchecked 这样的警告。它是在 Java 1.7 的版本中加入的。
@FunctionalInterface函数式接口注解,这个是 Java 1.8 版本引入的新特性,函数式编程。

函数式接口 (Functional Interface) 就是一个具有一个方法的普通接口。

元注解

元注解是可以注解到注解上的注解,或者说元注解是一种基本注解,但是它能够应用到其它的注解上面。

有 @Retention、@Documented、@Target、@Inherited、@Repeatable 5 种。

@Retention

当 @Retention 应用到一个注解上的时候,它解释说明了这个注解的的存活时间。
它的取值如下:

RetentionPolicy.SOURCE 注解只在源码阶段保留,在编译器进行编译时它将被丢弃忽视。 
RetentionPolicy.CLASS 注解只被保留到编译进行的时候,它并不会被加载到 JVM 中。 
RetentionPolicy.RUNTIME 注解可以保留到程序运行的时候,它会被加载进入到 JVM 中,所以在程序运行时可以获取到它们。

@Documented

它的作用是能够将注解中的元素包含到 Javadoc 中去。

@Target

@Target 指定了注解运用的地方。当一个注解被 @Target 注解时,这个注解就被限定了运用的场景。

ElementType.ANNOTATION_TYPE 可以给一个注解进行注解
ElementType.CONSTRUCTOR 可以给构造方法进行注解
ElementType.FIELD 可以给属性进行注解
ElementType.LOCAL_VARIABLE 可以给局部变量进行注解
ElementType.METHOD 可以给方法进行注解
ElementType.PACKAGE 可以给一个包进行注解
ElementType.PARAMETER 可以给一个方法内的参数进行注解
ElementType.TYPE 可以给一个类型进行注解,比如类、接口、枚举

@Inherited

Inherited 是继承的意思,但是它并不是说注解本身可以继承,而是说如果一个类使用了被Inherited标注的注解,那么这个类的子类也继承这个注解。

@Repeatable

Repeatable是可重复的意思,注解容器。@Repeatable 是 Java 1.8 才加进来的,所以算是一个新的特性。

1
2
3
4
5
6
7
8
9
10
11
12
13
@interface Persons {
Person[] value();
}

@Repeatable(Persons.class)//相当于用来保存该注解内容的容器。
@interface Person{
String role default "";
}

@Person(role="teach")br/>@Person(role="coder")
@Person(role="PM")
public class Man{
}

注解的属性

注解的属性也叫做成员变量。注解只有成员变量,没有方法。注解的成员变量在注解的定义中以“无形参的方法”形式来声明,其方法名定义了该成员变量的名字,其返回值定义了该成员变量的类型。

1
2
3
4
public @interface TestAnnotation {
int id();
String value();
}

使用注解

1
2
3
@TestAnnotation(id=1,value="xxxxx")
public class Test {
}

注解中属性可以有默认值,默认值需要用 default 关键值指定。public int id() default -1;

注:

如果一个注解内只有一个属性时,使用注解时可以直接接属性值填写到括号内。
如果没有属性,使用的时候可以省略括号。

注解的提取

一个注解要在运行时被成功提取,那么 @Retention(RetentionPolicy.RUNTIME) 是必须的。

注解通过反射获取。首先可以通过 Class 对象的 isAnnotationPresent() 方法判断它是否应用了某个注解
public boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) {}

然后通过 getAnnotation() 方法来获取 Annotation 对象。public A getAnnotation(Class annotationClass) {}

或者是 getAnnotations() 方法。public Annotation[] getAnnotations() {}

前一种方法返回指定类型的注解。

后一种方法返回应用到这个元素上的所有注解。

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {

public int id() default 2;
public String value() default "hello";
}

@TestAnnotation
public class AnnotationTest {
public static void main(String[] args) throws ClassNotFoundException {
//Class c = Class.forName("com.temp.annotation.AnnotationTest");
boolean hasAnnotation = AnnotationTest.class.isAnnotationPresent(TestAnnotation.class);
if (hasAnnotation ) {
TestAnnotation testAnnotation = AnnotationTest.class.getAnnotation(TestAnnotation.class);

Annotation[] ans = AnnotationTest.class.getAnnotations();

System.out.println(ans[0]);

System.out.println("id:"+testAnnotation.id());
System.out.println("value:"+testAnnotation.value());
}
}
}

运行结果:

1
2
3
@com.temp.annotation.TestAnnotation(value=hello, id=2)
id:2
value:hello

spring AOP

发表于 2017-05-09 | 分类于 spring

面向切面编程( Aspect Oriented Programming)

AOP称为面向切面编程,在程序开发中主要用来解决一些系统层面上的问题,比如日志,事务,权限等待,Struts2的拦截器设计就是基于AOP的思想,是个比较经典的例子。

AOP的基本概念

1
2
3
4
5
6
7
8
9
10
(1)Aspect(切面):通常是一个类,里面可以定义切入点和通知
(2)JointPoint(连接点):程序执行过程中明确的点,一般是方法的调用
(3)Advice(通知):AOP在特定的切入点上执行的增强处理
before(前置通知)在方法执行之前执行
after(后置通知)在方法执行之后执行
afterReturning(返回通知)在方法返回结果之后执行
afterThrowing(异常通知) 在方法抛出异常之后
around(环绕通知)围绕着方法执行
(4)Pointcut(切入点):就是带有通知的连接点,在程序中主要体现为书写切入点表达式
(5)AOP代理:AOP框架创建的对象,代理就是目标对象的加强。Spring中的AOP代理可以使JDK动态代理,也可以是CGLIB代理,前者基于接口,后者基于子类

Spring AOP

Spring中的AOP代理还是离不开Spring的IOC容器,代理的生成,管理及其依赖关系都是由IOC容器负责,Spring默认使用JDK动态代理,在需要代理类而不是代理接口的时候,Spring会自动切换为使用CGLIB代理。

优点:高扩展性;在原有功能相当于释放了部分逻辑,让职责更加明确。

在程序原有纵向执行流程中,针对某一个或某一些方法添加通知,形成横切面过程就叫做面向切面编程。

Spring中常见切面编程概念:

1
2
3
4
5
6
原有功能:切点pointcut
前置通知:在切点之前执行的功能before advice
后置通知:在切点之后执行的功能after advice
异常通知:如果切点执行过程中出现异常,会触发异常通知throws advice
切面:所有功能总称叫做切面.
织入:把切面嵌入到原有功能的过程叫做织入

Spring 的 AOP 配置元素简化了基于 POJO 切面声明

20200407123026

AOP实现方式

spring 提供了 2 种 AOP 实现方式

  • Schema-based
    每个通知都需要实现接口或类 配置 spring 配置文件时在<aop:config>配置
  • AspectJ
    每个通知不需要实现接口或类,配置 spring 配置文件是在<aop:config>的子标签<aop:aspect>中配置

Schema-based实现

创建前置类

1
2
3
4
5
6
7
8
9
10
11
public class MyBeforeAdvice implements MethodBeforeAdvice{
/*
* arg0:切点方法对象Method
* arg1:切点方法参数
* arg2:切点在哪个对象中
*/
@Override
public void before(Method arg0, Object[] arg1, Object arg2) throws Throwable {
System.out.println("执行前置通知");
}
}

创建后置类:

1
2
3
4
5
6
7
8
9
10
11
12
public class MyAfterAdvice implements AfterReturningAdvice{
/*
* arg0:切点方法返回值
* arg1:切点方法对象
* arg2:切点方法参数
* arg3:切点方法所在类的对象
*/
@Override
public void afterReturning(Object arg0, Method arg1, Object[] arg2, Object arg3) throws Throwable {
System.out.println("执行后置通知");
}
}

切点方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Demo {
public void demo01() {
System.out.println("demo01");
}
public void demo02() {
System.out.println("demo02");
}
public void demo03() {
System.out.println("demo03");
}
public void demo04() {
System.out.println("demo04");
}
}

配置aop:

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
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">


<!-- 配置通知类对象, 在切面中引入 -->
<bean id="mybefore" class="com.hu.advice.MyBeforeAdvice"></bean>
<bean id="myafter" class="com.hu.advice.MyAfterAdvice"></bean>


<!-- 配置切面 -->
<aop:config>
<!-- 配置切点 *通配符 可以比如任意类的任意方法:execution(* com.hu.test.*.*(..) (..)标识任意参数的方法-->
<aop:pointcut expression="execution(* com.hu.test.Demo.demo02())" id="mypoint" />
<!-- 引入通知 -->
<aop:advisor advice-ref="mybefore" pointcut-ref="mypoint" />
<aop:advisor advice-ref="myafter" pointcut-ref="mypoint" />
</aop:config>


<!-- 配置 Demo 类,测试使用 -->
<bean id="demo" class="com.hu.test.Demo"></bean>
</beans>

测试:

1
2
3
4
5
6
7
8
9
public class Test01 {
public static void main(String[] args) {
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
Demo demo = ac.getBean("demo", Demo.class);
demo.demo01();
demo.demo02();
demo.demo03();
}
}
配置异常通知
1
2
3
4
5
6
7
8
9
10
<bean id="mythrow" class="com.hu.advice.MyThrowAdvice"></bean>

<aop:config>
<!-- 切点 -->
<aop:pointcut expression="execution(* com.hu.test.Demo.demo02())" id="mypoint"/>
<aop:advisor advice-ref="mythrow" pointcut-ref="mypoint"/>
</aop:config>

<!-- 配置 Demo 类,测试使用 -->
<bean id="demo" class="com.hu.test.Demo"></bean>
1
2
3
4
5
6
public class MyThrowAdvice implements ThrowsAdvice{
//参数:Method m, Object[] args, Object target, Exception ex
public void afterThrowing(Exception ex) throws Throwable {
System.out.println("执行异常通过-schema-base 方式");
}
}

切点方法:

1
2
3
4
public void demo02() throws Exception{
int a=3/0;
System.out.println("demo02");
}
环绕通知
1
2
3
4
5
6
7
8
9
public class MyArround implements MethodInterceptor{
@Override
public Object invoke(MethodInvocation arg0) throws Throwable {
System.out.println("环绕-前置");
Object result = arg0.proceed();//放行,调用切点方式
System.out.println("环绕-后置");
return result;
}
}

配置:

1
2
3
4
5
6
7
8
9
10
<bean id="myarround" class="com.hu.advice.MyArround"></bean>

<aop:config>
<!-- 切点 -->
<aop:pointcut expression="execution(* com.hu.test.Demo.demo02())" id="mypoint"/>
<aop:advisor advice-ref="myarround" pointcut-ref="mypoint"/>
</aop:config>

<!-- 配置 Demo 类,测试使用 -->
<bean id="demo" class="com.hu.test.Demo"></bean>

切点:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
 public class Demo {
public void demo01() {
System.out.println("demo01");
}
public void demo02() {
System.out.println("demo02");
}
public void demo03() {
System.out.println("demo03");
}
public void demo04() {
System.out.println("demo04");
}
}

AspectJ方式实现

对通知类没有严格的要求,直接配置使用:

1
2
3
4
5
6
7
8
9
10
11
12
<bean id="myafter" class="com.hu.advice.MyAfterAdvice"></bean>


<aop:config>
<aop:aspect ref="myafter">
<aop:pointcut expression="execution(* com.hu.test.Demo.demo02())" id="mypoint"/>
<aop:after method="after" pointcut-ref="mypoint"/>
</aop:aspect>
</aop:config>

<!-- 配置 Demo 类,测试使用 -->
<bean id="demo" class="com.hu.test.Demo"></bean>
配置通知异常
  1. 只有当切点报异常才能触发异常通知

  2. 在 spring 中有 AspectJ 方式提供了异常通知的办法。

配置信息:

1
2
3
4
5
6
7
8
9
10
11
<bean id="mythrow" class="com.hu.advice.MyThrowAdvice"></bean>
<!-- 配置切面 -->
<aop:config>
<!-- 切点 -->
<aop:aspect ref="mythrow">
<aop:pointcut expression="execution(* com.hu.test.Demo.demo02())" id="mypoint"/>
<aop:after-throwing method="myException" pointcut-ref="mypoint" throwing="e"/>
</aop:aspect>
</aop:config>
<!-- 配置 Demo 类,测试使用 -->
<bean id="demo" class="com.hu.test.Demo"></bean>

异常通知类:

1
2
3
4
5
public class MyThrowAdvice {
public void myException(Exception e) {
System.out.println("执行异常通知"+e.getMessage());
}
}

切点:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Demo {
public void demo01() {
System.out.println("demo01");
}
public void demo02() throws Exception{
int a=3/0;
System.out.println("demo02");
}
public void demo03() {
System.out.println("demo03");
}
public void demo04() {
System.out.println("demo04");
}
}

测试:

1
2
3
4
5
6
7
8
9
10
11
public static void main(String[] args) {
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
Demo demo = ac.getBean("demo", Demo.class);
demo.demo01();
try {
demo.demo02();
} catch (Exception e) {
//e.printStackTrace();
}
demo.demo03();
}
环绕通知

通知类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class MyAdvice {
public void before(){
System.out.println("before");
}
public void after(){
System.out.println("after");
}
public void afterReturning(){
System.out.println("afterReturning Advice");
}
public void afterThrowing(){
System.out.println("afterThrowing Advice");
}
public Object around(ProceedingJoinPoint pjp) throws Throwable {
Object result=null;
System.out.println("环绕-前置");
result = pjp.proceed();//放行,调用切点方式
System.out.println("环绕-后置");
return result;
}
}

配置:

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
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="myadvice" class="com.hu.advice.MyAdvice"></bean>
<aop:config>
<aop:aspect ref="myadvice">
<aop:pointcut expression="execution(* com.hu.test.Demo.demo02())" id="mypoint"/>
<aop:after method="after" pointcut-ref="mypoint"/>
<!-- afterReturning和after类似,执行顺序与这里的顺序有关,但是当切点方法执行异常将不会执行afterReturning -->
<aop:after-returning method="afterReturning" pointcut-ref="mypoint"/>
<aop:before method="before" pointcut-ref="mypoint"/>
<aop:around method="around" pointcut-ref="mypoint"/>
<aop:after-throwing method="afterThrowing" pointcut-ref="mypoint"/>
</aop:aspect>
</aop:config>
<!-- 配置 Demo 类,测试使用 -->
<bean id="demo" class="com.hu.test.Demo"></bean>
</beans>
```java
测试:
```java
public class Test01 {
public static void main(String[] args) {
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
Demo demo = ac.getBean("demo", Demo.class);
demo.demo01();
demo.demo02();
demo.demo03();
}
}

expression规则

“aop:pointcut”标签中”expression”的写法规则如下:

execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?)

1
2
3
4
    ret-type-pattern,name-pattern(param-pattern)是必须的.
    ret-type-pattern:标识方法的返回值,需要使用全路径的类名如java.lang.String,也可以为*表示任何返回值;
    name-pattern:指定方法名,*代表所有,例如set*,代表以set开头的所有方法.
    param-pattern:指定方法参数(声明的类型),(..)代表所有参数,(*)代表一个参数,(*,String)代表第一个参数为任何值,第二个为String类型.

表达式例子如下:

1
2
3
4
5
6
7
8
9
10
11
12
  任意公共方法的执行:
    execution(public * *(..))
  任何一个以"set"开始的方法的执行:
    execution(* set*(..))
  AccountService 接口的任意方法的执行:
    execution(* com.xyz.service.AccountService.*(..))
  定义在service包里的任意方法的执行:
    execution(* com.xyz.service.*.*(..))
  定义在service包和所有子包里的任意类的任意方法的执行:
    execution(* com.xyz.service..*.*(..))
  定义在pointcutexp包和所有子包里的JoinPointObjP2类的任意方法的执行:
    execution(* com.test.spring.aop.pointcutexp..JoinPointObjP2.*(..))")

在多个表达式之间使用 ||,or表示 或,使用 &&,and表示 与,!表示 非.例如:

1
2
3
4
<aop:config>       
<aop:pointcut id="pointcut" expression="(execution(* com.ccboy.dao..*.find*(..))) or (execution(* com.ccboy.dao..*.query*(..)))"/>     
<aop:advisor advice-ref="jdbcInterceptor" pointcut-ref="pointcut" />
</aop:config>

使用注解(基于Aspect)实现AOP

使用注解实现AOP,它基于Aspect方式实现的。spring 不会自动去寻找注解,必须告诉 spring 哪些包下的类中可能有注解。扫描注解:引入xmlns:context。

配置:

1
2
3
4
<!-- 告诉spring哪些包下有注解 ,spring才会去扫描注解。-->
<context:component-scan base-package="com.hu.advice,com.hu.test"></context:component-scan>
<!-- true 使用cglib动态代理 false 使用jdk动态代理 -->
<aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy>

通知:

1
2
3
4
5
6
7
8
9
10
11
/**
* @Aspect 相当于<aop:aspect/>表示通知方法在当前类中
*/
@Component
@Aspect
public class MyAdvice {
@Before(value="com.hu.test.Demo.demo02(name,id)",argNames="name,id")
public void before(String name, int id) {
System.out.println("before--name: " + name + " id: " + id);
}
}

切点方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* 使用spring注解
* 相当于配置的<bean>标签
* 默认名字是类名的首字母小写。
* 也可以自定义名字@Component(value="demo12")
* 或者省略value:@Component("demo12")
* 不配置参数,默认是类名的首字母小写。
*/
@Component
public class Demo {
/**
* 配置切点方法
*/
@Pointcut(value="execution(* com.hu.test.Demo.demo02(..)) && args(name,id)")
public void demo02(String name,int id) {
System.out.println("demo02--name: "+name+" id: "+id);
}
}

测试:

1
2
3
4
5
6
7
public class Test01 {
public static void main(String[] args) {
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
Demo demo = ac.getBean("demo", Demo.class);
demo.demo02("huang xiao ming",1);
}
}

代理模式

代理设计模式的优点:保护真实对象,让真实对象职责更明确,可以更好的扩展应用程序。

代理设计模式有两种:静态代理和动态代理。

静态代理
* 由代理对象代理所有真实对象的功能,需要自己编写代理类,每个代理的功能需要单独编写。
* 缺点:当代理功能比较多时,代理类中方法需要写很多。

动态代理
*为了解决静态代理频繁编写代理功能的缺点而产生动态代理。

动态代理分为两种:JDK动态代理和cglib动态代理。两种代理方式各有优缺点。

1
2
3
4
JDK动态代理:
优点:jdk 自带,不需要额外导入 jar
缺点:真实对象必须实现接口,利用反射机制,效率不高。
使用 JDK 动态代理时可能出现ClassCastException异常,出现原因:希望把接口对象转换为具体真实对象。
1
2
3
4
5
6
7
8
cglib动态代理:
优点:

基于字节码,生成真实对象的子类。
运行效率高于 JDK 动态代理,不需要实现接口。

缺点:
非 JDK 功能,需要额外导入 jar(cglib-2.2.2.jar/asm-3.3.1.jar)

spring事务管理

发表于 2017-05-09 | 分类于 spring

事务分为两种:声明式事务和编程式事务

编程式事务

由程序员编程事务控制代码; OpenSessionInView 编程式事务都是编程式事务。

所谓编程式事务指的是通过编码方式实现事务,允许用户在代码中精确定义事务的边界。即类似于JDBC编程实现事务管理。管理使用TransactionTemplate或者直接使用底层的PlatformTransactionManager。对于编程式事务管理,spring推荐使用TransactionTemplate。

声明式事务

管理建立在AOP之上的。其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。声明式事务最大的优点就是不需要通过编程的方式管理事务,这样就不需要在业务逻辑代码中掺杂事务管理的代码,只需在配置文件中做相关的事务规则声明(或通过基于@Transactional注解的方式),便可以将事务规则应用到业务逻辑中。

简单地说,编程式事务侵入到了业务代码里面,但是提供了更加详细的事务管理;而声明式事务由于基于AOP,所以既能起到事务管理的作用,又可以不影响业务代码的具体实现。

Spring编程式事务
1
2
3
4
5
6
7
8
9
<!-- 创建模板 -->
<bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
<property name="transactionManager" ref="txManager"></property>
</bean>

<!-- 配置事务管理器 ,管理器需要事务,事务从Connection获得,连接从连接池DataSource获得 -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>

Service中声明属性

1
2
3
4
5
6
7
8
9
10
11
12
private TransactionTemplate transactionTemplate;
@Override
public void transfer(final String outer,final String inner,final int money) {
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus arg0) {
accountDao.out(outer, money);
//int i = 1/0;
accountDao.in(inner, money);
}
});
}
Spring声明式事务
事务控制代码已经由 spring写好,程序员只需要声明出哪些方法需要进行事务控制和如何进行事务控制。声明式事务都是针对于 ServiceImpl 类下方法的,事务管理基于AOP通知(advice)。

实现细节:

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
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd"
default-autowire="byName">

<!-- 加载properties文件 如果加载多个文件,后面用逗号分隔开 -->
<context:property-placeholder location="classpath:db.properties"/>

<!-- 扫描注解 -->
<context:component-scan base-package="com.hu.pojo"></context:component-scan>

<!-- true 使用cglib动态代理 false 使用jdk动态代理 -->
<aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy>

<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="${jdbc.driver}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
<bean id="factory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="typeAliasesPackage" value="com.hu.pojo"></property>
</bean>

<!-- 扫描器 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!-- 要扫描 mapper接口 -->
<property name="basePackage" value="com.hu.mapper"></property>
<property name="sqlSessionFactoryBeanName" value="factory"/>
</bean>
<!-- 事务管理在 spring-jdbc-*.jar 中 -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置声明式事务 -->
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<!-- 哪些方法需要有事务控制 使用通配符 方法以 ins 开头事务管理 -->
<!--
事务控制属性解析:
1、name="" 是哪些方法需要事务控制,支持*通配符
2、read-only="" 设置事务是否是只读事务,默认值是false。
true告诉数据库此事务为只读事务,会对性能有一定提升,所以只要是查询的方法,建议使用true。
false(默认值),事务需要提交的事务,建议新增/删除/修改使用。
3、propagation="" 控制事务传播行为。
当一个具有事务控制的方法被另一个有事务控制的方法调用后,需要如何管理事务(新建事务、在事务中执行、把事务挂起、报异常?)
REQUIRED(默认值):如果当前有事务,就在事务中执行;如果当前没有事务,新建一个事务。
SUPPORTS:如果当前有事务就在事务中执行,如果当前没有事务,就在非事务状态下执行。
MANDATORY:必须在事务内部执行,如果当前有事务,就在事务中执行,如果没有事务就会报异常。
REQUIRES_NEW:必须在事务中执行,如果当前没有事务,新建事务;如果当前有事务,把当前事务挂起。
NOT_SUPPORTED:必须在非事务下执行,如果当前没有事务,正常执行;如果当前有事务,把当前事务挂起。
NEVER:必须在非事务状态下执行,如果当前没有事务,正常执行;如果当前有事务就会报异常。
NESTED:必须在事务状态下执行,如果没有事务,新建事务;如果当前有事务,创建一个嵌套事务。
4、isolation="" 事务隔离级别。
数据完整性:脏读、不可重复读、幻读。
脏读:一个事务读取到另一个事务中未提交的数据,此时事务读取的数据可能和数据库中数据不一致,此时认为数据是脏数据,读取脏数据过程叫脏读。
不可重复读:主要针对的是某行或某列数据。当一个事务第一次读取后,另一个事务对当前事务读取的数据进行修改,当前事务再次读取数据和之前读取的数据不一致。
幻读:主要针对新增或删除数据。一个事务按照特定条件查询出结果,另一个事务新增了一条符合条件的数据,此时当前事务中查询的数据和数据库中的数据不一致,当前事务好像出现了幻觉。

DEFAULT: 默认值,由底层数据库自动判断应该使用什么隔离级别。
READ_UNCOMMITTED: 可以读取未提交数据,可能出现脏读,不重复读,幻读。效率最高
READ_COMMITTED:只能读取其他事务已提交数据,可以防止脏读,但是可能出现不可重复读和幻读。
REPEATABLE_READ: 读取的数据被添加锁,防止其他事务修改此数据,可以防止不可重复读,脏读,但是可能出现幻读。(记录级别)
SERIALIZABLE: 对整个表添加锁,一个事务在操作数据时,另一个事务等待事务操作完成后才能操作这个表。(表级别)
这种级别最安全的但也是 效率最低。.
5、rollback-for="" 回滚事务,当出现什么异常(如:java.lang.Exception)时回滚。(注意:手动抛出异常一定要给定该属性值)
6、no-rollback-for="" 当出现什么异常时不滚回事务。
-->
<tx:method name="insert*" />
<tx:method name="delete*" />
<tx:method name="update*" />
<tx:method name="*" read-only="true"/>
</tx:attributes>
</tx:advice>
<aop:config>
<!-- spring事务管理是以AOP的方式,切点范围设置 一般事务都是在service中处理-->
<aop:pointcut expression="execution(* com.hu.service.impl.*.*(..))" id="mypoint" />
<aop:advisor advice-ref="txAdvice" pointcut-ref="mypoint" />
</aop:config>
</beans>

控制事务传播行为:

当一个具有事务控制的方法被另一个有事务控制的方法调用后,需要如何管理事务(新建事务、在事务中执行、把事务挂起、报异常?)

  • REQUIRED(默认值):如果当前有事务,就在事务中执行;如果当前没有事务,新建一个事务。
  • SUPPORTS:如果当前有事务就在事务中执行,如果当前没有事务,就在非事务状态下执行。
  • MANDATORY:必须在事务内部执行,如果当前有事务,就在事务中执行,如果没有事务就会报异常。
  • REQUIRES_NEW:必须在事务中执行,如果当前没有事务,新建事务;如果当前有事务,把当前事务挂起。
  • NOT_SUPPORTED:必须在非事务下执行,如果当前没有事务,正常执行;如果当前有事务,把当前事务挂起。
  • NEVER:必须在非事务状态下执行,如果当前没有事务,正常执行;如果当前有事务就会报异常。
  • NESTED:必须在事务状态下执行,如果没有事务,新建事务;如果当前有事务,创建一个嵌套事务。

事务隔离级别:

数据完整性:脏读、不可重复读、幻读。

  • 脏读:一个事务读取到另一个事务中未提交的数据,此时事务读取的数据可能和数据库中数据不一致,此时认为数据是脏数据,读取脏数据过程叫脏读。
  • 不可重复读:主要针对的是某行或某列数据。当一个事务第一次读取后,另一个事务对当前事务读取的数据进行修改,当前事务再次读取数据和之前读取的数据不一致。
  • 幻读:主要针对新增或删除数据。一个事务按照特定条件查询出结果,另一个事务新增了一条符合条件的数据,此时当前事务中查询的数据和数据库中的数据不一致,当前事务好像出现了幻觉。

spring mvc

发表于 2017-05-09 | 分类于 spring

介绍

Spring Web MVC是基于Servlet API构建的原始Web框架,从一开始就包含在Spring Framework中。正式名称”Spring Web MVC”来自其源模块(spring-webmvc)的名称,但它通常被称为”Spring MVC”。

Spring MVC 是一个模型 - 视图 - 控制器(MVC)的Web框架建立在中央前端控制器servlet(DispatcherServlet),它负责发送每个请求到合适的处理程序,使用视图来最终返回响应结果的概念。Spring MVC 是 Spring 产品组合的一部分,它享有 Spring IoC容器紧密结合Spring松耦合等特点,因此它有Spring的所有优点。

框架运行原理特点

SpringMVC架构图所下所示

20200410153217

20200410153312

Spring Web MVC框架特点

清晰的角色划分:控制器(controller)、验证器(validator)、 命令对象(command object)、表单对象(form object)、模型对象(model object)、 Servlet分发器(DispatcherServlet)、 处理器映射(handler mapping)、视图解析器(view resolver)等等。 每一个角色都可以由一个专门的对象来实现。

Spring MVC大致的执行流程如下

  • 1、首先浏览器发送请求给前端控制器DispatcherServlet,DispatcherSerlvet根据请求信息,基于一定的原则选择合适的控制器进行处理并把 请求委托给它。
  • 2、页面控制器接收到请求之后进行功能处理,首先需要收集、绑定请求参数到一个对象(命令对象),并进行验证,然后将该对象委托给业务对象进行处理(service层);业务对象处理之后控制器将返回一个ModelAndView(模型数据和逻辑视图名);
  • 3、DispatcherServlet根据返回的逻辑视图名,选择合适的视图进行渲染(界面展示、资源加载),并把模型数据传入以便视图渲染。
  • 4、前端控制器将响应返回个客户端浏览器。

主要类

HandlerMapping

将请求映射到处理程序以及用于预处理和后处理的拦截器列表 。映射基于某些标准,其细节因HandlerMapping 实施而异。

两个主要HandlerMapping实现RequestMappingHandlerMapping(支持带@RequestMapping注释的方法)和SimpleUrlHandlerMapping(维护URI路径模式到处理程序的显式注册)。

HandlerAdapter

DispatcherServlet无论实际调用处理程序如何,都可以帮助调用映射到请求的处理程序。例如,调用带注释的控制器需要解析注释。主要目是保护DispatcherServlet这些细节。

ViewResolver

将从处理程序返回的基于逻辑的视图名称解析为View 用于呈现给响应的实际视图。

主要组件

  • 1、DispatcherServlet:前端控制器,接收所有请求。
  • 2、HandlerMapping:解析请求格式的,判断希望要执行哪个具体的方法。
  • 3、HandlerAdapter:负责调用具体的方法。
  • 4、ViewResovler:视图解析器,解析结果,准备跳转到具体的物理视图。

Spring 容器和 SpringMVC 容器的关系

20200410153705

从源码可以知道:Spring 容器和 SpringMVC 容器是父子容器,SpringMVC 容器中能够调用 Spring 容器的所有内容。

运行原理

当用户发起请求 , 请求 一个控制器 , 首先会执行DispatcherServlet;由DispatcherServlet 调 用 HandlerMapping 的DefaultAnnotationHandlerMapping 解析URL;解析后调用HandlerAdatper 组 件 的 AnnotationMethodHandlerAdapter;调用Controller中的 HandlerMethod,当 HandlerMethod 执行完成后会返回View,会被 ViewResovler 进行视图解析,解析后调用 jsp 对应的class 文件并运行,最终把运行class 文件的结果响应给客户端。

环境配置

配置启动入口web.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">


<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 修改配置文件路径和名称,默认在 WEB-INF下,springmvc-servlet.xml-->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value>
</init-param>
<!-- 自启动, 优先级,tomacat启动时启动servlet-->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>

配置spring mvc

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
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">


<bean id="demo" class="com.hu.controller.DemoController"></bean>
<bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="urlMap">
<map>
<!-- 解析出来控制器逻辑名 -->
<entry key="demo" value-ref="demo"></entry>
</map>
</property>
</bean>
<!-- SimpleControllerHandlerAdapter 可以执行 Controller 类型的 Handler -->
<bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"></bean>
<!-- 配置视图解析器 把Controller方法返回的视图解析为实际视图 -->
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/"></property>
<property name="suffix" value=".jsp"></property>
</bean>
</beans>

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd">


<!-- 配置自动扫描注解的包 -->
<context:component-scan base-package="com.hu.controller"></context:component-scan>
<!-- 注解驱动 相当于配置了HandlerMapping和HandlerAdapter
因为使用的是注解,所以相当于省略配置下面两个,直接使用<mvc:annotation-driven></mvc:annotation-driven>-->
<!-- org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping -->
<!-- org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter -->
<mvc:annotation-driven></mvc:annotation-driven>
<!--ViewResolver 静态资源处理 -->
<mvc:resources location="/js/" mapping="/js/**"></mvc:resources>
<!--/css/** 根目录下css下所有子文件及其文件 -->
<mvc:resources location="/css/" mapping="/css/**"></mvc:resources>
<mvc:resources location="/images/" mapping="/images/**"></mvc:resources>
</beans>

测试controller和jsp

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
public class DemoController implements Controller {
@Override
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
ModelAndView mav = new ModelAndView("index");
return mav;
}
}

<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
index.jsp
</body>
</html>

@Controller
public class HelloController {
@RequestMapping("/hello")
public String helloWord() {
System.out.println("hello word");
return "index.jsp";
}
}

运行过程:

20200410161856

参数传递

前台提交表单,SpringMVC 只要有这个内容,就会自动注入内容,HandMethod参数中直接接收参数就行。

1、基本数据类型参数传递

A、默认保证参数名称和请求中传递的参数名相同,直接能获取到。

1
2
3
4
5
@RequestMapping("/hello")
public String helloWord(String name, int age) {
System.out.println("hello word");
return "index.jsp";
}

B、 如果请求参数名和方法参数名不对应使用@RequestParam()赋值

1
2
3
4
5
@RequestMapping("/hello")
public String helloWord(@RequestParam(value="name1")String name, @RequestParam(value="age1")int age) {
System.out.println("hello word");
return "index.jsp";
}

C、如果方法参数是基本数据类型(不是封装类)可以通过@RequestParam 设置默认初值,防止出现错误,没有参数时 500

1
2
3
4
5
@RequestMapping("/hello")
public String helloWord(@RequestParam(defaultValue="张三")String name, @RequestParam(defaultValue="12")int age) {
System.out.println("hello word");
return "index.jsp";
}

D、如果强制要求必须有某个参数。@RequestParam(required=true)

1
2
3
4
5
@RequestMapping("/hello")
public String helloWord(@RequestParam(required=true)String name, @RequestParam(defaultValue="12")int age) {
System.out.println("hello word");
return "index.jsp";
}

2、HandlerMethod 中参数是对象类型传递

1
2
3
4
5
6
  请求参数名和对象中属性名对应(get/set 方法),spring自动注入到对象属性。
@RequestMapping("/demo")
public String demo(People people) {
//前端传入的name,age会自动注入到people
return "index.jsp";
}

3、请求参数中包含多个同名参数的获取方式

比如: 复选框传递的参数就是多个同名参数。

1
2
3
<input type="checkbox" name="hover" value="看书"/>
<input type="checkbox" name="hover" value="游戏"/>
<input type="checkbox" name="hover" value="运动"/>
传递时,会把这些参数注入到集合元素中。
1
2
3
4
@RequestMapping("/demo")
public String demo(@RequestParam("hover")List<String> hovers) {
return "index.jsp";
}

4、 请求参数中对象.属性格式

<input type="text" name="people.name"/>
<input type="text" name="people.age"/>

  编写一个类:
  public class Demo {
      private People people;
  }
传递参数方法:
@RequestMapping("/demo")
public String demo(Demo demo) {
        return "index.jsp";
}
这样,就会自动注入到demo属性中。

5、在请求参数中传递集合对象类型参数

1
2
3
4
<input type="text" name="peoples[0].name"/>
<input type="text" name="peoples[0].age"/>
<input type="text" name="peoples[1].name"/>
<input type="text" name="peoples[1].age"/>
Java类:
1
2
3
public class Demo {
private List<People> peoples;
}
方法接收参数:
1
2
3
4
@RequestMapping("/demo")
public String demo(Demo demo) {
return "index.jsp";
}

6、restful 传值方式

当简化参数编写方式时,比如demo?name=zhangsan&age=11这样的格式,简化为特定的如demo/zhangsan/11

这样的格式处理获取参数的方式,如下:

在@RequestMapping 中一定要和请求格式对应,通过{名称}匹配格式:@RequestMapping("/demo/{name}/{age}")

使用@PathVariable获取RequestMapping 参数
1
2
3
4
5
@RequestMapping("/demo/{name}/{age}")
public String demo(@PathVariable String name, @PathVariable int age) {
System.out.println(name + " " + age);
return "/index.jsp";
}

测试结果:http://localhost/springMVC_1_/demo/zhangsan/11
zhangsan 11

字符编码过滤

1
2
3
4
5
6
7
8
9
10
11
12
13
<filter>
<filter-name>encoding</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter
</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>utf-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encoding</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

转发跳转

添加 redirect:资源路径 重定向

添加 forward:资源路径 或省略 forward: 转发

1
2
3
4
5
@RequestMapping("/demo")
public String demo1() {
//return "forward:/index.jsp";//转发,可以省略
return "redirect:/index.jsp";//重定向
}

视图解析器

1、视图解析器

SpringMVC 会提供默认视图解析器,可以自定义视图解析器

1
2
3
4
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/"></property>
<property name="suffix" value=".jsp"></property>
</bean>

如果不希望执行自定义的视图解析器,可以在方法返回值前面添加forward或redirect。

2、@ResponseBody

1、在方法上只有@RequestMapping 时,无论方法返回值是什么都会跳转。

2、如果再加上@ResponseBody注解,则不会跳转,会把返回值转化为json字符串后发送到前端(以流的方式响应到前端)。

如果返回值满足对象或map形式,就会把响应头设置为application/json;charset=utf-8
如果返回值不满足对象或者map形式,则返回为string,并且响应头设置为text/html。
返回值中如果有中文,会出现乱码,需要在响应头中设置编码格式。
@RequestMapping(value="/demo",produces="text/html;charset=utf-8") 如果转为json,需要导入Jackson的jar包。

作用域传值方式

JSP九大内置对象和四大作用域

九大对象:

out对象:用于向客户端、浏览器输出数据。response.getWriter()
request对象:封装了来自客户端、浏览器的各种信息。
response对象:封装了服务器的响应信息。
exception对象:封装了jsp程序执行过程中发生的异常和错误信息。
config对象:封装了应用程序的配置信息。
page对象:指向了当前jsp程序本身。
session对象:用来保存会话信息。也就是说,可以实现在同一用户的不同请求之间共享数req.getSession()
application对象:代表了当前应用程序的上下文。可以在不同的用户之间共享信息。getServletContext();request.getServletContext();
pageContext对象:提供了对jsp页面所有对象以及命名空间的访问。

jsp四大作用域:
page范围:只在一个页面保留数据(javax.servlet.jsp.PageContext(抽象类))
request范围:只在一个请求中保存数据(javax.servlet.httpServletRequest)
Session范围:在一次会话中保存数据,仅供单个用户使用(javax.servlet.http.HttpSession)只要客户端 Cookie 中传递的 Jsessionid 不变,Session 不会重新实力会(不超过默认时间.)
Application范围:在整个服务器中保存数据,全部用户共享(javax.servlet.ServletContext)只有在 tomcat 启动项目时菜实例化.关闭tomcat 时销毁application

SpringMVC作用域传值方式

1、使用原生态Servlet,直接在HandlerMethod参数中添加作用域对象。

1
2
3
4
5
6
@RequestMapping(value="/demo")
public String demo(HttpServletRequest req,HttpSession session) {
HttpSession session1 = req.getSession();
ServletContext application=req.getServletContext();
return "/index.jsp";
}

2、使用Map集合,本质是把map中内容放在request作用域中。spring会对map集合通过BindingAwareModelMap进行实例化。

1
2
3
4
5
6
@RequestMapping(value="/demo1")
public String demo1(Map<String, Object> map) {
System.out.println(map.getClass());
map.put("map", "map值");//jsp可以直接${map }获得
return "/index.jsp";
}

3、 使用 SpringMVC 中 Model 接口,把内容最终放入到 request 作用域中

1
2
3
4
5
@RequestMapping(value="/demo2")
public String demo2(Model model) {
model.addAttribute("mode", "mode 值");
return "/index.jsp";
}

4、使用 SpringMVC 中 ModelAndView 类

1
2
3
4
5
6
7
@RequestMapping(value="/demo3")
public ModelAndView demo3() {
//通过跳转视图
ModelAndView mav=new ModelAndView("/index.jsp");
mav.addObject("mav","mav值");
return mav;
}

上述作用域值在jsp页面中都可以通过这样直接获取
${requestScope.req }

${map }

${mode }

文件下载和上传实例

文件下载

浏览器访问资源时,当是文件下载需要设置响应头Content-Disposition,如果没有设置按照inline处理(inline是能显示文件内容就显示,不能显示就会下载)

设置文件下载响应头:Context-Dispositon=”attachment;filename=文件名”,是以附件的形式下载。

实现文件下载,需要导入Apache文件处理工具包。
commons-io-2.2.jar
commons-fileupload-1.3.1.jar

实现
springmvc中添加放行静态资源。
<mvc:resources location=”/file/“ mapping=”/file/**”>
jsp代码:

1
2
3
4
5
6
7
8
9
10
11
12
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>文件下载</title>
</head>
<body>
<a href="download?fileName=abc.txt">下载</a>
</body>
</html>

控制器代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@RequestMapping("download")
public void download(String fileName, HttpServletResponse res, HttpServletRequest req) throws IOException {
//设置响应流中文件进行下载
res.setHeader("Content-Disposition", "attachment;filename="+fileName);
//把二进制流放入到响应体中
ServletOutputStream os = res.getOutputStream();
String path = req.getServletContext().getRealPath("files");
File file = new File(path, fileName);
//把文件转换为二进制流
byte[] bytes = FileUtils.readFileToByteArray(file);
os.write(bytes);
os.flush();
os.close();
}

文件上传

    1. 基于 apache 的 commons-fileupload.jar 完成文件上传.
    1. MultipartResovler 作用:把客户端上传的文件流转换成 MutipartFile 封装类;通过 MutipartFile 封装类获取到文件流。
    1. 表单数据类型分类,<form>的 enctype 属性控制表单类型

      application/x-www-form-urlencoded默认值,普通表单数据(少量文字信息)

      text/plain 大文字量时使用的类型,邮件,论文。

      multipart/form-data 表单中包含二进制文件内容。

    1. 实现步骤

导入 springmvc 包和 apache 文件上传 commons-fileupload 和commons-io 两个 jar

配置文件解析器,因为如果上传大小超过设定,会有异常,对应配置异常解析处理。

1
2
3
4
5
6
7
8
9
10
11
12
  <!-- MultipartResovler 解析器 -->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="maxUploadSize" value="5000000"></property>
</bean>
<!-- 异常解析器 -->
<bean id="exceptionResolver" class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="exceptionMappings">
<props>
<prop key="org.springframework.web.multipart.MaxUploadSizeExceededException">/error.jsp</prop>
</props>
</property>
</bean>

JSP 页面:

1
2
3
4
5
6
7
8
9
10
<title>文件上传</title>
</head>
<body>
<form action="upload" enctype="multipart/form-data" method="post">
姓名:<input type="text" name="name" /><br />
文件:<input type="file" name="file" /><br />
<input type="submit" value="提交 " />
</form>
</body>
</html>

控制器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@RequestMapping("upload")// MultipartFile 对象名必须和<input type="file"/>的 name 属性值相同    
public String upload(MultipartFile file, HttpServletRequest req) throws IOException {
String fileName = file.getOriginalFilename();
String suffix = fileName.substring(fileName.lastIndexOf("."));
String path = req.getServletContext().getRealPath("files");
// 判断上传文件类型
if (suffix.equalsIgnoreCase(".png")) {
String uuid = UUID.randomUUID().toString();
FileUtils.copyInputStreamToFile(file.getInputStream(), new File(path + uuid + suffix));
return "/index.jsp";
} else {
return "error.jsp";
}
}

拦截器

springMVC拦截器和servlet过滤器类似,通过拦截器可以进行权限验证、记录请求信息的日志、判断用户是否登录等。

和AOP区分开,AOP针对的是特定方法前后的扩充,主要用于Service。而拦截器针对的是控制器方法。

拦截器和过滤器Controller的区别。

拦截器只能拦截controller;Filter可以拦截任何请求。

1、通过实现HandlerInterceptor接口,或继承HandlerInterceptor接口的实现类(如HandlerInterceptorAdapter)来定义。

2、通过实现WebRequestInterceptor接口,或继承WebRequestInterceptor接口的实现类来定义。

新建拦截器类,实现HandlerInterceptor:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
 public class DemoHandlerInterceptor implements HandlerInterceptor{
//在进入控制器之前执行,如果返回false,会阻止进入控制器。
@Override
public boolean preHandle(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2) throws Exception {
return false;
}
//控制器执行完成,进入到jsp之前执行。
// 如果控制器出现异常直接到异常处理afterCompletion
//比如做日志记录、敏感词语过滤
@Override
public void postHandle(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, ModelAndView arg3)
throws Exception {

}
//jsp执行完成后执行
//异常日志记录
@Override
public void afterCompletion(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, Exception arg3)
throws Exception {
}
}

配置文件配置拦截器生效

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!-- 方式一:拦截所有的控制器 -->
<mvc:interceptors>
<bean class="com.hu.interceptor.DemoHandlerInterceptor"></bean>
</mvc:interceptors>
<!-- 拦截特定的url -->
<mvc:interceptors>
<mvc:interceptor>
<!-- 配置拦截路径 --!>
<mvc:mapping path="/demo" />
<mvc:mapping path="/demo1" />
<mvc:mapping path="/demo2" />
<bean class="com.hu.interceptor.DemoHandlerInterceptor"></bean>
</mvc:interceptor>
</mvc:interceptors>

拦截器栈

当多个拦截器同时生效时,组成了拦截器栈。遵行先进后出的规律。
执行顺序和在 springmvc.xml 中配置顺序有关
比如:设置先配置拦截器 A 在配置拦截器 B 执行顺序为 preHandle(A) –> preHandle(B) –> 控制器方法 –> postHandle(B)
–> postHanle(A) –> JSP –> afterCompletion(B) –> afterCompletion(A)

Spring MVC中关于关于Content-Type类型应用

@RequestMapping注解的定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public @interface RequestMapping {
String name() default "";

//指定请求的实际地址
@AliasFor("path")
String[] value() default {};
//
@AliasFor("value")
String[] path() default {};
//指定请求的method类型
RequestMethod[] method() default {};
//指定request中必须包含某些参数值是,才让该方法处理
String[] params() default {};
//指定request中必须包含某些指定的header值,才能让该方法处理请求
String[] headers() default {};
//指定处理请求的提交内容类型(Content-Type)
String[] consumes() default {};
//指定返回的内容类型,仅当request请求头中的(Accept)类型中包含该指定类型才返回
String[] produces() default {};
}

上一页1…212223…25下一页
初晨

初晨

永远不要说你知道本质,更别说真相了。

249 日志
46 分类
109 标签
近期文章
  • WebSocket、Socket、TCP、HTTP区别
  • Springboot项目的接口防刷
  • 深入理解Volatile关键字及其实现原理
  • 使用vscode搭建个人笔记环境
  • HBase介绍安装与操作
© 2018 — 2020 Copyright
由 Hexo 强力驱动
|
主题 — NexT.Gemini v5.1.4