阶段一:java语言基础知识
1.泛型
(1)定义:泛型是指参数化类型的能力,泛型的主要优点是能够在编译时而不是运行时检测出错误。ArrayList
(2)泛型接口、类和方法
泛型接口和泛型类比较简单,只需在类型后面加上
publicstatic
调用泛型方法:
GenericMethodDemo.
(3)受限和非受限泛型类型
受限泛型类型:
非受限泛型类型:
说明:加入A是B的子类,并不能说明ArrayList是ArrayList的子类型。但是ArrayList是ArrayList
(4)在jdk5之前的版本中,并没有泛型,为了向下兼容,泛型采用的方式为,在运行期间再消除泛型,恢复到jdk5之前的方式,因此在运行期间就不存在泛型这种东西,因此,在使用泛型的时候是有一些限制的:
限制一:不能使用new E()
出错原因:在运行期间执行的是new E(),但是在运行期间泛型是不可用的
限制二:不能使用new E[],但是可以通过如下方式达到这个目的
E[]es = (E[])new Object[length]
ArrayList
ArrayList
限制三:在静态环境下不允许类的参数是时泛型类型
出错原因:在实例化的时候才实例化泛型类型,但是静态的东西是属于某各类的。
限制四:异常类不能是泛型类型
2.Java集合框架
(1)Java集合框架的设计是使用接口、抽象类和具体类的一个很好的例子。用接口定义框架,用抽象类实现部分接口中的方法,每个子类再具体实现其他抽象方法。这种设计方式很好,当接口中又新增一个接口时,若是所有子类都需要的,可以在抽象类中实现,若只是部分子类需要,也可以在抽象类中实现,但是在不需要的子类中覆盖这个方法,并抛出不支持这个方法的异常,亦可以在需要的子类中逐个定义。
(2)所有的集合类都实现了serializable和cloneable这两个接口。
(3)Java集合分为三大类:List、set、quene
(4)Set是用来存储相互不同的元素的,有三个具体的实现类:hashset、linkedhashset和treeset。如何判断两个对象是否相等:如果两个对象相等,那么hashcode相等,但是两个对象的hashcode相等,两个对象不一定相等。因此为了保证效率,最好让hashcode尽量唯一。JavaApi中的大部分api都实现了hashcode方法,Integer中的hashcode返回他的int值,character返回字符的ascii码
(5)Hashset中的元素是没有顺序的,linkedhashset中的元素是按照插入时的顺序访问的,如果不需要维护这样的顺序,就使用hashset,效率会高一些。Treeset中的元素是有顺序的,是按照comparable接口中的compare方法进行比较的。若bean的结构已经固定了,则可以定义一个比较器实现comparator接口,当需要的时候将这个比较器的对象传入到treeset构造方法中即可。
(6)数组一旦创建之后,长度就不能变化了,但是list则是可变的,实现方式是,产生一个(2*原来的长度+1)的新数组,并将原来的数组中的元素复制到新数组中并返回。List和set的区别在于,list可以存放相同的元素。ArrayList和linkedlist的区别在于,如果只需要在线性表末尾进行插入和删除,那么前者效率高,否则,后者效率高。
(7)Collections和Arrays中封装了很多静态方法,有助于日常程序的编写效率。
(8)Vector和Arraylist是一样的,区别仅在于vector中的方法是同步的,在效率方面,后者要更高一些。
(9)Map有三个实现类,hashmap,linkedhashmap和treemap,map适合存储大量数据的检索任务,hashmap中的元素是没有顺序的,linkedhashmap中的元素是按照插入的顺序或者是最后访问时间来排序的,treemap则是按照comparable的顺序来排序的。
(10) Hashmap和hashtable的区别,后者是同步方法,前者效率更高些。
(11) 利用collections中的接口创建不可修改的集合和同步的集合
Collections.unmodified\\collection\\list\\map\\set等
Collections.sychronized\\collection\\list\\map\\set等
3.排序
(1)冒泡排序:每一趟的比较,选出一个最大值,时间复杂度:O(n2)
for(inti = 0; i < a.size - 1; i++) {
boolean flag = false;
for(intj = 0; j < size – i - 1; j++) {
if(a[j] > a[j + 1]) {
swap(a,i, j);
flag= true;
}
}
if(!flag) {
break;
}
}
(2)选择排序:每次选择一个最小的和第i个元素交换,时间复杂度:O(n2)
for(inti = 0; i < a.size - 1; i++) {
int min = a[0];
int index = 0;
for(intj = 0; j < a.size; j++) {
if(a[j] < min) {
min = a[j];
index= j;
}
}
swap(a, i, index);
}
(3)插入排序:每次向已经排好序的数组中插入新元素,时间复杂度:O(n2)
for(inti = 1; i < a.size - 1; i++) {
for(intj = 0; j < i; j++) {
if(a[j] > a[i]) {
break;
}
}
swap(i, j, a);
}
(4)归并排序:不断地将集合对半拆分,每部分递归地调用自己,再将拆分的两部分合并。时间复杂度O(nlogn)
voidmergeSort(a) {
if(a.length > 1) {
System.arraycopy(list, 0, list1, 0,a.length / 2);
int second = a.length-a.length/2;
System.arraycopy(list, 0, list2, a.length/2,second);
mergeSort(list1);
mergeSort(list2);
merge(list, list1, list2);
}
}
void merge(list, list1, list2) {
inti = 0, j = 0, k = 0;
while(i< list1.length && j < list2.length) {
if(list1[i]< list2[j]) {
list[k++] = list1[i++];
} else{
list[k++] = list2[j++];
}
}
while(i< list1.length) {
list[k++] = list1[i++];
}
while(j< list2.length) {
list[k++] = list2[j++];
}
}
(5)快速排序:不断的对数组进行划分,再利用这个划分将数组分成两部分,每部分继续递归调用自己。时间复杂度:最坏情况下O(n2),平均情况下O(nlogn).
voidquickSort(list) {
quickSort(list, 0, list.length - 1);
}
voidquickSort(list, i, j) {
if(i < j) {
int index = partition(list, i, j);
quickSort(list, i, index - 1);
quickSort(list, index + 1, j);
}
}
voidpartition(list, i, j) {
int low = i + 1;
int high = j;
int value = list[i];
while(low < high){
while(low <= high &&list[low] < value) {
low++;
}
while(low <= high && list[high]> value) {
high++;
}
if(low < high) {
swap(list, low, high);
low++;
high--;
}
}
if(list[high] < list[i]) {
swap(list,high, i);
returnhigh;
} else {
returni;
}
}
(6)堆排序:首先构造堆,堆是一颗完全二叉树,最大堆中的根要大于他的孩子,构造完成后,不断删除堆的根,即是最后的排序。堆排序的时间复杂度:O(nlogn)
利用list存储二叉树,每个节点的左孩子的索引为2i+1,右孩子的索引为2i+2,父亲的索引为(i-1)/2.
每个节点的数据结构为node
intvalue;
nodeleft;
noderight;
publicvoid add(int value) {
list.add(value);
int index = list.length – 1;
int current = (index – 1) / 2;
while(current > 0) {
if(list[current]< list[index]) {
swap(list,current, index);
index= current;
current= (index – 1) / 2;
} else {
break;
}
}
}
publicvoid remove(list) {
int current = 0;
list[0]= list[list.length - 1];
list[list.length - 1].remove();
left = 1;
right = 2;
while (list[left] != null) {
if(list[left]> list[right]) {
swap(list,current, left);
current= left;
} else {
swap(list,current, right);
current = right;
}
left = 2current + 1;
right= 2current + 2;
}
}
4.二叉查找树
二叉查找树的结构为:当前节点的左孩子都小于当前节点,当前节点的右孩子都大于当前节点。
(1)树节点的数据结构
Node
Eelement;
Nodeleft;
Noderight;
(2)查找一个值
publicboolean search(int value) {
node n= root;
while(n != null) {
if(n.value > value) {
n= n.left;
} else if(n.value < value){
n= n.right;
} else {
returntrue;
}
}
return false;
}
(3)插入一个值
publicvoid insert(int value) {
if(root == null) {
直接作为root插入
} else {
parent= current = root;
while(current!= null) {
if(current.value> value) {
parent = current;
current = current.left;
} else if(current.value < value) {
parent = current;
current= current.right;
} else {
return;
}
}
if(parent > value) {
parent.left= value;
} else {
parent.right= value;
}
}
}
(4)删除一个节点
分为两种情况:
case1:当当前节点没有左孩子,则将当前节点的右孩子作为当前节点的parent的做孩子。
case2:当当前节点有左孩子时,首先找到左孩子中最大节点,此最大节点肯定没有右孩子,将此最大节点的左孩子作为其父亲的孩子,再将此最大值赋给要删除的节点即可。
(5)二叉查找树的遍历
前序遍历:根左右,也是深度优先搜索
中序遍历:左根右
后序遍历:左右根
广度优先搜索:利用队列,先将根节点插入到队列中,当队列不空时,从队列中删除一个元素,遍历,然后将当前节点的所有孩子放到队列中
5.图
(1)定点的表示:数组
(2)边的表示:数组、edge对象的数组,邻接矩阵、邻接线性表
(3)深度优先搜索
Dfs(root){
访问root
for(遍历root的邻接点) {
if(若未被遍历) {
dfs(当前节点)
}
}
}
(4)广度优先搜索
Dfs(root){
将root放到队列中
while(队列不为空时) {
从队列中删除一个元素,并访问
for(遍历当前元素的邻接点) {
if(当前节点未被访问过) {
将其放到队列中
}
}
}
}
(5)最小生成树算法之prim算法:以点为中心,不断向已找到的点集中加入点,加入的原则是,和这个点相连的边最小。
(6)最小生成树算法之dijkstra算法:以边为中心,不断找到最小边,直到包含了所有的节点,找最小边时候的原则为:已找到的边不形成环
6.多线程
(1)创建线程和任务
任务类必须实现Runnable接口,和其中的run方法
Threadt = new Thread(new Tast());
t.start();在调用此方法之后,java虚拟机就会调用run方法
(2)Join方法:使当前线程等待另一个线程结束
(3)Yied()方法会让当前线程暂停一会,sleep方法会让当前线程停止指定的时间,但是不会释放掉锁,但是await方法会使当前线程释放掉锁。
(4)线程池:java提供Executor接口来支持线程池中的线程的执行,此接口中只有一个方法,execute,ExecutorService方法实现Executor接口,又提供了shutdown和isTerminated方法。Executors中有两个静态方法,newFixedTreadPool和newCachedThreadPool两个方法
(5)线程同步:1、sychronized修饰方法,2、sychronized块,3、利用Lock方法
(6)线程间通信
利用Lock的newCondition方法获得一个条件,再利用条件的await、signal和signalAll方法实现通信
例子:多个线程向一个账户存钱和取钱
publicAccount a = new Account();
Main方法:
ExecutorServiceexecutor = Executors.newCachedThreadPool();
for(inti = 0; i < 100; i++) {
executor.execute(newAdd());
}
for(inti = 0; i < 100; i++) {
executor.execute(new Sub());
}
Task:Add类中的run方法(内部类)
while(true){
a.add(1);
}
Task:Sub类中的run方法(内部类)
while(true){
a.sub(1);
}
Account类
{
int balance = 0;
private Lock lock = new ReentrantLock();
Condition newAdd = lock.newCondition();
public add(int v) {
lock.lock();
balance+= v;
newAdd.signalAll();
lock.unlock();(放到try,catch,finally语句中)
}
public sub(int v) {
lock.lock();
while(balance < v) {
newAdd.await();
}
balance -= v;
lock.unlock();(放到try,catch,finally语句中)
}
}
(7)阻塞队列
在试图向一个满队列加元素或者从空队列中删除元素时会导致阻塞。BlockingQunene实现Qunene接口,提供put和take两个方法。他的实现类有
ArrayBlockingQunene、
LinkedBlockingQunene和PriorityArrayBlockingQunene
(8)信号量:用来限制访问共享资源的线程数。在访问资源前,必须经信号量许可(acquire方法),在访问完资源后,线程必须将许可返回给信号量(release方法)
(9)避免死锁的方法:资源排序,每个线程按照这个顺序获取资源
(10) 线程的状态:新建、就绪、运行、结束
(11) 同步集合:
Setset = Collections.sychronizedSet(new HashSet());
synchronized(set){(为什么要这样,因为Iterator具有快速失败的特性)
IteratorI = set.iterate();
while(i.hasNext()) {
i.next();
}
}
阶段二:java虚拟机
1.java体系结构
(1) java结构包括四个部分:java虚拟机、java api、java class文件和java程序设计语言。其中,java虚拟机和java api又称java平台。
(2) java虚拟机:主要任务就是装载class文件,并执行。执行class的执行引擎,根据不同厂商有不同的实现,大致分为三种,第一种是一次性解释字节码,第二种是即时编译型,第三种是自适应优化器,一次解释型不利于重用,即时编译型先将字节码编译为本地机器代码,并缓存当方法以后调用的时候会直接调用,但是很耗内存,而自使用方式是将最常用的方法缓存,其他代码则利用第一种方式。
支持平台无关性:java应用程序是通过调用java api来调用系统资源的,而每个操作系统上的虚拟机都必须实现这些api,这些实现是平台相关的,但是程序调用的api确实相同的,因此也就保证了应用程序的平台无关性。
支持安全性:
支持网络移动性:
Java虚拟机的体系结构包括类加载器、方法区、堆、java栈、pc寄存器、本地方法栈和执行引擎。其中,方法区和堆都是所有线程共享的,java栈和寄存器是线程单独享有的。方法区中主要存储类型信息,常量池;堆中主要存放对象;栈中主要用来存放调用的方法的局部变量,形参,返回值以及中间结果等,java栈的单位是栈帧,每个栈帧代表一个方法。
(3) 类装载器:类装载器分为两种,一种是启动类装载器,一种是用户定义的类装载器,启动类装载器是jvm的一部分。
支持安全性:每个类装载器都有自己的命名空间,不能相互访问,当加载一个字节码时,发现需要其他的类,则动态扩展她,即用当前的类装载器装载这个类,而一个类装载器装载一个类得过程是:利用双亲委派的模式,先让父装载器转载,再返给自己,从而保证安全性。即:装载-把二进制形式的java类型读入到java虚拟机中、连接(验证-验证类型是否正确、准备-准备内存、解析-将常量池中的符号引用转化为实际引用,这一步可以到初始化之后再进行)-把已经装载到虚拟机的二进制形式的类型合并到虚拟机的运行时状态中去、初始化-给类变量赋予程序中的初始值。初始化的时机:首次主动使用时初始化(创建了类得实例,使用了某个类得静态方法或字段,初始化子类的时候)。
支持网络移动性:在类装载的过程中,允许动态扩展类,这就为加载网络上的class成为可能。
(4) class文件:
支持平台无关性:java致力于产生一种独立于底层的二进制代码,因此这种class文件可运行于任何有java虚拟机、硬件和操作系统的平台之上。
支持网络移动性:首先,class文件设计的非常紧凑,可以快速在网络上传输,其次,java程序是动态连接和动态加载的,因此可以从网络上获取class文件。
(5) java api:
支持平台无关性:java api本身是平台相关的,但是每个安装了java虚拟机的机器上都实现了java api,在不同平台上的api接口是一样的,这就使应用程序支持平台无关性成为可能。
支持安全性:java api在进行相对比较危险的操作的时候,有一个访问控制器来对其进行检验,若未经允许,是不允许这种操作的。
(6) java程序设计语言
支持平台无关性:java程序设计语言中的一些类型在任何系统的字节长度都是一样的
支持安全性:java语言中不允许使用指针,其访问内存都是结构化的,不能通过指针地址的变化来引用不同的对象;java语言支持垃圾收集,不需要程序员自己释放内存,减少了内存泄露的可能。
(7) java的缺陷:速度相对于其他的语言要慢,主要是把垃圾收集的任务让jvm来做了,而改善这一缺点可以从多方面努力。在算法上,在数据库的sql优化上等。也可根据你的开发目的选择垃圾收集的算法。
(8) 保证java安全性的组件主要是沙箱,沙箱的组成部分如下:
类加载器,class文件检验器(会进行四趟检验),内置于java虚拟机的安全特性(1.结构化内存的访问,2.自动垃圾收集,3.数组边界检查),安全管理器及java api
(9)网络移动性:把一个大的class文件,分割成小的文件,而且这些文件可以按需动态扩展。
(10) 方法区:
方法区存放的类型信息有:该类的权限定名,该类的直接超类得权限定名,该类是接口还是类,这个类型的访问修饰符,该类型的常量池,字段信息,方法信息,类变量,classloader的引用,class类得引用。
常量池:存储的是该类型的所有常量,包括直接常量(eg:integer I = 1)和对其他类型的符号引用。这里的常量不是编译时常量,而是运行时常量。
Class的引用:可以利用Class.forname或者对象.getclass()来获取类型的引用,class类中还有一些方法,getName(),getSuperClass(),getClassLoader()等。
(11) 堆:堆中存放的是对象的数据和指向此对象对应的类型信息,可能还有一个方发表。方发表是存储在方法区中的,如果有方法表的话,堆中的对象有一个引用,指向一个方法区中的特定结构,这个结构包括指向类型信息的引用和指向每个方法的指针。堆中也存储数组的数据和其对类型信息的引用。
2.java垃圾收集
(1) 当一个对象不再被引用指向时,就有可能被收集,在收集之前会调用这个对象的finalizer方法,在这个方法中可以复活某个对象或者自己,当把自己复活后,当再次收集这个对象的时候,就不再执行finalizer方法了。垃圾收集的目的在于收集不再被引用的对象和使堆空间变得紧凑。区分垃圾对象和活动对象的方法一般为引用计数和跟踪。
(2) 引用计数算法:每个对象维护一个变量,这个变量用来存放当前对象的被引用的次数。此算法的优点是:可以很快的执行,交织到程序的运行中,这种算法对于程序不能被长时间打扰的实时情况比较实用。缺点在于不能检测出循环引用,即使他们无法被触及,也永远不会被收集。
(3) 跟踪收集算法:追踪从根节点开始对象引用图,被引用的打上标记,当遍历结束后,未打标记的全部都可以回收。
(4) 压缩收集算法:将对象移动到堆空间的一边,被移动对象的引用也随之被更新。还有一种改进的方法:引用不直接引用堆上的对象,而是引用一个间接地址,当对象被移动时,只需改变间接地址即可。
(5) 拷贝收集算法:将空间分为两个部分,一部分始终为空,另一部分装对象,将装对象的那部分拷贝到新区域,使之没有空隙。这种算法有两个缺点,首先要停止程序运行,有时候的等到时间使得用户无法忍受。其次,只能利用一半的堆空间。
(6) 为了让垃圾收集的时间不至于过长,发明了火车算法:将堆空间分成一个个火车,每个火车中的每个车厢装一个对象。每个火车和车厢依次排序。当执行垃圾收集的时候,每次只收集一个火车的空间,如果当前火车中的每个对象都没有被引用,则将整个火车的对象空间全部收集,如果有对象仍然被引用,则将这个对象移动到引用对象的火车中,如果那个火车已满,则放到一个新火车中。
阶段三:activiti
1.activity简介
工作流是将部分或者全部的工作流程交给计算机来自动执行,而工作流引擎就是为这一目标提供解决方案的。Activiti就是比较常用的工作流引擎。试想如果没有流程引擎,整个流程的逻辑都要自己实现,也是可行的,但是很容易出错,activiti就是将整个流程的架子搭起来,只是需要我们把细节敲定,再调用提供的api来完成我们的逻辑,事情就变得简单了。
2.activity核心组件
ProcessEngine:流程引擎对象,通过他可以获得提供的所有服务。
RepositoryService:部署一个流程时,将流程文件部署的服务。
RuntimeService:提供了启动流程、查询流程和获取或设置流程中的变量的方法。
TaskService:流程中的每个节点称为一个task,对每个任务的变量设置,一个任务的完成、查询、删除等都需要调用此服务的接口。
HistoryService:HistoryService用于获取正在运行或已经完成的流程实例的信息,与RuntimeService中获取的流程信息不同,历史信息包含已经持久化存储的永久信息,并已经被针对查询优化。
3.activity与spring结合的配置
配置数据源(和正常的配置一样)--配置事务(和正常的配置一样)--配置processEngineConfiguration并引用上述数据源和事务—配置processEngine—配置五个服务。
阶段四:数据挖掘常用算法
1.数据挖掘的步骤
数据清理(缺失值、噪声数据)—数据集成、规约(数据降维)--数据挖掘—可视化。
2.Apriori(先验)(关联规则算法)
先验性质:频繁项集的所有非空子集也一定是频繁的。
步骤:首先扫描事务数据库,产生候选一项集,根据候选一项集产生频繁一项集(满足最小支持度阀值),让频繁一项集做自连接操作,产生候选二项集,如此循环下去。产生所有的频繁项集之后,利用频繁项集产生关联规则,如当前的频繁项集为:1、2、5,先产生频繁项集的所有非空子集,eg:1,产生的关联规则为1—>2、5,计算此规则的置信度,用125的支持度除以1的支持度即可,若大于最小置信度阀值,则此关联规则为强关联规则。
3.FP-Grouth算法(关联规则算法)
步骤:先产生频繁一项集,然后构造一颗以null为根的树,扫描事务数据库,将每个事务以频繁一项集的降序排列,并以此为顺序构造树,每个树节点都保存当前节点的个数,当把事务数据库全部扫描完后,以每个叶子节点为后缀,产生其条件模式基,再利用每个叶子节点的条件模式基继续构造树,从而产生频繁模式。产生关联规则的过程如2。
此算法相对于2的优势在于只需扫描一次事务数据库。
4.决策树算法(信息增益)(分类算法)
步骤:首先计算整个分类的期望信息(-plogp求和,其中p为每个类别的概率),再计算每个属性的信息增益(总信息期望信息-当前属性的期望信息),而当前属性的期望信息=p1(-p2logp2求和)求和,其中p1为该属性上的每个类别的概率,而p2为当前属性上当前类别的每个属性值分布的概率。然后比较每个属性的信息增益,较大者作为当前的分裂属性,每个属性值又产生一个事务数据库的子数据库,继续此过程,知道可以分类。
5.贝叶斯算法(朴素)(分类算法)
步骤:给定一个元组x,两个类别c1和c2,其实就是看p(ci/x)哪个大,而
p(ci/x)=p(x/ci)p(ci)/p(x),从而将问题转化为最大化p(x/ci)p(ci)的问题。而何为朴素,就是假设p(x/ci)= p(x1/ci)*…p(xn/ci)。从而解决问题。正是因为有朴素这个假设,是的当有一个属性的概率为零的时候,就回将其他属性的影响消除,因此一般的解决办法是利用拉普拉斯校对,将每个属性的个数加1,分母加n,即可解决此问题。
说明:分类的好坏的度量:准确率。但是有时候准确率不能完全衡量好坏,比如癌症这类分类不平衡问题,可用精度与召回率来衡量。
6.神经网络之向后传播算法(分类算法)
步骤:首先决定隐藏层的层数,再初始化网络拓扑的所有权重和偏倚,这些都是根据经验定的初始值,然后再向前计算每个层的每个单元的净输入和输出,再向后计算输入层和隐藏层的每个单元的误差,再根据这些值调整权重和偏倚。直到所有元组遍历完成或者权重和偏倚都收敛。
分类:对于给定的元组x,把该元组放到网络中,计算每个单元的净输入和输出,具有最高输出值的输出层单元即为类标号。
7.K-均值算法(聚类算法)
首先确定要分为几个簇,初始情况下,假定前两个点为簇中心,再分别计算每个点和这两个中心的距离,从而确定属于哪个簇,分好之后,计算总误差,再重新计算每个簇的中心,用平均值作为新簇的中心,再继续计算每个点到哪个簇的距离更近。如此循环下去,直到每个节点的归属不再发生变化。
阶段五:JavaWeb面试题
1.jsp中静态include和动态include的区别
动态 INCLUDE 用 jsp:include 动作实现
静态 INCLUDE 用 include 伪码实现 , 定不会检查所含文件的变化 , 适用于包含静态页面 <%@ include file="included.htm"%> 。先将文件的代码被原封不动地加入到了主页面从而合成一个文件,然后再进行翻译,此时不允许有相同的变量。
2.下面哪些类可以被继承?Java.lang.Thread(可以)、java.lang.Number(可以,抽象类)、java.lang.Number.Double(不可以,被final修饰了)、java.lang.Number.Math(不可以,被final修饰了)、java.lang.Number.ClassLoader(可以,抽象类被final修饰了)
3.在myjsp.jsp中,关于下面的代码说法错误的是: ( )
<%@ pagelanguage="java" import="java.util.*"errorPage="error.jsp" isErrorPage="false" %>
A. 该页面可以使用exception对象
B. 该页面发生异常会转向error.jsp
C. 存在 errorPage属性时,isErrorPage是必须的属性值且一定为 false
D. error.jsp页面一定要有isErrorPage属性且值为 true
解析:exception是JSP九大内置对象之一,其实例代表其他页面的异常和错误。只有当页面是错误处理页面时,即isErroePage为 true时,该对象才可以使用。对于C项,errorPage的实质就是JSP的异常处理机制,发生异常时才会跳转到 errorPage指定的页面,没必要给errorPage再设置一个errorPage。所以当errorPage属性存在时, isErrorPage属性值为false
4.forward获取到url,跳转一次,地址栏不变redirect 获取url,然后运行url,地址栏变为url;
5.给定includel.isp文件代码片段,如下:
<% pageContext.setAttribute(“User”,”HAHA”);%>
______ // 此处填写代码
给定include2.jsp文件代码片段如下:
<%=pageContext.getAttribute(“User”)%>
要求运行include1.jsp时,浏览器上输出:HAHA
A.
B. <%@includefile=”include2.jsp”%>
C.
D. <% response.sendRedirect(“include2.jsp”);%>
6. 父类B静态代码块->子类A静态代码块->父类B非静态代码块->父类B构造函数->子类A非静态代码块->子类A构造函数
解析:父类的构造在子类之前,静态代码块的执行在构造函数之前,非静态代码块的执行在构造函数之后。
7.springMVC原理:
1).spring mvc请所有的请求都提交给DispatcherServlet,它会委托应用系统的其他模块负责负责对请求进行真正的处理工作。
2).DispatcherServlet查询一个或多个HandlerMapping,找到处理请求的Controller.
3).DispatcherServlet请请求提交到目标Controller
4).Controller进行业务逻辑处理后,会返回一个ModelAndView
5).Dispathcher查询一个或多个ViewResolver视图解析器,找到ModelAndView对象指定的视图对象
6).视图对象负责渲染返回给客户端。
8.hibernate推荐使用双向一对多:因为当在一方维护关联关系时,要想执行保存操作,就要先构造多的一方并保存,再在一得一方设置,再保存一得一方,hibernate会再执行更新多的一方的操作。而如果多的一方维护关联关系时,先构造一得一方并保存,再构造多的一方保存,免得执行多余的update操作了。所以推荐使用双向一对多。
9.Hibernate一级缓存是Session的缓存,第一级缓存为必需,不允许且事实上也无法被卸除。
10. Hibernate get和load区别
1)从返回结果上看:load方式检索不到的话会抛出
org.hibernate.ObjectNotFoundException异常。get方法检索不到的话会返回null。
2)从返回结果上看:从检索执行机制上对比: get方法和find方法都是直接从数据库中检索 而load方法的执行则比较复杂首先查找session的persistent Context中是否有缓存,如果有则直接返回如果没有则判断是否是lazy,如果不是直接访问数据库检索,查到记录返回,查不到抛出异常 如果是lazy则需要建立代理对象,对象的initialized属性为false,target属性为null 在访问获得的代理对象的属性时,检索数据库,如果找到记录则把该记录的对象复制到代理对象的target上,并将initialized=true,如果找不到就抛出异常。
11.
publicclassStringDemo{
privatestaticfinalString MESSAGE="taobao";
publicstaticvoidmain(String [] args) {
Stringa ="tao"+"bao";
Stringb="tao";
Stringc="bao";
System.out.println(a==MESSAGE);
System.out.println((b+c)==MESSAGE);
}
}
打印结果为true和false
原因:对于"tao"+"bao",在编译时,就执行了相加操作,所以在栈内存中存储的是"taobao",而b+c操作是等到运行时,才进行相加操作,否则当编译时就相加,b改变了后,还要再执行相加,效率则不够高。
12写一段JDBC连接Oracle的程序,并实现数据查询。
publicclass JDBCDemo {
publicstatic void main(String[] args){
Connectionconn = null;
ResultSetrs = null;
try{
//加载驱动
Clas敏感词orName("oracle.jdbc.OracleDriver");
//获得连接
conn= DriverManager.getConnection(
"jdbc:oracle:thin:@localhost:1521:orcl",
"scott",
"tiger");
Statementstat = conn.createStatement();
//sql语句
Stringsql = "SELECT * FROM emp";
//执行语句获得结果集
rs= stat.executeQuery(sql);
//遍历结果集
while(rs.next()){
Stringname = rs.getString("name");
System.out.println(name);
}
} catch(Exception e) {
e.printStackTrace();
} finally{
//关闭连接
try{
conn.close();
} catch(SQLException e) {
//TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
13 cookie和session的作用、区别、应用范围,session的工作原理???
Cookie:主要用在保存客户端,其值在客户端与服务端之间传送,不安全,存储的数据量有限。
Session:保存在服务端,每一个session在服务端有一个sessionID作一个标识。存储的数据量大,安全性高。占用服务端的内存资源。
14. 过滤器和拦截器的区别
拦截器 :是在面向切面编程的就是在你的service或者一个方法,前调用一个方法,或者在方法后调用一个方法比如动态代理就是拦截器的简单实现,在你调用方法前打印出字符串(或者做其它业务逻辑的操作),也可以在你调用方法后打印出字符串,甚至在你抛出异常的时候做业务逻辑的操作。
过滤器:是在java web中,你传入的request,response提前过滤掉一些信息,或者提前设置一些参数,然后再传入servlet或者struts的 action进行业务逻辑,比如过滤掉非法url(不是login.do的地址请求,如果用户没有登陆都过滤掉),或者在传入servlet或者 struts的action前统一设置字符集,或者去除掉一些非法字符。
15. hql和sql的区别
sql是面向数据库表查询
hql是面向对象查询的,其form子句返回的是对象的实例。
16.SpringIoc
SpringIoc又称控制反转或者依赖注入,设计目的是使接口和实现类之间充分解耦,让这个引用的实例化的控制权交给spring容器,在xml中配置好依赖注入的bean引用关系即可。spring底层的实现是借助java反射机制的,通过反射机制构造对象,并赋给调用类的属性。注入的方式有属性注入和构造方法注入。
16.SpringAop
SpringAop即面向切面的编程,其实就是为了复用代码,但是这些代码是横向的,因此要想实现复用的效果,不对代码产生耦合,就需要在运行期间动态织入增强,具体原理为:jdk的动态代理或者CGLib动态代理,首先讲jdk动态代理,jdk只能为借口创建代理,两个主要的核心为InvocationHandler和proxy,定义一个类实现InvocationHandler,将横切逻辑放到方法中并利用反射机制执行代理方法,利用proxy产生被代理接口的一个代理,再调用被代理方法即可。而CGLib动态代理则是利用字节码技术,为被代理类创建一个子类,并在子类方法被调用的时候顺势织入横切逻辑。首先定义一个实现MethodIntercepter接口的类,再利用这个类产生被代理类的代理对象,从而实现既定效果。
17. hibernate中建议不用一对一,用多对一取代
如果用了一对一,那么关联表的主键就是主表的主键,一旦业务有变,就需要改表结构。
18. 说说在hibernate中使用Integer做映射和使用int做映射之间有什么差别:
Integercode和intcode;的区别:
Integer是对象.code=null;对象可以为空.int是普通类型,不可能=null.
根据你的数据库code是可以空的,故应该映射成Integer.你没理由hbm.xml里写Integer,类里却写int
19. oracle中数据表的交集怎么表示?
Intersect或in,并集:union
20. 面向接口编程的好处?
使用接口编程,易于程序扩展,降低程序耦合性。
21.jdk5之后引入的并发包?
看自己的博客。
网址:
http://blog.csdn.net/aa20132413013/article/details/46882493(java CopyOnWriteArrayList的使用)
http://blog.csdn.net/aa20132413013/article/details/46882351(java.util.concurrent包)
http://blog.csdn.net/aa20132413013/article/details/46882333(Java中CopyOnWriteArrayList 的使用)
http://blog.csdn.net/aa20132413013/article/details/46882267(ConcurrentHashMap原理分析)
阶段六:spring详解
1.Spring定义
Spring(由Rod Johnson发明)是分层的javaEE/javaSE应用一站式的轻量级开源框架,以IoC和AOP为内核,提供了展现层SpringMVC和持久层Spring JDBC以及业务层事务管理等众多的企业级应用技术,此外spring还整合了很多著名的第三方框架和类库。
2.关于Spring的配置问题
因为spring容器是依赖于servlet容器的,applicationContext和servletContext两者是可以相互访问的,因此必须先启动好servlet容器,得到servletContext对象后,才能启动spring容器。而servlet容器,其实就是web.xml,当 servlet容器启动后,必须告诉spring,并让其启动,因此需在web.xml中配置一个监听器,监听其是否加载完成,同时需要配置spring的配置文件的位置,由此,spring容器也就启动了,但是这只是配置了spring容器,要想在web环境下,和spring关联起来,还必须配置地址拦截,这就要配置springMVC,而springMVC的核心就是DispacherServlet,因此在web.xml中,我们必须配置这个servlet用于启动springMVC,默认情况下,springMVC的配置文件的地址为:servlet的名字-servlet.xml,会自动加载此文件,此servlet拦截的地址为所有浏览器过来的地址。这样就全部配置好了。
SpringMVC配置文件 对应webapplicationcontext
Spring配置文件 对应 applicationcontext
web.xml 对应 servletContext
3.SpringIoc
SpringIoc又称控制反转或者依赖注入,设计目的是使接口和实现类之间充分解耦,让这个引用的实例化的控制权交给spring容器,在xml中配置好依赖注入的bean引用关系即可。spring底层的实现是借助java反射机制的,通过反射机制构造对象,并赋给调用类的属性。注入的方式有属性注入和构造方法注入。
4.SpringIoc的类型
从注入方法上分,可分为三种类型:属性注入、接口注入(将需要注入的属性都抽象为方法,单独放到一个接口中,然后让需要注入的类继承此接口并实现方法,达到注入的目的)、构造函数注入。
5.java反射机制
1) 获取Class实例的方法:
第一种:通过Class.forName(“类的全限定名”)
第二种:ClassLoader load =
Thread.currentThread().getContextClassLoader();
Load.loadClass(“类的全限定名”);
第三种:通过对象.getClass();
第四种:类名.class
2)Constructor
通过Class#getDeclaredConstructors()方法可以获得类的所有构造方法,也可通过getDeclaredConstructor(Class… parameterTypes)获取拥有特定参数的构造方法,Constructor类的一个主要的方法为 newInstance(Object[]in),通过该方法获得一个新的对象。
3) Method
通过Class#getDeclaredMethods()方法可以获得类的所有方法数组,也可通过getDeclaredMethod (String name, Class… parameterTypes)获取拥有特定参数的构造方法,其最主要的方法为invoke(Object obj, Object…args),其中,obj为目标对象,args为方法入参。还有一些比较重要的方法:
ClassgetReturnType(): 获取方法的返回类型
Class[]getParameterTypes():获取方法的入参的类型数组
Class[]getExceptionTypes():获取方法的异常类型数组
Annotation[][]getParameterAnnotations():获取方法的注解。
4) Field
通过Class#getDeclaredFields()方法可以获得类的所有属性数组,也可通过getDeclaredFields (String name)获取特定名的属性,Field的最主要的方法为set(Object obj, Object value),若此属性为基本类型,可以用setBoolean(Objectobj, boolean value)和setInt (Object obj, int value)等。
注:对于类的私有成员变量和方法,在类的外面是不能够访问的,要想在反射机制中使用私有的,需要调用目标.setAccessible(true)。
6.spring的资源访问器(用到了策略模式)
Resource接口:exists(),isOpen,getURL(),getFile(),getInputStream()
实现类:
ByteArrayResource:二进制数组表示的资源。
ClassPathResource:类路径下资源。
FileSystemResource:文件系统资源:D:/bean.xml。
InputStreamResource:对应一个InputStream资源。
ServletContextResource:相对于web根目录的资源。
UrlResource:能访问任何能用URL表示的资源。
Classpath和classpath*的区别:假设有多个jar包都拥有一个相同的包名,前者只在会在第一个加载的包下查找,而后者会扫描所有的jar包出现的包下查找。
Eg:
ResourcePatternResolverresolver = new
PathMatchingResourcePatternResolver();
Resource[]r = resolver.getResources(“classpath*:com/**/*.xml”);
这种方式类似于ClassPathResource(“com/**/*.xml”)
7.BeanFactory介绍
该接口中最主要的方法为getBean(StringbeanName);
ResourcePatternResolverresolver = new
PathMatchingResourcePatternResolver();
Resourcer = resolver.getResource(“classpath*:com/**/bean.xml”);
BeanFactorybf = new XmlBeanFactory(r)
Carcar = bf.getBean(“car”, Car.class)
8.ApplicationContext介绍
ApplicationContext建立在BeanFactory之上,增加了很多功能,如国际化功能等。主要的实现类:
ClassPathXmlApplicationContext:在类路径下加载资源。
FileSystemXmlApplicationContext:在系统路径下加载资源。
AnnotationConfigApplicationContext:加载用java类配置的bean信息
Eg:ApplicationContext tx = new ClassPathXmlApplicationContext(
newString[]{“conf/bean1.xml”, “conf/bean2.xml”}
)
Carcar = txbf.getBean(“car”, Car.class)
BeanFactory和ApplicationContext的区别:前者在初始化时并未实例化bean,直到第一次访问时才实例化。而ApplicationContext在初始化上下文时就实例化所有的bean,因此ApplicationContext初始化时间要长一些。
9.WebApplicationContext介绍
WebApplicationContext和ServletContext相互得到对方:
WebApplicationContextUtils.getWebApplicationContext(ServletContextsc);
webApplicationContext.getServletContext();
因此必须先初始化web容器,再初始化WebApplicationContext
由于WebApplicationContext需要使用日志的功能,用户可以将Log4J的配置文件放到WEB-INF/classes下,这时Log4J便可顺利启动。若配置文件不在class下,则需要在web.xml中配置。配置时,需要保证Log4J在WebApplicationContext之前被初始化。
10.Bean生命周期
实例化bean前调用postProcessBeforeInstantiation()方法-》实例化bean-》调用postProcessAfterInstantiation()方法-》如果bean设置了属性,调用postProcessPropertiesValues()->设置属性值-》调用setBeanName()-》setBeanFactory()方法,将BeanFactory实例设置到bean中-》调用BeanPostProcessor对bean进行加工,这一步在spring中占据重要地位,Aop和动态代理等神奇功能都是在这个时候实现的-》调用ini-Method()方法->若scope=prototype,则返回bean,spring不载管这个bean的生命周期,若scope=singleton,则将bean加入到bean缓冲池中,spring管理此bean的后续声明周期-》对于cope=singleton的bean,调用destroy-Method()方法。
ApplicationContext和BeanFactory的另一个最大的不同在于,前者会利用java反射机制自动识别出配置文件中按顺序定义的BeanPostProcessor等,并自动注册到上下文中,而后者需要在代码中通过手工调用addBeanPostProcessor(),这也是为什么开发中都用ApplicationContext而不用BeanFactory的原因。
11.spring容器内部结构
Bean配置信息(就是bean的元数据信息,包括bean的实现类、属性信息和依赖关系)定义了bean的实现及依赖关系,spring容器根据各种形式的bean配置信息在容器内部建立bean定义注册表,然后根据注册表加载、实例化bean,并建立bean和bean之间的依赖关系,最后将这些准备就绪的bean放到bean缓冲池中,以供外层的应用程序调用。
12.bean的命名
Id命名:名称有限制,必须以字母开头。且id名唯一
Name命名:名称无限制,可包含特殊字符,name不唯一,当有多个name相同的bean,调用getBean时,得到的是最后一个bean(覆盖)
如果既没有id也没有name,那么将以类的全限定名为id,如果定义了多个class的名字一样的bean,则getBean(“全限定名”)获取的是第一个bean,要想获取第n个bean,需用getBean(“全限定名#n”)的形式。
13.属性注入和构造方法注入的问题
属性注入时,如果属性的命名以iDCode方式命名,则spring启动会失败。
构造方法注入时,存在循环依赖的问题,如,构造car时,要传入boss,构造boss时,要传入car,这样,这两个实例都不能构造成功。
14.方法注入
无状态的bean的作用域一般可配置为singleton单例模式,如果我们往singleton的boss中注入prototype的car,并希望调用boss的getCar方法能够返回新的实例,但是这种配置方式是不会如愿以偿的,此时只能够通过lookup方法注入即可。
15.bean之间的关系
继承:父亲bean定义为abstract=”true”,子bean定义parent=”父亲bean的名字”
依赖:当一个bean,必须等到另一个bean实例化之后,再实例化,可以用depends-on属性标注。
引用:eg:p:cardId=”car”
16.bean的作用域
Singleton:单例模式:一般无状态的bean才定义为单例,按理来说,spring的dao层拥有connection这个非线程安全的变量,不应该定义为单例,但是spring通过ThreadLoacle机制,为每个线程保存一份connection副本,这样就转化为线程安全的了。从而提高了效率,是以空间换取时间的典型。
Prototype:每次都产生一个新的bean
后面三个只有在web环境下才有
Request:一次request请求,用一个bean。
Session:一次Session请求,用一个bean。
Globalsession
在singleton中引用request作用域的bean,需要将request作用域的bean用
17.spring注解和xml结合方式配置
@Autowired(required=false)
PrivateList
以上注解将会把xml中配置的bean全部装载到plugins中。
18.在spring中,bean的生命周期中可以注册很多事件,这用到了观察者模式。
19.spring国际化
本地化信息也称为国际化信息,需要国家和语言两个才能够确定,java通过java.util.Locale类表示一个本地化对象,可通过语言和国家两个来构造Locale对象,locale.getFefault()可以获取本地系统默认的本地信息。
Jdk提供的本地化工具类:NumberFormat、DateFormat、MessageFormat,用于对数字、日期和字符串的本地化操作。
Jdk也提供了加载本地化资源的类,java.util.ResourceBoundle,调用其静态方法getBundle()方法加载资源,资源的命名方式为:<资源名>_<语言代码>_<国家代码>.properties,其中语言和国家代码都是可选的,加载顺序为,精确匹配。
Spring提供了MessageSource,调用getMessage()方法即可。有两个重要的实现类,ResourceBoundleMessageSource和
ReloadableResourceBoundleMessageSource,两者都需要配置一个bean,在bean中指定资源位置。然后得到这个bean,调用getMessage即可。定义容器级别的MessageSource,因为applicationContext也实现了MessageSource接口,因此,在配置文件中定义一个id为messageSource的bean(只能是这个id,spring会自动调用),并指定资源位置,然后用applicationContext.getMessage()即可。
20.SpringAop
SpringAop即面向切面的编程,其实就是为了复用代码,但是这些代码是横向的,因此要想实现复用的效果,不对代码产生耦合,就需要在运行期间动态织入增强,具体原理为:jdk的动态代理或者CGLib动态代理,首先讲jdk动态代理,jdk只能为借口创建代理,两个主要的核心为InvocationHandler和proxy,定义一个类实现InvocationHandler,将横切逻辑放到方法中并利用反射机制执行代理方法,利用proxy产生被代理接口的一个代理,再调用被代理方法即可。而CGLib动态代理则是利用字节码技术,为被代理类创建一个子类,并在子类方法被调用的时候顺势织入横切逻辑。首先定义一个实现MethodIntercepter接口的类,再利用这个类产生被代理类的代理对象,从而实现既定效果。
21.springAOP术语
连接点:连接点有很多,一个类的一个方法的执行前、执行后都是一个连接点。连接点由两个信息确定:哪个方法+哪个方位(前还是后)。
切点:其实就是查询一个或者多个连接点的查询条件,切点和连接点的对应关系为一对多。
增强:包含要织入的代码+方位信息
引介:引介是一种特殊的增强,他可以为类添加属性和方法,若一个类没有实现一个接口,通过引介可以让一个类在运行期实现这个接口。
代理:一个类被AOP织入增强后,就产生了一个结果类,根据不同的代理方式,代理类可能是和原类具有相同的接口(Jdk代理),也可能是原类的子类(CGLib代理),所以我们可以和原类相同的方式调用代理类。
切面:由切点和增强组成。
22.jdk代理
Jdk的动态代理主要涉及到java.lang.reflect包中的两个类,Proxy和InvocationHandler,其中InvocationHandler是一个接口,可以实现该接口定义横切逻辑并通过反射机制调用目标类的代码,动态的将横切逻辑和业务逻辑整合在一起。
23.CGLib代理
CGLib采用非常底层的字节码技术,可以为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,并顺势织入横切代码。
说明:CGLib动态代理创建出的对象的性能比jdk的强很多(大概10倍),而Jdk代理创建代理对象所用的时间比CGLib快很多(大概8倍),所以对于singleton的代理对象或具有代理池的代理,因无需频繁的创建代理对象,推荐使用CGLib,否则使用Jdk。
24.增强类型
前置增强、后置增强、环绕增强、异常抛出增强,引介增强。
25.数据库事务
数据库事务必须满足四个特性:ACID特性(原子性、一致性、隔离性、持久性)
原子性:表示组成一个事务的多个数据库操作是一个不可分割的整体,只有都操作成功,才提交事务,否则回滚事务,返回到初始状态。
一致性:事务操作成功后,数据库所处的状态和他的业务规则是一致的,即数据不会被破坏,如A转账100给B,A和B的总额是不变的。所有的特性都是为了保证数据库的一致性。
隔离性:在并发数据库操作时,不同的事务拥有各自的数据空间,他们的操作不会对对方产生干扰,准确的说,并不要求做到完全不干扰,数据库规定了多种数据库隔离级别(数据库锁),不同的隔离级别对应着不同的干扰程度,数据库隔离级别越高,数据库的一致性就越好,但并发性越弱。在web开发中,service层中的操作,往往组成一个事务,而多个线程对应着一份service代码,但是在操作数据库时,拥有着不同的数据库连接,但是事务提交后,可能操作同一张表的同一条记录,这时怎么保证数据库的并发问题,就是数据库隔离级别的设置问题了。
持久性
数据库管理一般采用重执行日志保证原子性、一致性和持久性。
26.数据库并发问题
一个数据库可能拥有多个访问客户端,这些客户端可能并发的访问数据库,数据库中的相同数据可能同时被多个事务访问,如果没有采取隔离措施(加锁),就会产生数据的不一致问题,这些为题归结为五类,3类数据读的问题(脏读、不可重复读和幻想读)和2类数据更新问题(第一类丢失更新问题和第二类丢失更新问题)。
脏读:当A读取了B尚未提交的更改数据后(脏读),B事务又回滚了,而A依旧按照读取的值更改数据库。(A事务读取了B事务尚未提交的更改数据)
不可重复读:是指A事务读取了数据库的一个值,B事务又把这个值更改并提交了,A再读这个数据时,两次读取的不一样。(A事务读取了B事务已经提交的更改数据)
幻想读:A事务读取了数据库中的一个值,B事务又对这个值新增了,事务A再次读这个值时,产生了幻想。(A事务读取B事务提交的新增数据)
第一类丢失更新:A开启事务并查询余额为1000,B也开启事务并将存入100后提交事务,此时A事务回滚,将余额改为1000元,使得本来应该为1100,现在却变为了1000。(A事务撤销时,把已经提交的B事务的更新数据覆盖了)
第二类丢失更新:A开启事务并查询余额为1000元,B也开启事务并查询余额为1000,B事务取出100元并提交事务,此时A汇入100元,提交事务,使得余额为1100元,而实际应该为1000元。(A事务覆盖B事务已经提交的数据)。
数据库通过锁机制解决并发问题,数据库常用的5钟锁定,行共享锁定、行独占锁定、表共享锁定、表独占锁定,表共享行独占锁定。
27.事务隔离级别
尽管数据库为用户提供了锁机制,但直接使用锁机制是比较麻烦的,因此数据库为用户提供了自动锁机制。只要用户指定会话的事务隔离级别,数据库就会分析sql语句,并自动加锁。有4个数据库隔离级别,在其他条件相同的情况下,不同的隔离级别可能导致不同的结果。Read UnCommited、Read Commited、 Repeatable Read、Serializable。推荐使用Repeatable Read,不过用户可以根据自身的情况自行选择。
28.JDBC对事务的支持
并不是所用的数据库都支持事务,支持事务的数据库也不一定支持全部的蛤蜊级别。Connection默认情况下是自动提交的,即每条sql都对应一个事务,为了能够将多条sql当成一个事务执行,必须先调用Connection#setAutoCommit(false),阻止自动提交,并通过Connection#setTransactionIsolation()设置事务的隔离级别,Connection中定义了四个隔离级别的常量,利用Connection#commit()提交事务,利用Connection#rollback()回滚事务。
29.ThreadLocal
Spring提供的模板类都是线程安全的,但是每个模板类都是访问数据库的,因此模板类应该有自己的数据库连接,但是又必须共享一个模板类,spring是怎么实现的呢?尽管模板类是通过连接池获取连接的,但是连接池解决的是数据连接的缓存问题,并没有解决线程安全问题,正常情况下我们应该通过加锁的方式实现,但是这样不仅实现难度大,而且效率也低,spring采用的是ThreadLocal机制实现的。
ThreadLocal通过为每个变量保存一个副本,即每个线程都有一个变量的副本,从而达到线程安全的目的。其与Thread同步机制的比较,ThreadLocal是以空间换时间,而Thread同步是以时间换空间。
一般的Web应用划分为展现层、服务层、持久层,在不同的层编写逻辑,下层为上层提供接口,在一般情况下,一次请求到响应为一个线程(实际上大多数web容器采用共享线程池)。
在相同线程中进行相互嵌套调用的事务方法工作于相同的事务中,如果这些相互嵌套调用的方法工作在不同的线程中,则不同线程下的事务方法工作在独立的事务中。通过DataSourceUtils类(spring提供的)可以获得和当前线程绑定的数据库连接,可以放心的使用,不用关心关闭的操作,但是如果从datasource中获取一个连接,则我们必须手动关闭,否则会导致连接泄露,甚至导致数据库崩溃。Spring为每个持久化技术提供了获取和当前线程绑定的数据库连接的类。
30.Spring对事务管理的支持
事务隔离:有四种,默认使用数据库提供的隔离级别
事务传播:当我们调用一个基于spring的service接口方法时,他会运行于spring的事务中,此方法可能又调用其他的service接口方法,因此就会产生事务方法产生嵌套的情况,spring传播行为就是规定发生嵌套时,事务如何传播。
1) Propagation_required:如果当前没有事务就新建一个事务,如果已经存在事务,则加入当前事务中。
2) Propagation_supports:支持当前事务,如果当前没有事务,就以非事务方式运行。
3) Propagation_required_new:新建事务,如果当前事务存在,则把当前事务挂起。
4) Propagation_not_supports:以非事务方式运行,如果当前存在事务,则挂起。
5) Propagation_never:以非事务方式运行,如果当前存在事务,则抛出异常。
6) Propagation_mandatory:使用当前的事务,如果当前没有事务,抛出异常
7) Propagation_nested:如果当前存在事务,则在嵌套事务中执行,如果当前没有事务,则执行与Propagation_required类似的操作。
和事务传播一起制定的还有isolation、readonly、rollbackfor和norollbackfor。
Spring为不同的持久化框架提供了不同的事务管理器实现类,如为jdbc和mybatis提供了DatasourceTransactionManager,在幕后,管理器使用DataSource的Connection的commit和rollback等方法管理事务。
事务同步管理器:spring将jdbc的connection、hibernate的session等访问数据库的连接或会话对象统称为资源,这些资源是不能够被线程共享的,为了让dao、service类做到singleton,spring的同步管理器通过ThreadLocal机制来实现。
31.Springdao和事务管理
使用spring,并不一定要使用spring的事务管理器,事务管理器是为了将几个sql当做一个整体去提交,如果不使用事务管理器,则每个sql都会自动提交。对于强调速度的应用,数据库本身就不支持事务,如使用MyISAM引擎的Mysql数据库。这时无需配置事务管理器,因为即使配置了,也没有。
32.Spring分层
在应用spring时,一般都分层为action、service、dao层,但是并非必须分成这3层,也可都写在action中,因为spring所提供的各种好处(aop、注解增强、注解MVC等)唯一前提就是让pojo类变成一个受spring容器管理的bean,初次之外没有任何要求。
33.SpringJDBC
如果既不想向JDBC原生API那样复杂的操作,又想向原生API那样灵活的操作数据库,那SpringJDBC是唯一的选择。SpringJDBC为我们提供的JDBCTemplate,是线程安全的,可以在dao层共享,非常方便。
34.数据库表主键产生的方式选择
Mysql提供了自增主键的功能,但是这种方式非常的不安全,当插入一条记录后,想要获取这条记录的主键,需要查询数据库,但是如果数据库的并发率要求很高时,可能已经在你插入一条记录之后,查询之前,已经又插入了几条记录了,此时你的查询结果就是错误的。因此这也是为什么Oracle故意不提供自增键,只提供序列的原因了,在插入一条记录之前,强制要求先获取主键。
35.批量更新比正常的逐条更新效率高,但是有些JDBC驱动不支持批量更新。
36.hibernate延迟加载的问题
Hibernate允许关联对象和属性进行延迟加载,但必须保证延迟加载的操作仅限于同一个hibernate session中,如果service层返回了一个启用了延迟加载的对象给web层,当web层想要加载那些延迟的对象时,由于session已经关闭,将会导致加载数据的异常。Spring专门为这种情况提供了一个OpenSessionInViewFilter过滤器,他的作用是使每个请求绑定一个session,及时事务提交了,在web层也可进行延迟加载的功能。对于小型应用,OpenSessionInViewFilter很有效,但是对应大型且高并发的应用,因其会让每个请求都绑定一个session(一个数据库连接),有些web根本不需要获取session,他也延长了每个web请求占用数据库连接的时长,我们知道,应用系统的瓶颈往往都出现在数据库上,而使用这种方式会更容易产生数据库瓶颈。如果要抛弃这种方式,就要设计好service层的代码,可以提供两种接口给web使用,其一是提供一个带延迟加载的接口,另一个不带。
37.Quartz
Quartz是任务调度领域享誉盛名的开源框架,包括调度器、任务和触发器这3个核心的概念。
Job:是一个接口,只有一个方法,execute(JobExecutionContext context), context中封装了调度上下文中的各种信息,Job运行时的信息保存在JobDataMap中。
JobDetail:Quartz在每次执行Job时,都需要根据我们实现Job接口的任务类,利用反射机制在运行期重新创建一个job实例,因此我们要传送一些关于我们定义的Job类的信息,从JobDetail的构造方法中就可以看得出来,JobDetail(name, group, jobClass),其中name和group是在scheduler中的组名和job名称。
Trigger:是一个类,描述job的时间出发规则,主要有SimpleTrigger和CronTrigger,当今需要触发一次或者以固定间隔周期的执行,最好用SimpleTrigger,而比较复杂的用CronTrigger。TriggerUtils是定义Trigger的便利类,而且提供了常见的Trigger。
Calendar:org.quarz. Calendar和java.util.Calendar不同,他是java日历的集合。一个trigger可以和多个Calendar关联,以排除或者包含某些时间点。Calendar是一个接口,有很多实现类。
Scheduler:是一个Quartz的独立运行容器,Trigger和JobDetail可以注册到Scheduler中,两者在Scheduler中拥有各自的组及名称,两者的组及名称必须唯一,以便定位,Scheduler可以将Trigger绑定到JobDetail中,一个JobDetail可以有多个Trigger,而一个Trigger只能对应一个JobDetail。
ThreadPool:Scheduler通过线程池调度任务,通过共享线程池,提高效率。
可以将运行时的数据保存到数据库中,只需要修改配置文件即可,默认情况下是在内存中的。
Quartz和spring结合,使得Quartz的job中可以访问applicationContext,也可在配置文件中指定包含job逻辑的类和方法,免得必须定义一个额外的job实现类,但是,这种方法不能持久化,要想持久化,必须单独定义一个实现了Job接口的类。同理也可以指定trigger和scheduler的bean。
37.spring中使用JDK Timer
JDKTimer中只能执行一些任务时间不长并且触发时机不能很复杂。TimerTask代表一个需要多次运行的任务,他实现了Runnable接口,可以在run方法中定义逻辑,而Timer负责指定调度规则并调度TimerTask。
TimerTask类似于job,但是job每次执行时都创建一个新的实例,而TimerTask每次执行同一个实例,因此当TimerTask中有状态时,对于下一个执行是可见的。每一个Timer对象对应一个背景线程,所有的TimerTask都在这个线程中执行,如果其中一个TimerTask执行时间很长,将会导致到了执行下次任务的时间了,而本次任务还未结束,造成扎堆的现象,Timer默认情况下,使用非后台线程,这样用户可以调用Time#cancel()方法结束线程。Spring提供了特定的bean用于配置。
38.web中的启动线程的说明
静态变量是classloader级别的,如果web应用程序停止,这些静态变量会从JVM中清除,但是线城是JVM级别的,如果用户在Web应用中启动一个线程,这个线程的生命周期并不会和web应用程序保持一致。所有不赞成在web程序中启动单独的线程,如果我们手工使用JDK Timer,在Web中启动Timer,当web关闭时,除非用户手工关闭,否则,这个线程还会继续运行。而spring为JDK Timer和QuartzScheduler提供的TimerFactoryBean和SchedulerFactoryBean能够和spring容器的生命周期联系在一起,一起开启、一起关闭。
39.属性编辑器
属性编辑器的主要功能就是将外部的值转化为JVM内部的类型,所以属性编辑器就是一个类型转换器。任何实现java.beans.propertyEditor接口的类都叫属性编辑器。
40.SpringMVC
从http请求到响应的整个过程中,springMVC的DispacheServlet都处于核心的位置,
->客户端发出一个http请求
->web服务器接受这个请求
->如果匹配DispacheServlet的拦截路径,交给DispacheServlet处理
将请求的信息(URL、HTTP方法、请求报文头、请求参数、cookie等)及Handlermapping的配置找到处理请求的处理器Handler
->通过HandlerAdapter对Handler进行封装再以同意的适配器接口调用Handler->处理器完成业务逻辑的处理后,返回一个ModelAndView给DispacheServlet->ModelAndView中包含的逻辑试图名并非真正的视图对象,借由ViewResover完成逻辑视图名到真实视图对象的解析
->当得到view后,用模型对视图进行渲染,springmvc对不同的媒体类型提供了不同的视图实现类。常见的jsp和jstl的视图解析器为InternalResourceViewResolver
->最终用户得到一个普通的html页面或者xml或者json或者图片或者pdf等不同的媒体类型。
Spring在webapplicationcontext启动之后,会自动执行initStrategies()(DispacheServlet中的方法),该方法通过反射机制查找并装配spring容器中用户显示定义的组件bean,如果找不到,就使用DispacheServlet.properties配置文件中定义的默认的解析器。这些解析器有本地化解析器、主题解析器、处理器映射器、处理器适配器、处理器异常解析器、视图名解析器、视图解析器。其中没有提供文件上传解析器,若需要,自己注册。
注解:
@RequestMapping(value=””, method=RequestMethod.POST,params=”userid”),其中params中的为url中必须包含的,method可选,params可选,value必填,params中如果有多个,则用params={“param1=value1”, “param2”}
@PathVariable(“userid”)String userid
@RequestParam(“username”)String username
@CookieValue(“sessionid”)String sessionid
@RequestHeader(“Accept-Language”)String language
注:如下的url可以自动封装到User对象中,url=/handle.html?username=tom&dept.deptid=1&dept.address.tel=103
也可使用HttpServletRequest和HttpServletResponse、HttpSession,OutputStream作为方法入参。在我们的代码中可以使用spring的便利类,Resource和FileCopyUtils等。