JVM

Java被设计为一门简单且高级的语言,把内存管理和安全等复杂任务交给虚拟机管理,最后在面试时还是逃不掉被问。

JVM,Java Virtual Machine Java虚拟机。JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。

Java虚拟机是实现平台无关性的关键。一般的高级语言如果要在不同的平台上运行需要编译成不同的目标代码,Java语言使用Java虚拟机屏蔽了与具体平台相关的信息,使得Java语言编译程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。Java虚拟机在执行字节码时,把字节码解释成具体平台上的机器指令执行。这就是Java的能够“一次编译,到处运行”的原因。所以跨平台的是Java程序,不是JVM。JVM是用C/C++开发的,是编译后的机器码,不能跨平台,不同平台下需要安装不同版本的JVM。
http://www.mamicode.com/info-detail-1028149.html

JVM 的生命周期

JVM实例对应了一个独立运行的java程序,它是进程级别的。JVM执行引擎实例则对应了属于用户运行程序的线程,它是线程级别的。
a)启动。启动一个Java程序时,一个JVM实例就产生了,任何一个拥有public static void main(String[] args)函数的class都可以作为JVM实例运行的起点。
b)运行。main()作为该程序初始线程的起点,任何其他线程均由该线程启动。JVM内部有两种线程:守护线程和非守护线程,main()属于非守护线程,守护线程通常由JVM自己使用,java程序也可以标明自己创建的线程是守护线程。
c)消亡。当程序中的所有非守护线程都终止时,JVM退出;若安全管理器允许,程序也可以使用Runtime类或者System.exit()来退出。

详细的说,每一个运行的Java程序对应一个运行中的虚拟机,一个运行中的Java虚拟机有着一个清晰的任务:执行Java程序。程序开始执行时它才运行,程序结束时它就停止。Java虚拟机总是开始于一个main()方法,这个方法必须是公有、返回void、直接受一个字符串数组。在程序执行时必须给Java虚拟机指明这个包含main()方法的类名。 Main()方法是程序的起点,他被执行的线程初始化为程序的初始线程。程序中其他的线程都由它来启动。Java中的线程分为两种:守护线程(daemon)和普通线程(non-daemon)。守护线程是Java虚拟机自己使用的线程,比如负责垃圾收集的线程就是一个守护线程。也可以把自己的程序设置为守护线程,但包含Main()方法的初始线程不能是守护线程。只要Java虚拟机中还有普通的线程在执行,Java虚拟机就不会停止。如果有足够的权限,可以调用exit()方法终止程序。

HotSpot VM

HotSpot VM是绝对的主流的JVM。从Java SE 7开始,HotSpot VM就是Java规范的“参考实现”(RI,Reference Implementation)。JDK8的HotSpot VM已经是以前的HotSpot VM与JRockit VM的合并版,也就是传说中的“HotRockit”,只是产品里名字还是叫HotSpot VM。

JVM的体系结构

1.装载器(ClassLoader),用来装载class文件。
2.执行引擎,执行字节码或者执行本地方法。
3.运行时数据区,包括方法区、堆、java栈、PC寄存器、本地方法栈。

JVM 类加载器

JVM 整个类加载过程的步骤:
1.装载
装载过程负责找到二进制字节码并加载至JVM中,JVM根据类名和类所在的包名,使用ClassLoader来完成类的加载。也采用以上三个元素来标识一个被加载了的类:类名+包名+ClassLoader实例ID。
2.链接
链接过程包括三步:对二进制字节码的格式进行校验;分配存储空间并初始化装载类中的静态变量;解析类中调用的接口和类。
其中,在完成校验后,JVM初始化类中的静态变量,将其值赋为默认值。最后对类中的所有属性、方法进行验证,确保其需要调用的属性、方法存在,以及具备应的权限(例如public、private域权限等)。解析失败可能出现NoSuchMethodError、NoSuchFieldError等错误信息。
3.初始化
初始化过程即为执行类中的静态初始化代码、构造器代码和对静态属性初始化。在四种情况下初始化过程会被触发执行:
调用了new;反射调用了类中的方法;子类调用了初始化;JVM启动过程中指定的初始化类。

https://yq.aliyun.com/ziliao/307947

JVM运行时数据区

Java虚拟机管理的内存包括几个运行时数据内存:程序计数器、虚拟机栈、本地方法栈、堆、方法区,其中方法区和堆是线程共享的,其他几个是线程隔离的。

1:程序计数器(PC 寄存器)
程序计数器是一个比较小的内存区域,位于处理器内部,记录当前线程所执行的Java字节码地址,是线程隔离的。PC寄存器是用于存储每个线程下一步将执行的JVM指令,如果该方法为native的,则PC寄存器中不存储任何信息。
2:栈(JVM栈,区别于本地方法栈)
栈是线程私有的,每个线程创建的同时都会创建JVM栈,JVM栈中存放的为当前线程中局部基本类型的变量(java中定义的八种基本类型:boolean、char、byte、short、int、long、float、double)、部分的返回结果以及Stack Frame,非基本类型的对象在JVM栈上仅存放一个指向堆上的地址。
方法中的局部变量,存放在栈区。
3:本地方法堆栈(Native Method Stacks )
JVM采用本地方法堆栈来支持native方法的执行,此区域用于存储每个native方法调用的状态。
4:堆(Heap)
存储对象实例以及数组值的区域,可以认为所有通过new创建的对象都在此分配。Heap中的对象的内存需要等待GC进行回收。对象实例,包括其中的成员变量,存放在堆区。
(1)堆是所有线程共享的,因此在其上进行对象内存的分配均需要进行加锁,这也导致了new对象的开销是比较大的。
(2)Sun Hotspot JVM为了提升对象内存分配的效率,对于所创建的线程都会分配一块独立的空间TLAB(Thread Local Allocation Buffer),其大小由JVM根据运行的情况计算而得,在TLAB上分配对象时不需要加锁,因此JVM在给线程的对象分配内存时会尽量的在TLAB上分配,在这种情况下JVM中分配对象内存的性能和C基本是一样高效的,但如果对象过大的话则仍然是直接使用堆空间分配
(3)TLAB仅作用于新生代的Eden Space,因此在编写Java程序时,通常多个小的对象比大的对象分配起来更加高效。
5:方法区(静态存储区)
(1)方法区对应的为Permanet Generation,又称为持久代,存放class文件和静态数据,线程共享。
(2)方法区域存放了所加载的类的信息(名称、修饰符等)、类中的静态变量、类中定义为final类型的常量、类中的Field信息、类中的方法信息,通过Class对象中的getName、isInterface等方法来获取信息时,这些数据都来源于方法区域,同时方法区域也是全局共享的,在一定的条件下也会被GC,当方法区域需要使用的内存超过其允许的大小时,会抛出OutOfMemory的错误信息。
(3)运行时常量池(Runtime Constant Pool )
存放类中的固定的常量信息、编译时期生成的各种字面量和符号引用、未经 new 的常量、方法和Field的引用信息等。其空间从方法区域中分配,受方法区大小的影响。

Java内存模型

JMM:Java Memory Model,Java内存模型。
JMM规定了所有共享变量都存储在主内存(Main Memory)中,每个线程还有自己的线程工作内存(Working Memory),线程的工作内存中保存了该线程使用到的变量的主内存的副本拷贝,线程对变量的所有操作(读取、赋值等)都必须在工作内存中进行,而不能直接读写主内存中的变量(volatile变量仍然有工作内存的拷贝,但是由于它特殊的操作顺序性规定,所以看起来如同直接在主内存中读写访问一般)。不同的线程之间也无法直接访问对方工作内存中的变量,线程之间值的传递都需要通过主内存来完成。

JVM垃圾回收

分代回收是目前比较先进的垃圾回收方案,分代垃圾回收采用分而治之的思想,把不同生命周期的对象放在不同代上,不同代上采用最适合它的垃圾回收方式进行回收。把堆分代的作用就是优化GC性能。很多对象都是朝生夕死的,把新创建的对象放到某一块地方,GC的时候先把这块存“朝生夕死”对象的区域进行回收,可以高效地腾出很大的空间。

虚拟机中共划分为三个代:

  • 年轻代(Young Generation):新生成的对象首先放在年轻代,年轻代的目标是尽可能快速的收集掉那些生命周期短的对象。HotSpot JVM把年轻代又分为3个区:一个Eden区和二个Survivor区(分别叫from和to),默认比例为8:1。增加年轻代大小会减小老年代,年轻代大小一般设置为堆大小的1/4到1/3。
  • 年老代(Tenured Generation):年轻代中的在多次垃圾回收后仍然存活的对象会被放到年老代中,年老代中存放的是一些生命周期较长的对象。
  • 持久代(Permanent Generation):持久代也称为方法区,存储虚拟机加载的类信息、常量、静态变量、JIT编译后的代码等数据。Java7 已经把字符串常量池移动到了堆中,在调用 String 的 intern方法时,如果堆中存在相同的字符串对象,则会直接保存对象的引用,不会重新创建对象。MaxPermSize一般设置为64M或128M。

什么情况下触发垃圾回收?由于对象进行了分代处理,因此垃圾回收区域、时间不一样。GC有两种类型:Scavenge GC和Full GC

  • Scavenge GC(搜寻):一般情况下,当新对象生成,并且在Eden申请空间失败时,就会触发Scavenge GC,对Eden区域进行GC,清除非存活对象,并且把尚且存活的对象移动到Survivor区,然后整理Survivor的两个区。这种方式的GC是对年轻代的Eden区进行,不会影响到年老代。因为大部分对象都是从Eden区开始的,同时Eden区不会分配的很大,所以Eden区的GC会频繁进行。因而要使用效率高的算法使Eden去能尽快空闲出来。
  • Full GC:对整个堆进行整理,包括Young、Tenured和Perm。Full GC因为需要对整个堆进行回收,所以比ScavengeGC要慢,因此应该尽可能减少Full GC的次数。在对JVM调优的过程中,很大一部分工作就是对于Full GC的调节。有如下原因可能导致Full GC:年老代(Tenured)或 持久代(Perm)被写满、 System.gc()被显示调用。

内存泄露无的产生原因之一是无用对象间互相引用现象,导致对象无法回收,另一种对象回收的条件是根搜索算法不可达,哪些对象可以作为根呢?

  • 方法区中类静态变量引用的对象、常量引用的对象。
  • 虚拟机栈栈帧中引用的对象。
  • 本地方法栈栈帧中引用的对象。

从这些根出发,不可达的对象就是需要回收的对象。

对象引用级别

从JDK1.2版本开始,为了使程序能更加灵活的控制对象的生命周期,把对象的引用分为四种级别,由高到低依次为:强引用、软引用、弱引用和虚引用。
在程序设计中一般很少使用弱引用与虚引用,使用软引用的情况较多,这是因为软引用可以加速JVM对垃圾内存的回收速度,可以维护系统的运行安全,防止内存溢出(OutOfMemory)等问题的产生。

不同的对象引用类型,GC 会采用不同的方法进行回收,JVM 对象的引用分为了四种类型:
(1)强引用:默认情况下,对象采用的均为强引用(这个对象的实例没有其他对象引用,GC时才会被回收)
(2)软引用:软引用是Java中提供的一种比较适合于缓存场景的应用(只有在内存不够用的情况下才会被GC)
(3)弱引用:在GC时一定会被GC回收
(4)虚引用:虚引用只是用来得知对象是否被GC

程序设计中一般很少使用弱引用与虚引用,使用软用的情况较多,因为软引用可以加速JVM对垃圾内存的回收速度,可以维护系统的运行安全,防止内存溢出(OutOfMemory)等问题的产生。

https://my.oschina.net/ydsakyclguozi/blog/404389

JVM配置参数,和线程的内存分析

在内存小的机器上经常出现的问题有:Cann’t allocate memory 和 OutOfMemoryError。在jvm内存调整过程中,我们经常使用的参数有:
-Xms 设置jvm启动时分配堆内存大小,也代表最小堆,比如 -Xms200m 表示分配200M。
-Xmx 设置jvm运行过程中分配最大堆内存大小,比如 -Xmx500m 表示jvm进程最多只能够占用500M内存。
-Xss 设置jvm每个线程分配栈内存大小,默认JDK1.4中是256K,JDK1.5+中是1M。
-Xmn 设置jvm年轻代(新生代)大小。整个堆大小 = 年轻代大小 + 年老代大小 + 持久代大小。持久代一般固定大小为64m,所以增大年轻代后将会减小年老代大小。此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8。比如-Xmn2g设置年轻代大小为2G。新生代大部分要回收,采用Copying算法,速度快。老年代大部分不需要回收,采用Mark-Compact算法。
-XXSurvivorRatio=3:代表Eden:Survivor = 3。

一般出现Cannt’ allocate memory错误是机器内存不够,系统无法为jvm分配给定的内存,在启动时常见,所以可以在启动参数中设置-Xms来指定。
OutOfMemoryError错误一般会在系统运行一段情况后出现,多数也是机器内存不够或是JVM本身的内存空间已被用尽,这时要根据情况进行调整:如果是JVM本身的内存空间用尽,则需要调整-Xmx参数来分类jvm的可用内存。如果是机器内存不够则要增加内存或是调优程序。

Xms、Xmx主要是设置jvm的最小可用内存和最大可用内存,属于进程级别的内存控制。在java中每个线程需要分配线程内存,用来存储自身的线程变量,在java中每new一个线程,jvm都是向操作系统请求new一个本地线程,此时操作系统会使用剩余的内存空间来为线程分配内存,而不是使用jvm的内存。这样当操作系统的可用内存越少,则jvm可用创建的新线程也就越少。操作系统对一个进程内的线程数是有限制的,不能无限生成。经验值在3000~5000左右。

Server端-Xms值最好设置与-Xmx相同,以避免每次垃圾回收完成后JVM重新分配内存,同时最好让-Xmn值约等于-Xmx的1/3。
增加Heap的大小虽然会降低GC的频率,但也增加了每次GC的时间。GC运行时所有的用户线程将暂停,也就是GC期间Java应用程序不做任何工作。
Heap大小并不决定进程的内存使用量。进程的内存使用量要大于-Xmx定义的值,因为Java为其他任务分配内存,例如每个线程的Stack。

运行时配置内存

当内存申请出现问题时,会出现错误日志:
Java HotSpot(TM) 64-Bit Server VM warning: INFO: os::commit_memory(0x00000000f7d00000, 106430464, 0) failed; error=’Cannot allocate memory’ (errno=12)。
运行时配置最小运行内存、最大运行内存:java -Xms64m -Xmx512m -jar xxx.jar
-Xss:指定每个线程的栈大小。Xss越大,每个线程占用的内存越多,总线程数就越少;而Xss越小,则递归的最大深度越小,容易出现栈溢出StackOverflowError 问题。适量减少局部变量的声明,可以节省栈帧大小,增加调用深度。

https://stackoverflow.com/questions/31002612/java-hotspottm-64-bit-server-vm-warning

JAVA_OPTS设置 https://blog.csdn.net/kongls08/article/details/8468713

垃圾回收基础
https://segmentfault.com/a/1190000004638653

运行模式

JVM有两种运行模式Server与Client。Client模式启动速度较快,Server模式启动较慢;但是启动进入稳定期长期运行之后Server模式的程序运行速度比Client要快很多。这是因为Server模式启动的JVM采用的是重量级的虚拟机,对程序采用了更多的优化;而Client模式启动的JVM采用的是轻量级的虚拟机。所以Server启动慢,但稳定后速度比Client远远要快。

使用Java -version命令就能显示出当前虚拟机处于哪种模式、虚拟机类型、位数。64位只支持server模式。

class的形成和使用

1)编译器把java文件编译为class文件,编译器和JVM是不同的。
2)JVM对class文件进行加载、校验、执行

类对象被初始化与不被初始化

虚拟机规范严格规定了有且只有五种情况必须立即对类进行“初始化”:
1.使用new关键字实例化对象的时候、读取或设置一个类的静态字段的时候,已经调用一个类的静态方法的时候。
2.使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没有初始化,则需要先触发其初始化。
3.当初始化一个类的时候,如果发现其父类没有被初始化就会先初始化它的父类。
4.当虚拟机启动的时候,用户需要指定一个要执行的主类(就是包含main()方法的那个类),虚拟机会先初始化这个类;
5.使用Jdk1.7动态语言支持的时候的一些情况。
除了这五种,其他的所有引用类的方式都不会触发初始化,称为被动引用。下面是被动引用的例子:
1.通过子类引用父类的的静态字段,不会导致子类初始化。
2.通过数组定义来引用类,不会触发此类的初始化。例如:Class1[] sca = new Class1[10];
3.常量在编译阶段会存入调用类的常量池中,本质上没有直接引用到定义常量的类,因此不会触发定义常量的类的初始化。

代码块

class HelloA
{
 public HelloA()
 {
//构造方法
 }
 {
//构造代码块
     System.out.println("I’m A class");
 }
 static
 {
//静态代码块
     System.out.println("static A");
 }
}

(1)静态代码块、构造代码块、构造方法。按上述顺序执行。
(2)静态代码块:是在类的加载过程的第三步初始化的时候进行的,主要目的是给类变量赋予初始值。
构造代码块:是独立的,必须依附载体才能运行,Java会把构造代码块放到每种构造方法的前面,用于实例化一些共有的实例变量,减少代码量。
构造方法:用于实例化变量。
静态代码块是类级别的,构造代码块和构造方法是实例级别的。对子类得主动使用会导致对其父类得主动使用,所以尽管实例化的是子类,但也会导致父类的初始化和实例化,且优于子类执行。

Spring-AOP

定义装备

定义切点

Spring中表示方式切点常用的有两种方式:1.使用正则表达式 2.使用AspectJ表达式
使用org.springframework.aop.support.JdkRegexpMethodPointcut来定义正则表达式切点

<bean id="sleepPointcut" class="org.springframework.aop.support.JdkRegexpMethodPointcut">
 <property name="pattern" value=".*sleep"/>
</bean>

上面示例中pattern属性指定了正则表达式,它匹配所有的sleep方法

定义顾问

把通知和切点结合起来:
org.springframework.aop.support.DefaultPointcutAdvisor

<bean id="sleepHelperAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
 <property name="advice" ref="sleepHelper"/>
 <property name="pointcut" ref="sleepPointcut"/>
</bean>

定义代理对象

<bean id="humanProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
     <property name="target" ref="human"/>
     <property name="interceptorNames" value="sleepHelperAdvisor" />
     <property name="proxyInterfaces" value="test.spring.aop.bean.Sleepable" />
</bean>

Spring还提供了一种自动代理的功能,不用显示定义代理对象,能让通知者和业务对象自动匹配,如下:

<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/>

http://blog.csdn.net/udbnny/article/details/5870076
http://www.jb51.net/article/83173.htm

AOP概念

AOP是Aspect Oriented Programming的缩写,意思是面向切面(方面)编程。
主要的功能有日志记录,性能统计,安全控制,事务处理,异常处理等,主要意图是将功能代码从业务逻辑代码中划分出来,通过对这些行为的分离,我们希望可以将它们独立到非主导业务逻辑的方法中,进而改变这些行为的时候不影响主业务逻辑的代码。
AOP是其实是通过动态代理模式来实现业务逻辑的插入,使开发者在开发时不用关注其他与业务无关的点,通过代理的方式做到了插拔式操作。

AOP,OOP区别概念

  • AOP是OOP的补充
  • OOP编程将程序分解成各个层次的对象,AOP将程序运行过程分解成各个切面。
  • AOP从程序运行角度考虑程序的结构,提取业务处理过程的切面,
  • OOP是静态的抽象,AOP是动态的抽象,
  • AOP是对应用执行过程中的步骤进行抽象,从而获得步骤之间的逻辑划分。

AOP框架具有的两个特征:
1.各个步骤之间的良好隔离性
2.源代码无关性

IOC概念

IoC,(Inverse of Control)控制反转,其包含两个内容:其一是控制,其二是反转。在程序中,被调用类的选择控制权从调用它的类中移除,转交给第三方裁决。这个第三方比如Spring容器。IoC另解,依赖注入(Dependency Injection),调用类对被调用类的依赖关系由第三方注入。
IoC其实是遵循了软件设计理念的依赖倒转原则,面向对象的设计就是为了实现软件的更好的复用行和扩展性,我们就必须降低我们每个pojo的依赖关系,也就是解耦,耦合度低了,才能更好的重用和扩展。
IoC中用到了工厂模式
若不是所有操作都要求有相同的效应,则要考虑是否使用切面。

WebService

JAX-WS全称是JavaTM API forXML-Based WebServices
JAX-RS全称是JavaTM APIforRESTful Web Services
JAX-WS是针对WebService,JAX-RS是针对RESTful HTTP Service
这是两种风格的SOA架构风格:
JAX-WS以动词为中心,指定的是每次执行函数,大力支持的厂商如BEA,IBM,MS基本都是开发工具厂商
JAX-RS以名词为中心,每次执行的时候指的是资源,大力支持的厂商如Google,Yahoo,亚马孙等都是服务运营厂商

soap rest 区别
http://www.cnblogs.com/zhangchenglzhao/p/3728455.html

相对来说,REST是一种轻量级架构。
以前旧的基于方法的Web Service都是属于JAX-WS,现在较新的RESTful的Web Service则是属于JAX-RS。其中Jersey是SUN的JAX-RS的参考实现,RESTeasy则是Jboss的JAX-RS的实现。RESTful的Web Service简单来说就是将所有东西看作资源,每个资源都有一个URI,对资源的CRUD操作借助于HTTP协议的POST、GET、PUT、DELETE来实现。效率比旧的基于方法的Web Service要高。

开源WebService框架
CXF、Axis,SpringRMI

组播

组播 MulticastSocket 广播学习
http://hbiao68.iteye.com/blog/1943354
Java IP组播:MulticastSocket
http://m.blog.csdn.net/CorozoNut/article/details/71515455

在单播模式中有服务器端和客户端之分,而组播模式中每个端都是以路由器或交换机做为中转广播站,任意一端向路由器或交换机发送消息,路由或交换机负责发送消息到其他节点,每个节点都是同等的。所以在编程模式上可以用同一个类表示:MulticastSocket。

InetAddress和SocketAddress的区别
InetAddress 只有IP没有端口,InetAddress group = InetAddress.getByName(“localhost”);
InetSocketAddress IP+端口,SocketAddress remoteAddr=new InetSocketAddress(“localhost”,8000);

NetworkInterface 网络接口类
网络接口名是用于标识物理或逻辑网络接口的名字,通常是由操作系统设置的。网络接口名在多数操作系统上常以eth开头,后面是网络接口的索引號,从0開始。如本机安了三块网卡,那么网络接口名就依次是eth0、eth1和eth2。每一个网络接口都能够绑定一个ip地址,也能够据此得到设备的MAC地址。

广播(broadcast),组播(multicast),单播(unicast)的Java实现
http://blog.onlycatch.com/post/%E5%B9%BF%E6%92%AD%EF%BC%88broadcast-%EF%BC%8C%E7%BB%84%E6%92%AD-multicast-%EF%BC%8C%E5%8D%95%E6%92%AD-unicast-%E7%9A%84Java%E5%AE%9E%E7%8E%B0

持续接收http消息

       
try {
            URL realUrl = new URL(url);
            HttpURLConnection connection = (HttpURLConnection)realUrl.openConnection();
            connection.setRequestProperty("accept", "*/*");
            connection.setRequestProperty("connection", "Keep-Alive");
            connection.connect();
            if(connection.getResponseCode()==200){
                InputStream is=connection.getInputStream();
                //ByteArrayOutputStream baos=new ByteArrayOutputStream();
                //10MB的缓存
                byte [] buffer=new byte[10485760];
                int len=0;
                while((len=is.read(buffer))!=-1){
                    //baos.write(buffer, 0, len);
                    System.out.print("len:" + len + " " + new String(buffer, 0, len));
                }
                //String jsonString=baos.toString();
                //baos.close();
                is.close();
            }
        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IOException ex) {
           ex.printStackTrace();
        }

服务器上组播网络设置过程:
/etc/network/interfaces中
外网网卡p3p1 设置为dhcp时
eth0 gateway 配置 打开时能收到message,但上不了外网。
eth0 gateway 配置 关闭时不能收到message,但能上外网。

初步测试是gateway影响的,组播有效和上外网有效中,只能配置一个gateway选择。同时配置两个gateway,能上外网、组播无效。

Mybatis

Mybatis

与其他的ORM(对象关系映射)框架不同, MyBatis没有将Java对象与数据库表关联,而是将 Java 方法与 SQL 语句关联。 MyBatis 允许用户充分利用数据库的各种功能,例如存 储过程、视图、各种复杂的查询以及某数据库的专有特性 。如果要对遗留数据库、不规范的数 据库进行操作, 或者要完全控制SQL的执行, MyBatis将会是一个不错的选择。

MyBatis 3.0 相比 2.0 版本的 一个最大变化是支持使用接口来调用方法。使用接口调用方式就会方便很多, MyBatis使用 Java的动态代理可以直接通过接口来调 用 相 应 的方法,不需要提供接口的实现类。

@Delete、@Update、@Select、@Insert四种注解或xml标签使用时,都要在后面加sql语句。能否混用,它们有什么不同?调用存储过程时又要使用哪种注解呢?

dao层中接口的方法名与xml中的标签id值是一致的,所以一旦出现方法重载之后就可会出现相同的方法名,xml中的id也是重名的了,mybaits中的dao层接口是不能进行方法重载的。

choose-when-otherwise标签相当于switch-case

查询语句中in()括号中的变量要使用${param},使用#{param}无效。如果param是字符串序列,要格式化为引号序列

String str="";
		String[] strs=dates.split(",");
		for(int i=0;i 小于 strs.length;i++) {
			str+="'"+strs[i]+"'";
			if(i 小于 strs.length-1) {
				str+=",";
			}
		}

Like查找

dateTrack LIKE CONCAT(#{dateTrack}, '%')

PageHelper

PageHelper首先将传递的参数保存到page这个对象中,接着将page的副本存放入ThreadLoacl中,保证在分页的时候参数互不影响,接着利用了mybatis提供的拦截器,取得ThreadLocal的值,重新拼装分页SQL,完成分页。

@select注解形式查询

@Select({"<script>",
		"select w.*,d.name as dutyName,r.name as roleName,e.imei as imei,g.name as groupName,p.name as projectName "
			+ "from worker_table as w "
			+ "left join duty_table as d on w.dutyId=d.id "
			+ "left join role_table as r on w.roleId=r.id "
			+ "left join wtsr_equipment as e on w.equipmentId=e.id "
			+ "left join group_table as g on w.groupId=g.id "
			+ "left join project_table as p on g.projectId=p.id "
			+ "where w.groupId=#{groupId} and w.type=#{type} ",
			"<when test='state!=null and (state==1 || state==2 || state==3)'>and w.state=#{state} and unix_timestamp(now())*1000-w.stateTimestamp &lt; 20*60*1000</when>",//20分钟内更新是在线
			"<when test='state!=null and state==4'>and unix_timestamp(now())*1000-w.stateTimestamp <![CDATA[>=]]> 1200000</when>",//20分钟未更新则离线
			"order by id desc",
			"</script>"})
	public List findWorkerInfos(@Param("groupId")long groupId,@Param("type")int type,@Param("state")Integer state);

OGNL

OGNL:Object-Graph Navigation Language,也称为对象导航语言,是一种功能强大的表达式语言。它通过简单一致的表达式语法,可以存取对象的任意属性,调用对象的方法,遍历整个对象的结构图,实现字段类型转化等功能。

Hibernate load与get

load(Object, Serializable):根据id查询 。查询返回的是代理对象,不会立刻访问数据库,是懒加载的。当真正去使用对象的时候才会访问数据库。
load()的时候会发现不会打印出查询语句,而使用get()的时候会打印出查询语句。
使用load()时如果在session关闭之后再查询此对象,会报异常:could not initialize proxy – no Session。处理办法:在session关闭之前初始化一下查询出来的对象:Hibernate.initialize(entity);

load()默认支持延迟加载,在用到对象中的其他属性数 据时才查询数据库,但是万一数据库中不存在该记录,只能抛异常ObjectNotFoundException;所说的load方法抛异常是指在使用该对象的数据时,数据库中不存在该数据时抛异常,而不是在创建这个对象时。由于session中的缓存对于hibernate来说是个相当廉价的资源,所以在load时会先查一下session缓存看看该id对应的对象是否存在,不存在则创建代理。
get()先在一级缓存找,没有就去二级缓存找,再没有就去数据库找,最后若没有就返回null,get方法一定要获取到真实的数据,否则返回null。

在 hibernate 开发中,关于 POJO 类对象的状态:
A、自由状态(Transient):实体在内存中自由存在,与数据库中的记录无关
B、持久状态(Persistent):实体处于由Hibernate框架所管理的状态,对应了数据库中的一条记录,同时与某个session实例发生了关联
C、游离状态(Detached):在session 关闭之后,可以使对象从持久状态转换到游离状态。
D、可以将对象从游离状态转换成持久态。

输出sql日志,要记的把控制台console的级别修改为对应的级别。

Mybatis-Plus
https://mp.baomidou.com/guide/wrapper.html#notexists

TCP服务器

可选的TCP服务器有Mina、Netty、Twisted等,它们都是异步、事件驱动(asynchronous、event-driven)的网络编程框架。
其中MINA和Netty是基于Java语言的,Twisted是Python语言的。语言不是重点,重点的是理念。

  • 传统的BIO(Blocking IO/阻塞IO)进行网络编程时,进行网络IO读写时都会阻塞当前线程,使用BIO实现一个TCP服务器,需要对每个客户端连接开启一个线程,很多线程可能会阻塞住等待读写数据,系统资源消耗大。
  • NIO(Non-Blocking IO/非阻塞IO)或AIO(Asynchronous IO/异步IO)通过IO多路复用技术实现,不需要为每个连接创建一个线程,其底层实现使用了操作系统的一些特性如select、pool、epoll、iocp等。

Mina、Netty、Twisted一起学(一):实现简单的TCP服务器
Netty 4.x 用户指南
Netty in Action 学习
一起学Netty
netty(一) java NIO

同步阻塞I/O

Java的两个IO方法 read和write都是同步阻塞的。
阻塞指的是read和write都会阻塞线程直到读取或发送完成或异常。
同步指的是发送方和接收方是同步的,缓慢的发送和网络传输会使接收长时间阻塞,缓慢的接收和网络传输和狭小的TCP缓冲区也会使发送长时间阻塞,所以发送和接收的速度互相影响。
输入流和输出流的读和写操作都是同步阻塞的,阻塞的时间取决于对方I/O线程的处理速度和网络I/O的传输速度。

Java NIO

JDK1.4引入了新的输入/输出(NIO)库。相比jdk1.3,NIO弥补了原来同步阻塞I/O的不足,提供了高速的、面向块的I/O。
一些NIO新增的类库和概念:
1.缓冲区Buffer
在面向流的I/O中,可以将数据直接写入或读到Stream对象中。
在NIO库中,数据读写都是要经过缓冲区。缓冲区实质上是一个数组,并且一个缓冲区不仅仅是一个数组,它还提供了对数据的结构化访问以及维护读写位置(limit)等信息。
最常用的缓冲区是ByteBuffer,一个ByteBuffer提供了一组功能用于操作byte数组。包括ByteBuffer,每一种java基本类型(除了Boolean类型)都对应一种缓冲区:
ByteBuffer:字节缓冲区
CharBuffer:字符缓冲区
ShortBuffer:短整型缓冲区
IntBuffer:整形缓冲区
LongBuffer:长整型缓冲区
FloadBuffer:浮点型缓冲区
DoubleBuffer:双精度浮点型缓冲区
2.通道Channel
通道与流的不同之处在于通道是双向的,可以用于读、写或者二者同时进行,流只是在一个方向上移动(一个流必须是InputStream或者OutputStream的子类)。
因为Channnel是双全工的,所以它可以比流更好地映射底层操作系统的API。在UNIX网络编程模型中,底层操作系统的通道都是双全工的,同时支持读写操作。
实际上Channel可以分为两大类:用于网络读写的SelectbleChannel和用于文件操作的FileChannel。
与Socket类和ServerSocket类相对应,NIO提供了SocketChannel和ServerSocketChannel两种不同的套接字通道实现,它们都是SelectableChannel的子类,都支持阻塞和非阻塞两种模式。
3.多路复用器Selector
多路复用器提供选择已经就绪的任务的能力。Selector会不断地轮询注册在其上的Channel,如果某个Channel上面发生读写事件,这个Channel就处于了就绪状态,处于了就绪状态的Channel会被Selector轮询出来,然后通过SelectionKey可以读取就绪Channel集合,进行后续的I/O操作。
JDK在Linux等主流操作系统上通过epoll实现,所以它并没有最大连接句柄1024/2048的限制。这意味着只需要一个线程负责Selector的轮询,就可以接入成千上万的客户端。
Reactor反应器模式:使用单线程模拟多线程,提高资源的利用率和程序的效率,增加系统吞吐量。
Selector类似一个观察者,我们把需要探知的socketchannel告诉Selector,当有事件发生时,Selector会通知我们并传回一组SelectionKey,我们读取这些Key,会获得注册过的socketchannel,然后从这个Channel中读取数据。即主程序不用主动阻塞的等待数据,而是被通知有数据就绪。
4.AIO
JDK1.7升级了NIO类库,升级后的NIO类库被成为NIO2.0,正式提供了异步文件I/O操作,同时提供了与UNIX网络编程事件驱动I/O对应的AIO。NIO2.0引入了新的异步通道的概念,并提供了异步文件通道和异步套接字通道的实现。异步通道提供以下两种方式获取操作结果。
(1)通过java.util.concurrent.Future类来表示异步操作的结果;
(2)在异步操作的时候传入一个java.nio.channels.CompletionHandler接口的实现类作为操作完成的回调。
NIO2.0的异步套接字通道是真正的异步非阻塞I/O,对应了UNIX网络编程中的事件驱动I/O(AIO),它不需要通过多路复用器(selector)对注册的通道进行轮询操作即可实现异步读写,从而简化了NIO的编程模型。

Base 128 Varints

Google Protobuf3

Google Protobuf 官方文档之Language Guide (proto3)
http://blog.163.com/lnsjc321@126/blog/static/5348428720154325730234/
https://developers.google.com/protocol-buffers/docs/proto3

Netty

LengthFieldBasedFrameDecoder 构造参数:

  • lengthFieldOffset:用于指示长度字段,设置为长度字段在整个数据包中开始的位置。
  • lengthFieldLength:用于指示长度字段,设置为长度字段占用的字节数。
  • lengthAdjustment:用于指示内容字段长度,表示在整个数据包长度的基础上,内容字段的长度的调节值。要小于等于0。
  • initialBytesToStrip:用于指示内容字段长度,设置为内容字段在整个数据包中开始的位置。

如果测试发送数据后,但总收不到响应数据。可能是上述参数没有配置对。
https://blog.csdn.net/thinking_fioa/article/details/80573483

Java多线程

线程与进程

进程是资源分配的最小单元,线程是程序执行的最小单元。线程间的切换和调度成本远小于进程间。

Java线程的种类

Java中有两类线程:用户线程 (User Thread)和守护线程 (Daemon Thread)。

  • 守护线程依赖于创建它的线程。例如main线程中创建了一个守护线程,当main方法运行完毕之后,守护线程也会随着消亡。守护线程完成一些系统性工作,如垃圾回收线程、JIT线程就是守护线程。
  • 用户线程是系统的工作线程,不会依赖创建它的线程,用户线程会一直运行直到其运行完毕。
  • 线程组 ThreadGroup:分组管理多个线程。

Java线程的状态

Thread的状态定义在Thread类中的State枚举中,Java线程的状态有以下几种:

  • 新建(new):新创建了一个线程对象。
  • 可运行(runnable):线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取cpu 的使用权 。
  • 运行(running):可运行状态(runnable)的线程获得了cpu 时间片(timeslice) ,执行程序代码。
  • 阻塞(block):阻塞状态是指线程因为某种原因放弃了cpu 使用权,也即让出了cpu timeslice,暂时停止运行。直到线程进入可运行(runnable)状态,才有机会再次获得cpu timeslice 转到运行(running)状态。阻塞的情况分三种:
    (一). 等待阻塞:运行(running)的线程执行o.wait()方法,JVM会把该线程放入等待队列(waitting queue)中。同时释放对象锁
    (二). 同步阻塞:运行(running)的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池(lock pool)中。
    (三). 其他阻塞:运行(running)的线程执行Thread.sleep(long ms)或t.join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入可运行(runnable)状态。
  • 死亡(dead):线程run()、main() 方法执行结束,或者因异常退出了run()方法,则该线程结束生命周期。死亡的线程不可再次复生。

线程中断也是一种线程协作机制,不会使线程立即结束,而是设置线程的中断标志,并给线程发一个希望它结束的通知。

线程中抛出一个异常时会终止当前线程的运行。

等待/通知机制

等待/通知机制:是为了支持线程间的协作,等待者线程首先获取了对象的锁,然后调用对象的wait(mills)方法又放弃了锁,进入了对象的等待队列中,变为等待状态。通知者线程随后获取了对象的锁,然后调用对象的notify()或notifyAll()方法,等待者线程被从等待队列中移到同步队列中,变为阻塞状态。

等待方遵循以下原则:

  • 获取对象的锁
  • 如果条件不满足,那么调用对象的wait(mills)方法,被通知后仍要检查条件
  • 条件满足则执行后续操作
  • 通知方遵循以下原则:
  • 获得对象的锁
  • 改变条件
  • 通知所有等待在对象上的线程

wait, notify 和 notifyAll方法属于对象锁操作,定义在Object类中,而不是在Thread类里面。每个对象都有锁,通过线程获得。线程中调用对象A的wait方法后,线程会转为等待状态,并进入到对象A的等待队列,直到对象A的notify方法被调用,此是对象A成为了线程间的通信者。

1.必须在synchronized内调用wait()、notify()、norifyAll()方法。Java API强制要求这样做,否则代码会抛出IllegalMonitorStateException异常。还有一个原因是为了避免wait和notify之间产生竞态条件。线程不能随意调用对象上等待或通知的方法,除非它拥有那个对象的锁。即wait()等方法的调用必须放在synchronized方法或synchronized块中。
2.wait(),notify(),notifyAll()都是Object类的实例方法。与每个对象具有锁一样,每个对象可以有一个线程列表,他们等待通知信号。线程通过执行对象上的wait()方法获得等待列表。notify()方法会唤醒一个等待当前对象的锁的线程。被唤醒的线程需要等到当前线程放弃这个对象的锁才会被执行。被唤醒的线程将和其他线程以通常的方式进行竞争来获得对象的锁。
3.应该在循环中检查等待条件。

wait与sleep区别

要在同步块中调用wait,调用wait()方法后会释放掉对象的锁;而调用sleep不需要在同步块中,所以Thread.sleep()只会导致线程暂停,不会释放掉对象的锁。

与线程同步和线程调度相关的方法有:
wait():使一个线程处于等待(阻塞)状态,并且释放对象的锁;
sleep():使一个正在运行的线程处于睡眠状态,不会释放任何资源。它是一个静态方法,会抛出InterruptedException异常;
yield():Thread.yield()和Thread.sleep()方法相同点是都不会释放“锁标志”,不同点是它不会阻塞该线程,只是将该线程转入就绪状态。它会先去判断是否有和当前线程相同或者更高优先级的线程,如果没有则自己继续执行,如果有则将CPU资源让给它,然后进入到就绪状态。sleep()方法比yield()方法具有更好的可移动性,所以建议不要使用yield()方法来控制并发线程的执行。
notify():唤醒一个处于等待状态的线程,当然在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由JVM确定唤醒哪个线程,而且与优先级无关;
notityAll():唤醒所有处于等待状态的线程,该方法并不是将对象的锁给所有线程,而是让它们竞争,只有获得锁的线程才能进入就绪状态;

join():在主线程中调用子线程的join方法,目的是使主线程阻塞,直到子线程执行完毕才返回到主线程中。简单理解,在主线程中调用t.join(),相当于在主线程中加入了t线程的代码。join方法的本质是在主线程中调用了子线程的wait方法,使主线程等待。
holdsLock():它返回true如果当且仅当当前线程拥有某个具体对象的锁。

如果wait需要条件判断,则条件一定要放在循环判断中,而不采用放在if判断中,这是因为,如果采用if判断,当线程从wait中唤醒时,那么将直接执行处理其他业务逻辑的代码,而这时候可能条件谓词已经不满足处理业务逻辑的条件了,从而出现错误的结果,于是有必要进行再一次判断,而循环则对上述写法简化,唤醒后再次进入while条件判断。示例如下:

#使用while判断
synchronized (monitor) {
    //  判断条件谓词是否得到满足
    while(!locked) {
        //  等待唤醒
        monitor.wait();
    }
    //  处理其他的业务逻辑
}
#使用再次判断
synchronized (monitor) {
    //  判断条件谓词是否得到满足
    if(!locked) {
        //  等待唤醒
        monitor.wait();
        if(locked) {
            //  处理其他的业务逻辑
        } else {
            //  跳转到monitor.wait(); 
        }
    }
}

在Java中一共有四种方法支持同步,其中前三个是同步方法,一个是管道方法。
(1)wait() / notify()方法:wait() / nofity()方法是基类Object的两个方法,也就意味着所有Java类都会拥有这两个方法,这样,我们就可以为任何对象实现同步机制。
(2)await() / signal()方法:在JDK5.0之后,Java提供了更加健壮的线程处理机制,包括同步、锁定、线程池等,它们可以实现更细粒度的线程控制。await()和signal()就是其中用来做同步的两种方法,它们的功能基本上和wait() / nofity()相同,完全可以取代它们,但是它们和新引入的锁定机制Lock直接挂钩,具有更大的灵活性。
(3)BlockingQueue阻塞队列方法:BlockingQueue是JDK5.0的新增内容,它是一个已经在内部实现了同步的队列,实现方式采用的是await() / signal()方法。
(4)PipedInputStream / PipedOutputStream

Java多线程中需要保证的三个性质

原子性:指一个操作不可中断。
可见性:线程对数据的修改要能让其它线程立即看的见。
有序性:有序性可能被打破的原因是指令重排。指令重排可以优化提高CPU的性能。

先行发生原则

先行发生原则 happens-before,是指令不重排的规则。

内存可见性

可见性是并发的三个特性之一,指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程要能够立即看得到修改的值。即其它线程要立即使其工作内存中的变量副本值保持最新。
线程工作内存是cpu寄存器和高速缓存的抽象描述,使用频率高的数据从主存拷贝到高速缓存中,每个线程在cpu高速缓存中对拷贝的数据进行读取、计算、赋值,再在合适的时候同步更新到主存。

三种声明保证可见性:

  • final变量:初始化 final宇段确保可见性,这里需要注意 final修饰基本数据类型,可以 保证此字段的可见性,但是如果修饰的是对象,那么需要保证此对象是状态不可变 的才能保证可见性,即保证对象的每个字段也是 final 的 。
  • volatile变量:此关键字的语义保证了新值能够立即同步到主内存,并且每次使用前都立即从主内存刷新。volatile声明变量是告诉jvm这个变量不稳定容易被修改,要采取手段保证其线程间可见性。
  • synchronized块:在同步块内读/写字段也确保了可见性 。

volatile关键字

volatile关键字的作用:

  • 保证变量可见性。提供内存屏障(memory barrier),写一个volatile变量时,把线程工作内存中的共享变量刷新到主内存。当读一个volatile变量时,把线程工作内存置为无效,线程接下来将从主内存中读取共享变量。
  • 禁止指令重排序
  • 修饰 long 和 double 变量按原子类型读写。double 和 long 都是64位宽,32位JVM中,这两种类型的读是分为两部分,读取第一个 32 位,然后再读剩下的 32 位,两次读就不是原子的了。volatile不能替代锁,它不保证复合操作的原子性。

但volatile变量的运算在并发下是并不保证原子性,即不是多线程安全的,例如java里的运算等复合操作,比如自增,对于变量不是原子性的。
java.util.concurrent.atomic包下提供了12种原子操作类型,例如 AtomicInteger。可通过CAS(Compare and Swap)来实现原子操作。CAS是一种乐观锁技术。

可以创建 volatile 类型数组,不过只是一个指向数组的引用,而不是整个数组。如果改变引用指向的数组,将会受到 volatile 的保护,但是如果多个线程同时改变数组的元素,volatile 标示符就不能起到保护作用了。

volatile 变量提供顺序和可见性保证,例如,JVM 或者 JIT为了获得更好的性能会对语句重排序,但是 volatile 类型变量即使在没有同步块的情况下赋值也不会与其他语句重排序。 volatile 提供 happens-before 的保证,确保一个线程的修改能对其他线程是可见的。某些情况下,volatile 还能提供原子性,如读 64 位数据类型,像 long 和 double 都不是原子的,但 volatile 类型的 double 和 long 就是原子的。

volatile是一个特殊的修饰符,只有成员变量才能使用它。在Java并发程序缺少同步类的情况下,多线程对成员变量的操作对其它线程是透明的。volatile变量可以保证下一个读取操作会在前一个写操作之后发生。

volatile 变量和 atomic 变量有什么不同?
Volatile变量可以确保先行关系,即写操作会发生在后续的读操作之前, 但它并不能保证原子性。例如用volatile修饰count变量时count++操作就不是原子性的。
AtomicInteger类提供的atomic方法可以让这种操作具有原子性,如getAndIncrement()方法会原子性的进行增量操作把当前值加一,其它数据类型和引用变量也可以进行相似操作。

synchronized关键字

三种应用形式:

  • 代码块指定加锁对象:java的每个对象都有一个内置锁,synchronized通过锁住一个对象,来控制对代码段的并发访问。
  • 用于实例方法:锁的是对象本身也就是this,对象只能被一个线程锁住,所以不同线程不能同时调用一个对象的任何同步方法。
  • 用于类方法:锁住的是当前类。

synchronized还保证了线程间的可见性和有序性,因为被synchronized限制的多个线程是串行执行临界区代码的。
synchronized的原理和数据库中事务锁的原理类似。在使用过程中应该尽量缩减synchronized覆盖的范围,原因是被它覆盖的范围是串行的,效率低并且容易产生死锁。即应减小锁的粒度,使代码更大程度的并发。
synchronized就是一种独占锁(悲观锁)。

虽然可以在方法外面定义语句块(类语句块),但不能用synchronized修饰方法外面的语句块,如果这样做会遇到编译错误。这里涉及到了Java里面的对象初始化的部分知识,大概的原因是synchronized锁住的是对象,当初始化对象的时候,JVM在对象初始化完成之前会调用方法外面的语句块,这个时候对象还不存在,所以就不存在锁了。

共享数据的所有访问都必须使用synchronized加锁,所有的对共享数据的访问都是临界区,临界区必须使用synchronized加锁。

重入锁

重入锁,也叫做递归锁,指的是在同一线程内连续多次获取该锁。JDK5.0早期版本中,重入锁 java.util.concurrent.locks.ReentrantLock 性能优于synchronized,在JDK6.0开始对synchronized做了大量优化,使性能差距减小。

  • 重入锁有显示的操作开始和释放过程,对逻辑控制的灵活性优于synchronized
  • 重入锁可以使等待线程中断等待,而synchronized只能使等待线程等待直到执行。
  • tryLock()可以接收最大等待时间,线程不会傻傻等待,避免可能出现死锁。
  • ReentrantLock构造函数允许设置是否使用公平锁。公平锁的特点是不会产生饥饿现象,synchronized只有非公平锁。公平锁要求系统维护一个有序队列,实现成本高,性能低。

reentrantLock.newCondition()生成一个与当前锁绑定的Condition实例,来实现线程间协作。

信号量 Semaphore

信号量Semaphore是对锁的扩展,提供了更强大的多线程协作,把同一个Semaphore对象用在多个线程中,根据设定值控制最大并行线程数,使一定量的多个线程同时访问相同的资源。

import java.util.concurrent.Semaphore;
            Semaphore semaphore = new Semaphore(10);
            semaphore.acquire();//获取一个许可,等待直到获得许可
            Thread.sleep(1000);
            semaphore.release();//释放一个许可

读写锁 ReadWriteLock

JDK5中提供了读写分离锁ReadWriteLodk。读读不互斥,读写和写写互斥。如果说减小粒度是通过分割数据结构实现,读写分离锁则是对系统功能点分割。

倒数器 CountDownLatch

CountDownLatch即倒数器,它有countDown()和await()两个方法,可以实现在主线程中等待其它所有线程完成,方法是把同一个CountDownLatch对象用在不同线程中,其中在子线程中调用latch.countDown(),而在主线程中调用latch.await()使主线程挂起等待至计数为0。

循环栅栏 CyclicBarrier

CyclicBarrier 字面意思回环栅栏,让一组线程互相等待至某个状态之后全部同时执行。当调用它的await()方法之后,其所在的线程就处于阻塞状态了,当所有线程都达到这个状态后,各线程才再继续运行。
Cyclic循环是因为当所有等待线程都被释放以后,CyclicBarrier还可以被重用。

线程阻塞工具类 LockSupport

可以在线程任意位置让线程阻塞,它不需要先获得某个对象的锁。

Guava和RateLimiter限流

Google Guava核心库可以视为JDK标准库的重要补充,RateLimiter是其中的一款限流工具,即限制每秒能处理的线程数量。

Runnable和FutureTask

Runnable不是线程,它只是一个接口定义了一个任务方法,Thread启动才是一个线程。FutureTask同理。
FutureTask表示一个可以取消的异步运算。它有启动运算、取消运算、查询运算是否完成、取回运算结果等方法。只有当运算完成的时候结果才能取回。如果运算尚未完成,get方法将会阻塞。一个FutureTask对象可以对调用了Callable和Runnable的对象进行包装,由于FutureTask也是调用了Runnable接口,所以它可以提交给Executor来执行。

import java.util.concurrent.Callable;

public class Target implements Callable {
    int i=0;
    public Integer call() throws Exception {
        for (; i < 20; i++) {
            System.out.println(Thread.currentThread().getName()+""+i);
        }
        return i;
    }
}

Target t1=new Target();
FutureTask ft=new FutureTask(t1);
new Thread(ft,"新线程");

 

线程同步

线程同步一般是要解决在“单对象多线程”的情况下,控制共享变量的访问,或是控制执行步骤顺序。
控制共享变量的策略:

  • 将“单对象多线程”修改成“多对象多线程”;
  • 将“全局变量”降级为“局部变量”;
  • ThreadLocal机制,线程中使用ThreadLocal类型的包装变量,为每一个使用该变量的线程提供一个副本。
  • 用final域,有锁保护的域和volatile域可以避免非同步的问题。

使用特殊域变量(volatile)实现线程同步
a.volatile关键字为域变量的访问提供了一种免锁机制,
b.使用volatile修饰域相当于告诉虚拟机该域可能会被其他线程更新,
c.因此每次使用该域就要重新计算,而不是使用寄存器中的值
d.volatile不会提供任何原子操作,它也不能用来修饰final类型的变量
volatile保证此变量对所有线程的可见性,以及禁止指令重排序优化。

 

volatile和synchronized

volatile和synchronized四个不同点:

  • 粒度不同。volatile针对变量,synchronized锁对象和类;
  • syn阻塞,volatile线程不阻塞;
  • syn保证三大特性,volatile不保证原子性;
  • syn编译器优化,volatile不优化;

要使 volatile 变量提供理想的线程安全,必须同时满足下面两个条件:

  • 对变量的写操作不依赖于当前值。
  • 该变量没有包含在具有其他变量的不变式中。

synchronized 与 ReentrantLock

Synchronized 与 Lock都是可重入锁,都实现了多线程同步和内存可见性。
synchronized可以修饰实例方法(锁住实例对象)、静态方法(锁住类对象)、代码块(显示指定锁对象),reentrantlock要显示调用trylock()/lock()方法,需要在finally块中释放锁。
synchronized依赖jvm内存模型保证包含共享变量的多线程内存可见性,reentrantlock 是在jdk中通过volatile state保证包含共享变量的多线程内存可见性。

在资源竞争不是很激烈的情况下,偶尔会有同步的情形下,synchronized是很合适的。原因在于,编译程序通常会尽可能的进行优化synchronized。

相比之下,ReentrantLock提供了多样化的同步。比如有时间限制的同步,可以被Interrupt的同步(synchronized的同步是不能Interrupt的)等。在资源竞争不激烈的情形下,性能稍微比synchronized差点点。但是当同步非常激烈的时候,synchronized的性能一下子能下降好几十倍,而ReentrantLock确还能维持常态。

Java5中的concurrent工具包

有些情况下需要使用线程的返回值,可以使用Callable和CompletionService,前者返回单个线程的结果,后者返回一组线程的结果。

ThreadLocal

使用ThreadLocal管理变量,则每一个使用该变量的线程都获得该变量的副本,副本之间相互独立,每一个线程都可以随意修改自己的变量副本。
ThreadLocal不属于同步机制,采用以”空间换时间”的方法,而同步机制采用以”时间换空间”的方式。ThreadLocal用于需要在线程间数据隔离而不需要数据同步的场景中。
ThreadLocal不是集合,它不存储任何内容,真正存储数据的集合在Thread中。ThreadLocal只是一个工具,它的内部原理是往各个线程的ThreadLocal.ThreadLocalMap中table的某一位置set一个值。

ThreadLocal是Java里一种特殊的变量,每个线程都有一个ThreadLocal,竞争被消除了。比如可以用ThreadLocal让SimpleDateFormat变成线程安全的,因为SimpleDateFormat类创建代价高昂,且每次调用都需要创建不同的实例,所以不值得在局部范围使用它,如果为每个线程提供一个自己独有的变量拷贝,将大大提高效率。首先,通过复用减少了代价高昂的对象的创建个数。其次,在没有使用高代价的同步或者不变性的情况下获得了线程安全。线程局部变量的另一个例子是ThreadLocalRandom类,它在多线程环境中减少了创建代价高昂的Random对象的个数。

ThreadLocal实例通常是类中的 private static 字段,它们希望将状态与某一个线程(例如,用户 ID 或事务 ID)相关联。

ThreadLocal的使用场景是对象共用但值不共享。首选在共用对象的内部声明private static ThreadLocal<> xxxLocal变量,然后把同一个共用对象用到多个线程中。

public class TestNum {  
    // ①通过匿名内部类覆盖ThreadLocal的initialValue()方法,指定初始值  
    private static ThreadLocal seqNum = new ThreadLocal() {  
        public Integer initialValue() {  
            return 0;  
        }  
    };  
  
    // ②获取下一个序列值  
    public int getNextNum() {  
        seqNum.set(seqNum.get() + 1);  
        return seqNum.get();  
    }  
  
    public static void main(String[] args) {  
        TestNum sn = new TestNum();  
        // ③ 3个线程共享sn,各自产生序列号  
        TestClient t1 = new TestClient(sn);  
        TestClient t2 = new TestClient(sn);  
        TestClient t3 = new TestClient(sn);  
        t1.start();  
        t2.start();  
        t3.start();  
    }  
  
    private static class TestClient extends Thread {  
        private TestNum sn;  
  
        public TestClient(TestNum sn) {  
            this.sn = sn;  
        }  
  
        public void run() {  
            for (int i = 0; i < 3; i++) { // ④每个线程打出3个序列值 System.out.println("thread[" + Thread.currentThread().getName() + "] --> sn["  
                         + sn.getNextNum() + "]");  
            }  
        }  
    }  
}  

线程局部存储TLS(thread local storage),以下表述:
A、解决多线程中的对同一变量的访问冲突的一种技术
B、TLS会为每一个线程维护一个和该线程绑定的变量的副本
C、每一个线程都拥有自己的变量副本,如果是静态变量是共享的话,那必须同步。
D、Java平台的java.lang.ThreadLocal是TLS技术的一种实现

Future

java1.5中Future是一个未来对象,里面保存线程处理结果,它像一个提货凭证,可以随时去提取结果。Future就是对于具体的Runnable或者Callable任务的执行结果进行取消、查询是否完成、获取结果。必要时可以通过get方法获取执行结果,该方法会阻塞直到任务返回结果。
在两种情况下离开Future较难处理:

  • 一种情况是拆分订单,比如应用收到一个批量订单,此时如果要求最快的处理订单,那么需要并发处理,并发的结果如何收集的问题如果自己编程非常繁琐,此时可以使用CompletionService解决这个问题。CompletionService将Future收集到一个队列里,可以按结果处理完成的先后顺序进队。
  • 另一种情况是如果需要并发查询一些东西(比如爬虫),并发查询只要有一个结果返回就认为查询到了,并且结束查询,这时也需要用CompletionService和Future来解决。

 

Semaphore、CountDownLatch、CyclicBarrier联系和区别

Semaphore、CountDownLatch、CyclicBarrier,它们就像一个调度员,同时驻守到这些线程中来管理这些线程的并发行为。
1)CountDownLatch和CyclicBarrier都能够实现线程之间的等待,只不过它们侧重点不同:
CountDownLatch一般用于某个线程A等待若干个其他线程执行完任务之后,它才执行;
而CyclicBarrier一般用于一组线程互相等待至某个状态,然后这一组线程再同时执行;
另外,CountDownLatch是不能够重用的,而CyclicBarrier是可以重用的。
2)Semaphore和锁有点类似,它一般用于控制对某组资源的访问权限。

《Java工程师修炼之道》7.3节 Java 并发编程

并发是CPU多核特性充分利用的关键技术。
Java 内存模型是屏敲了底层硬件环境 的差异而 给 Java程序提供的统一的内存访问模式,可以认为是一种虚拟内存环境。 在 Java程序中, 所有线程都共享主内存,但是对于每一个线程都有自己的工作内存(一个虚拟的概念,包括寄存器、栈、写缓冲区、缓存以及其他硬件 、编译器优化等),工作内存与主内存通过一些规定的操作来交互同步数据,而线程只能访问自己的工作内存。 因此在多线程环境下,很容易造成工作内存数据不一致而引起并发问题

Java提供了两个关键字volatile和synchronized来保证多线程之间操作的有序性,volatile关键字本身通过加入内存屏障来禁止指令的重排序,而synchronized关键字通过一个变量在同一时间只允许有一个线程对其进行加锁的规则来实现。

CopyOnWriteArrayList

CopyOnWriteArrayList : 写时加锁,当添加一个元素的时候,将原来的容器进行copy,复制出一个新的容器,然后在新的容器里面写,写完之后再将原容器的引用指向新的容器,而读的时候是读旧容器的数据,所以可以进行并发的读,但这是一种弱一致性的策略。
使用场景:CopyOnWriteArrayList适合使用在读操作远远大于写操作的场景里,比如缓存。

java 高并发面试题

https://blog.csdn.net/u012998254/article/details/79400549

Java中编写多线程程最佳实践

a)给线程命名,可以帮助调试。
b)最小化同步的范围,只对关键部分做同步,而不是将整个方法同步。
c)如果可以,更偏向于使用 volatile 而不是 synchronized。
d)使用更高层次的并发工具,而不是使用 wait() 和 notify() 来实现线程间通信,如 BlockingQueue,CountDownLatch 及 Semeaphore、CyclicBarrier 和 Exchanger,这些同步类简化了编码操作,它们由最好的企业编写和维护,在后续的JDK中它们还会不断优化和完善,使用这些更高等级的同步工具你的程序可以不费吹灰之力获得优化。
e)优先使用并发集合,而不是对集合进行同步。并发集合提供更好的可扩展性。应该首先想到用ConcurrentHashMap。
b)将线程和任务分离,使用线程池执行器来执行 Runnable 或 Callable。
c)使用线程池

Java中停止一个线程

JDK 1.0原来有一些像stop(), suspend() 和 resume()的控制方法,由于潜在的死锁威胁而在后续的JDK版本中被弃用了,之后Java API没有提供一个兼容且线程安全的方法来停止一个线程。当run() 或者 call() 方法执行完的时候,线程会自动结束。要手动结束一个线程,可以用volatile布尔变量来退出run()方法的循环,或者是取消任务来中断线程。


Java多线程和堆栈

栈是一块和线程紧密相关的内存区域。每个线程都有自己的栈内存,用于存储本地变量、方法参数和栈调用,一个线程中存储的变量对其它线程是不可见的。
堆是所有线程共享的一片公用内存区域。对象都在堆里创建,为了提升效率,线程会从堆中弄一个缓存到自己的栈,如果多个线程使用该变量就可能引发问题,这时volatile 变量就可以发挥作用了,它要求线程从主存中读取变量的值。

死锁、活锁、饥饿、无锁。

多线程中的读数据错误(事务并发问题)

  • 脏读:指当一个事务正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中,这时,另外一个事务也访问这个数据,然后使用了这个数据。这个数据是还没有提交的数据,那么另外一个事务读到的这个数据是脏数据(Dirty Data)。
  • 不可重复读:指在一个事务内,多次读同一数据。在这个事务还没有结束时,另外一个事务也访问该同一数据,并在第一个事务中的两次读数据之间,修改了这个数据,那么第一个事务两次读到的数据可能是不一样的。这样就发生了在一个事务内两次读到的数据是不一样的,因此称为是不可重复读。
  • 幻读

 

Web服务端基础技术

使用jstl
<%@ taglib uri=”http://java.sun.com/jsp/jstl/core” prefix=”c” %>
<%@ taglib uri=”http://java.sun.com/jsp/jstl/functions” prefix=”fn” %>

jsp界面的$通常不嵌套
jstl的字符串拼接不需要+,直接让多个$相邻即可。

标签及函数使用示例:

${fn:substring(image.path,7,pathLength)}
‘${sub}’,${strArray}

jsp页面中jstl标签详解
http://blog.csdn.net/justjackwang/article/details/8804528

在java领域,表现层技术主要有三种:jsp、freemarker、velocity。

jsp是大家最熟悉的技术
优点:
1、功能强大,可以写java代码
2、支持jsp标签(jsp tag)
3、支持表达式语言(el)
4、官方标准,用户群广,丰富的第三方jsp标签库
5、性能良好。jsp编译成class文件执行,有很好的性能表现
缺点:
jsp没有明显缺点,非要挑点骨头那就是,由于可以编写java代码,如使用不当容易破坏mvc结构。

velocity是较早出现的用于代替jsp的模板语言
优点:
1、不能编写java代码,可以实现严格的mvc分离
2、性能良好,据说比jsp性能还要好些
3、使用表达式语言,据说jsp的表达式语言就是学velocity的
缺点:
1、不是官方标准
2、用户群体和第三方标签库没有jsp多。
3、对jsp标签支持不够好

freemarker
优点:
1、不能编写java代码,可以实现严格的mvc分离
2、性能非常不错
3、对jsp标签支持良好
4、内置大量常用功能,使用非常方便
5、宏定义(类似jsp标签)非常方便
6、使用表达式语言
缺点:
1、不是官方标准
2、用户群体和第三方标签库没有jsp多

选择freemarker的原因:
1、性能。velocity应该是最好的,其次是jsp,普通的页面freemarker性能最差(虽然只是几毫秒到十几毫秒的差距)。但是在复杂页面上(包含大量判断、日期金额格式化)的页面上,freemarker的性能比使用tag和el的jsp好。
2、宏定义比jsp tag方便
3、内置大量常用功能。比如html过滤,日期金额格式化等等,使用非常方便
4、支持jsp标签
5、可以实现严格的mvc分离

Servlet

Servlet容器默认采用单实例多线程的方式来处理所有请求,这样减少产生Servlet实例的开销,提升了对请求的响应时间。对于Tomcat可以在server.xml中通过元素设置线程池中线程的数目。
servlet中的init方法只有在启动(例如web容器启动,要看loadOnStartup的设置)的时候调用,也就是只初始化一次,这就是单实例。
servlet在处理请求的时候调用的是service方法。

动态网页的技术进化

1.在程序中输出html:最早实现动态网页的方法是使用Perl和CGI。在Perl程序中输出HTML内容,由HTTP服务器调用Perl程序,将结果返回给客户端。这种方式在互联网刚兴起的20世纪90年代非常流行。但问题在于如果HTML内容比较多,维护非常不方便。
2.以模板为中心的架构:大概在2000年左右,以ASP、PHP、JSP 的为代表的以模板为基础的语言出现了,这种语言的使用方法与CGI相反,是在以HTML为主的模板中插入程序代码。这种方式在2002年前后非常流行,但它的问题是页面和程序辑紧密耦合,任何一个网站规模变大以后,都会遇到结构混乱,难以处理的问题。
3.MVC架构:为了解决这种问题,以MVC架构为基础的平台逐渐兴起, Ruby on Rails、Django、Zend Framework 都是基于 MVC 架构。

Session

Session又称为会话状态,用于维护和当前浏览器实例相关的一些信息。Session对于每一个客户端(或者说浏览器实例)是“人手一份”,用户首次与Web服务器建立连接的时候,服务器会给用户分发一个SessionID作为标识。SessionID是一个由24个字符组成的随机字符串。用户每次提交页面,浏览器都会把这个SessionID包含在HTTP头中提交给Web服务器,这样Web服务器就能区分当前请求页面的是哪一个客户端。

前后端分离,session会失效,因为前端是跨域请求,每次跨域请求时ajax发送的都是新的sessionid。spring boot+react项目中使用session成功解决方案的文章 https://blog.csdn.net/fwk19840301/article/details/80675547
https://segmentfault.com/a/1190000009208644

多负载会话管理的方式:
1.会话复制
2.集中式会话。在分布式架构中,可以把session存储到redis等一个公共服务中。

自定义注解

@interface的作用是用来自定义注解,使用@interface自定义注解时,自动继承了java.lang.annotation.Annotation接口,由编译程序自动完成其他细节。在定义注解时,不能继承其他的注解或接口。
@interface用来声明一个注解,其中的每一个方法实际上是声明了一个配置参数。方法的名称就是参数的名称,返回值类型就是参数的类型(返回值类型只能是基本类型、Class、String、enum)。可以通过default来声明参数的默认值。

定义注解格式:
public @interface 注解名 {定义体}

Redirect与Forward

Redirect,即重定向,会让请求端重新发一次请求,要求客户端跳转。
Forward,是服务端跳转。

Java8新功能

Java8

Java 8发行版是自Java 5(发行于2004)以来最具革命性的版本。Java 8 为Java语言、编译器、类库、开发工具与JVM(Java虚拟机)带来了大量新特性。
Java SE 8中的一些特性:

  • 函数式接口(functional interface)与lambda表达式
  • 方法和构造方法引用
  • 接口的默认方法

函数式接口(functional interface)与lambda表达式

简单来说,函数式接口是只包含一个抽象方法的接口,即像使用函数似的使用这个接口。比如Java标准库中的java.lang.Runnable和java.util.Comparator都是典型的函数式接口。函数式接口还可以使用lambda表达式来创建实现对象。
使用lambda表达式时,只需要提供形参和方法体。由于函数式接口只有一个抽象方法,所以通过lambda表达式声明的方法体就肯定是这个唯一的抽象方法的实现,而且形参的类型可以根据方法的类型声明进行自动推断。
比较下面两个方法:

public void runThread() {
    new Thread(new Runnable() {
        public void run() {
            System.out.println("Run!");
        }
    }).start();
}
public void runThreadUseLambda() {
    new Thread(() -> {
        System.out.println("Run!");
    }).start();
}

Java SE 8增加了java.util.function包,里面都是可以在开发中使用的函数式接口。开发人员也可以创建新的函数式接口。最好在接口上使用注解@FunctionalInterface进行声明,以免团队的其他人员错误地往接口中添加新的方法。

方法和构造方法引用(Method References)

方法引用是另外一种实现函数式接口的方法,在某些情况下,方法引用可以进一步简化代码。
Java 8中的方法也是一种对象,可以By名字来引用。不过方法引用的唯一用途是支持Lambda的简写,使用方法名称来表示Lambda。
方法引用可以在不调用某个方法的情况下引用一个方法。
构造方法引用可以在不创建对象的情况下引用一个构造方法。

接口的默认方法

接口的默认方法即在接口中新添加的方法使用default关键词来修饰,并可以有自己的方法体。
接口的默认方法的主要目标之一是解决接口的演化问题。
接口的默认方法的另外一个作用是实现行为的多继承。
通过默认方法,可以创建出类似的帮助接口,即接口中包含的都是通过默认方法实现的帮助方法。
Java 8 接口也可以有静态方法,从职责定位来讲,接口静态方法就是个工具方法。

接口与抽象类

Java之父曾说,若再给他一次设计Java的机会,Java里就不会有抽象类。
总的来说抽象类和接口越来越接近了,是接口向抽象类靠近,剥夺抽象类的生存空间。

  • 抽象类能够定义非static final 的属性(field) ,而接口不能。接口的属性都是static final的。
  • 抽象类能够定义非public方法,而接口不能。接口的方法都是public的。
  • 接口可以多继承(实现),而抽象类不能。抽象类只能单继承。

常量接口(Constant Interface)

常量接口是只包括常量定义的接口,Java中一直存在“常量接口(Constant Interface)”的用法。通过实现这样的接口,就可以省去常量前的挂靠单位而直接引用这些常量。

静态成员导入Static Import

J2SE 1.5里引入了“Static Import”机制,借助这一机制,可以用略掉所在的类或接口名的方式,来使用静态成员。
在Java程序中,是不允许定义独立的函数和常量(当然,准确的说,只是被final修饰、只能赋值一次的变量)的。即使从它们本身的功能来看,完全不需要依附于什么东西,也要找个类或接口作为挂靠单位才行(在类里可以挂靠各种成员,而接口里则只能挂靠常量)。
挂靠的方法,是把它们加上static修饰符,定义为这个类或接口的静态成员。这方面的典型例子是java.lang.Math类——包含了大量的sin、cos这样的“函数”和PI、E这样的“常量”。
传统上,在访问这些挂靠了的函数、变量和常量的时候,需要在前面加上它们挂靠单位的名称。如果只是偶尔访问这些东西一下,这样的写法可以工作得很好;但是如果要频繁访问这些成员的话,这样的写法就显得比较罗嗦了。
静态成员导入没有常量接口的可以从“一个类实现了哪个接口”推断出“这个类需要使用哪些常量”,即“会暴露实现细节”的问题。
使用import static语句,可以导入一个类里的一切被static修饰的东西,包括变量、常量、方法和内类。在J2SE 1.4以后,无论是import语句,还是import static语句,都要求给一个所在包名出来,对于不属于任何包的类和接口,不能用import导入它本身,也不能用import static导入它的静态成员。
import static 包名.类或接口名.*;
注意这种方式只是指出遇到来历不明的成员时,可以到这个类或接口里来查找,并不是把这个类或接口里的所有静态成员全部导入。
静态成员导入只是语法糖的作用,编译时,所有因Static Import的存在而简化了的名字,都会被编译器打回原型。因此在性能方面,Static Import没有任何影响。
但是名字简化却可能造成一些维护方面的问题。去掉静态成员前面的类型名,虽然有助于在频繁调用时显得简洁,但是同时也失去了关于“这个东西在哪里定义”的提示信息,增加了阅读理解的麻烦。所以这一机制在使用不当的时候可能给维护工作带来一定的困扰。

http://www.cnblogs.com/Fndroid/p/6087380.html

Optional

Optional可以优雅的解决 NullPointException 的问题,它的isPresent() 与 obj != null 功能一样, 没有 isPresent() 作铺垫的 get() 调用在 IntelliJ IDEA 中会收到告警。
而 Optional 中我们真正需要常使用的是除了 isPresent() 和 get() 的其他以下方法:

map
orElse
orElseGet
ifPresent
filter
flatMap
orElseThrow

Spring MVC

Spring注解

Spring1.0时代,XML配置。
Spring2.0时代,注解配置。随着JDK1.5带来的注解支持,Spring提供了声明Bean的注解,如@Service、@Component。
Spring3.0时代,Java配置。使用Java配置可以更好的理解配置的Bean。
这些配置本身都被称为配置元数据,即对数据的描述。元数据本身不具备可执行能力,Spring容器负责解析配置元数据,然后进行Bean初始化、配置和管理依赖。

每一个被Spring管理的Java对象都称为Bean。

Spring框架四大原则:
1、使用POJO进行轻量级和最小侵入式开发。
2、通过依赖注入和基于接口编程实现松耦合。
3、通过AOP和默认习惯进行声明式编程。
4、使用AOP和模板(template)减少模式化代码。

声明Bean的注解(IOC注解)有:
@Component,没有明确的角色,加到类路径自动扫描。
@Service,逻辑层角色,一个无状态的切面。Spring为了迎合三层架构,对每一层也给出了具有语义的注解。
@Repository,数据管理/存储层角色,用在数据库操作相关的类上。
@Controller,Web控制层角色,在Spring MVC中使用。

@Component和@Bean的区别:
@Component以及他的特殊化(@Controller, @Service 和 @Repository)允许在通过类路径扫描自动发现。
@Bean是一个方法级别上的注解,表示方法的返回值是一个bean,bean的id为方法名。主要用在@Configuration或@Component注解的类里,只能在配置类中明确的声明一个单例的bean。

可以通过在入口类上使用@MapperScan指定要扫描的Mapper类的多个包的路径。

注入Bean的注解(DI注解)有:
@AutoWired,Spring2.5引入了 @Autowired 注释,它可以对类成员变量、方法及构造函数进行标注,完成自动装配的工作。
@Inject,JSR-330提供的注解。
@Resource,JSR-250提供的注解。
以上几种注解一般情况下通用,可注解在属性或Set方法上。

JAVA配置:
JAVA配置是通过@Confirguration和@Bean注解来实现的。
@Confirguration声明类为一个配置类,相当于Spring的XML配置文件。使用java配置的方式,就不需要在类上写注解了,直接在配置类里面进行声明。
JAVA配置与注解配置的选择是:涉及到全局配置的,例如数据库相关配置、MVC相关配置等,用JAVA配置的方式;涉及到业务配置的,使用注解配置方式。

搭建Eclipse SpringMVC开发环境

下载安装jdk,maven,
eclipse(Eclipse Java EE IDE for Web Developers.Version: Mars.2 Release (4.5.2)Build id: 20160218-0600)
http://www.oracle.com/
http://maven.apache.org/

解压maven后,新建环境变量MAVEN_HOME,在PATH里加入maven的bin的路径。
配置完成,可打开命令行,输入mvn -v,测试。
http://dead-knight.iteye.com/blog/1841658

在mac os中配置maven
http://www.jianshu.com/p/191685a33786

下载Spring
http://repo.spring.io/libs-release-local/org/springframework/spring/

在创建maven项目过程中,Select an Archetype时选择maven-archetype-webapp
下一步中Artifact id是工程的名字,Group id是组织的名字,packge可手动置空。
src/main/java是源代码目录
src/main/resources是项目配置目录
src/test/java是测试源代码目录
src/test/resources是测试配置目录
它们都应该出现在java build path中。
由于项目打包、发布时不需要测试代码、测试的配置文件、以及运行时产生的额外文件(target),所以配置web目录也就是Deployment Assembly,不需要src/test及target路径下的内容。

如果报Could not resolve archetype org.apache.maven.archetypes:maven-archetype-webapp
则可能是网络有问题下载地址不能正常访问,等待网络正常。
如果下载一闪而过,而转到这个目录 /Users/admin/.m2/repository/org/apache/maven/archetypes/maven-archetype-webapp,查看其中的内容可能未下载完全,则删除这个目录。
http://blog.csdn.net/afgasdg/article/details/12757433

http://www.iteye.com/topic/973166
http://www.itnose.net/detail/6098368.html

http://www.admin10000.com/document/6436.html
http://blog.csdn.net/clj198606061111/article/details/20492887

jsp文件放到WEB-INFO中可以使其不被直接访问到,WEB-INF是Java的WEB应用的安全目录,客户端无法访问,只有服务端可以访问。如果想在页面中直接访问其中的文件,必须通过web.xml文件对要访问的文件进行相应映射才能访问。

如果报javax.http.servlet找不到,是因为还没有添加Server runtime,在Jararva Build Path/Library中点击add library,选择server runtime…

Spring3.1新特性

Spring2.5之前是通过实现Controller接口或其实现来定义处理器类。
Spring2.5引入注解式处理器支持,通过@Controller 和 @RequestMapping注解定义处理器类,并且提供了一组强大的注解:
需要通过处理器映射DefaultAnnotationHandlerMapping和处理器适配器AnnotationMethodHandlerAdapter来开启支持@Controller 和 @RequestMapping注解的处理器。
@Controller:用于标识是处理器类;
@RequestMapping:请求到处理器功能方法的映射规则;
@RequestParam:请求参数到处理器功能处理方法的方法参数上的绑定;
@ModelAttribute:请求参数到命令对象的绑定;
@SessionAttributes:用于声明session级别存储的属性,放置在处理器类上,通常列出模型属性(如@ModelAttribute)对应的名称,则这些属性会透明的保存到session中;
@InitBinder:自定义数据绑定注册支持,用于将请求参数转换到命令对象属性的对应类型;
Spring3.0引入RESTful架构风格支持(通过@PathVariable注解和一些其他特性支持),并且又引入了更多的注解支持:
@CookieValue:cookie数据到处理器功能处理方法的方法参数上的绑定;
@RequestHeader:请求头(header)数据到处理器功能处理方法的方法参数上的绑定;
@RequestBody:请求的body体的绑定(通过HttpMessageConverter进行类型转换);
@ResponseBody:处理器功能处理方法的返回值作为响应体(通过HttpMessageConverter进行类型转换),即返回的是文本而不是视图名称;
@ResponseStatus:定义处理器功能处理方法/异常处理器返回的状态码和原因;
@ExceptionHandler:注解式声明异常处理器;
@PathVariable:请求URI中的模板变量部分到处理器功能处理方法的方法参数上的绑定,从而支持RESTful架构风格的URI;
还有比如:
JSR-303验证框架的无缝支持(通过@Valid注解定义验证元数据);
使用Spring 3开始的ConversionService进行类型转换(PropertyEditor依然有效),支持使用@NumberFormat 和 @DateTimeFormat来进行数字和日期的格式化;
HttpMessageConverter(Http输入/输出转换器,比如JSON、XML等的数据输出转换器);
ContentNegotiatingViewResolver,内容协商视图解析器,它还是视图解析器,只是它支持根据请求信息将同一模型数据以不同的视图方式展示(如json、xml、html等),RESTful架构风格中很重要的概念(同一资源,多种表现形式);
Spring3 引入 一个 mvc XML的命名空间用于支持mvc配置,包括如:

自动注册基于注解风格的处理器需要的DefaultAnnotationHandlerMapping、AnnotationMethodHandlerAdapter
支持Spring3的ConversionService自动注册
支持JSR-303验证框架的自动探测并注册(只需把JSR-303实现放置到classpath)
自动注册相应的HttpMessageConverter(用于支持@RequestBody 和 @ResponseBody)(如XML输入输出转换器(只需将JAXP实现放置到classpath)、JSON输入输出转换器(只需将Jackson实现放置到classpath))等。
:注册自定义的处理器拦截器;
:和ParameterizableViewController类似,收到相应请求后直接选择相应的视图;
:逻辑静态资源路径到物理静态资源路径的支持;
:当在web.xml 中DispatcherServlet使用/ 映射时,能映射静态资源(当Spring Web MVC框架没有处理请求对应的控制器时(如一些静态资源),转交给默认的Servlet来响应静态文件,否则报404找不到资源错误,)。

Spring3.1新特性:
对Servlet 3.0的全面支持。
@EnableWebMvc:用于在基于Java类定义Bean配置中开启MVC支持,和XML中的功能一样;
新的@Contoller和@RequestMapping注解支持类:处理器映射RequestMappingHandlerMapping 和 处理器适配器RequestMappingHandlerAdapter组合来代替Spring2.5开始的处理器映射DefaultAnnotationHandlerMapping和处理器适配器AnnotationMethodHandlerAdapter,提供更多的扩展点,它们之间的区别我们在处理器映射一章介绍。
新的@ExceptionHandler 注解支持类:ExceptionHandlerExceptionResolver来代替Spring3.0的AnnotationMethodHandlerExceptionResolver,在异常处理器一章我们再详细讲解它们的区别。
@RequestMapping的”consumes” 和 “produces”,用于支持@RequestBody 和 @ResponseBody:
consumes指定接受的请求的Content-Type,如consumes=”application/json”表示只接受请求头中Content-Type值为application/json的请求。
produces指定接受的请求的Accept。

URI模板变量增强:URI模板变量可以直接绑定到@ModelAttribute指定的命令对象、@PathVariable方法参数在视图渲染之前被合并到模型数据中(除JSON序列化、XML混搭场景下)。
@Validated:JSR-303的javax.validation.Valid一种变体(非JSR-303规范定义的,而是Spring自定义的),用于提供对Spring的验证器(org.springframework.validation.Validator)支持,需要Hibernate Validator 4.2及更高版本支持;
@RequestPart:提供对“multipart/form-data”请求的全面支持,支持Servlet 3.0文件上传(javax.servlet.http.Part)、支持内容的HttpMessageConverter(即根据请求头的Content-Type,来判断内容区数据是什么类型,如JSON、XML,能自动转换为命令对象),比@RequestParam更强大(只能对请求参数数据绑定,key-alue格式),而@RequestPart支持如JSON、XML内容区数据的绑定;详见本章的第×××节;
Flash 属性 和 RedirectAttribute:通过FlashMap存储一个请求的输出,当进入另一个请求时作为该请求的输入,典型场景如重定向(POST-REDIRECT-GET模式,1、POST时将下一次需要的数据放在FlashMap;2、重定向;3、通过GET访问重定向的地址,此时FlashMap会把1放到FlashMap的数据取出放到请求中,并从FlashMap中删除;从而支持在两次请求之间保存数据并防止了重复表单提交)。
Spring Web MVC提供FlashMapManager用于管理FlashMap,默认使用SessionFlashMapManager,即数据默认存储在session中。

SpringMVC常用注解

@Controller
负责注册一个bean 到spring 上下文中
@Controller, @RestController都是用来表示spring某个类的是否可以接收HTTP请求,区别是@Controller是标识一个Spring类是Spring MVC controller处理器,而@RestController是@Controller和@ResponseBody的结合体。
@RestController:a convenience annotation that does nothing more than adding the @Controller and @ResponseBody annotations。
参考http://blog.csdn.net/alan_liuyue/article/details/53837127

@RequestMapping
可以作用在类上或方法上,指定控制器可以处理哪些 URL 请求。一些路径映射会使用资源复数形式,例如使用@RequestMapping(“/todos”) 来自定义路径。URL映射中也可以使用${}来获得配置变量值。
@RequestMapping有多个属性,用来进一步匹配请求,包括:value、method、consumes、produces、params、headers。
Spring提供了简化的@RequestMapping,有GetMapping、PostMapping、PutMapping、DeleteMapping、PatchMapping。

@ResponseBody
该注解用于将Controller的方法返回的对象,通过适当的HttpMessageConverter转换为指定格式后,写入到Response对象的body数据区。如果是对象,则默认使用Jackson序列化成JSON字符串。

@ModelAttribute
在方法定义上使用 @ModelAttribute 注解:Spring MVC 在调用目标处理方法前,会先逐个调用在方法级上标注了@ModelAttribute 的方法

在方法的入参前使用 @ModelAttribute 注解:可以从隐含对象中获取隐含的模型数据中获取对象,再将请求参数 –绑定到对象中,再传入入参将方法入参对象添加到模型中

@ExceptionHandler
注解到方法上,出现异常时会执行该方法

@ControllerAdvice
使一个Contoller成为全局的异常处理类,类中用@ExceptionHandler方法注解的方法可以处理所有Controller发生的异常

配置tomcat
选下载安装tomcat,然后在eclipse偏好设置中找到”server“菜单下的”Runtime Environment“,之后在此页面下点击”add“,选择”tomcat6.0“之后,点击”Next“,点击”Browse“之后找到”Tomcat“的存储路径,点击”Finsh“即可完成。

方法参数映射

1.路径基本类型参数 @PathVariable
绑定 URL 占位符到入参,@PathVariable修饰符表示方法中的参数是从路径中映射而来。URL路径匹配,例如:

@RequestMapping(value = "/abc/{id}/{name}", method = RequestMethod.GET)
    public Todo getTodo(@PathVariable String id,@PathVariable("name") String name,) {
        return repository.findOne(id);
    }

Spring中也支持URL的矩阵变量。
2.@RequestParam 基本类型参数
在处理方法入参处使用 @RequestParam 可以把请求参数传递给请求方法,如果参数名和变量名不一致,可以使用@RequestParam(name),告诉spring,使用指定名字入参。
例如:
@RequestParam(value = “name”, required=true, defaultValue = “https://my.oschina.net/gaussik/blog/World”) String name。
3.对象参数
Spring有一套HTTP参数到JavaBean的自动映射规则。
4.@RequestBody 对象参数
@RequestBody 表示参数应该从 Http Request的body 中解析,类似的 @RequestHeader 表示参数是 Http Request的Header中定义的。
@RequestBody 参数意味着请求消息体的内容是一段json,Spring boot内置默认使用jackson来处理反序列化工作。
Spring会根据相应的HttpMessageConverter进行请求内容区数据到@RequestBody注解的命令对象的转换,及模型数据(返回值)到JSON响应内容的转换。
该注解用于读取Request请求的body部分数据,使用系统默认配置的HttpMessageConverter进行解析,然后把相应的数据绑定到要返回的对象上 ,再把HttpMessageConverter返回的对象数据绑定到 controller中方法的参数上。
5.MultiparFile
对上传文件大小限定,maxFileSize 是单个文件大小,maxRequestSize是设置总上传的数据大小。默认maxFileSize是1M,maxRequestSize是10M。

spring.servlet.multipart.max-file-size=10Mb
spring.servlet.multipart.max-request-size=10Mb
或者
    @Bean
    public MultipartConfigElement multipartConfigElement() {
        MultipartConfigFactory factory = new MultipartConfigFactory();
        //单个文件最大
        factory.setMaxFileSize("10MB"); //KB,MB
        //总上传数据总大小
        factory.setMaxRequestSize("10MB");
        return factory.createMultipartConfig();
    }

事务

@Service和@Transactional搭配使用。@Transactional可以使用在类上或方法上。
在Controller中,每一次Service方法调用是一次事务,调用中的数据库操作都处在一个事务中,调用结束时提交事务。如果有RuntimeException抛出则回滚。
不过对于mongodb可能无效,因为mongodb 3还不支持事务。Spring中事务的实现方式(部分)如下:

(1)编程式事务管理对基于 POJO 的应用来说是唯一选择。在代码中调用beginTransaction()、commit()、rollback()等事务管理相关的方法,这就是编程式事务管理。
(2)基于 TransactionProxyFactoryBean的声明式事务管理
(3)基于 @Transactional 的声明式事务管理
(4)基于Aspectj AOP配置事务

@Value

@Value("${test.user.mobile:}")
    public String testUserMobile;

@Value 使用的配置如果没有找到,会报 Caused by: java.lang.IllegalArgumentException: Could not resolve placeholder
可以设置默认值来解决这个问题,在配置key后加冒号和默认值。还可以使用SpringEL:#{null}来设置默认值为null。

PUT请求

http协议的put方法是没有请求参数的,tomcat默认只解析POST的表单,对于PUT和DELETE的不处理,所以spring对put的请求参数默认是不解析的。
解决方案:1、修改tomcat的server.xml:

<Connector port="8080" protocol="HTTP/1.1" 
           connectionTimeout="20000"
           redirectPort="8443"
           parseBodyMethods="POST,PUT,DELETE"
           URIEncoding="UTF-8" />

解决方案2、在web.xml中添加HttpPutFormContentFilter

 <!--Servlet不支持PUT表单,需要Spring支持-->
    <filter>
        <filter-name>httpPutFormContentFilter</filter-name>
        <filter-class>org.springframework.web.filter.HttpPutFormContentFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>httpPutFormContentFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>