面试资料整理(三)

55、请谈谈 ReadWriteLock 和 StampedLock

ReadWriteLock包括两种子锁

(1)ReadWriteLock

ReadWriteLock 可以实现多个读锁同时进行,但是读与写和写与写互斥,只能有一个写锁线
程在进行。

(2)StampedLock

StampedLock是Jdk在1.8提供的一种读写锁,相比较ReentrantReadWriteLock性能更好,因
为ReentrantReadWriteLock在读写之间是互斥的,使用的是一种悲观策略,在读线程特别多
的情况下,会造成写线程处于饥饿状态,虽然可以在初始化的时候设置为true指定为公平,但
是吞吐量又下去了,而StampedLock是提供了一种乐观策略,更好的实现读写分离,并且吞
吐量不会下降。

StampedLock包括三种锁:

(1)写锁writeLock:

writeLock是一个独占锁写锁,当一个线程获得该锁后,其他请求读锁或者写锁的线程阻塞,
获取成功后,会返回一个stamp(凭据)变量来表示该锁的版本,在释放锁时调用
unlockWrite方法传递stamp参数。提供了非阻塞式获取锁tryWriteLock。

(2)悲观读锁readLock:
readLock是一个共享读锁,在没有线程获取写锁情况下,多个线程可以获取该锁。如果有写
锁获取,那么其他线程请求读锁会被阻塞。悲观读锁会认为其他线程可能要对自己操作的数
据进行修改,所以需要先对数据进行加锁,这是在读少写多的情况下考虑的。请求该锁成功
后会返回一个stamp值,在释放锁时调用unlockRead方法传递stamp参数。提供了非阻塞式获
取锁方法tryWriteLock。

(3)乐观读锁tryOptimisticRead:
tryOptimisticRead相对比悲观读锁,在操作数据前并没有通过CAS设置锁的状态,如果没有线
程获取写锁,则返回一个非0的stamp变量,获取该stamp后在操作数据前还需要调用validate
方法来判断期间是否有线程获取了写锁,如果是返回值为0则有线程获取写锁,如果不是0则

可以使用stamp变量的锁来操作数据。由于tryOptimisticRead并没有修改锁状态,所以不需要
释放锁。这是读多写少的情况下考虑的,不涉及CAS操作,所以效率较高,在保证数据一致性
上需要复制一份要操作的变量到方法栈中,并且在操作数据时可能其他写线程已经修改了数
据,而我们操作的是方法栈里面的数据,也就是一个快照,所以最多返回的不是最新的数
据,但是一致性得到了保证。

56、线程的run()和start()有什么区别?

每个线程都是通过某个特定Thread对象所对应的方法run()来完成其操作的,
run()方法称为线程体。
通过调用Thread类的start()方法来启动一个线程。

start() 方法用于启动线程,run() 方法用于执行线程的运行时代码。
run() 可以重复调用,而start() 只能调用一次。

start()方法来启动一个线程,真正实现了多线程运行。
调用start()方法无需等待run方法体代码执行完毕,可以直接继续执行其他的代码;此时线程是处于就绪状态,并没有运行。
然后通过此Thread类调用方法run()来完成其运行状态, run()方法运行结束, 此线程终止。
然后CPU再调度其它线程。
run()方法是在本线程里的,只是线程里的一个函数,而不是多线程的。如果直接调用run(),
其实就相当于是调用了一个普通函数而已,直接用run()方法必须等待run()方法执行完毕才能
执行下面的代码,所以执行路径还是只有一条,根本就没有线程的特征,所以在多线程执行
时要使用start()方法而不是run()方法。

57、为什么我们调用 start() 方法时会执行 run() 方法,为什么我们不能
直接调用 run() 方法?

这是另一个非常经典的 java 多线程面试问题,而且在面试中会经常被问到。很简单,但是很
多人都会答不上来!

new 一个 Thread,线程进入了新建状态。
调用 start() 方法,会启动一个线程并使线程进入
了就绪状态,当分配到时间片后就可以开始运行了。
start() 会执行线程的相应准备工作,然后自动执行 run() 方法的内容,这是真正的多线程工作。
而直接执行 run() 方法,会把 run 方法当成一个 main 线程下的普通方法去执行,并不会在某
个线程中执行它,所以这并不是多线程工作。

总结:调用 start 方法方可启动线程并使线程进入就绪状态,而 run 方法只是 thread 的一个
普通方法调用,还是在主线程里执行。

58、Synchronized 用过吗,其原理是什么?

1)可重入性

synchronized的锁对象中有一个计数器(recursions变量)会记录线程获得几次锁;

可重入的好处:

  1. 可以避免死锁;
  2. 可以让我们更好的封装代码;

synchronized是可重入锁,每部锁对象会有一个计数器记录线程获取几次锁,在执行完同步
代码块时,计数器的数量会-1,直到计数器的数量为0,就释放这个锁。

2)不可中断性

  1. 一个线程获得锁后,另一个线程想要获得锁,必须处于阻塞或等待状态,如果第一个线程
    不释放锁,第二个线程会一直阻塞或等待,不可被中断;

  2. synchronized 属于不可被中断;

  3. Lock lock方法是不可中断的;

  4. Lock tryLock方法是可中断的;

59、JVM对Java的原生锁做了哪些优化?

1)自旋锁
在线程进行阻塞的时候,先让线程自旋等待一段时间,可能这段时间其它线程已经解锁,这
时就无需让线程再进行阻塞操作了。
自旋默认次数是10次。

2)自适应自旋锁
自旋锁的升级,自旋的次数不再固定,由前一次自旋次数和锁的拥有者的状态决定。

3)锁消除
在动态编译同步代码块的时候,JIT编译器借助逃逸分析技术来判断锁对象是否只被一个线程
访问,而没有其他线程,这时就可以取消锁了。

4)锁粗化
当JIT编译器发现一系列的操作都对同一个对象反复加锁解锁,甚至加锁操作出现在循环中,
此时会将加锁同步的范围粗化到整个操作系列的外部。
锁粒度:不要锁住一些无关的代码。
锁粗化:可以一次性执行完的不要多次加锁执行。

60、为什么 wait(), notify()和 notifyAll()必须在同步方法或者同步块中被调用?

Java中,任何对象都可以作为锁,并且 wait(),notify()等方法用于等待对象的锁或者唤醒线
程,在 Java 的线程中并没有可供任何对象使用的锁,所以任意对象调用方法一定定义在
Object类中。
wait(), notify()和 notifyAll()这些方法在同步代码块中调用
有的人会说,既然是线程放弃对象锁,那也可以把wait()定义在Thread类里面啊,新定义的线
程继承于Thread类,也不需要重新定义wait()方法的实现。然而,这样做有一个非常大的问
题,一个线程完全可以持有很多锁,你一个线程放弃锁的时候,到底要放弃哪个锁?当然
了,这种设计并不是不能实现,只是管理起来更加复杂。

综上所述,wait()、notify()和notifyAll()方法要定义在Object类中。

61、Java 如何实现多线程之间的通讯和协作?

可以通过中断和共享变量的方式实现线程间的通讯和协作。
比如说最经典的生产者-消费者模型:当队列满时,生产者需要等待队列有空间才能继续往里
面放入商品,而在等待的期间内,生产者必须释放对临界资源(即队列)的占用权。因为生
产者如果不释放对临界资源的占用权,那么消费者就无法消费队列中的商品,就不会让队列
有空间,那么生产者就会一直无限等待下去。因此,一般情况下,当队列满时,会让生产者
交出对临界资源的占用权,并进入挂起状态。然后等待消费者消费了商品,然后消费者通知
生产者队列有空间了。同样地,当队列空时,消费者也必须等待,等待生产者通知它队列中
有商品了。这种互相通信的过程就是线程间的协作。

Java中线程通信协作的最常见的两种方式:

  1. syncrhoized加锁的线程的Object类的wait()/notify()/notifyAll()
  2. ReentrantLock类加锁的线程的Condition类的await()/signal()/signalAll()

线程间直接的数据交换:

通过管道进行线程间通信:
1)字节流;
2)字符流

62、Thread 类中的 yield 方法有什么作用?

yield()应该做的是让当前运行线程回到可运行状态,以允许具有相同优先级的其他线程获运行机会。
因此,使用yield()的目的是让相同优先级的线程之间能适当的轮转执行。
但是,实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。

结论:yield()从未导致线程转到等待/睡眠/阻塞状态。在大多数情况下,yield()将导致线程
从运行状态转到可运行状态,但有可能没有效果。

63、为什么说 Synchronized 是非公平锁?

当锁被释放后,任何一个线程都有机会竞争得到锁,这样做的目的是提高效率,但缺点是可
能产生线程饥饿现象。

64、请谈谈 volatile 有什么特点,为什么它能保证变量对所有线程的可见性?

volatile只能作用于变量,保证了操作可见性和有序性,不保证原子性。

在Java的内存模型中分为主内存和工作内存,Java内存模型规定所有的变量存储在主内存中,
每条线程都有自己的工作内存。

主内存和工作内存之间的交互分为8个原子操作:

  1. lock
  2. unlock
  3. read
  4. load
  5. assign
  6. use
  7. store
  8. write

volatile修饰的变量,只有对volatile进行assign操作,才可以load,只有load才可以use,这
样就保证了在工作内存操作volatile变量,都会同步到主内存中。

65、为什么说 Synchronized 是一个悲观锁?乐观锁的实现原理又是什么?什么是 CAS,它有什么特性?

Synchronized的并发策略是悲观的,不管是否产生竞争,任何数据的操作都必须加锁。
乐观锁的核心是CAS,CAS包括内存值、预期值、新值,只有当内存值等于预期值时,才会将内存值修改为新值。

66、乐观锁一定就是好的吗?

乐观锁认为对一个对象的操作不会引发冲突,所以每次操作都不进行加锁,只是在最后提交
更改时验证是否发生冲突,如果冲突则再试一遍,直至成功为止,这个尝试的过程称为自旋。

乐观锁没有加锁,但乐观锁引入了ABA问题,此时一般采用版本号进行控制;
也可能产生自旋次数过多问题,此时并不能提高效率,反而不如直接加锁的效率高;
只能保证一个对象的原子性,可以封装成对象,再进行CAS操作;

67、请尽可能详尽地对比下 Synchronized 和 ReentrantLock 的异同。

1)相似点

它们都是阻塞式的同步,也就是说一个线程获得了对象锁,进入代码块,其它访问该同步块
的线程都必须阻塞在同步代码块外面等待,而进行线程阻塞和唤醒的代码是比较高的。

2)功能区别

Synchronized是java语言的关键字,是原生语法层面的互斥,需要JVM实现;
ReentrantLock是JDK1.5之后提供的API层面的互斥锁,需要lock和unlock()方法配合try/finally代码块来完成。
Synchronized使用较ReentrantLock 便利一些;锁的细粒度和灵活性:ReentrantLock强于Synchronized;

3)性能区别
Synchronized引入偏向锁,自旋锁之后,两者的性能差不多,在这种情况下,官方建议使用Synchronized。

① Synchronized
Synchronized会在同步块的前后分别形成monitorenter和monitorexit两个字节码指令。
在执行monitorenter指令时,首先要尝试获取对象锁。如果这个对象没被锁定,或者当前线
程已经拥有了那个对象锁,把锁的计数器+1,相应的执行monitorexit时,计数器-1,当计数
器为0时,锁就会被释放。如果获取锁失败,当前线程就要阻塞,直到对象锁被另一个线程释
放为止。

② ReentrantLock
ReentrantLock是java.util.concurrent包下提供的一套互斥锁,相比Synchronized,

ReentrantLock类提供了一些高级功能,主要有如下三项:
等待可中断,持有锁的线程长期不释放的时候,正在等待的线程可以选择放弃等待,这相当
于Synchronized避免出现死锁的情况。通过lock.lockInterruptibly()来实现这一机制;公平
锁,多个线程等待同一个锁时,必须按照申请锁的时间顺序获得锁,Synchronized锁是非公
平锁;ReentrantLock默认也是非公平锁,可以通过参数true设为公平锁,但公平锁表现的性
能不是很好;锁绑定多个条件,一个ReentrantLock对象可以同时绑定多个对象。
ReentrantLock提供了一个Condition(条件)类,用来实现分组唤醒需要唤醒的线程们,而不
是像Synchronized要么随机唤醒一个线程,要么唤醒全部线程。

68、ReentrantLock 是如何实现可重入性的?

1)什么是可重入性
一个线程持有锁时,当其他线程尝试获取该锁时,会被阻塞;而这个线程尝试获取自己持有
锁时,如果成功说明该锁是可重入的,反之则不可重入。

2)synchronized是如何实现可重入性
synchronized关键字经过编译后,会在同步块的前后分别形成monitorenter和monitorexit两
个字节码指令。每个锁对象内部维护一个计数器,该计数器初始值为0,表示任何线程都可以
获取该锁并执行相应的方法。根据虚拟机规范要求,在执行monitorenter指令时,首先要尝
试获取对象的锁,如果这个对象没有被锁定,或者当前线程已经拥有了对象的锁,把锁的计
数器+1,相应的在执行monitorexit指令后锁计数器-1,当计数器为0时,锁就被释放。如果获
取对象锁失败,那当前线程就要阻塞等待,直到对象锁被另一个线程释放为止。

3)ReentrantLock如何实现可重入性

ReentrantLock使用内部类Sync来管理锁,所以真正的获取锁是由Sync的实现类控制的。
Sync有两个实现,分别为NonfairSync(非公公平锁)和FairSync(公平锁)。
Sync通过继承AQS实现,在AQS中维护了一个private volatile int state来计算重入次数,避免频繁的持有释放操作带来的线程问题。

4)ReentrantLock代码实例

// Sync继承于AQS
abstract static class Sync extends AbstractQueuedSynchronizer {
...
}
// ReentrantLock默认是非公平锁
public ReentrantLock() {
sync = new NonfairSync();
}
// 可以通过向构造方法中传true来实现公平锁
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
protected final boolean tryAcquire(int acquires) {
// 当前想要获取锁的线程
final Thread current = Thread.currentThread();
// 当前锁的状态
int c = getState();
// state == 0 此时此刻没有线程持有锁
if (c == 0) {
// 虽然此时此刻锁是可以用的,但是这是公平锁,既然是公平,就得讲究先来后到,
// 看看有没有别人在队列中等了半天了
if (!hasQueuedPredecessors() &&
// 如果没有线程在等待,那就用CAS尝试一下,成功了就获取到锁了,
// 不成功的话,只能说明一个问题,就在刚刚几乎同一时刻有个线程抢先了 =_=
// 因为刚刚还没人的,我判断过了
compareAndSetState(0, acquires)) {
// 到这里就是获取到锁了,标记一下,告诉大家,现在是我占用了锁
setExclusiveOwnerThread(current);
return true;
}
}
// 会进入这个else if分支,说明是重入了,需要操作:state=state+1
// 这里不存在并发问题
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
// 如果到这里,说明前面的if和else if都没有返回true,说明没有获取到锁
return false;
}

5)代码分析
当一个线程在获取锁过程中,先判断state的值是否为0,如果是表示没有线程持有锁,就可以
尝试获取锁。当state的值不为0时,表示锁已经被一个线程占用了,这时会做一个判断
current==getExclusiveOwnerThread(),这个方法返回的是当前持有锁的线程,这个判断是
看当前持有锁的线程是不是自己,如果是自己,那么将state的值+1,表示重入返回即可。

69、什么是锁消除和锁粗化?

1)锁消除
所消除就是虚拟机根据一个对象是否真正存在同步情况,若不存在同步情况,则对该对象的访问无需经过加锁解锁的操作。

比如StringBuffer的append方法,因为append方法需要判断对象是否被占用,而如果代码不存在锁竞争,那么这部分的性能消耗是无意的。

于是虚拟机在即时编译的时候就会将上面的代码进行优化,也就是锁消除。

@Override
public synchronized StringBuffer append(String str) {
toStringCache = null;
super.append(str);
return this;
}

从源码可以看出,append方法用了 synchronized关键字,它是线程安全的。但我们可能仅在
线程内部把StringBuffer当做局部变量使用;StringBuffer仅在方法内作用域有效,不存在线
程安全的问题,这时我们可以通过编译器将其优化,将锁消除,前提是Java必须运行在server
模式,同时必须开启逃逸分析;

-server -XX:+DoEscapeAnalysis -XX:+EliminateLocks

其中+DoEscapeAnalysis表示开启逃逸分析,+EliminateLocks表示锁消除。

public static String createStringBuffer(String str1, String str2) {
StringBuffer sBuf = new StringBuffer();
sBuf.append(str1);// append方法是同步操作
sBuf.append(str2);
return sBuf.toString();
}

逃逸分析:比如上面的代码,它要看sBuf是否可能逃出它的作用域?如果将sBuf作为方法的
返回值进行返回,那么它在方法外部可能被当作一个全局对象使用,就有可能发生线程安全
问题,这时就可以说sBuf这个对象发生逃逸了,因而不应将append操作的锁消除,但我们上
面的代码没有发生锁逃逸,锁消除就可以带来一定的性能提升。

2)锁粗化
锁的请求、同步、释放都会消耗一定的系统资源,如果高频的锁请求反而不利于系统性能的
优化,锁粗化就是把多次的锁请求合并成一个请求,扩大锁的范围,降低锁请求、同步、释
放带来的性能损耗。

70、跟Synchronized相比,可重入锁ReentrantLock其实现原理有什么不同?

1)都是可重入锁;
2)ReentrantLock内部是实现了Sync,Sync继承于AQS抽象类。
Sync有两个实现,一个是公平锁,一个是非公平锁,通过构造函数定义。
AQS中维护了一个state来计算重入次数,避免频繁的持有释放操作带来的线程问题。
3)ReentrantLock只能定义代码块,而Synchronized可以定义方法和代码块;
4)Synchronized是JVM的一个内部关键字,ReentrantLock是JDK1.5之后引入的一个API层面的互斥锁;
5)Synchronized实现自动的加锁、释放锁,ReentrantLock需要手动加锁和释放锁,中间可以暂停;
6)Synchronized由于引进了偏向锁和自旋锁,所以性能上和ReentrantLock差不多,但操作上方便很多,所以优先使用Synchronized。

71、那么请谈谈 AQS 框架是怎么回事儿?

1)AQS是AbstractQueuedSynchronizer的缩写,它提供了一个FIFO队列,可以看成是一个实现同步锁的核心组件。
AQS是一个抽象类,主要通过继承的方式来使用,它本身没有实现任何的同步接口,仅仅是定义了同步状态的获取和释放的方法来自义
的同步组件。
2)AQS的两种功能:独占锁和共享锁
3)AQS的内部实现
AQS的实现依赖内部的同步队列,也就是FIFO的双向队列,如果当前线程竞争失败,那么AQS
会把当前线程以及等待状态信息构造成一个Node加入到同步队列中,同时再阻塞该线程。当
获取锁的线程释放锁以后,会从队列中唤醒一个阻塞的节点(线程)。
AQS队列内部维护的是一个FIFO的双向链表,这种结构的特点是每个数据结构都有两个指针,
分别指向直接的后继节点和直接前驱节点。所以双向链表可以从任意一个节点开始很方便的
访问前驱和后继节点。每个Node其实是由线程封装,当线程争抢锁失败后会封装成Node加入到AQS队列中。

72、AQS 对资源的共享方式?

AQS定义两种资源共享方式

1)Exclusive(独占)
只有一个线程能执行,如ReentrantLock。又可分为公平锁和非公平锁:
公平锁:按照线程在队列中的排队顺序,先到者先拿到锁
非公平锁:当线程要获取锁时,无视队列顺序直接去抢锁,谁抢到就是谁的

2)Share(共享)
多个线程可同时执行,如Semaphore/CountDownLatch。Semaphore、CountDownLatch、
CyclicBarrier、ReadWriteLock 我们都会在后面讲到。
ReentrantReadWriteLock 可以看成是组合式,因为ReentrantReadWriteLock也就是读写锁允
许多个线程同时对某一资源进行读。
不同的自定义同步器争用共享资源的方式也不同。自定义同步器在实现时只需要实现共享资
源 state 的获取与释放方式即可,至于具体线程等待队列的维护(如获取资源失败入队/唤醒
出队等),AQS已经在顶层实现好了。

73、如何让 Java 的线程彼此同步?

  1. synchronized
  2. volatile
  3. ReenreantLock
  4. 使用局部变量实现线程同步

74、你了解过哪些同步器?请分别介绍下。

1)Semaphore同步器
特征:
经典的信号量,通过计数器控制对共享资源的访问 Semaphore(int count):创建拥有count个许
可证的信号量 acquire()/acquire(int num) : 获取1/num个许可证 release/release(int num) :
释放1/num个许可证

2)CountDownLatch同步器
特征:
必须发生指定数量的事件后才可以继续运行(比如赛跑比赛,裁判喊出3,2,1之后大家才同时
跑) CountDownLatch(int count):必须发生count个数量才可以打开锁存器
await:等待锁存器
countDown:触发事件

3)CyclicBarrier同步器
特征:
适用于只有多个线程都到达预定点时才可以继续执行(比如斗地主,需要等齐三个人才开始)
CyclicBarrier(int num) :等待线程的数量
CyclicBarrier(int num, Runnable action) :等待线程的数量以及所有线程到达后的操作
await() : 到达临界点后暂停线程

4)交换器(Exchanger)同步器
5)Phaser同步器

75、Java 中的线程池是如何实现的

创建一个阻塞队列来容纳任务,在第一次执行任务时创建足够多的线程,并处理任务,之后
每个工作线程自动从任务队列中获取线程,直到任务队列中任务为0为止,此时线程处于等待
状态,一旦有工作任务加入任务队列中,即刻唤醒工作线程进行处理,实现线程的可复用性。

线程池一般包括四个基本组成部分:

1)线程池管理器
用于创建线程池,销毁线程池,添加新任务。
2)工作线程
线程池中线程,可循环执行任务,在没有任务时处于等待状态。
3)任务队列
用于存放没有处理的任务,一种缓存机制。
4)任务接口
每个任务必须实现的接口,供工作线程调度任务的执行,主要规定了任务的开始和收尾工作,和任务的状态。

76、创建线程池的几个核心构造参数

// Java线程池的完整构造函数

public ThreadPoolExecutor(
int corePoolSize, // 线程池长期维持的最小线程数,即使线程处于Idle状态,也不会回收。
int maximumPoolSize, // 线程数的上限
long keepAliveTime, // 线程最大生命周期。
TimeUnit unit, //时间单位
BlockingQueue workQueue, //任务队列。当线程池中的线程都处于运行状态,而此时任务数量继续增
ThreadFactory threadFactory, // 线程工厂。定义如何启动一个线程,可以设置线程名称,并且可以确认是否是

RejectedExecutionHandler handler // 拒绝任务处理器。由于超出线程数量和队列容量而对继续增加的任务进行)

77、线程池中的线程是怎么创建的?是一开始就随着线程池的启动创建好的吗?

线程池中的线程是在第一次提交任务submit时创建的
创建线程的方式有继承Thread和实现Runnable,重写run方法,start开始执行,wait等待,

1)newSingleThreadExecutor:单线程池。
顾名思义就是一个池中只有一个线程在运行,该线程永不超时,而且由于是一个线程,当有
多个任务需要处理时,会将它们放置到一个无界阻塞队列中逐个处理,它的实现代码如下:

public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable()));
}

它的使用方法也很简单,下面是简单的示例:

public static void main(String[] args) throws ExecutionException,InterruptedException {
// 创建单线程执行器
ExecutorService es = Executors.newSingleThreadExecutor();
// 执行一个任务
Future<String> future = es.submit(new Callable<String>() {
@Override
public String call() throws Exception {
return "";
}
});
// 获得任务执行后的返回值
System.out.println("返回值:" + future.get());
// 关闭执行器
es.shutdown();
}

2)newCachedThreadPool:缓冲功能的线程。
建立了一个线程池,而且线程数量是没有限制的(当然,不能超过Integer的最大值),新增一
个任务即有一个线程处理,或者复用之前空闲的线程,或者重亲启动一个线程,但是一旦一
个线程在60秒内一直处于等待状态时(也就是一分钟无事可做),则会被终止,其源码如下:

public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}

这里需要说明的是,任务队列使用了同步阻塞队列,这意味着向队列中加入一个元素,即可
唤醒一个线程(新创建的线程或复用空闲线程来处理),这种队列已经没有队列深度的概念了。

3)newFixedThreadPool:固定线程数量的线程池。

在初始化时已经决定了线程的最大数量,若任务添加的能力超出了线程的处理能力,则建立
阻塞队列容纳多余的任务,其源码如下:

public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}

上面返回的是一个ThreadPoolExecutor,它的corePoolSize和maximumPoolSize是相等的,
也就是说,最大线程数量为nThreads。如果任务增长的速度非常快,超过了
LinkedBlockingQuene的最大容量(Integer的最大值),那此时会如何处理呢?会按照
ThreadPoolExecutor默认的拒绝策略(默认是DiscardPolicy,直接丢弃)来处理。

以上三种线程池执行器都是ThreadPoolExecutor的简化版,目的是帮助开发人员屏蔽过得线
程细节,简化多线程开发。当需要运行异步任务时,可以直接通过Executors获得一个线程
池,然后运行任务,不需要关注ThreadPoolExecutor的一系列参数时什么含义。当然,有时
候这三个线程不能满足要求,此时则可以直接操作ThreadPoolExecutor来实现复杂的多线程计算。

newSingleThreadExecutor、newCachedThreadPool、newFixedThreadPool是线程池的简化
版,而ThreadPoolExecutor则是旗舰版___简化版容易操作,需要了解的知识相对少些,方便
使用,而旗舰版功能齐全,适用面广,难以驾驭。

78、volatile 关键字的作用

对于可见性,Java提供了volatile关键字来保证可见性和禁止指令重排。volatile 提供
happens-before 的保证,确保一个线程的修改能对其他线程是可见的。
当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去
内存中读取新值。

从实践角度而言,volatile 的一个重要作用就是和 CAS 结合,保证了原子性,详细的可以参见
java.util.concurrent.atomic 包下的类,比如 AtomicInteger。
volatile常用于多线程环境下的单次操作(单次读或者单次写)。

79、既然volatile能够保证线程间的变量可见性,是不是就意味着基于volatile变量的运算就是并发安全的?

volatile修饰的变量在各个线程的工作内存中不存在一致性的问题(在各个线程工作的内存
中,volatile修饰的变量也会存在不一致的情况,但是由于每次使用之前都会先刷新主存中的
数据到工作内存,执行引擎看不到不一致的情况,因此可以认为不存在不一致的问题),但
是java的运算并非原子性的操作,导致volatile在并发下并非是线程安全的。

80、ThreadLocal 是什么?有哪些使用场景?

ThreadLocal 是一个本地线程副本变量工具类,在每个线程中都创建了一个ThreadLocalMap
对象,简单说 ThreadLocal就是一种以空间换时间的做法,每个线程可以访问自己内部
ThreadLocalMap对象内的value。通过这种方式,避免资源在多线程间共享。

原理:线程局部变量是局限于线程内部的变量,属于线程自身所有,不在多个线程间共享。
Java提供ThreadLocal类来支持线程局部变量,是一种实现线程安全的方式。但是在管理环境
下(如 web 服务器)使用线程局部变量的时候要特别小心,在这种情况下,工作线程的生命
周期比任何应用变量的生命周期都要长。任何线程局部变量一旦在工作完成后没有释放,Java
应用就存在内存泄露的风险。
经典的使用场景是为每个线程分配一个 JDBC 连接 Connection。这样就可以保证每个线程的
都在各自的 Connection 上进行数据库的操作,不会出现 A 线程关了 B线程正在使用的Connection;
还有 Session 管理 等问题。

81、请谈谈ThreadLocal是怎么解决并发安全的?

在java程序中,常用的有两种机制来解决多线程并发问题,一种是sychronized方式,通过锁
机制,一个线程执行时,让另一个线程等待,是以时间换空间的方式来让多线程串行执行。
而另外一种方式就是ThreadLocal方式,通过创建线程局部变量,以空间换时间的方式来让多
线程并行执行。两种方式各有优劣,适用于不同的场景,要根据不同的业务场景来进行选择。

在spring的源码中,就使用了ThreadLocal来管理连接,在很多开源项目中,都经常使用
ThreadLocal来控制多线程并发问题,因为它足够的简单,我们不需要关心是否有线程安全问
题,因为变量是每个线程所特有的。

82、很多人都说要慎用ThreadLocal,谈谈你的理解,使用ThreadLocal需要注意些什么?

ThreadLocal 变量解决了多线程环境下单个线程中变量的共享问题,使用名为
ThreadLocalMap的哈希表进行维护(key为ThreadLocal变量名,value为ThreadLocal变量的值);

使用时需要注意以下几点:

线程之间的threadLocal变量是互不影响的,

使用private final static进行修饰,防止多实例时内存的泄露问题

线程池环境下使用后将threadLocal变量remove掉或设置成一个初始值

83、为什么代码会重排序?

在执行程序时,为了提供性能,处理器和编译器常常会对指令进行重排序,但是不能随意重
排序,不是你想怎么排序就怎么排序,它需要满足以下两个条件:
在单线程环境下不能改变程序运行的结果;存在数据依赖关系的不允许重排序 需要注意的
是:重排序不会影响单线程环境的执行结果,但是会破坏多线程的执行语义。

84、什么是自旋?

很多 synchronized 里面的代码只是一些很简单的代码,执行时间非常快,此时等待的线程都
加锁可能是一种不太值得的操作,因为线程阻塞涉及到用户态和内核态切换的问题。既然
synchronized 里面的代码执行得非常快,不妨让等待锁的线程不要被阻塞,而是在
synchronized 的边界做忙循环,这就是自旋。如果做了多次循环发现还没有获得锁,再阻
塞,这样可能是一种更好的策略。

85、多线程中synchronized锁升级的原理是什么?

synchronized 锁升级原理:在锁对象的对象头里面有一个 threadid 字段,在第一次访问的时
候 threadid 为空,jvm 让其持有偏向锁,并将 threadid 设置为其线程 id,再次进入的时候会
先判断 threadid 是否与其线程 id 一致,如果一致则可以直接使用此对象,如果不一致,则升
级偏向锁为轻量级锁,通过自旋循环一定次数来获取锁,执行一定次数之后,如果还没有正
常获取到要使用的对象,此时就会把锁从轻量级升级为重量级锁,此过程就构成了synchronized 锁的升级。

锁的升级的目的:锁升级是为了减低了锁带来的性能消耗。在 Java 6 之后优化 synchronized
的实现方式,使用了偏向锁升级为轻量级锁再升级到重量级锁的方式,从而减低了锁带来的
性能消耗。

86、synchronized 和 ReentrantLock区别是什么?

synchronized 是和 if、else、for、while 一样的关键字,ReentrantLock 是类,这是二者的本
质区别。既然 ReentrantLock 是类,那么它就提供了比synchronized 更多更灵活的特性,可
以被继承、可以有方法、可以有各种各样的类变量
synchronized 早期的实现比较低效,对比 ReentrantLock,大多数场景性能都相差较大,但
是在 Java 6 中对 synchronized 进行了非常多的改进。

相同点:两者都是可重入锁

两者都是可重入锁。“可重入锁”概念是:自己可以再次获取自己的内部锁。比如一个线程获得
了某个对象的锁,此时这个对象锁还没有释放,当其再次想要获取这个对象的锁的时候还是
可以获取的,如果不可锁重入的话,就会造成死锁。同一个线程每次获取锁,锁的计数器都
自增1,所以要等到锁的计数器下降为0时才能释放锁。

主要区别如下:

ReentrantLock 使用起来比较灵活,但是必须有释放锁的配合动作;
ReentrantLock 必须手动获取与释放锁,而synchronized不需要手动释放和开启锁;
ReentrantLock只适用于代码块锁,而synchronized可以修饰类、方法、变量等。
二者的锁机制其实也是不一样的。ReentrantLock底层调用的是Unsafe的park方法加
锁,synchronized 操作的应该是对象中 mark word。

Java中每一个对象都可以作为锁,这是synchronized实现同步的基础:
普通同步方法,锁是当前实例对象
静态同步方法,锁是当前类的class对象
同步方法块,锁是括号里面的对象

87、Java Concurrency API 中的Lock接口(Lock interface)是什么?对比同步它有什么优势?

Lock 接口比同步方法和同步块提供了更具扩展性的锁操作。他们允许更灵活的结构,可以具
有完全不同的性质,并且可以支持多个相关类的条件对象。

它的优势有:

1)可以使锁更公平
2)可以使线程在等待锁的时候响应中断
3)可以让线程尝试获取锁,并在无法获取锁的时候立即返回或者等待一段时间
4)可以在不同的范围,以不同的顺序获取和释放锁

整体上来说 Lock 是 synchronized 的扩展版,Lock 提供了无条件的、可轮询的(tryLock 方法)、
定时的(tryLock 带参方法)、可中断的(lockInterruptibly)、可多条件队列的(newCondition 方法)锁操作。

另外 Lock 的实现类基本都支持非公平锁(默认)和公平锁,
synchronized 只支持非公平锁,当然,在大部分情况下,非公平锁是高效的选择。

88、jsp 和 servlet 有什么区别?

1)servlet是服务器端的Java程序,它担当客户端和服务端的中间层。

2)jsp全名为Java server pages,中文名叫Java服务器页面,其本质是一个简化的servlet设计。
JSP是一种动态页面设计,它的主要目的是将表示逻辑从servlet中分离出来。

3)JVM只能识别Java代码,不能识别JSP,JSP编译后变成了servlet,web容器将JSP的代码
编译成JVM能够识别的Java类(servlet)。

4)JSP有内置对象、servlet没有内置对象。

89、jsp 有哪些内置对象?作用分别是什么?

JSP九大内置对象:

  1. pageContext,页面上下文对象,相当于页面中所有功能的集合,
    通过它可以获取JSP页面的out、request、response、session、application对象。
  2. request
  3. response
  4. session
  5. application,应用程序对象,application实现了用户间数据的共享,可存放全局变量,
    它开始于服务器启动,直到服务器关闭。
  6. page,就是JSP本身。
  7. exception
  8. out,out用于在web浏览器内输出信息,并且管理应用服务器上的输出缓冲区,作用域page。
  9. config,取得服务器的配置信息。

90、forward 和 redirect 的区别?

  1. forward是直接请求转发;redirect是间接请求转发,又叫重定向。
  2. forward,客户端和浏览器执行一次请求;redirect,客户端和浏览器执行两次请求。
  3. forward,经典的MVC模式就是forward;redirect,用于避免用户的非正常访问。(例如
    用户非正常访问,servlet就可以将HTTP请求重定向到登录页面)。
  4. forward,地址不变;redirect,地址改变。
  5. forward常用方法:RequestDispatcher类的forward()方法;
  6. redirect常用方法:HttpServletRequest类的sendRedirect()方法。

91、说一下 jsp 的 4 种作用域?

application、session、request、page

92、session 和 cookie 有什么区别?

1)存储位置不同
cookie在客户端浏览器;
session在服务器;

2)存储容量不同
cookie<=4K,一个站点最多保留20个cookie;
session没有上限,出于对服务器的保护,session内不可存过多东西,并且要设置session删除机制;

3)存储方式不同

cookie只能保存ASCII字符串,并需要通过编码方式存储为Unicode字符或者二进制数据;
session中能存储任何类型的数据,包括并不局限于String、integer、list、map等;

4)隐私策略不同

cookie对客户端是可见的,不安全;
session存储在服务器上,安全;

(5)有效期不同

开发可以通过设置cookie的属性,达到使cookie长期有效的效果;
session依赖于名为JESSIONID的cookie,而cookie JSESSIONID的过期时间默认为-1,只需
关闭窗口该cookie就会失效,因而session达不到长期有效的效果;

6)跨域支持上不同

cookie支持跨域;
session不支持跨域;

93、如果客户端禁止cookie能实现session还能用吗?

一般默认情况下,在会话中,服务器存储 session 的 sessionid 是通过 cookie 存到浏览器里。

如果浏览器禁用了 cookie,浏览器请求服务器无法携带 sessionid,服务器无法识别请求中的
用户身份,session失效。

但是可以通过其他方法在禁用 cookie 的情况下,可以继续使用session。

通过url重写,把 sessionid 作为参数追加的原 url 中,后续的浏览器与服务器交互中携带sessionid 参数。
服务器的返回数据中包含 sessionid,浏览器发送请求时,携带 sessionid 参数。
通过 Http 协议其他 header 字段,服务器每次返回时设置该 header 字段信息,浏览器中
js 读取该 header 字段,请求服务器时,js设置携带该 header 字段。

94、什么是上下文切换?

多线程编程中一般线程的个数都大于 CPU 核心的个数,而一个 CPU 核心在任意时刻只能被一
个线程使用,为了让这些线程都能得到有效执行,CPU 采取的策略是为每个线程分配时间片
并轮转的形式。当一个线程的时间片用完的时候就会重新处于就绪状态让给其他线程使用,
这个过程就属于一次上下文切换。

概括来说就是:当前任务在执行完 CPU 时间片切换到另一个任务之前会先保存自己的状态,
以便下次再切换回这个任务时,可以再加载这个任务的状态。任务从保存到再加载的过程就
是一次上下文切换。

上下文切换通常是计算密集型的。也就是说,它需要相当可观的处理器时间,在每秒几十上
百次的切换中,每次切换都需要纳秒量级的时间。所以,上下文切换对系统来说意味着消耗
大量的 CPU 时间,事实上,可能是操作系统中时间消耗最大的操作。

Linux 相比与其他操作系统(包括其他类 Unix 系统)有很多的优点,其中有一项就是,其上
下文切换和模式切换的时间消耗非常少。

95、cookie、session、token

1、session机制

session是服务端存储的一个对象,主要用来存储所有访问过该服务端的客户端的用户信息
(也可以存储其他信息),从而实现保持用户会话状态。但是服务器重启时,内存会被销
毁,存储的用户信息也就消失了。
不同的用户访问服务端的时候会在session对象中存储键值对,“键”用来存储开启这个用户信
息的“钥匙”,在登录成功后,“钥匙”通过cookie返回给客户端,客户端存储为sessionId记录在
cookie中。当客户端再次访问时,会默认携带cookie中的sessionId来实现会话机制。

1)session是基于cookie的。

cookie的数据4k左右;
cookie存储数据的格式:字符串key=value
cookie存储有效期:可以自行通过expires进行具体的日期设置,如果没设置,默认是关闭浏览器时失效。
cookie有效范围:当前域名下有效。
所以session这种会话存储方式方式只适用于客户端代码和服务端代码运行在同一台服务器上
(前后端项目协议、域名、端口号都一致,即在个项目下)

2)session持久化

用于解决重启服务器后session消失的问题。在数据库中存储session,而不是存储在内存中。
通过包:express-mysql-session。
当客户端存储的cookie失效后,服务端的session不会立即销毁,会有一个延时,服务端会定
期清理无效session,不会造成无效数据占用存储空间的问题。

2、token机制

适用于前后端分离的项目(前后端代码运行在不同的服务器下)
请求登录时,token和sessionid原理相同,是对key和key对应的用户信息进行加密后的加密字
符,登录成功后,会在响应主体中将{token:“字符串”}返回给客户端。
客户端通过cookie都可以进行存储。再次请求时不会默认携带,需要在请求拦截器位置给请求
头中添加认证字段Authorization携带token信息,服务器就可以通过token信息查找用户登录
状态。

96、说一下 session 的工作原理?

当客户端登录完成后,会在服务端产生一个session,此时服务端会将sessionid返回给客户端
浏览器。

客户端将sessionid储存在浏览器的cookie中,当用户再次登录时,会获得对应的
sessionid,然后将sessionid发送到服务端请求登录,服务端在内存中找到对应的sessionid,
完成登录,如果找不到,返回登录页面。

97、http 响应码 301 和 302 代表的是什么?有什么区别?

301和302状态码都表示重定向,当浏览器拿到服务器返回的这个状态码后会自动跳转到一个新的URL地址。
301代表永久性重定向,旧地址被永久移除,客户端向新地址发送请求。
302代表暂时性重定向,旧地址还在,客户端继续向旧地址发送请求。

303代表暂时性重定向,重定向到新地址时,必须使用GET方法请求新地址。

307代表暂时性重定向,与302的区别在于307不允许从POST改为GET。
308代表永久性重定向,与301的区别在于308不允许从POST改为GET。

98、简述 tcp 和 udp的区别?

TCP是传输控制协议,UDP是用户数据表协议;
TCP长连接,UDP无连接;
UDP程序结构较简单,只需发送,无须接收;
TCP可靠,保证数据正确性、顺序性;UDP不可靠,可能丢数据;
TCP适用于少量数据,UDP适用于大量数据传输;
TCP速度慢,UDP速度快;

99、tcp 为什么要三次握手,两次不行吗?为什么?

因为客户端和服务端都要确认连接,
①客户端请求连接服务端;
②针对客户端的请求确认应答,并请求建立连接;
③针对服务端的请求确认应答,建立连接;

两次无法确保A能收到B的数据;

100、OSI 的七层模型都有哪些?

101、get 和 post 请求有哪些区别?

     get请求参数是连接在url后面的,
     而post请求参数是存放在requestbody内的;
     get请求因为浏览器对url长度有限制,所以参数个数有限制, 而post请求参数个数没有限制;
     因为get请求参数暴露在url上,所以安全方面post比get更加安全;
     get请求只能进行url编码,而post请求可以支持多种编码方式;
     get请求参数会保存在浏览器历史记录内,post请求并不会;
     get请求浏览器会主动cache,post并不会,除非主动设置;
     get请求产生1个tcp数据包,post请求产生2个tcp数据包;
     在浏览器进行回退操作时,get请求是无害的,而post请求则会重新请求一次;
     
     浏览器在发送get请求时会将header和data一起发送给服务器,服务器返回200状态码,
     在发送post请求时,会先将header发送给服务器,服务器返回100,之后再将data发送给服务
     器,服务器返回200 OK;

102、什么是 XSS 攻击,如何避免?

    xss(Cross Site Scripting),即跨站脚本攻击,是一种常见于web应用程序中的计算机安全漏
     洞。指的是在用户浏览器上,在渲染DOM树的时候,执行了不可预期的JS脚本,从而发生了
     安全问题。
     
     XSS就是通过在用户端注入恶意的可运行脚本,若服务端对用户的输入不进行处理,直接将用
     户的输入输出到浏览器,然后浏览器将会执行用户注入的脚本。所以XSS攻击的核心就是浏览

     器渲染DOM的时候将文本信息解析成JS脚本从而引发JS脚本注入,那么XSS攻击的防御手段就
     是基于浏览器渲染这一步去做防御。只要我们使用HTML编码将浏览器需要渲染的信息编码
     后,浏览器在渲染DOM元素的时候,会自动解码需要渲染的信息,将上述信息解析成字符串
     而不是JS脚本,这就是我们防御XSS攻击的核心想法。

预防:

   1、获取用户的输入,不用innerHtml,用innerText. 
    2、对用户的输入进行过滤,如对& < > " '/等进行转义;

103、什么是 CSRF 攻击,如何避免?

跨站请求伪造(英语:Cross-site request forgery),也被称为 one-click attack 或者 session
riding,通常缩写为 CSRF 或者 XSRF, 是一种挟制用户在当前已登录的Web应用程序上执行
非本意的操作的攻击方法。跟跨网站脚本(XSS)相比,XSS 利用的是用户对指定网站的信
任,CSRF 利用的是网站对用户网页浏览器的信任。

1、攻击细节

跨站请求攻击,简单地说,是攻击者通过一些技术手段欺骗用户的浏览器去访问一个自己曾
经认证过的网站并运行一些操作(如发邮件,发消息,甚至财产操作如转账和购买商品)。
由于浏览器曾经认证过,所以被访问的网站会认为是真正的用户操作而去运行。这利用了web
中用户身份验证的一个漏洞:简单的身份验证只能保证请求发自某个用户的浏览器,却不能
保证请求本身是用户自愿发出的。

例子

假如一家银行用以运行转账操作的URL地址如下: http://www.examplebank.com/withdraw?a
ccount=AccoutName&amount=1000&for=PayeeName
那么,一个恶意攻击者可以在另一个网站上放置如下代码:
如果有账户名为Alice的用户访问了恶意站点,而她之前刚访问过银行不久,登录信息尚未过
期,那么她就会损失1000资金。
这种恶意的网址可以有很多种形式,藏身于网页中的许多地方。此外,攻击者也不需要控制
放置恶意网址的网站。例如他可以将这种地址藏在论坛,博客等任何用户生成信息的网站
中。这意味着如果服务端没有合适的防御措施的话,用户即使访问熟悉的可信网站也有受攻
击的危险。

透过例子能够看出,攻击者并不能通过CSRF攻击来直接获取用户的账户控制权,也不能直接
窃取用户的任何信息。他们能做到的,是欺骗用户浏览器,让其以用户的名义运行操作。

2、防御措施

检查Referer字段

HTTP头中有一个Referer字段,这个字段用以标明请求来源于哪个地址。在处理敏感数据请求
时,通常来说,Referer字段应和请求的地址位于同一域名下。
以上文银行操作为例,Referer字段地址通常应该是转账按钮所在的网页地址,应该也位于 www.examplebank.com 之下。
而如果是CSRF攻击传来的请求,Referer字段会是包含恶意网址的地址,不会位于 www.example
bank.com 之下,这时候服务器就能识别出恶意的访问。

这种办法简单易行,工作量低,仅需要在关键访问处增加一步校验。但这种办法也有其局限
性,因其完全依赖浏览器发送正确的Referer字段。虽然http协议对此字段的内容有明确的规
定,但并无法保证来访的浏览器的具体实现,亦无法保证浏览器没有安全漏洞影响到此字
段。并且也存在攻击者攻击某些浏览器,篡改其Referer字段的可能。

3、添加校验token

由于CSRF的本质在于攻击者欺骗用户去访问自己设置的地址,所以如果要求在访问敏感数据
请求时,要求用户浏览器提供不保存在cookie中,并且攻击者无法伪造的数据作为校验,那么
攻击者就无法再运行CSRF攻击。这种数据通常是窗体中的一个数据项。服务器将其生成并附
加在窗体中,其内容是一个伪随机数。当客户端通过窗体提交请求时,这个伪随机数也一并

提交上去以供校验。正常的访问时,客户端浏览器能够正确得到并传回这个伪随机数,而通
过CSRF传来的欺骗性攻击中,攻击者无从事先得知这个伪随机数的值,服务端就会因为校验
token的值为空或者错误,拒绝这个可疑请求。

104、如何实现跨域?说一下 JSONP 实现原理?

1)jsonp原理详解——终于搞清楚jsonp是啥了

2)最流行的跨域方案cors

cors是目前主流的跨域解决方案,跨域资源共享(CORS) 是一种机制,它使用额外的 HTTP 头
来告诉浏览器 让运行在一个 origin (domain) 上的Web应用被准许访问来自不同源服务器上的
指定的资源。

当一个资源从与该资源本身所在的服务器不同的域、协议或端口请求一个资源时,资源会发起一个跨域 HTTP 请求。

3)最方便的跨域方案Nginx

nginx是一款极其强大的web服务器,其优点就是轻量级、启动快、高并发。
现在的新项目中nginx几乎是首选,我们用node或者java开发的服务通常都需要经过nginx的反向代理。

反向代理的原理很简单,即所有客户端的请求都必须先经过nginx的处理,nginx作为代理服务器再讲行请求转发给node或者java服务,
这样就规避了同源策略。

105、websocket应用的是哪个协议

WebSocket是一个允许Web应用程序(通常指浏览器)与服务器进行双向通信的协议。
HTML5的WebSocket API主要是为浏览器端提供了一个基于TCP协议实现全双工通信的方法。
WebSocket优势:浏览器和服务器只需要要做一个握手的动作,在建立连接之后,双方可以在
任意时刻,相互推送信息。

同时,服务器与客户端之间交换的头信息很小。

106、说一下 tcp 粘包是怎么产生的?

发送方需要等缓冲区满才能发送出去,造成粘包;
接收方不及时接收缓冲区的包,造成粘包;

107、请列举出在JDK中几个常用的设计模式?

1、单例模式
作用:保证类只有一个实例。
JDK中体现:Runtime类。

2、静态工厂模式
作用:代替构造函数创建对象,方法名比构造函数清晰。
JDK中体现:Integer.valueOf、Class.forName

3、抽象工厂
作用:创建某一种类的对象。
JDK中体现:Java.sql包。

4、原型模式
clone();
原型模式的本质是拷贝原型来创建新的对象,拷贝是比new更快的创建对象的方法,
当需要大批量创建新对象而且都是同一个类的对象的时候考虑使用原型模式。

  一般的克隆只是浅拷贝(对象的hash值不一样,但是对象里面的成员变量的hash值是一样的)。
  
    有些场景需要深拷贝,这时我们就要重写clone方法,以ArrayList为例:

5、适配器模式

  作用:使不兼容的接口相容。
  JDK中体现:InputStream、OutputStream。

6、装饰器模式

作用:为类添加新的功能,防止类继承带来的类爆炸。
JDK中体现:io类、Collections、List。

7、外观模式

作用:封装一组交互类,一直对外提供接口。
JDK中体现:logging包。

8、享元模式

作用:共享对象、节省内存。

JDK中体现:Integer.valueOf、String常量池。

9、代理模式

作用:
1)透明调用被代理对象,无须知道复杂实现细节;
2)增加被代理类的功能;

JDK中体现:动态代理。

10、迭代器模式

 作用:将集合的迭代和集合本身分离。
 JDK中体现:Iterator

11、命令模式

 作用:封装操作,使接口一致。
 JDK中体现:Runable、Callable、ThreadPoolExecutor。

108、什么是设计模式?你是否在你的代码里面使用过任何设计模式?

1、什么是设计模式?

设计模式是解决软件开发某些特定问题而提出的一些解决方案,也可以理解为解决问题的一
些固定思路。

通过设计模式可以帮助我们增强代码的可复用性、可扩展性、灵活性。

我们使用设计模式的最终目的是实现代码的高内聚、低耦合。

2、设计模式的七大原则

  1. 单一职责原则
  2. 接口隔离原则
  3. 依赖倒转原则
  4. 里式替换原则
  5. 开闭原则
  6. 迪米特法则
  7. 合成复用原则

3、你是否在你的代码里面使用过任何设计模式?

(1)单例模式
JDK中的runtime,Spring中的singeton。

(2)简单工厂模式
Spring的BeanFactory,根据传入一个唯一标识来获得bean对象。

(3)原型模式
clone()

(4)代理模式
Spring的AOP中,Spring实现AOP功能的原理就是代理模式:
①JDK动态代理。
②CGLIB动态代理使用Advice(通知)对类进行方法级别的切面增强。

(5)装饰器模式
为类添加新的功能,防止类爆炸;
IO流、数据源包装,Spring中用到的装饰器模式表现在Wrapper。

109、Java 中什么叫单例设计模式?请用 Java 写出线程安全的单例模式

    保证程序只有一个对象的实例,叫做单例模式;
    内部类的方式实现单例模式,是线程安全的;
    双重验证方式实现单例模式也是线程安全的;

110、在 Java 中,什么叫观察者设计模式(observer design pattern)?

    1、观察者模式是一种一对多的依赖关系,让多个观察者同时监听某一主题对象。
    当这个主题对象发生变化时,会通知所有观察者对象,使它们能够自动更新自己。

2、JAVA提供的对观察者模式的支持

  在JAVA语言的java.util库里面,提供了一个Observable类以及一个Observer接口,构成JAVA语
  言对观察者模式的支持。

(1)Observer接口

这个接口只定义了一个方法,即update()方法,当被观察者对象的状态发生变化时,
被观察者对象的notifyObservers()方法就会调用这一方法。

public interface Observer {
    void update(Observable o, Object arg);
}

(2)Observable类

被观察者类都是java.util.Observable类的子类。java.util.Observable提供公开的方法支持观察者对象,
这些方法中有两个对Observable的子类非常重要:一个是setChanged(),另一个是 notifyObservers()。
第一方法setChanged()被调用之后会设置一个内部标记变量,代表被观察者对象的状态发生了变化。
第二个是notifyObservers(),这个方法被调用时,会调用所有登记过的观察者对象的update()方法,
使这些观察者对象可以更新自己。

111、使用工厂模式最主要的好处是什么?在哪里使用?

1、工厂模式好处

       良好的封装性、代码结构清晰;
       扩展性好,如果想增加一个产品,只需扩展一个工厂类即可;
       典型的解耦框架;
       
2、在哪里使用?

      需要生成对象的地方;
      不同数据库的访问;

112、请解释自动装配模式的区别?

       有五种自动装配的方式,可以用来指导 Spring 容器用自动装配方式来进行依赖注入。
       
      1、no
       默认的方式是不进行自动装配,通过显式设置ref属性来进行装配。
       
      2、byName
      通过参数名 自动装配,Spring 容器在配置文件中发现 bean的 autowire 属性被设置成 byname,
      之后容器试图匹配、装配和该 bean 的属性具有相同名字的 bean。
      
      3、byType:
      通过参数类型自动装配,Spring 容器在配置文件中发现 bean的 autowire 属性被设置成 byType,
      之后容器试图匹配、装配和该 bean 的属性具有相同类型的 bean。如果有多个 bean 符合条件,则抛出错误。
      
      4、constructor
      这个方式类似于byType, 但是要提供给构造器参数,如果没有确定的带参数的构造器参数类型,将会抛出异常。
      
      5、autodetect
      首先尝试使用 constructor 来自动装配,如果无法工作,则使用 byType 方式。

113、举一个用 Java 实现的装饰模式(decorator design pattern)?它是作用于对象层次还是类层次?

      在Java IO中运用了装饰器模式,inputStream作为抽象类,其下有几个实现类,表示从不同的数据源输入:
      
       byteArrayInputStream
       fileInputStream
       StringBufferInputStream
       PipedInputStream,从管道产生输入;
       SequenceInputStream,可将其他流收集合并到一个流内;
       FilterInputStream作为装饰器在JDK中是一个普通类,其下面有多个具体装饰器比如
       BufferedInputStream、DataInputStream等。
       FilterInputStream内部封装了基础构件:
       protected volatile InputStream in;

      而BufferedInputStream在调用其read()读取数据时会委托基础构件来进行更底层的操作,而
      它自己所起的装饰作用就是缓冲,在源码中可以很清楚的看到这一切。

114、什么是 Spring 框架?Spring 框架有哪些主要模块?

    Spring是一个控制反转(IOC)和面向切面(AOP)的容器框架。
    
    Spring有七大功能模块:
    
    1、Core
         Core模块是Spring的核心类库,Core实现了IOC功能。
         
     2、AOP
           spring AOP模块是Spring的AOP库,提供了AOP(拦截器)机制,并提供常见的拦截器,
           供用户自定义和配置。
           
      3、orm
           提供对常用ORM框架的管理和支持,hibernate、mybatis等。
           
      4、Dao
           Spring提供对JDBC的支持,对JDBC进行封装。
           
      5、Web
           对Struts2的支持。
           
      6、Context
           Context模块提供框架式的Bean的访问方式,其它程序可以通过Context访问Spring的Bean资源,相当于资源注入。
           
      7、MVC
            MVC模块为spring提供了一套轻量级的MVC实现,即Spring MVC。

115、使用 Spring 框架能带来哪些好处?

     1、轻量级容器、框架
     
      Spring是一个容器,管理对象的生命周期和配置。
      基于一个可配置原型prototype,你的bean可以是单例的,也可以每次需要时都生成一个新的实例。
      
     2、控制反转IOC
     
     Spring通过控制反转(IOC)实现松耦合。
     
     3、支持AOP
     Spring提供对AOP的支持,它允许将一些通用任务,如安全、事务、日志等进行集中式处理,
     从而提高了程序的复用性。
     
    4、轻量级框架
    5、方便测试
    Spring提供Junit4的支持,可以通过注解方便测试spring程序。
    6、对Java中很多API进行了封装
    7、方便集成各种优秀框架
    如Struts、hibernate、mybstis。
    8、支持声明式事务处理
    只需通过配置就可以完成对事务的管理,而无须手动编程。

116、Spring IOC、AOP举例说明

  1、IOC理论的背景
  我们都知道,在采用面向对象方法设计的软件系统中,它的底层实现都是由N个对象组成的,
  所有的对象通过彼此的合作,最终实现系统的业务逻辑。
  
 如果我们打开机械式手表的后盖,就会看到与上面类似的情形,各个齿轮分别带动时针、分
针和秒针顺时针旋转,从而在表盘上产生正确的时间。
图1中描述的就是这样的一个齿轮组,它拥有多个独立的齿轮,这些齿轮相互啮合在一起,协同工作,共同完成某项任务。
我们可以看到,在这样的齿轮组中,如果有一个齿轮出了问题,就可能会影响到整个齿轮组的正常运转。
齿轮组中齿轮之间的啮合关系,与软件系统中对象之间的耦合关系非常相似。
对象之间的耦合关系是无法避免的,也是必要的,这是协同工作的基础。
现在,伴随着工业级应用的规模越来越庞大,对象之间的依赖关系也越来越复杂,经常会出现对象之间的多重依赖性关
系,因此,架构师和设计师对于系统的分析和设计,将面临更大的挑战。对象之间耦合度过高的系统,必然会出现牵一发而动全身的情形。

耦合关系不仅会出现在对象与对象之间,也会出现在软件系统的各模块之间,以及软件系统和硬件系统之间。
如何降低系统之间、模块之间和对象之间的耦合度,是软件工程永远追求的目标之一。
为了解决对象之间的耦合度过高的问题,软件专家Michael Mattson提出了IOC理论,用来实现对象之间的“解耦”,
目前这个理论已经被成功地应用到实践当中,很多的J2EE项目均采用了IOC框架产品Spring。

2、什么是控制反转

IOC是Inversion of Control的缩写,多数书籍翻译成“控制反转”,还有些书籍翻译成为“控制反

向”或者“控制倒置”。1996年,Michael Mattson在一篇有关探讨面向对象框架的文章中,首先
提出了IOC 这个概念。对于面向对象设计及编程的基本思想,前面我们已经讲了很多了,不再
赘述,简单来说就是把复杂系统分解成相互合作的对象,这些对象类通过封装以后,内部实
现对外部是透明的,从而降低了解决问题的复杂度,而且可以灵活地被重用和扩展。IOC理论
提出的观点大体是这样的:借助于“第三方”实现具有依赖关系的对象之间的解耦,如下图:
大家看到了吧,由于引进了中间位置的“第三方”,也就是IOC容器,使得A、B、C、D这4个对
象没有了耦合关系,齿轮之间的传动全部依靠“第三方”了,全部对象的控制权全部上缴给“第
三方”IOC容器,所以,IOC容器成了整个系统的关键核心,它起到了一种类似“粘合剂”的作
用,把系统中的所有对象粘合在一起发挥作用,如果没有这个“粘合剂”,对象与对象之间会彼
此失去联系,这就是有人把IOC容器比喻成“粘合剂”的由来。
我们再来做个试验:把上图中间的IOC容器拿掉,然后再来看看这套系统(拿掉IoC容器后的系统):
我们现在看到的画面,就是我们要实现整个系统所需要完成的全部内容。
这时候,A、B、C、D这4个对象之间已经没有了耦合关系,彼此毫无联系,这样的话,当你在实现A的时候,
根本无须再去考虑B、C和D了,对象之间的依赖关系已经降低到了最低程度。
所以,如果真能实现IOC容器,对于系统开发而言,这将是一件多么美好的事情,参与开发的每一成员只要实现自
己的类就可以了,跟别人没有任何关系!

我们再来看看,控制反转(IOC)到底为什么要起这么个名字?
我们来对比一下:软件系统在没有引入IOC容器之前,如图1所示,对象A依赖于对象
B,那么对象A在初始化或者运行到某一点的时候,自己必须主动去创建对象B或者使用已经创
建的对象B。无论是创建还是使用对象B,控制权都在自己手上。软件系统在引入IOC容器之
后,这种情形就完全改变了,如图3所示,由于IOC容器的加入,对象A与对象B之间失去了直
接联系,所以,当对象A运行到需要对象B的时候,IOC容器会主动创建一个对象B注入到对象
A需要的地方。通过前后的对比,我们不难看出来:对象A获得依赖对象B的过程,由主动行为
变为了被动行为,控制权颠倒过来了,这就是“控制反转”这个名称的由来。

3、IOC的别名:依赖注入(DI)

 2004年,Martin Fowler探讨了同一个问题,既然IOC是控制反转,那么到底是“哪些方面的控
 制被反转了呢?”,经过详细地分析和论证后,他得出了答案:“获得依赖对象的过程被反转了”。
 
 控制被反转之后,获得依赖对象的过程由自身管理变为了由IOC容器主动注入。于是,
 他给“控制反转”取了一个更合适的名字叫做“依赖注入(Dependency Injection)”。
 
 他的这个答案,实际上给出了实现IOC的方法:注入。
 所谓依赖注入,就是由IOC容器在运行期间,动态地将某种依赖关系注入到对象之中。
 
所以,依赖注入(DI)和控制反转(IOC)是从不同的角度的描述的同一件事情,就是指通过引入
IOC容器,利用依赖关系注入的方式,实现对象之间的解耦。

我们举一个生活中的例子,来帮助理解依赖注入的过程。
大家对USB接口和USB设备应该都很熟悉吧,USB为我们使用电脑提
供了很大的方便,现在有很多的外部设备都支持USB接口。

现在,我们利用电脑主机和USB接口来实现一个任务:从外部USB设备读取一个文件。电脑主

机读取文件的时候,它一点也不会关心USB接口上连接的是什么外部设备,而且它确实也无须
知道。它的任务就是读取USB接口,挂接的外部设备只要符合USB接口标准即可。

所以,如果我给电脑主机连接上一个U盘,那么主机就从U盘上读取文件;
如果我给电脑主机连接上一个外置硬盘,那么电脑主机就从外置硬盘上读取文件。

挂接外部设备的权力由我作主,即控制权归我,至于USB接口挂接的是什么设备,电脑主机是决定不了,它只能被动的接受。

电脑主机需要外部设备的时候,根本不用它告诉我,我就会主动帮它挂上它想要的外部设备,你看
我的服务是多么的到位。这就是我们生活中常见的一个依赖注入的例子。

在这个过程中,我就起到了IOC容器的作用。
通过这个例子,依赖注入的思路已经非常清楚:当电脑主机读取文件的时候,我就把它所要依赖的外部设备,帮它挂接上。
整个外部设备注入的过程和一个被依赖的对象在系统运行时被注入另外一个对象内部的过程完全一样。

我们把依赖注入应用到软件系统中,再来描述一下这个过程:对象A依赖于对象B,当对象 A需要用到对象B的时候,IOC
容器就会立即创建一个对象B送给对象A。IOC容器就是一个对象制造工厂,你需要什么,它会给你送去,你直接使用就行了,
而再也不用去关心你所用的东西是如何制成的,也不用关心最后是怎么被销毁的,这一切全部由IOC容器包办。
在传统的实现中,由程序内部代码来控制组件之间的关系。

我们经常使用new关键字来实现两个组件之间关系的组合,这种实现方式会造成组件之间耦合。
IOC很好地解决了该问题,它将实现组件间关系从程序内部提到外部容器,也就是说由容器在运行期将组件间的某种依赖关系
动态注入组件中。

4、IOC为我们带来了什么好处

我们还是从USB的例子说起,使用USB外部设备比使用内置硬盘,到底带来什么好处?

第一、USB设备作为电脑主机的外部设备,在插入主机之前,与电脑主机没有任何的关系,
只有被我们连接在一起之后,两者才发生联系,具有相关性。所以,无论两者中的任何一方出现什么
的问题,都不会影响另一方的运行。这种特性体现在软件工程中,就是可维护性比较好,非
常便于进行单元测试,便于调试程序和诊断故障。代码中的每一个Class都可以单独测试,彼
此之间互不影响,只要保证自身的功能无误即可,这就是组件之间低耦合或者无耦合带来的
好处。

第二、USB设备和电脑主机的之间无关性,还带来了另外一个好处,生产USB设备的厂
商和生产电脑主机的厂商完全可以是互不相干的人,各干各事,他们之间唯一需要遵守的就
是USB接口标准。这种特性体现在软件开发过程中,好处可是太大了。每个开发团队的成员都
只需要关心实现自身的业务逻辑,完全不用去关心其它的人工作进展,因为你的任务跟别人
没有任何关系,你的任务可以单独测试,你的任务也不用依赖于别人的组件,再也不用扯不
清责任了。所以,在一个大中型项目中,团队成员分工明确、责任明晰,很容易将一个大的
任务划分为细小的任务,开发效率和产品质量必将得到大幅度的提高。

第三、同一个USB外部设备可以插接到任何支持USB的设备,可以插接到电脑主机,也可以
插接到DV机,USB外部设备可以被反复利用。

在软件工程中,这种特性就是可复用性好,我们可以把具有普遍性的常用组件独立出来,反复
利用到项目中的其它部分,或者是其它项目,当然这也是面向对象的基本特征。

显然,IOC不仅更好地贯彻了这个原则,提高了模块的可复用性。

符合接口标准的实现,都可以插接到支持此标准的模块中。

第四、同USB外部设备一样,模块具有热插拔特性。
IOC生成对象的方式转为外置方式,也就是把对象生成放在配置文件里进行定义,这样,
当我们更换一个实现子类将会变得很简单,只要修改配置文件就可以了,完全具有热插拨的
特性。

以上几点好处,难道还不足以打动我们,让我们在项目开发过程中使用IOC框架吗?

5、IOC容器的技术剖析

IOC中最基本的技术就是“反射(Reflection)”编程,目前.Net C#、Java和PHP5等语言均支持,
其中PHP5的技术书籍中,有时候也被翻译成“映射”。有关反射的概念和用法,大家应该都很
清楚,通俗来讲就是根据给出的类名(字符串方式)来动态地生成对象。

这种编程方式可以让对象在生成时才决定到底是哪一种对象。

反射的应用是很广泛的,很多的成熟的框架,比如象Java中的Hibernate、Spring框架,.Net
NHibernate、Spring.Net框架都是把“反射”做为最基本的技术手段。反射技术其实很早就出现了,
但一直被忽略,没有被进一步的利用。当时的反射编程方式相对于正常的对象生成方式要慢至少得10倍。
现在的反射技术经过改良优化,已经非常成熟,反射方式生成对象和通常对象生成方式,速度已经相差不大了,
大约为1-2倍的差距。我们可以把IOC容器的工作模式看做是工厂模式的升华,可以把IOC容器看作是一
个工厂,这个工厂里要生产的对象都在配置文件中给出定义,然后利用编程语言的的反射编
程,根据配置文件中给出的类名生成相应的对象。
从实现来看,IOC是把以前在工厂方法里写死的对象生成代码,改变为由配置文件来定义,也就是把工厂和
对象生成这两者独立分隔开来,目的就是提高灵活性和可维护性。

6、IOC容器的一些产品

Sun ONE技术体系下的IOC容器有:轻量级的有Spring、Guice、Pico Container、Avalon、
HiveMind;重量级的有EJB;不轻不重的有JBoss,Jdon等等。

Spring框架作为Java开发中SSH(Struts、Spring、Hibernate)三剑客之一,大中小项目中都有使用,非常成熟,
应用广泛,EJB在关键性的工业级项目中也被使用,比如某些电信业务。

.Net技术体系下的IOC容器有:Spring.Net、Castle等等。
Spring.Net是从Java的Spring移植过来的IOC容器,Castle的IOC容器就是Windsor部分。
它们均是轻量级的框架,比较成熟,其中Spring.Net已经被逐渐应用于各种项目中。

7、使用IOC框架应该注意什么

使用IOC框架产品能够给我们的开发过程带来很大的好处,但是也要充分认识引入IOC框架的
缺点,做到心中有数,杜绝滥用框架。

1)软件系统中由于引入了第三方IOC容器,生成对象的步骤变得有些复杂,本来是两者之
间的事情,又凭空多出一道手续,所以,我们在刚开始使用IOC框架的时候,会感觉系统变得
不太直观。

所以,引入了一个全新的框架,就会增加团队成员学习和认识的培训成本,并且在以后的运行维护中,
还得让新加入者具备同样的知识体系。

2)由于IOC容器生成对象是通过反射方式,在运行效率上有一定的损耗。如果你要追求运
行效率的话,就必须对此进行权衡。

3)、具体到IOC框架产品(比如:Spring)来讲,需要进行大量的配制工作,比较繁琐,对于
一些小的项目而言,客观上也可能加大一些工作成本。

4)IOC框架产品本身的成熟度需要进行评估,如果引入一个不成熟的IOC框架产品,那么会
影响到整个项目,所以这也是一个隐性的风险。

我们大体可以得出这样的结论:一些工作量不大的项目或者产品,不太适合使用IOC框架产品。

另外,如果团队成员的知识能力欠缺,对于IOC框架产品缺乏深入的理解,也不要贸然引入。

最后,特别强调运行效率的项目或者产品,也不太适合引入IOC框架产品,象WEB2.0网
站就是这种情况。

117、什么是控制反转(IOC)?什么是依赖注入?

借助Spring实现具有依赖关系的对象之间的解耦。
对象A运行需要对象B,由主动创建变为IOC容器注入,这便是控制反转。
获得依赖对象的过程被反转了,获取依赖对象的过程由自身创建变为由IOC容器注入,这便是
依赖注入。

118、BeanFactory 和 ApplicationContext有什么区别?

1)BeanFactory是Spring的最底层接口,包含bean的定义,管理bean的加载,实例化,控
制bean的生命周期,特点是每次获取对象时才会创建对象。

ApplicationContext是BeanFactory的子接口,拥有BeanFactory的全部功能,并且扩展了很多
高级特性,每次容器启动时就会创建所有的对象。

  1. ApplicationContext的额外功能:
  2. 继承MessageSource,支持国际化;
  3. 统一的资源文件访问方式;
  4. 提供在监听器中注册bean;
  5. 同时加载配置文件;
  6. 载入多个(有继承关系)上下文,使得每个上下文都专注于一个特定的层次,比如应用的web层;

2)BeanFactory通常以编程的方式被创建,ApplicationContext可以以声明的方式创建,如
使用ContextLoader。

3)BeanFactory 和 ApplicationContext 都支持 BeanPostProcessor,BeanFactoryPostProcessor,
但BeanFactory需要手动注册,ApplicationContext则是自动注册。

119、什么是 JavaConfig?

JavaConfig是Spring3.0新增的概念,就是以注解的形式取代Spring中繁琐的xml文件。
JavaConfig结合了xml的解耦和java编译时检查的优点。

  1. @Configuration,表示这个类是配置类;
  2. @ComponentScan,相当于xml的<context:componentScan basepackage=>;
  3. @Bean,相当于xml的;
  4. @EnableWebMvc,相当于xml的mvc:annotation-driven;
  5. @ImportResource,相当于xml的;
  6. @PropertySource,用于读取properties配置文件;
  7. @Profile,一般用于多环境配置,激活时可用@ActiveProfile(“dev”)注解;

120、什么是 ORM 框架?

ORM(Object-relational mapping),对象关系映射。
是为了解决面向对象与关系型数据库存在的不匹配问题。

ORM框架的优点:

  1. 开发效率更高
  2. 数据访问更抽象、轻便
  3. 支持面向对象封装

121、Spring 有几种配置方式?

1、xml配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
<bean id="jackma" class="com.tyq.dto.User">
   <property name="name" value="jackma" />
   <property name="age" value="55" />
   <property name="dog" ref="jm" />
</bean>
<bean id="jm" class="com.tyq.dto.Dog">
   <property name="name" value="jack" />
   <property name="breed" value="金毛" />
   <property name="age" value="2" />
</bean>
</beans>

2、基于注解的方式

项目越来越大,基于xml配置太麻烦,Spring 2.x时代提供了声明bean的注解。

1)Bean的定义
@Component、@Controller、@Service、@Repository。

2)Bean的注入
@Autowire

3、基于Java的方式

Spring 3.x以后,可以通过Java代码装配Bean。

@Configuration
public class DemoConfig {

@Bean
public User zs(){
  return new User();
}

@Bean
public Dog dog(){
   return new Dog();
}

@Bean //两个狗
public Dog haqi(){
   return new Dog();
}
}

@Component("zs")
public class User {
   private String name;
   private int age;
   private Dog dog;
   //get,set方法略
}

122、请解释 Spring Bean 的生命周期?

  1. 通过构造器或工厂方法创建bean实例;
  2. 为bean的属性赋值;
  3. 调用bean的初始化方法;
  4. 使用bean;
  5. 当容器关闭时,调用bean的销毁方法;

123、Spring Bean 的作用域之间有什么区别?

Spring容器中的bean可以分为5个范围:

  1. singleton:这种bean范围是默认的,这种范围确保不管接受多少请求,
    每个容器中只有一个bean的实例,单例模式;
  2. prototype:为每一个bean提供一个实例;
  3. request:在请求bean范围内为每一个来自客户端的网络请求创建一个实例,
    在请求完毕后,bean会失效并被垃圾回收器回收;
  4. session:为每个session创建一个实例,session过期后,bean会随之消失;
  5. global-session:global-session和Portlet应用相关。当你的应用部署在Portlet
    容器中工作时,它包含很多portlet。如果你想要声明让所有的portlet公用全局
    的存储变量的话,那么全局变量需要存储在global-session中。

124、如何在Spring Boot中禁用Actuator端点安全性?

 默认情况下,所有敏感的HTTP端点都是安全的,只有具有Actuator角色的用户才能访问它们。
 安全性是使用标准的HTTPServletRequest.isUserInRole方法实施的。
 我们可以使用management.security.enable = false来禁用安全性。
 只有在执行机构端点在防火墙后访问时,才建议禁用安全性。

125、什么是 Spring inner beans?

在Spring框架中,无论何时bean被使用时,当仅被调用一个属性。
可以将这个bean声明为内部bean。
内部bean可以用setter注入“属性”和构造方法注入“构造参数”的方式来实现。

比如,在我们的应用程序中,一个Customer类引用了一个Person类,我们要做的是创建一个Person
实例,然后在Customer内部使用。

package com;
public class Customer {
   private Person person;
}

class Person{
  private int id;
  private String name;
  private int age;
}

<bean id="CustomerBean" class="com.Customer">
  <property name="person">
       <bean class="com.person">
          <property name="id" value=1 />
          <property name="name" value="素小暖" />
          <property name="age" value=18 />
      </bean>
  </property>
</bean>

126、Spring 框架中的单例 Beans 是线程安全的么?

Spring框架并没有对单例bean进行任何多线程的封装处理。关于单例bean的线程安全和并发
问题需要开发者自行去搞定。但实际上,大部分的Spring bean并没有可变的状态,所以在某
种程度上说Spring的单例bean是线程安全的。如果你的bean有多种状态的话,比如view
model,就需要自行保证线程安全啦。
最浅显的解决办法就是将多态bean的作用域由singleton变更为prototype。

127、请解释 Spring Bean 的自动装配?

Spring支持IOC,自动装配不用类实例化,直接从bean容器中取。
(1)配置在xml中

(2)@Autowired自动装配

128、如何开启基于注解的自动装配?

要使用 @Autowired,需要注册 AutowiredAnnotationBeanPostProcessor,可以有以下两种
方式来实现:
引入配置文件中的下引入 context:annotation-config

<context:annotation-config />

在bean配置文件中直接引入 AutowiredAnnotationBeanPostProcessor

<bean class="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProc

129、什么是 Spring Batch?

1、什么是spring batch?

spring batch是一个轻量级的、完善的批处理框架,它主要的目的在于帮助企业建立健壮、高
效的批处理应用。

spring batch是Spring的一个子项目,它使用java语言并基于spring框架作为基础开发,使得
已经使用Spring框架的开发者或者是企业可以更加容易访问和利用企业服务。

spring batch提供了大量可重用的组件,包括了日志、追踪、事务、任务作业统计、任务重启、
跳过、重复、资源管理。

对大数据和高性能的批处理任务,spring batch同样提供了高级功能和特性来支持。

例如:分区功能、远程功能。
总的来说,spring batch可以支持简单的、复杂的和大数据的批处理作业。

2)spring batch业务场景

周期性的提交批处理
把一个任务并行处理
消息驱动应用分级处理
大规模并行批处理
手工或调度使任务失败之后重新启动
有依赖步骤的顺序执行(使用工作流驱动扩展)
处理时跳过部分记录
成批事务:为小批量的或有的存储过程/脚本的场景使用

130、spring mvc 和 struts2 的区别是什么?

1)拦截机制的不同

Struts2是类级别的拦截,每次请求就会创建一个Action,和Spring整合时Struts2的
ActionBean注入作用域是原型模式prototype,然后通过setter,getter把request数据注入到
属性。

Struts2中,一个Action对应一个request,response上下文,在接收参数时,可以通过
属性接收,这说明属性参数是让多个方法共享的。
Struts2中Action的一个方法可以对应一个url,而其类属性却被所有方法共享,
这也就无法用注解或其他方式标识其所属方法了,只能设计为多例。

SpringMVC是方法级别的拦截,一个方法对应一个Request上下文,所以方法直接基本上是独
立的,独享request,response数据。而每个方法同时又和一个url对应,参数的传递是直接注
入到方法中的,是方法所独有的。

处理结果通过ModeMap返回给框架。在Spring整合时,SpringMVC的Controller Bean默认单例模式Singleton,
所以默认对所有的请求,只会创建一个Controller,因为没有共享的属性,所以是线程安全的,如果要改变默认的作用域,
需要添加@Scope注解修改。

Struts2有自己的拦截Interceptor机制,SpringMVC这时用的是独立的Aop方式,这样导致
Struts2的配置文件量还是比SpringMVC大。

2)底层框架的不同

Struts2采用Filter(StrutsPrepareAndExecuteFilter)实现,SpringMVC(DispatcherServlet)则采用Servlet实现。
Filter在容器启动之后即初始化;服务停止以后销毁,晚于Servlet。

Servlet是在调用时初始化,先于Filter调用,服务停止后销毁。

3)性能方面

Struts2是类级别的拦截,每次请求对应实例一个新的Action,需要加载所有的属性值注入,

SpringMVC实现了零配置,由于SpringMVC基于方法的拦截,又加载一次单例模式bean注
入。所以,SpringMVC开发效率和性能高于Struts2。

4)配置方面

spring MVC和Spring是无缝的。从这个项目的管理和安全上也比Struts2高。

131、请举例解释@Required 注解?

@Required注解应用于bean属性的setter方法,它表明影响的bean属性在配置时必须放在
XML配置文件中。
如果在xml中定义了一种类型的多个bean,同时在java注解中又想把其中一个bean对象作为属
性,那么此时可以使用@Qualifier加@Autowired来达到这一目的,若不加@Qualifier这个注
解,在运行时会出现“ No qualifying bean of type [com.tutorialspoint.Student] is defined:
expected single matching bean but found 2: student1,student2”这个异常。

132、Spring常用注解

Spring常用注解大总结 + 思维导图

133、项目中是如何实现权限验证的,权限验证需要几张表

现在最普遍的权限管理模型就是RBAC(Role-Based Access Control)。

1、权限控制分类

  菜单功能 url控制(控制访问不同的控制器)

2、RBAC的优缺点

1)优点
简化了用户和权限的关系易扩展、易维护

2)缺点
RBAC模型没有提供操作顺序的控制机制,这一缺陷使得RBAC模型很难适应那些对操作次序有
严格要求的系统。

3、RBAC支持的安全原则

1)最小权限原则
RBAC可以将角色配置成其完成任务所需的最小权限集合。

2)责任分离原则
可以通过调用相互独立互斥的角色来共同完成敏感的任务,
例如要求一个记账员和财务管理员共同参与统一过账操作。

3)数据抽象原则
可以通过权限的抽象来体现,例如财务操作用借款、存款等抽象权限,而不是使用典型的读
写权限。

4、远古时代的权限控制

 当时还没有RBAC,也没有这个概念,就是一堆程序员在那鼓捣,觉得登录这块该做点什么。
 新建一个用户,对这个用户进行赋予权限。
 但是一旦用户多了,权限复杂了,这工作量也是蛮大的。

5、RBAC

基于RBAC 1.0模型的基础上,进行了角色的访问控制
RBAC2中的一个基本限制是互斥角色的限制,互斥角色是指各自权限可以互相制约的两个角
色。

对于这类角色一个用户在某一次活动中只能被分配其中的一个角色,不能同时获得两个
角色的使用权。

该模型有以下几种约束

互斥角色 :同一用户只能分配到一组互斥角色集合中至多一个角色,支持责任分离的原则。
互斥角色是指各自权限互相制约的两个角色。

对于这类角色一个用户在某一次活动中只能被分配其中的一个角色,不能同时获得两个角色的使用权。

常举的例子:在审计活动中,一个角色不能同时被指派给会计角色和审计员角色。

基数约束 :一个角色被分配的用户数量受限;一个用户可拥有的角色数目受限;同样一个
角色对应的访问权限数目也应受限,以控制高级权限在系统中的分配。

先决条件角色 :可以分配角色给用户仅当该用户已经是另一角色的成员;

对应的可以分配访问权限给角色,仅当该角色已经拥有另一种访问权限。

指要想获得较高的权限,要首先拥有低一级的权限。

运行时互斥 :

例如,允许一个用户具有两个角色的成员资格,但在运行中不可同时激活这两个角色。

6、rbac的实现理论分析

进入登录页面;拿到通过post传过来的用户名和密码;使用orm进行过滤查找;如果能找到
值,则说明登录成功:登录成功后调用rbac初始化函数,初始化函数的主要功能是获取用户
的权限和菜单保存到session中,并跳转客户列表页面;如果失败,页面进行友好提示;

7、url权限控制关键代码

134、谈谈controller,接口调用的路径问题

1、Spring MVC如何匹配请求路径

@RequestMapping是用来映射请求的,比如get请求、post请求、或者REST风格与非REST风
格的。该注解可以用在类上或方法上,如果用在类上,表示是该类中所有方法的父路径。

@RequestMapping("/springmvc")
@Controller
public class SpringMVCTest {
   @RequestMapping("/testRequestMapping")
   public String testRequestMapping(){
       System.out.println("testRequestMapping");
    return SUCCESS;
  }
}

在类上还添加了一个@Controller注解,该注解在SpringMVC中负责处理由DispatcherServlet
分发的请求,它把用户请求的数据经过业务处理层处理之后封装成一个model,然后再把该
model返回给对应的view进行展示。
我们可以通过“springmvc/testRequestMapping”这个路径来定位到testRequestMapping这个
方法,然后执行方法内的方法体。

RequestMapping可以实现模糊匹配路径,比如:

?表示一个字符;
*表示任意字符;
**匹配多层路径;

/springmvc/**/testRequestMapping 就可以匹
配/springmvc/stu/getStudentInfo/testRequestMapping 这样的路径了。

2、SpringMVC如何获取请求的参数

1)@PathVariable

该注解用来映射请求URL中绑定的占位符。通过@PathVariable可以将URL中占位符的参数绑
定到controller处理方法的入参中。

@RequestMapping("/testPathVariable/{id}")
    public String testPathVariable(@PathVariable(value="id") Integer id){
       System.out.println("testPathVariable:" + id);
       return SUCCESS;
}

在index.jsp中我们添加一条连接,用来触发一个请求:

<a href="springmvc/testPathVariable/1">testPathVariable</a>

2) @RequestParam
该注解也是用来获取请求参数的,那么该注解和@PathVariable有什么不同呢?

@RequestMapping(value="/testRequestParam")
public String testRequestParam(@RequestParam(value="username") String username, @RequestParam String age){
     System.out.println("testRequestParam" + " username:" + username + " age:" +age);
     return SUCCESS;
}

在index.jsp添加超链接标签

<a href="springmvc/testRequestParam?username=jackie&age=12">testRequestParam</a>

3、REST风格的请求
在SpringMVC中业务最多的应该是CRUD了

@RequestMapping(value="/testRest/{id}", method=RequestMethod.PUT)
public String testRestPut(@PathVariable(value="id") Integer id){
System.out.println("test put:" + id);
return SUCCESS;
}
@RequestMapping(value="/testRest/{id}", method=RequestMethod.DELETE)
public String testRestDelete(@PathVariable(value="id") Integer id){
System.out.println("test delete:" + id);
return SUCCESS;
}
@RequestMapping(value="/testRest", method=RequestMethod.POST)
public String testRest(){
System.out.println("test post");
return SUCCESS;
}
@RequestMapping(value="/testRest/{id}", method=RequestMethod.GET)
public String testRest(@PathVariable(value="id") Integer id){
System.out.println("test get:" + id);
return SUCCESS;
}

135、如何防止表单重复提交

  1. 通过JavaScript屏蔽提交按钮(不推荐)
  2. 给数据库增加唯一键约束(简单粗暴)
  3. 利用Session防止表单重复提交(推荐)
  4. 使用AOP自定义切入实现

136、Spring中都应用了哪些设计模式

1)简单工厂模式

 简单工厂模式的本质就是一个工厂类根据传入的参数,动态的决定实例化哪个类。
 Spring中的BeanFactory就是简单工厂模式的体现,根据传入一个唯一的标识来获得bean对象。

2)工厂方法模式

 应用程序将对象的创建及初始化职责交给工厂对象,工厂Bean。
 定义工厂方法,然后通过config.xml配置文件,将其纳入Spring容器来管理,需要通过
 factory-method指定静态方法名称。

3)单例模式

 Spring用的是双重判断加锁的单例模式,通过getSingleton方法从singletonObjects中获取bean。
/**
* Return the (raw) singleton object registered under the given name.
* <p>Checks already instantiated singletons and also allows for an early
* reference to a currently created singleton (resolving a circular reference).
* @param beanName the name of the bean to look for
* @param allowEarlyReference whether early references should be created or not
* @return the registered singleton object, or {@code null} if none found
*/
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName)
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return (singletonObject != NULL_OBJECT ? singletonObject : null);
}

4)代理模式

Spring的AOP中,使用的Advice(通知)来增强被代理类的功能。
Spring实现AOP功能的原理就是代理模式(① JDK动态代理,② CGLIB字节码生成技术代理。)
对类进行方法级别的切面增强。

5)装饰器模式

装饰器模式:动态的给一个对象添加一些额外的功能。
Spring的ApplicationContext中配置所有的DataSource。这些DataSource可能是不同的数据
库,然后SessionFactory根据用户的每次请求,将DataSource设置成不同的数据源,以达到切
换数据源的目的。

在Spring中有两种表现:一种是类名中含有Wrapper,另一种是类名中含有Decorator。

6)观察者模式

定义对象间的一对多的关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通
知并自动更新。

Spring中观察者模式一般用在listener的实现。

7)策略模式

策略模式是行为性模式,调用不同的方法,适应行为的变化 ,强调父类的调用子类的特性 。

getHandler是HandlerMapping接口中的唯一方法,用于根据请求找到匹配的处理器。

8)模板方法模式

Spring JdbcTemplate的query方法总体结构是一个模板方法+回调函数,query方法中调用的
execute()是一个模板方法,而预期的回调doInStatement(Statement state)方法也是一个模板
方法。

137、请举例说明如何在Spring中注入一个Java Collection?

Spring注入有四种方式,

  1. 、set注入;
    2)、构造器注入;
    3)、基于注解的注入;
    4)、xml配置文件注入;

想要注入java collection,就是注入集合类:

list set map props:该标签支持注入键和值都是字符串类型的键值对。

list和set都使用value标签;
map使用entry标签;
props使用prop标签;

138、mybatis 中 #{}和 ${}的区别是什么?

#{}带引号,${}不带引号;
#{}可以防止SQL注入;
KaTeX parse error: Expected 'EOF', got '#' at position 28: …rder by子句; 一般能用#̲{}就不要使用{};

139、mybatis 是否支持延迟加载?延迟加载的原理是什么?

1)mybatis 是否支持延迟加载?

延迟加载其实就是讲数据加载时机推迟,比如推迟嵌套查询的时机。
延迟加载可以实现先查询主表,按需实时做关联查询,返回关联表结果集,一定程度上提高了效率。

mybatis仅支持关联对象association和关联集合对象collection的延迟加载,association是一对
一,collection是一对多查询,在mybatis配置文件中可以配置lazyloadingEnable=true/false。

2)延迟加载的原理是什么?

使用CGLIB为目标对象建立代理对象,当调用目标对象的方法时进入拦截器方法。

比如调用a.getB().getName(),拦截器方法invoke()发现a.getB()为null,会单独发送事先准备
好的查询关联B对象的sql语句,把B查询出来然后调用a.setB(b),也是a的对象的属性b就有值
了,然后调用getName(),这就是延迟加载的原理。

140、说一下 mybatis 的一级缓存和二级缓存?

一级缓存是session级别的缓存,默认开启,当查询一次数据库时,对查询结果进行缓存,如
果之后的查询在一级缓存中存在,则无需再访问数据库;

二级缓存是sessionFactory级别的缓存,需要配置才会开启。

当进行sql语句查询时,先查看一级缓存,如果不存在,访问二级缓存,降低数据库访问压力。

141、mybatis 有哪些执行器(Executor)?

1、mybatis有三种基本的Executor执行器:

1)、SimpleExecutor
每执行一次update或select,就开启一个Statement对象,用完立刻关闭Statement对象。

2)、PauseExecutor
执行update或select,以sql作为key查找Statement对象,存在就使用,不存在就创建,用完
后,不关闭Statement对象,而且放置于Map内,供下一次使用。简言之,就是重复使用
Statement对象。

3)、BatchExecutor

       执行update,将所有sql通过addBatch()都添加到批处理中,等待统一执行executeBatch(),
       它缓存了多个Statement对象,每个Statement对象都是addBatch()完毕后,等待逐一执行
       executeBatch()批处理。与JDBC批处理相同。

2、作用范围:

      Executor的这些特点,都严格限制在SqlSession生命周期范围内。

3、Mybatis中如何指定使用哪一种Executor执行器?

     在mybatis的配置文件中,可以指定默认的ExecutorType执行器类型,也可以手动给
     DefaultSqlSessionFactory的创建SqlSession的方法传递ExecutorType类型参数。

142、mybatis 和 hibernate 的区别有哪些?

1)两者最大的区别

  针对简单逻辑,都有对应的代码生成工具,可以生成简单基本的dao层方法;
  针对高级查询,mybatis要手动编写sql语句和resultMap,而hibernate有良好的映射机制;

2)开发难度对比
hibernate > mybatis

3)日志统计
hibernate有自己的日志统计功能,而mybatis需要借助log4j来记录日志。

4)数据库扩展比较
hibernate > mybatis

5)缓存机制比较
因为hibernate对查询对象有良好的管理机制,用户无需关心sql,所以使用二级缓存如果出现
脏数据,系统会报错。

 而mybatis,如果不能获取最新数据,应该避免缓存的使用,脏数据的出现会给系统的正常运行带来很大的隐患。

6)如何选择

mybatis需要编写sql和映射规则,工作量大于hibernate;
mybatis支持的工具也有限,不能像hibernate那样有许多插件可以帮助生成映射代码和关联关系;

对于性能要求不太苛刻的系统,比如管理系统、ERP等推荐hibernate;
对于性能要求高、响应快、灵活的系统,比如电商系统,推荐使用mybatis;

143、myBatis查询多个id、myBatis常用属性

myBatis查询多个id(我居然回答用对象来传递…)

Page<UserPoJo> getUserListByIds(@Param("ids") List<Integer> ids);

<!--根据id列表批量查询user-->
<select id="getUserListByIds" resultType="com.guor.UserPoJo">
  select * from student
      where id in
       <foreach collection="ids" item="userid" open="(" close=")" separator=",">
     #{userid}
     </foreach>
</select>

144、mybatis一级缓存、二级缓存

1)一级缓存:指的是mybatis中sqlSession对象的缓存,当我们执行查询以后,查询的结果
会同时存入sqlSession中,再次查询的时候,先去sqlSession中查询,有的话直接拿出;

当sqlSession消失时,mybatis的一级缓存也就消失了,当调用sqlSession的修改、添加、删除、
commit()、close()等方法时,会清空一级缓存。

2)二级缓存:指的是mybatis中的sqlSessionFactory对象的缓存,由同一个
sqlSessionFactory对象创建的sqlSession共享其缓存,但是其中缓存的是数据而不是对象。
当命中二级缓存时,通过存储的数据构造成对象返回。

查询数据的时候,查询的流程是:二级缓存 > 一级缓存 > 数据库。

3)如果开启了二级缓存,sqlSession进行close()后,才会把sqlSession一级缓存中的数据
添加到二级缓存中;

为了将缓存数据取出执行反序列化,还需要将要缓存的pojo实现Serializable接口,因为二级缓存
数据存储介质多种多样,不一定只存在内存中,也可能存在硬盘中。

4)mybatis框架主要是围绕sqlSessionFactory进行的,具体的步骤:

1、. 定义一个configuration对象,其中包含数据源、事务、mapper文件资源以及影响数据库行为属性设置settings。
2. 通过配置对象,则可以创建一个sqlSessionFactoryBuilder对象。
3. 通过sqlSessionFactoryBuilder获得sqlSessionFactory实例。
4. 通过sqlSessionFactory实例创建qlSession实例,通过sqlSession对数据库进行操作。

5)代码实例

mybatis-config.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">

<configuration>
<!-- 加载类路径下的属性文件 -->
<properties resource="db.properties"/>
<!-- 设置类型别名 -->
<typeAliases>
     <typeAlias type="cn.itcast.javaee.mybatis.app04.Student" alias="student"/>
</typeAliases>
<!-- 设置一个默认的连接环境信息 -->
<environments default="mysql_developer">
<!-- 连接环境信息,取一个任意唯一的名字 -->
<environment id="mysql_developer">
<!-- mybatis使用jdbc事务管理方式 -->
<transactionManager type="jdbc"/>
<!-- mybatis使用连接池方式来获取连接 -->
<dataSource type="pooled">
<!-- 配置与数据库交互的4个必要属性 -->
<property name="driver" value="${mysql.driver}"/>
<property name="url" value="${mysql.url}"/>
<property name="username" value="${mysql.username}"/>
<property name="password" value="${mysql.password}"/>
</dataSource>
</environment>

<!-- 连接环境信息,取一个任意唯一的名字 -->
<environment id="oracle_developer">
<!-- mybatis使用jdbc事务管理方式 -->
<transactionManager type="jdbc"/>
<!-- mybatis使用连接池方式来获取连接 -->
<dataSource type="pooled">
<!-- 配置与数据库交互的4个必要属性 -->
<property name="driver" value="${oracle.driver}"/>
<property name="url" value="${oracle.url}"/>
<property name="username" value="${oracle.username}"/>
<property name="password" value="${oracle.password}"/>
</dataSource>
</environment>
</environments>
<!-- 加载映射文件-->
<mappers>
<mapper resource="cn/itcast/javaee/mybatis/app14/StudentMapper.xml"/>
</mappers>
</configuration>
public class MyBatisTest {
public static void main(String[] args) {
try {
//读取mybatis-config.xml文件
InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml"
//初始化mybatis,创建SqlSessionFactory类的实例
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resou
//创建session实例
SqlSession session = sqlSessionFactory.openSession();
/*
* 接下来在这里做很多事情,到目前为止,目的已经达到得到了SqlSession对象.通过调用SqlSession里
* 可以测试MyBatis和Dao层接口方法之间的正确性,当然也可以做别的很多事情,在这里就不列举
*/
//插入数据
User user = new User();
user.setC_password("123");
user.setC_username("123");
user.setC_salt("123");

//第一个参数为方法的完全限定名:位置信息+映射文件当中的id
session.insert("com.cn.dao.UserMapping.insertUserInformation", user);

//提交事务
session.commit();
//关闭session
session.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}

145、mybatis如何防止sql注入

注意:但凡是sql注入漏洞的程序,都是因为程序要接受来自客户端用户输入的变量或URL传
递的参数,并且这个变量或参数是组成sql语句的一部分,对于用户输入的内容或传递的参
数,我们应该要时刻保持警惕,这是安全领域里的【外部数据不可信任】的原则,纵观web安
全领域的各种攻击方式,大多数都是因为开发者违反了这个原则而导致的,所以自然能想
到,就是变量的检测、过滤、验证下手,确保变量是开发者所预想的。

1)检查变量数据类型和格式

数据类型检查,sql执行前,要进行数据类型检查,如果是邮箱,参数就必须是邮箱的格式,
如果是日期,就必须是日期格式;

只要是有固定格式的变量,在SQL语句执行前,应该严格按照固定格式去检查,确保变量是我
们预想的格式,这样很大程度上可以避免SQL注入攻击。

如果上述例子中id是int型的,效果会怎样呢?

无法注入,因为输入注入参数会失败。比如上述中的name字段,我们应该在用户注册的时候,
就确定一个用户名规则,比如5-20个字符,只能由大小写字母、数字以及汉字组成,不包含特殊字符。

此时我们应该有一个函数来完成统一的用户名检查。不过,仍然有很多场景并不能用到这个方法,
比如写博客,评论系统,弹幕系统,必须允许用户可以提交任意形式的字符才行,否则用户体验感太差了。

2)过滤特殊符号

3)绑定变量,使用预编译语句

146、为什么要使用 hibernate?

  1. hibernate对jdbc进行了封装,简化了JDBC的重复性代码;
  2. hibernate对dao有一个封装类hibernateTemplate,可以继承它,实现简单的CRUD接口。
  3. hibernate使用注解和配置文件,可以对实体类和映射文件进行映射;
  4. hibernate有事务管理机制,保证了数据的安全性;
  5. hibernate有一级缓存和二级缓存;

146、hibernate 中如何在控制台查看打印的 sql 语句?

spring.jpa.properties.hibernate.show_sql=true
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.properties.hibernate.use_sql_comments=true

147、hibernate 有几种查询方式?

1)导航对象图查询:根据已加载的对象,导航到其他对象。

例如,对于已经加载的Customer对象,调用它的getOrders().iterator()方法就可以导航到所
有关联的Order对象,假如在关联级别使用了延迟加载检索策略,那么首次执行此方法时,
hibernate会从数据库中加载关联的Order对象,否则就从缓存中获得Order对象。

2)OID方式:按照对象的OID来检索对象

Session的get()和load()方法提供了这种功能,如果在应用程序中先知道了OID,就可以使用
这种方式检索对象。

get()和load()的用法完全一样,都需要两个参数,一个是持久化对象类名class,一个是行号
OID,返回固定的某一行的数据,但是需要注意的是,当输入的OID不存在时,get()会返回一
个空对象,load()则直接报错。

3)HQL检索方式:(hibernate query language)

使用面向对象的HQL查询语言,session的find()方法用于执行HQL查询语句。
此外,hibernate还提供了query接口,它是hibernate提供的专门的HQL查询接口,能够执行各种复杂的HQL查询语句。

它具备以下功能:

  1. 在查询语句中设定各种查询条件;
  2. 支持投影查询,即仅检索出对象的部分属性;
  3. 支持分页查询;
  4. 支持连接查询;
  5. 支持分组查询;
  6. 提供内置函数;
  7. 能够调用用户自定义的SQL函数;
  8. 支持子查询;
  9. 支持动态绑定参数;

例如:

Query query = session.createQuery(“from UserPo”);

获得一个query对象,注意参数字符串中不是一个SQL语句,from后面的是持久化对象名称;

List list = query.list();

就可以获取数据库中对应表的数据集合。

4)QBC检索方式:Query By Criteria的API来检索对象

这种API封装了基于字符串形式的查询语句,提供了更加面向对象的接口。

Criteria criteria = session.createCriteria(UserPo.class);

创建一个Criteria对象,参数是所关联的持久化对象,criteria.add(Restrictions.get(“id”,2));
将查询条件加入对象中,后面的操作就和Query对象一样了。

5)本地SQL

使用本地数据库的SQL查询语句,hibernate会负责把检索到的JDBC ResultSet结果映射为持久
化对象图。

148、hibernate实体类可以被定义为final吗?

可以将hibernate的实体类定义为final,但这种做法不好。
因为hibernate会使用代理模式在延迟关联的情况下提高性能,如果你把实体类定义成final类
之后,因为Java不允许对final类进行扩展,所以hibernate就无法再使用代理了,如此一来就
限制了使用可以提升性能的手段。

不过,如果你的持久化类实现了一个接口,而且在该接口中声明了所有定义于实体类中的所
有public的方法的话,就能避免出现前面所说的不利后果。

149、在 hibernate 中使用 Integer 和 int 做映射有什么区别?

hibernate是面向对象的ORM,所以一般定义成封装类型,要看数据库中的定义,如果数据库
中有对应字段存在null值,就要定义Integer。也可以定义基本类型,在配置文件中写清楚即
可。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值