设计模式:
说一下你熟悉的设计模式?
- 单例模式:保证被创建一次,节省系统开销。
- 工厂模式(简单工厂、抽象工厂):解耦代码。
- 观察者模式:定义了对象之间的一对多的依赖,这样一来,当一个对象改变时,它的所有的依赖者都会收到通知并自动更新。
- 外观模式:提供一个统一的接口,用来访问子系统中的一群接口,外观定义了一个高层的接口,让子系统更容易使用。
- 模版方法模式:定义了一个算法的骨架,而将一些步骤延迟到子类中,模版方法使得子类可以在不改变算法结构的情况下,重新定义算法的步骤。
- 状态模式:允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它的类。
什么是单例模式?有几种?
- 单例模式:某个类的实例在多线程环境下只会被创建一次出来。
- 单例模式有饿汉式单例模式、懒汉式单例模式和双检锁单例模式三种。
- 饿汉式:线程安全,一开始就初始化。
public class Singleton{
private static Singleton instance = new Singleton();
private Singleton(){}
public static Singleton getInstance(){
return instance;
}
}
- 懒汉式:非线程安全,延迟初始化。
public class Singleton{
private static Singleton instance;
private Singleton(){}
public static Singleton getInstance(){
if(instance == null ){
instance = new Singleton();
}
return instance;
}
}
- 双检锁:线程安全,延迟初始化。
public class Singleton{
private volatile static Singleton singleton;
private Singleton(){}
public static Singleton getSingleton(){
if(singleton == null){
synchronized(Singleton.class){
singleton = new Singleton();
}
}
return singleton;
}
}
简单工厂和抽象工厂有什么区别?
简单工厂:是由一个工厂对象创建产品实例,简单工厂模式的工厂类一般是使用静态方法,通过不同的参数的创建不同的对象实例。用来生产同一等级结构中的任意产品,对于增加新的产品,无能为力。
工厂方法:用来生产同一等级结构中的固定产品,支持增加任意产品。
抽象工厂:提供一个创建一系列相关或相互依赖对象的接口,而无需制定他们具体的类。用来生产不同产品族的全部产品,对于增加新的产品,无能为力;但支持增加产品族。
Spring:
为什么要使用 Spring?
- Spring 提供 IOC 技术,容器会帮你管理依赖的对象,从而不需要自己创建和管理依赖对象了,更轻松的实现了程序的解耦。
- Spring提供了事务支持,使得事务操作变的更加方便。
- Spring 提供了面向切片编程,这样可以更方便的处理某一类的问题。
- 更方便的框架集成,Spring 可以很方便的集成其他框架,比如 MyBatis、hibernate 等。
Spring 有哪些主要模块?
- Spring Core:框架的最基础部分,提供IOC 和依赖注入特性。
- Spring Context:构建于 core 封装包基础上的context 封装包,提供了一种框架式的对象访问方法。
- Spring Dao:(Data Access Object)提供了JDBC的抽象层。
- Spring AOP:提供了面向切面的编程实现,让你可以自定义拦截器、切点等。
- Spring Web:提供了针对Web 开发的集成特性,例如文件上传,利用 servlet listeners 进行IOC 容器初始化和针对 Web 的ApplicationContext。
- Spring Web MVC:Spring 中的 MVC 封装包提供了 Web 应用的Model-View-Controller(MVC)的实现。
Spring 常用的注入方式有哪些?
Setter 注入
构造方法注入
注解注入
Spring提供了哪些配置方式?
- xml配置文件
- 基于注解的方式
- 基于Java的方式
Spring 3.x以后,可以通过Java代码装配Bean。通过@Bean、@Component、getBean方式进行Bean的注册和发现。
Spring 中的 Bean 是线程安全的吗?
Spring 中的 Bean 默认是单例模式,Spring 框架并没有对单例 Bean 进行多线程的封装处理。
实际上大部分时候 Spring Bean 无状态的(比如 dao 类),所以某种程度上来说 bean 也是安全的,但如果 bean 有状态的话(比如 view model 对象),那就要开发者自己去保证线程安全了。
最简单的就是改变 bean 的作用域,把“singleton”变更为“prototype”,这样请求 bean 相当于 new Bean()了,所以就可以保证线程安全了。
补充:
有状态就是有数据存储功能。
无状态就是不会保存数据。
Spring 支持几种 Bean 的作用域?
Spring 支持 5 种作用域,如下:
- singleton:Spring IOC 容器中只存在一个 Bean 实例,Bean 以单例模式存在,是系统默认值;
- prototype:每次从容器调用 bean 时都会创建一个新的示例,既每次 getBean()相当于执行 new Bean()操作;
- Web 环境下的作用域: request:每次 http 请求都会创建一个 bean;
- session:同一个 http session共享一个 bean 实例;
- global-session:用于 portlet 容器,因为每个 portlet 有单独的session,globalsession 提供一个全局性的 http session。
注意: 使用 prototype作用域需要慎重的思考,因为频繁创建和销毁 bean 会带来很大的性能开销。
Spring 自动装配 Bean 有哪些方式?
no:默认值,表示没有自动装配,应使用显式 bean 引用进行装配。
byName:它根据 bean 的名称注入对象依赖项。
byType:它根据类型注入对象依赖项。
构造函数:通过构造函数来注入依赖项,需要设置大量的参数。
Autodetect:容器首先通过构造函数使用 Autowire 装配,如果不能,则通过 byType 自动装配。
Spring 的对象默认是单例的还是多例的? 单例 bean 存不存在线程安全问题呢?
- 在 Spring 中的对象默认是单例的,但是也可以配置为多例。
- 单例 bean 对象对应的类存在可变的成员变量并且其中存在改变这个变量的线程时, 多线程操作该 bean 对象时会出现线程安全问题。
- 原因是:多线程操作如果改变成员变量,其他线程无法访问该 bean 对象,造成数据
混乱。 - 解决办法:在 bean 对象中避免定义可变成员变量; 在 bean 对象中定义一个 ThreadLocal 成员变量,将需要的可变成员变量保存在 ThreadLocal 中。
- 原因是:多线程操作如果改变成员变量,其他线程无法访问该 bean 对象,造成数据
Spring 事务的实现方式和实现原理
- Spring 事务的本质其实就是数据库对事务的支持,没有数据库的事务支持,Spring 是 无法提供事务功能的。真正的数据库层的事务提交和回滚是通过 binlog 或者 redo log 实现的。
- Spring 事务实现主要有两种方法
1、编程式,beginTransaction()、commit()、rollback()等事务管理相关的方法
2、声明式,利用注解 Transactional 或者 aop 配置
Spring 的事务传播行为
Spring 事务的传播行为说的是,当多个事务同时存在的时候,Spring 如何处理这些事 务的行为。
- PROPAGATION_REQUIRED:如果当前没有事务,就创建一个新事务,如果当前存在事务,就加入该事务,该设置是最常用的设置。
- PROPAGATION_SUPPORTS:支持当前事务,如果当前存在事务,就加入该事务, 如果当前不存在事务,就以非事务执行。
- PROPAGATION_MANDATORY:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就抛出异常。
- PROPAGATION_REQUIRES_NEW:创建新事务,无论当前存不存在事务,都创 建新事务。
- PROPAGATION_NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事 务,就把当前事务挂起。
- PROPAGATION_NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。
- PROPAGATION_NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则按 REQUIRED 属性执行。
备注(方便记忆):
propagation 传播
require 必须的
suppor 支持
mandatory 强制托管
requires-new 需要新建
not -supported 不支持
never 从不
nested 嵌套的
说一下 Spring 的事务隔离?
Spring 有五大隔离级别,默认值为 ISOLATION_DEFAULT(使用数据库的设置),其他四个隔离级别和数据库的隔离级别一致:
- ISOLATION_DEFAULT:用底层数据库的设置隔离级别,数据库设置的是什么我就用什么;
- ISOLATION_READ_UNCOMMITTED:读未提交。最低隔离级别。事务未提交前,就可被其他事务读取(会出现幻读、脏读、不可重复读);
- ISOLATION_READ_COMMITTED:读已提交。一个事务提交后才能被其他事务读取到(会造成幻读、不可重复读),SQL server的默认级别;
- ISOLATION_REPEATABLE_READ:可重复读。保证多次读取同一个数据时,其值都和事务开始时候的内容是一致,禁止读取到别的事务未提交的数据(会造成幻读),MySQL的默认级别;
- ISOLATION_SERIALIZABLE:序列化。代价最高最可靠的隔离级别,该隔离级别能防止脏读、不可重复读、幻读。
补充:
- 脏读 :表示一个事务能够读取另一个事务中还未提交的数据。比如,某个事务尝试插入记录A,此时该事务还未提交,然后另一个事务尝试读取到了记录A。
- 不可重复读 :是指在一个事务内,多次读同一数据。
- 幻读 :指同一个事务内多次查询返回的结果集不一样。比如同一个事务 A 第一次查询时候有 n 条记录,但是第二次同等条件下查询却有 n+1条记录,这就好像产生了幻觉。发生幻读的原因也是另外一个事务新增或者删除或者修改了第一个事务结果集里面的数据,同一个记录的数据内容被修改了,所有数据行的记录就变多或者变少了。
BeanFactory 和 ApplicationContext 有什么区别
- BeanFactory:
- Spring 最顶层的接口,实现了 Spring 容器的最基础的一些功能, 调用起来比较麻烦, 一般面向 Spring 自身使用
- BeanFactory 在启动的时候不会去实例化 Bean,从容器中拿 Bean 的时候才会去实例化
- ApplicationContext:
- 是 BeanFactory 的子接口,扩展了其功能, 一般面向程序员身使用
- ApplicationContext 在启动的时候就把所有的 Bean 全部实例化了
Spring 的通知类型有哪些,分别在什么时候执行?
Spring 的通知类型有四种,分别为:
- 前置通知[]before]:在切点运行之前执行
- 后置通知[after-returning]:在切点正常结束之后执行
- 异常通知[after-throwing]:在切点发生异常的时候执行
- 最终通知[after]:在切点的最终执行
- Spring 还有一种特殊的通知,叫做环绕通知.环绕通知运行程序员以编码的方式自己定义通知的位置, 用于解决其他通知时序问题
Spring MVC:
谈一下你对 SpringMVC 框架的理解
SpringMVC 是一个基于 Java 的实现了 MVC 设计模式的请求驱动类型的轻量级 Web框架,通过把 Model,View,Controller 分离,将 web 层进行职责解耦,把复杂的 web应用分成逻辑清晰的几部分,简化开发,减少出错,方便组内开发人员之间的配合。
在我看来,SpringMVC 就是将我们原来开发在 Servlet 中的代码拆分了,一部分由
SpringMVC 完成,一部分由我们自己完成
说一下 Spring MVC 运行流程?
- 用户发送请求到前端控制器(DispatcherServlet)
- 前 端 控 制 器 ( DispatcherServlet ) 收 到 请 求 调 用 处 理 器 映 射 器(HandlerMapping),去查找处理器(Handler)。处理器映射器(HandlerMapping)找到具体的处理器(可以根据 xml 配置、注解进行查找),生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet。
- 前端控制器(DispatcherServlet)请求处理器适配器执行Handler,处理器适配器(HandlerAdapter)去调用自定义的处理器类(Controller,也叫后端控制器)。
- 自定义的处理器类(Controller,也叫后端控制器)将得到的参数进行处理并返回结果,处 理 器 适 配 器 ( HandlerAdapter ) 将 得 到 的 结 果 返 回 给 前 端 控 制 器(DispatcherServlet)
- DispatcherServlet( 前 端 控 制 器 ) 将 ModelAndView 传 给 视 图 解 析 器(ViewReslover)
- 视图解析器(ViewReslover)将得到的参数从逻辑视图转换为物理视图并返回给前端控制器(DispatcherServlet)
- 前端控制器(DispatcherServlet)调用物理视图进行渲染并返回
- 前端控制器(DispatcherServlet)将渲染后的结果返回
Spring MVC 有哪些组件?
- 前端控制器 DispatcherServlet:接收请求、响应结果,相当于转发器,有了
DispatcherServlet 就减少了其它组件之间的耦合度。 - 处理器映射器 HandlerMapping:根据请求的 URL 来查找 Handler
- 处理器适配器 HandlerAdapter:负责执行 Handler
- 处理器 Handler:处理业务逻辑的 Java 类
- 视图解析器 ViewResolver:进行视图的解析,根据视图逻辑名ModelAndView 解析成真正的视图(view)
- 视图 View : View 是 一 个接口 , 它的实现类支持不同的视图类型 ,如jsp,freemarker,pdf 等等
@RequestMapping 的作用是什么?
将 http 请求映射到相应的类/方法上。
@Autowired 的作用是什么?
@Autowired 它可以对类成员变量、方法及构造函数进行标注,完成自动装配的工作,通过@Autowired 的使用来消除 set/get 方法。
说一下 SpringMVC 支持的转发和重定向的写法
- 转发:
forward 方式:在返回值前面加"forward:“,如"forward:user.do?name=method4” - 重定向:
redirect 方式:在返回值前面加 redirect:, 比如"redirect:http://www.baidu.com"
在 SpringMVC 中, 如果想通过转发将数据传递到前台,有几种写法?
方式一:直接使用 request 域进行数据的传递
request.setAttirbuate("name", value);
方式二:使用 Model 进行传值,底层会将数据放入 request 域进行数据的传递
model.addAttribuate("name", value);
方式三:使用 ModelMap 进行传值,底层会将数据放入 request 域进行数据的传递
modelmap.put("name",value);
方式四:借用 ModelAndView 在其中设置数据和视图
mv.addObject("name",value);
mv.setView("success");
return mv;
谈一下 SpringMVC 统一异常处理的思想和实现方式
使用 SpringMVC 之后,代码的调用者是 SpringMVC 框架,也就是说最终的异常会抛到框架中,然后由框架指定异常处理类进行统一处理
- 方式一: 创建一个自定义异常处理器(实现 HandlerExceptionResolver 接口),并实现里面的异常处理方法,然后将这个类交给 Spring 容器管理
- 方式二: 在类上加注解(@ControllerAdvice)表明这是一个全局异常处理类,并在方法上加注解(@ExceptionHandler),在 ExceptionHandler 中有一个 value 属 性,可以指定可以处理的异常类型
拦截器
java里的拦截器是动态拦截Action调用的对象,它提供了一种机制可以使开发者在一个Action执行的前后执行一段代码,也可以在一个Action执行前阻止其执行,同时也提供了一种可以提取Action中可重用部分代码的方式。在AOP中,拦截器用于在某个方法或者字段被访问之前,进行拦截,然后再之前或者之后加入某些操作
什么是过滤器
过滤器是一个程序,它先于与之相关的servlet或JSP页面运行在服务器上。过滤器可附加到一个或多个servlet或JSP页面上,并且可以检查进入这些资源的请求信息。
过滤器与拦截器的区别
过滤器可以简单的理解为“取你所想取”,过滤器关注的是web请求;拦截器可以简单的理解为“拒你所想拒”,拦截器关注的是方法调用,比如拦截敏感词汇。
- 拦截器是基于java反射机制来实现的,而过滤器是基于函数回调来实现的。
- 拦截器不依赖servlet容器,过滤器依赖于servlet容器。
- 拦截器只对Action起作用,过滤器可以对所有请求起作用。
- 拦截器可以访问Action上下文和值栈中的对象,过滤器不能。
- 在Action的生命周期中,拦截器可以多次调用,而过滤器只能在容器初始化时调用一次。
在 SpringMVC 中拦截器的使用步骤是什么样的?
- 定义拦截器类:
SpringMVC 为 我 们 提 供 了 拦 截 器 规 范 的 接 口 , 创 建 一 个 类 实 现
HandlerInterceptor,重写接口中的抽象方法;
1)preHandle 方法:在调用处理器之前调用该方法,如果该方法返回 true 则请
求继续向下进行,否则请求不会继续向下进行,处理器也不会调用
2)postHandle 方法:在调用完处理器后调用该方法
3)afterCompletion 方法:在前端控制器渲染页面完成之后调用此方法 - 注册拦截器: 在 SpringMVC 核心配置文件中注册自定义的拦截器
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="拦截路径规则"/>
<mvc:exclude-mapping path="不拦截路径规则"/>
<bean class="自定义拦截器的类全限定名"/>
</mvc:interceptor>
</mvc:interceptors>
在 SpringMVC 中文件上传的使用步骤是什么样的? 前台三要素是什么?
文件上传步骤:
- 加入文件上传需要的 commons-fileupload 包
- 配置文件上传解析器,springmvc 的配置文件的文件上传解析器的 id 属性必须为multipartResolver
- 后端对应的接收文件的方法参数类型必须为 MultipartFile,参数名称必须与前端
的 name 属性保持一致
文件上传前端三要素:
- form 表单的提交方式必须为 post
- enctype 属性需要修改为:multipart/form-data
- 必须有一个 type 属性为 file 的 input 标签,其中需要有一个 name 属性;如果需 要上传多个文件需要添加 multiple 属性
SpringMVC 中如何解决 GET|POST 请求中文乱码问题?
(1)解决 post 请求乱码问题:
在 web.xml 中配置一个 CharacterEncodingFilter 过滤器,设置成 utf-8;
<filter>
<filter-name>CharacterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter
</ filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>utf-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CharacterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
(2) get 请求中文参数出现乱码解决方法有两个:
①修改 tomcat 配置文件添加编码与工程编码一致,如下:
<ConnectorURIEncoding="utf-8" connectionTimeout="20000" port="8080"
protocol="HTTP/1.1" redirectPort="8443"/>
②另外一种方法对参数进行重新编码:
String userName= new String(
request.getParamter("userName").getBytes("ISO8859-1"),"utf-8")
ISO8859-1 是 tomcat 默认编码,需要将 tomcat 编码后的内容按 utf-8 编码。
Spring IOC
解释一下什么是 IOC?
IOC:Inversion of Control(中文:控制反转)是 Spring 的核心,对于 Spring 框架来说,就是由 Spring 来负责控制对象的生命周期和对象间的关系。
- IOC就是控制反转,是指创建对象的控制权的转移,以前创建对象的主动权和时机是由自己把控的,而现在这种权力转移到Spring容器中,并由容器根据配置文件去创建实例和管理各个实例之间的依赖关系,对象与对象之间松散耦合,也利于功能的复用。
- DI依赖注入,和控制反转是同一个概念的不同角度的描述,即 应用程序在运行时依赖IoC容器来动态注入对象需要的外部资源。
- 最直观的表达就是,IOC让对象的创建不用去new了,可以由spring自动生产,使用java的反射机制,根据配置文件在运行时动态的去创建对象以及管理对象,并调用对象的方法的。
- Spring的IOC有三种注入方式 :构造器注入、setter方法注入、根据注解注入。
IoC让相互协作的组件保持松散的耦合,而AOP编程允许你把遍布于应用各层的功能分离出来形成可重用的功能组件。
解决Maven重复依赖问题(同一个jar,多个版本)
-
重复依赖:
在maven工程中往往会有重复依赖的问题,比如创建的工程A,依赖了b-1.0.jar,而b-1.0.jar又依赖了d-1.0.jar(这个我们本身是不能直接看到的),同时我们自己的工程又依赖了d-2.1.jar,或者工程A依赖了c-1.0.jar,c-1.0.jar依赖了d-2.0.jar,显然,d.jar有3个版本,3者之间是重复的,甚至是冲突的 -
重复依赖导致的问题:
首先从工程角度来讲,引用了同一个Jar的不同版本,这肯定是依赖有问题,或者就是错误的。
其次,重复依赖,在项目启动过程当中可能会有一些警告信息。
当然,最重要的是引发代码异常,最常见的就是NoSuchMethod。 -
解决思路:
- 寻找重复引用的jar。
- 定位这些Jar在哪里被引用了。
- 接下来需要分析舍与留,原则上保留高版本,大多数情况下是向下兼容的。但是不一定,有时候也得保留低版本,或者有时候两者都需要保留。
- 如果是一个工程,其实处理起来还比较好处理。但是如果有多个工程,最终我们可能将所有的依赖合在一块儿。处理起来可能会稍微麻烦些,比如工程1依赖了2.1版本,工程2依赖了2.2版本,你把工程1的2.1的依赖去掉,但同时还需要把2.2的加在工程1上面,否则可能编译不通过。
- 最重要的就是,调整之后,尽可能做全面测试。特别是一些间接依赖,如果去除的话,编译不会有问题,但运行起来会有问题。
Spring AOP
解释一下什么是 AOP?
- AOP (Aspect Oriented Programming)面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。
- 利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
- 简单来说就是统一处理某一“切面”(类)的问题的编程思想,比如统一处理日志、异常等。
- AOP技术并不是Spring框架内置技术,而是一门相对独立的技术,只是Spring框架很好的支持了AOP,并且,由于Spring框架被大量项目所使用,所以,目前主流的AOP实现都是基于Spring AOP的。
如何写一个切面?
1、定义注解 @RequestLog
2、定义切面并设置切点
- @Pointcut 就是切点,此处的切点定义的就是刚才定义的注解,当某些方法加上我的注解就走该切面
3、写切面的逻辑,此时是环绕还是前置还是后置自己考虑(根据实际业务情况)
- 此处根据自己的业务逻辑去判断该用什么类型的通知
- 前置通知:执行加了切面本方法前执行该业务逻辑
- 后置通知:执行加了切面的本方法后执行该业务逻辑
- 环绕通知:执行前和执行后均执行该业务逻辑 @Around
4、在需要走切面的地方(方法 或 controller 上加上@RequestLog这个注解)
注意:如果是对异常进行拦截,记录异常发生的时间以及ip地址等等,这种不需要你去自定义注解的,我们的切点就是 Exception.class 那么只要写个切面就好无需自定义注解