java面试知识迷你版

2021-02-15 04:22

阅读:371

标签:持久   磁盘满了   复制算法   tomcat   flag   速度   增加节点   response   headers   

java基础

  • 内部类:静态内部类、成员内部类、局部内部类、匿名内部类。

  • 重写(override)要求子类的返回值小于父类(类型相同),修饰符使用范围也大于分类,抛出异常小于父类。

  • 静态方法不能调用类非静态方法,因为静态方法可以在不生成对象的时候直接调用。

  • 默认构造方法,因为子类构造的时候也调用super()父类,所以需要增加一个默认构造函数,避免编译出错。

  • java只有值传递:按值调用,按引用调用(其实也是按值调用,因为传的是一个指针地址)

  • 线程状态:初始状态->就绪状态->运行状态、阻塞状态、等待状态、超时等待状态->终止状态。

  • try:不能单独用,可以不加catch,但是最后要finally{}

  • synchronized:优化加了偏向锁,自旋锁/适应性自旋锁,轻量级锁,锁粗化,锁消除

synchronized加在类方法上是锁的对象锁,加在静态方法上锁的是类锁。

  • volatile:直接从内存中取值,同时禁止指令重排。

  • 线程池的好处:1.减少创建和销毁线程池的开销、2.任务不用等创建线程即可运行,3.管理,调优,监控。

包括newFixedThreadExecutor、ScheduleThreadExecutor、newSingleThreadExecutor,newCacheThreadExecutor。满载后拒绝策略有直接抛异常,抛弃最老的,丢弃,有调用者自己执行。

创建线程的方式:1.继承thread、2.实现Runable、3.实现callable

executor.submit()返回执行结果Future(get()返回结果会阻塞线程去执行完成),executor.execute()不返回执行结果

public ThreadPoolExecutor(int corePoolSize,//核心线程数量
                         int maximumPoolSize,//最大线程数量
                         long keepAliveTime, //超过核心线程数量时,多出的线程等待keepAliveTime                                                 时间后自行销毁
                         TimeUnit unit,//上面时间的单位
                         BlockingQueueRunnable> workQueue,//等待队列
                         ThreadFactory threadFactory,//创建新线程的时候用到
                         RejectedExecutionHandler handler //拒绝策略
                        ){...}

thread拥有的方法:setPriority(.),sleep(),join(让调取它的线程等待本线程执行完),yield(暂停当前执行线程,执行其他线程),interrupt(中断线程),isAlive(测试线程是否处于活动状态)

线程安全的理解**:是有全局变量及静态变量引起的,只读没问题,但是写的要考虑线程同步。常量不会改没事,线程内的局部变量或者调用方法前每一个实例都是新建的,那也是线程安全的,因为不会访问共享的资源。

ReentrantLock 实现原理:基于AQS来实现。

Synchronized和Lock的异同:前者利用Object的notify/wait之类的调度,后者用Condition进行线程间调度。再者前者是可以加在方法/代码片段给JVM执行,后者直接代码段通过代码控制。前者自动释放,后者手动解锁。

Queue:实现类LinkedList,ArrayBlockingQueue, LinkedBlockingQueue,PriorityQueue, PriorityBlockingQueue,ConcurrentLinkedQueue。

Java内存模型(Java Memory Model ,JMM)就是一种符合内存模型规范的,屏蔽了各种硬件和操作系统的访问差异的,保证了Java程序在各种平台下对内存的访问都能保证效果一致的机制及规范。Java内存模型规定了所有的变量都存储在主内存中,每条线程还有自己的工作内存,线程的工作内存中保存了该线程中是用到的变量的主内存副本拷贝,线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存。不同的线程之间也无法直接访问对方工作内存中的变量,线程间变量的传递均需要自己的工作内存和主存之间进行数据同步进行。而JMM就作用于工作内存和主存之间数据同步过程。他规定了如何做数据同步以及什么时候做数据同步。

JDK8的应用,可以使用Instant代替Date,LocalDateTime代替Calendar,DateTimeFormatter代替SimpleDateFormat。

JAVA的JUC、AQS

其实就是指java.util.concurrent/.atomic包下的东西,例如常见的有volatile、cas、concurrentHashMap、ContDownLatch、实现Callable接口创建线程、ReentrantLock同步锁、ReadWriterLock读写锁、线程池

cas:compare and swap,比较旧的值,没有改变则更新为新的值。atomicInteger就是利用cas、volatile和native方法

AQS:位于java.util.concurrent.locks包下,AQS是一个用来构建锁和同步器的框架,例如ReentrantLock和Semaphore等等,原理:被请求的共享资源空闲,那么当前空闲的线程就会被标记为运行状态,资源也被标记为被锁定,需要一套线程阻塞等待以及被唤醒时锁分配的机制,AQS使用CLH队列锁(自旋锁,先来先服务,保证无饥饿)实现,即将暂时获取不到锁的线程放在队列中。

 

JVM

  • :对象和数组分配内存,是垃圾收集器管理的主要区域,又称为GC堆,1.7之后运行时常量池也在此

  • 方法区/元空间/直接内存:储存已被虚拟机加载的类信息,常量,静态变量,即时编译器编译后的代码数据

    线程独有

    • 程序计数器:保存下条指令、分支,循环,跳转、异常处理,线程恢复的位置,唯一不会OOM

    • 虚拟机栈:一个个栈帧,保存局部变量,操作数栈,动态链接,方法出入口信息

    • 本地方法栈:执行native方法

    运行时常量池:放class编译信息。

    字符串常量池:7之前放方法区,之后放堆。

垃圾回收

基本采用分代垃圾回收算法,粗分为新生代和老年代,细分为eden,from survivor, to survivor,老年区

堆内存常见分配策略:对象优先在eden区分配,大对象直接进入老年代,长期存活的对象将进入老年代。

Minor GC:新生代垃圾收集动作,Minor GC非常频繁,回收速度一般也比较快。

FULL GC/Major GC: 发生在老年代的GC,速度比Minor GC慢10倍以上。

判断对象死亡:引用计数法, 可达性分析法(GC ROOT,找出一些root节点然后构造树,需要两次成为不可达对象,才面临回收,GCROOT对象:虚拟机栈中引用的对象、方法区类静态属性引用的对象-方法区常量池引用的对象、本地方法栈JNI引用的对象。一般被标记两次才会真正被回收)

无用的类:所有实例被回收,加载该类的classLoader已被回收,对应的java.lang.class没有在任何地方被引用。

分代收集:新生代使用复制算法,老年代使用标记-清除或标记整理。

新生代收集器:Serial、ParNew、Parallel Scavenge(注重吞吐量);

老年代收集器:Serial Old、Parallel Old、CMS;

整堆收集器:G1;

CMS收集器**:Concurrent mark Sweep(并发标记清除) ,一种以获取最短回收停顿时间为目标的收集器,基本实现垃圾回收和用户线程同时工作。(缺点:对CPU资源敏感,无法处理浮动垃圾,标记清除有碎片)

  1. 初始标记:暂停其他所有线程,记录直接和root相连的对象

  2. 并发标记:同时开启GC和用户线程,记录可达对象(不能保证包含当前所有可达对象,因为还在更新)

  3. 重新标记:暂停用户线程,修正并发标记期间产生变动的那一部分标记记录

  4. 并发清楚:开启用户线程,同时GC线程开始对标记的区域做清扫。

G1收集器:Garbage-First 面向服务器的垃圾收集器,主要针对多颗处理器及大容量内存的机器,以高概率满足GC停顿时间的要求,还具备高吞吐量性能特征。使用标记整理、可预测的停顿时间模型。维护一个优先列表,优先回收价值最大的region

  1. 初始标记

  2. 并发标记

  3. 最终标记

  4. 筛选回收

finalize():可达性分析中,第一个标记会进行一次筛选,如果类有覆盖这个方法,会放到一个F-Queue中,虚拟机触发一个线程去执行,但不会承诺一直等待它运行完避免死锁从而内存回收系统崩溃,GC对处于F-Queue中的对象进行第二次被标记,这时该对象将会被移除“即将回收”集合,等待回收。

内存泄露:对象没有被使用了但是因为某些原因不能被回收。例如:栈中push进去的元素一直没有拿出来使用;HashMap的key改了hascode导致之前的找不回来。

Full GC本身不会先进行Minor GC,我们可以配置-XX:+ScavengeBeforeFullGC(非CMS回收算法)、CMSScavengeBeforeRemark(CMS回收算法)可以,让Full GC之前先进行一次Minor GC,因为老年代很多对象都会引用到新生代的对象,先进行一次Minor GC可以提高老年代GC的速度。

对象创建过程

  1. 类加载检查:虚拟机遇到一个new指令的时候,首先去检查这个指令的参数是否能在常量池中定位到这个类的符号引用,并且检查这个符号引用代表的类是否已被加载过、解析和初始化过,没有的话,必须执行相应的类加载过程。

  2. 分配内存:类加载过就可以确定所需内存大小,为对象分配内存就是在java堆中划分出来一块确定带下的内存。方式有“指针碰撞"和”空闲列表“,选择哪个方式取决于JAVA堆是否规整,而java堆是否规整又由采用的垃圾收集器是否带有压缩整理功能决定。涉及线程安全(采用CAS+失败重试,TLAB:每个线程预先分配内存,则首先在TLAB分配,大于剩余内存或快用完,就用上面的CAS)

指针碰撞:内存规整下,通过一个指针区分已使用和未使用的内存的位置,需要使用时,指针移动划分。

空闲列表:内存不规整,虚拟机维护一空闲列表,需要多大内存空闲列表又显示有就拿去用,如cms收集器

  1. 初始化零值:内存空间初始化,类字段初始值

  2. 设置对象头:类的元数据信息,对象的哈希码,GC分代年龄

  3. 执行init方法

对象访问定位方式

1.使用句柄:堆中划分一块内存作为句柄,引用(变量名)储存的是对象的句柄地址,句柄包含对象实例数据地址指针和类型数据指针地址。对象移动只需要改变句柄的实例数据指针。

2.直接指针:引用储存的时候直接对象的地址,直接对象中包含到对象类型数据的指针和对象实例数据。速度快。

PermGen space异常:永久代溢出,一般是加载的class太多超出默认的4m,-XX:PermSize=64M-XX:MaxPermSize=128m,但是在java8之后,这个是元空间(直接内存)了。

-XX:MaxNewSize:新生代内存大小,与-Xmn一样,Sun建议占总的JVM内存3/8。

-XX:NewRatio=4:年轻代与老年代比例1:4。

-XX:SurvivorRatio=4:年轻代的eden和一个survior区的比例1 : 4。和两个survior比就是2:4。

JVM垃圾回收器参数

参数 描述
UseSerialGC 运行在client模式下的默认值,Serial + Serial Old
UseParNewGC p
UseConcMarkSweepGC ParNew + CMS + Serial old(此为CMS出现失败后的后备收集器)
UseParallelGC 运行在Server模式下,Parallel Scavenge + Serial Old
UseParallelOldGC Parallel Scavenge + Parallel Old,jdk8默认

 

CLASS文件

.class文件包含以下:

技术图片

  • 魔数:确定这个class文件是否能被虚拟机接收。

  • class文件版本: class文件的版本号,保证编译正常执行。

类加载过程

类加载过程: 加载(文件二进制流到可使用的数据结构)->链接->初始化(init方法)。连接过程分为:验证(检查class文件的正确性)->准备(初始化零值)->解析(符号引用【因为一开始不知道引用的是什么,所以会使用特定符号来表示】转直接引用)

  1. 加载 :1.通过类名获取定义此类的二进制文字流(还可自定义类加载器)。2.字节流代表的静态储存结构转换为方法区的运行时数据结构。3.在内存中生成一个代表该类的Class对象,作为方法区这些数据的访问入口。

*数组类型不通过类加载器创建,它由Java虚拟机直接创建。

*类加载器:JVM内置了三个重要的ClassLoader,除了BootstrapClassLoader,其他都是由Java实现自java.lang.classloader。

  • BootstrapClassLoader(启动类加载器): 最顶层的加载类,有C++实现,负责加载%JAVA_HOME%/lib目录下的jar包和类或者被-Xbootclasspath参数指定的路径的所有类。

  • ExtensionClassLoader(拓展类加载器): 主要负责加载目录%JER_HOME%/lib/ext目录下的jar包和类,或被java.ext.dirs系统变量所指定的路径下的jar包。

  • AppClassLoader(应用程序类加载器): 面向我们用户的加载器,负责加载当前应用classpath下所有的jar包和类。

*双亲委派模型(并非父母两个,而是指父母这一辈的): 每一个类都有一个对应它的类加载器,系统中的ClassLoader在协同工作的时候会默认使用双亲委派模型,即在类加载的时候,系统会首先判断当前类是否被加载过,已经被加载过类会直接返回,否则才会被尝试加载,加载的时候,首先会把该请求委派该父类加载器的loadClass()处理,因此所有的请求最终都应该传到最顶层的启动类加载器bootstrapClassLoader中。当父类加载器无法处理时,才由自己来处理,当父类加载器为null时,会启动类加载器BootstrapClassLoader作为父类加载器。这中的父子是按优先级定的,不是继承关系。

好处:保证Java程序的稳定运行,可以避免类的重复加载(JVM区分不同类的方式不仅仅根据类名,系统的类文件被不同的类加载器加载产生的是两个不同的类),保证了Java的核心API不被篡改,如果没有使用双亲委派模型,而是每个类加载器都加载自己的话就会出现一些问题,比较我们编写一个称为java.lang.Object类的话,那么程序运行的时候,系统就会出现不同的Object类。反正意思就是向上寻找是否已经加载过了。

bootstrapClassLoader必用因为它在虚拟机实现,其他不想用的话自己定义一个类加载器,重载loadClass()即可,就是继承ClassLoader

隐式加载指的是程序在使用new等方式创建对象时,会隐式地调用类的加载器把对应的类加载到JVM中。显式加载指的是通过直接调用class.forName()方法把所需的类加载到JVM中。

mybatis

#{}是静态替换变量有注入风险,${}是利用PreparedStatement的?替换,相对于加‘‘,防止注入。

select|update|delete|insert|resultMap|parameterMap|sql|include|selectKey|

动态标签trim|where|set|foreach|if|choose|when|otherwise|bind

bind的作用是拼接字符串等操作,可以应对改数据库,sql注入

and user_name like #{userNameLike}

不同的xml写一样的namespace和id,可以但会覆盖,因为用的map,没有写namespace的话相同id则会报错。

mybatis的executor执行器:(严格限制在SqlSession生命周期范围)

  1. SimpleExecutor:执行一次update或select,就开启一个Statement对象,用完立即关闭。

  2. ReuseExecutor:执行update或select,以sql作为key查找Statement对象,存在就使用,不存在就创建,使用完不关闭,而是放在Map重复利用。

  3. BatchExecutor:执行update(jdbc批处理不支持select),将所有sql添加到批处理中(addBatch),等待统一执行(executeBatch),它缓存多个Statement对象,先addBatch,再executeBatch

使用#{}经过预编译,是安全的,防止Sql注入。

拦截器只能拦截四种类型的接口:Executor、StatementHander、ParameterHandler、ReusltSetHandler。想要支持其他接口得重写configuration,可以对这四个接口所有方法进行拦截。

 

缓存

  • mybatis默认开启一级缓存,一级缓存是查询数据库前的最后一层缓存,使用PerpetualCache(即HashMap缓存),利用自动生成的cacheKey查询出数据,update/delete/insert提交回滚事务时会删除缓存。SqlSession级别的缓存。关闭方式有配置文件,插件,

  • 二级缓存比较复杂,涉及事务脏读,需要在**-mapping.xml配置来开启。mapper级别的缓存。两个Mapper的namespace如果相同,那么这两个Mapper执行的sql查询会被缓存在同一个二级缓存中。要开启二级缓存需要在配置文件中设置cacheEnabled属性为true。

Spring

springbean生命周期:实例化、属性赋值、初始化、使用、销毁。

spring是一些模块的集合,包括核心容器(DI,验证,类型转换,资源,国际化,数据绑定,事件),数据访问/集成,Web,AOP,工具,消息,测试模块。

spring core,spring aspects,spring aop,spring jdbc,spring jms,orm,web,test。

spring aop是运行时增强,aspects是编译时增强。

注入对象可以是singleton、prototype,request,session。

spring框架中用到哪些设计模式

  1. 工厂设计模式:beanFactory、ApplicationContext创建bean对象。

  2. 代理设计模式:AOP

  3. 单例设计模式:默认都是单例的注入对象。

  4. 模板方法模式:spring中的jdbctemplate、hibernatetemplate,redistemplate等。

  5. 包装器模式:链接多个数据库,不同客户每次访问不同数据库。

spring包括自动装配和手动装配,手动装配基于Xml、构造方法、setter方法等,自动装配:no[不进行自动装配,显式设置ref属性进行装配],byName[通过参数名自动装配,例如autowire设置成byName,容器就会试图匹配相同名字的bean],byType[通过参数类型自动装配,例如autowire设置成byType,容器试图匹配和该bean属性具有相同类型的bean,如果有多个就会报错],constructor构造器参数,autodetect[先constructor,再byType]

事件:我们可以创建bean用来监听在ApplicationContext中发布的事件,ApplicationEvent类和在ApplicationContext接口中处理的事件,如果一个bean实现了ApplicationListener,当一个ApplicationEvent被发布以后,bean会自动被通知。有上下文更新、开始、停止、关闭事件、请求处理事件、自定义事件。

spring AOP

使用的是代理模式,如果代理的是一个接口的话,就用java动态代理,如果是其他的类,使用CGLIB。

核心概念

  1. 切面:对横切关注点的抽象

  2. 横切关注点:对哪些方法进行拦截,拦截后怎么处理。

  3. 连接点:被拦截到的点

  4. 切入点

  5. 通知:前置,后置,异常,最终,环绕通知

  6. 目标对象:代理的目标对象。

  7. 织入:将切面应用到目标对象并导致代理对象创建的过程。

  8. 引入:不修改代码的前提下,引入可以在运行期为类动态地添加一些方法或字段。

spring事务

  • 事务的特性:ACID(原子性、一致性、隔离性、持久性)

  • 并发事务带来的问题:脏读、丢失修改、不可重复读、幻读。

    TransactionDefinition接口定义了五个表示隔离级别的常量

    TransactionDefinition.ISOLATION_DEFAULT: 使用后端数据库默认的隔离级别,Mysql 默认采用的 REPEATABLE_READ隔离级别 Oracle 默认采用的 READ_COMMITTED隔离级别.

    TransactionDefinition.ISOLATION_READ_UNCOMMITTED: 最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读

    TransactionDefinition.ISOLATION_READ_COMMITTED: 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生

    TransactionDefinition.ISOLATION_REPEATABLE_READ: 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。

    TransactionDefinition.ISOLATION_SERIALIZABLE: 最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。

    事务传播行为

    当事务方法被另一个事务方法调用是,必须指定事务应该如何传播。

    支持当前事务的情况:

    • TransactionDefinition.PROPAGATION_REQUIRED: 如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。

    • TransactionDefinition.PROPAGATION_SUPPORTS: 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。

    • TransactionDefinition.PROPAGATION_MANDATORY: 如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。(mandatory:强制性)

    不支持当前事务的情况:

    • TransactionDefinition.PROPAGATION_REQUIRES_NEW: 创建一个新的事务,如果当前存在事务,则把当前事务挂起。

    • TransactionDefinition.PROPAGATION_NOT_SUPPORTED: 以非事务方式运行,如果当前存在事务,则把当前事务挂起。

    • TransactionDefinition.PROPAGATION_NEVER: 以非事务方式运行,如果当前存在事务,则抛出异常。

    其他情况:

    • TransactionDefinition.PROPAGATION_NESTED: 如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED。

    @Transaction失效场景

    1. 注解加在了非public方法上。

    2. 注解属性设置错误,就是事务传播行为设置了PROPAGATION_NOT_SUPPORTED这类的。

    3.  

    4. rollbackFor设置错误,抛出异常不用处理到。

    5. 同一个类方法调用,导致失效,例如类有A/B两个方法,B加了注解,A调B,则会事务失效。

    6. 异常被catch掉了,所以不抛出异常不处理事务。

    7. 数据库引擎不支持事务。

    分布式事务

    1. 两阶段提交2PC:在计算机网络以及数据库领域内,为了使基于分布式系统架构下的所有节点在进行事务提交时保持一致性而设计的一种算法。当一个事务跨越多个节点时,为了保持事务的ACID特性,需要引入一个作为协调者的组件来统一掌握所有节点,并最终指示这些节点是否要把操作结果进行真正的提交。概括为:参与者将操作成败通知协调者,再由协调者根据所有参与者的反馈情况决定各参与者是否提交操作还是中止操作。准备阶段(各系统准备好但不提交)-统一提交(失败还向每个参与者发送回滚)释放锁。问题会有阻塞、脑裂(网络问题协调者通知不到位,最后数据部分不一致)、宕机后再恢复不知道之前commit之类通知的情况。

    2. 三阶段提交3PC:加入超时机制,CanCommit(协调者向参与者发送commit请求,参与可以就返回YES/NO)--PreCommit(有一个NO则中断,全部YES就会下一步)--doCommit(真正的事务提交,协调者发送提交请求,参与者提交事务,参与者提交完成发送ack响应,协调者确定完成事务)。

    CAP:一致性(分布式系统中的所有数据备份在同一时刻是否同样的值)、可用性(集群某一部分节点故障后整体是否还能响应客户端读写请求)、分区容错性(分区相当于对通信时限要求,系统如果不能在时限内达成数据一致性,意味着发生了分区情况,必须就当前操作在C和A之间做出选择)。

    BASE:基本可用(Basically Available),柔性状态(Soft State)、最终一致性。

    柔性事务:两阶段型、补偿型(TCC)、异步确保型(最终消息一致性)、最大努力通知型。

     

springboot

  • 相对于spring更加纯注解开发,减少配置和大量样板代码。

  • 与spring生态系统的天然结合。

  • 可以使用内置工具(如Maven和Gradle)开发管理jar和测试Spring Boot应用程序。

  • 提供内嵌的HTTP服务器。

  • 提供命令行接口工具

1. 读取配置文件信息

  • @Value("${property}") 不被推荐

  • @ConfigurationProperties(prefix = "library") 与bean绑定,改bean注解@Component

  • @EnableConfigurationProperties(ProfileProperties.class) 用于启动类上, 配置类用@ConfigurationProperties("my-profile"),这个可以加@notnull之类的校验

  • @PropertySource("classpath:website.properties") 读取指定文件,加@Component

2.全局异常

  • @ControllerAdvice(assignableTypes = {ExceptionController.class})用于类、@ExceptionHandler(value = Exception.class)用于方法。

  • @ResponseStatus(code = HttpStatus.NOT_FOUND) 返回特定的码,用于异常类。

  • 方法里throw new ResponseStatusException,没效果。

过滤器

  • @Configuration类下生成FilterRegistrationBean的@Bean

@Bean
   public FilterRegistrationBeanMyFilter> setUpMyFilter() {
       FilterRegistrationBeanMyFilter> filterRegistrationBean = new                FilterRegistrationBean();
       filterRegistrationBean.setOrder(2);
       filterRegistrationBean.setFilter(myFilter);
       filterRegistrationBean.setUrlPatterns(new ArrayList(Arrays.asList("/api/*")));
?
       return filterRegistrationBean;
  }

多个过滤器按指定顺序执行的话需要用上面的方法。

  • @WebFilter(filterName = "MyFilterWithAnnotation", urlPatterns = "/api/*"),启动类要加@ServletComponentScan

参数校验

springboot引进来的基础包上已经包含所要用的校验注解,其中在方法上使用在类的,加@Valid,单独参数的@Valid @NotNull之类(此时controller需要加注解@Validated)。值得一提的是,因为之前有同事说处理参数校验出错的时候,不用在代理里处理bindResult,其实这种说法并不对,因为不单独处理的话时候会抛出异常MethodArgumentNotValidException,如果使用全局异常处理的话,就可以集中处理这类问题。

除了contoller之外,service这些也可以使用

@Service
@Validated
public class PersonService {
?
   public void validatePerson(@Valid Person person){
       // do something
  }
}
  • 自定义校验,实现的一个注解@interface,再实现一个具体类实现ConstraintValidator接口。

  • 校验组,例@NotNull(groups = DeletePersonGroup.class),DeletePersonGroup是接口,在方法上使用@Validated(AddPersonGroup.class)即可生效。

 

设计模式

SOLID模式

  1. 单一原则:对象单一功能

  2. 开闭原则:对拓展开放,对修改关闭

  3. 里氏替换:程序中的对象应该是可以在不改变程序正确性的前提下被它的子类所替换的

  4. 接口隔离:多个特定客户端接口要好于一个宽泛用途的接口

  5. 依赖倒转:代码应当取决于抽象概念,而不是具体实现

工厂模式:主要功能都是帮助我们把对象的实例化部分抽取出来,降低耦合度,增强拓展性。

简单工厂模式:实现对象的创建和对象使用分离,将对象的创建交给专门的工厂类负责,缺点在于工厂类不够灵活,增加新的具体产品需要修改成功类的判断逻辑,多的时候就非常复杂。例如传进来一个字符串,case判断得到一个所需的指定对象。

工厂方法模式:通过定义一个抽象的核心工厂类,并定义创建产品对象的接口。创建具体产品实例工作延迟到其工厂子类去完成。增加一个产品的时候,只需要增加一个具体实现的工厂子类。简单来说,客户端需要什么样的产品可以调子类方法创建对应的产品。

抽象工厂模式:处理一个工厂产生不同的产品,定义一个抽象类包括不同产品的创建。

单例模式、适配器模式、观察者模式、模板方法模式、代理模式、策略模式、外观模式、建造者模式、原型模式、修饰模式、状态模式、备忘录模式、组合模式、迭代器模式、桥接模式、命令模式、职责链模式、中介者模式、享元模式、解释器模式、访问者模式。

 

数据库

优化方向:数据库表设计,良好的SQL、分库分表,读写分离、缓存、搜索引擎、硬件升级、索引使用、系统配置

SQL调优

  1. 命中查询缓存

  2. explain使用索引

  3. limit 1

  4. 搜索字段走索引

  5. join字段走索引(属性相同,字符集系统)

  6. 避免使用order by rand()

  7. 避免select *,消耗网络传输、需要查数据字典、走不了索引字段需要回表,客户端映射字段多有消耗。

第一范式是字段最小不可拆解,第二范式是字段和主键关联依赖,第三范式是字段和主键无间接关联(传递依赖)无冗余。

索引类型:普通索引、唯一索引、主键索引、组合索引。

主从复制:主机更新记录到二进制日志binlog中,从服务器从主服务器中不断读取binlog日志到自己的中继日志(replay log),从服务器重做中继日志的时间,把更新应用到自己的数据库上。

MVCC 一词代表的是多版本并发控制,只在READ COMMITED和REPEATABLE READ两个隔离级别下工作。

mysql存储引擎

  1. MyISAM:性能极佳,全文索引,压缩。但不支持事务和行级锁,只有表级锁,查询较快,崩溃无法恢复,不支持外键,不支持MVCC

  2. InnoDB:支持外键,事务/回滚,行级锁,崩溃可恢复,支持MVCC

mysql索引 :有Hash索引(单个查询)、BTree索引,全文索引(full text)。

  1. MyISAM:B+Tree树叶节点的data域就是数据记录的地址,在索引检索的时候,先走B+Tree搜索索引,如果指定KEY存在,就取出data域的值,然后更新域值存的地址读取相应的数据记录。非聚簇索引

  2. InnoDB:其数据文件本身就是索引文件,相比MyISAM,索引文件和数据文件是分离的,其表数据文件本身就是按B+Tree组织的一个索引结构,树的叶节点data域保存完整的数据记录。这个索引的key是数据表的主键,因此InnoDB表数据文件本身就是主索引,这称为聚簇索引。其他索引是辅助索引,叶节点为主键key和索引字段数据。

数据库事务

ACID:原子性、一致性、隔离性、持久性

导致问题,1.脏读(读到别的事务尚未提交的数据),2.丢失修改(当一个事务修改是,另一个事务也在修改覆盖,导致前面事务修改丢失),3.不可重复读(两次读的数据不一致),4.幻读(读着的数据行数变化)

SQL标准定义四个隔离级别

  1. READ-UNCOMMITTED(读取未提交),允许读取尚未提交的数据。导致脏读,幻读,不可重复读

  2. READ-COMMITTED(读取已提交),允许读取并发事务已经提交的数据,还会幻读,不可重复读

  3. REPEATABLE-READ(可重复读),对同一个字段多次读取结果完全一致除非自己改,还会幻读

  4. SERIALIZABLE(可串行化),最高隔离级别,完全ACID隔离,一个个事务依次执行

mysql默认的是可重复读,使用next-key lock锁算法避免幻读。

InnoDB存储引擎锁算法:record lock(行锁)、Gap lock(间隙锁,锁一个范围,不包括记录本身),Next-key lock(record+gap锁定一个范围,包括记录本身)

 

redis

应用场景

list队列做一个FIFO双向队列,实现一个轻量级高性能消息队列服务。

用它的Set做高性能的tag系统。

会话缓存。

全页缓存。

排行榜/计数器。

发布/订阅。

使用redis的好处是,一处缓存多处使用,同时有丰富的数据类型。redis是单线程的,但是IO多路复用。

数据类型:String、List、Hash、Set、Sorted Set、bitmap位图,geo地理位置,hyperloglog等

过期删除:定期扫描随机删除,惰性删除查的时候检查是否要删。

提供了一些内存淘汰机制,一般用allkey-lru(内存不足时,删除最近最少使用的key)

  1. noeviction:默认策略,不淘汰,如果内存已满,添加数据是报错。

  2. allkeys-lru:在所有键中,选取最近最少使用的数据抛弃。类似LinkedHashMap。

  3. allkeys-random: 在所有键中,随机抛弃。

  4. volatile-lru:在设置了过期时间的所有键中,选取最近最少使用的数据抛弃。

  5. volatile-random: 在设置了过期时间的所有键,随机抛弃。

  6. volatile-ttl:在设置了过期时间的所有键,抛弃存活时间最短的数据。

持久化机制:RDB、AOF(数据有改就写、每秒写一次、让操作系统决定)

缓存雪崩:本身就要随机地设置过期时间。尽量保证redis集群高可用性,发现机器宕机尽快补上,选择合适的内存淘汰策略。事中本地ehcache缓存+hystrix限流&降级,避免Mysql崩掉。事后利用redis持久化机制恢复缓存。

还可以加锁或者队列来控制读数据库写缓存的线程数量,或者做二级缓存,A1缓存没有查A2(设置长期)。

缓存击穿:一个key失效了被大量访问:永不过期+异步刷新,互斥锁只能有一个线程更新缓存。

缓存穿透:null也缓存时间短些,布隆过滤器。

缓存一致:肯定不能读写串行化,一般是先更新数据库,再删除缓存(更新复杂,尤其涉及多表。写频繁就改频繁,再者也是赌你不会那么快读数据),但是在更新数据库的过程中,缓存的数据还是旧的。面对高并发之下的不一致(先删缓存再更数据),更新数据的时候,根据数据的唯一标识,将操作路由之后,发送到一个jvm内部队列中。读取数据的时候,如果发现数据不在缓存中,那


评论


亲,登录后才可以留言!