一.Spring
1.说下什么是spring?
spring是一款主流的非侵入式设计、轻量级web框架,最大特点是面向Java bean编程,降低了类之间的耦合性,其IOC、DI是spring 容器的核心,为bean提供了容身之处以及帮我们管理对象及其依赖关系,也为spring后续发展奠定基石,同时还集成了优秀理念AOP、MCV等,主要包含CORE、ORM、Context、Web、Web MVC、AOP、DAO等组件,一定程度上简化了web开发。
2. spring的模块有哪些?
- Spring Core:主要提供IOC和DI功能,是其他模块的基石。
- Spring AOP:提供面向切面的编程实现,支持JDK与CGLIB。
- Spring JDBC:Java数据库连接。
- Spring JMS:Java消息服务。
- Spring ORM:支持扩展Hibernate、mybatis等ORM工具。
- Spring Web:为创建Web应用程序提供支持
- Spring Test:提供了对JUnit和TestNG测试的支持。
- Spring tx:事务管理
3. 怎么理解spring的ioc(对spring ioc的理解和使用)?
-
首先spring会通过BeanDefinitionReader加载我们定义的bean信息到BeanFactory形成BeanDefinition,接着BeanFactoryPostProcessor会对BeanFactory进行前置和后置处理,比如占位符替换,之后就行成了完整的BeanDefinition对象
-
接着就会去创建Bean对象,这就来到了bean的生命周期,第一步会进行实例化,第二步会针对Bean中的属性进行赋值操作,第三步是初始化,这里会先检查是否实现了Aware接口,接着BeanPostProcessor会进行前置处理,接着会看bean是否实现了InitailizinBean接口,是否定义了init-method和@PostConstruct注解的方法,接着会进行BeanPostProcessor后置处理,这样初始化就完成了,后面就是Bean的使用和销毁。
4. Spring的Bean是如何创建的?
- 根据Context类型(xml、注解)初始化BeanDefinitionReader,通过BeanDefinitionReader确认哪些Bean需要被初始化,然后将一个个的bean信息封装层BeanDefinition,最后在包装成BeanDefinitionWrapper,放入到BeanDefinitionRegister对应map中。
- 遍历所有BeanDefinition,按照 实例化-> 依赖注入->初始化->AOP的顺序通过反射去创建bean对象
- 将创建完成的Bean放入到一级缓存中存储,供用户使用。
5. Spring注入Bean有哪几种方式?
- 通过xml方式注入bean
<bean id="bean" class="beandemo.Bean" />
- 通过注解注入bean
public class MyBean{ } //创建一个class配置文件 @Configuration public class MyConfiguration{ //将一个Bean交由Spring进行管理 @Bean public MyBean myBean(){ return new MyBean(); } }
- 通过构造方法注入bean
@Component public class TestBeanConstructor { private AnotherBean anotherBeanConstructor; @Autowired public TeanBeanConstructor(AnotherBean anotherBeanConstructor){ this.anotherBeanConstructor = anotherBeanConstructor; } @Override public String toString() { return "TeanBean{" + "anotherBeanConstructor=" + anotherBeanConstructor + '}'; } }
- 通过set方法注入Bean
@Component public class TestBeanSet { private AnotherBean anotherBeanSet; @Autowired public void setAnotherBeanSet(AnotherBean anotherBeanSet) { this.anotherBeanSet = anotherBeanSet; } @Override public String toString() { return "TestBeanSet{" + "anotherBeanSet=" + anotherBeanSet + '}'; } }
- 通过属性去注入Bean
@Component public class TestBeanProperty { @Autowired private AnotherBean anotherBeanProperty; @Override public String toString() { return "TestBeanProperty{" + "anotherBeanProperty=" + anotherBeanProperty + '}'; } }
- 通过List注入Bean
@Component public class TestBeanList { private List<String> stringList; @Autowired public void setStringList(List<String> stringList) { this.stringList = stringList; } public List<String> getStringList() { return stringList; } }
- 通过Map去注入Bean
@Component public class AfterClassServiceDataUploadJob { @Autowired private List<AfterSchoolProgramService> dataUploadAbstractServices; }
6. 说说bean的生命周期?
对于Spring Bean的生命周期来说,可以分为四个阶段:
实例化 Instantiation
属性赋值 Populate
初始化 Initialization
销毁 Destruction
7. Spring常用的设计模式有哪些?
- 单例设计模式:Spring中bean的默认作用域就是singleton。spring的一级缓存就是使用的容器式单例
- 模板方法设计模式:Spring中jdbcTemplate、hibernateTemplate等以Template结尾的对数据库操作的 类,它们就使用到模板模式。
- 代理设计模式:Spring AOP就是基于动态代理的。如果要代理的对象,实现了某个接口,那么Spring AOP 会使用JDK Proxy,去创建代理对象,而对于没有实现接口的对象,就无法使用JDK Proxy去进行代理了,这 时候Spring AOP会使用Cglib,这时候Spring AOP会使用Cglib生成一个被代理对象的子类来作为代理。
- 工厂设计模式:Spring使用工厂模式可以通过BeanFactory或ApplicationContext创建bean对象。
- 观察者设计模式:Spring事件驱动模型就是观察者模式很经典的应用。 spring的事件流程: 1)定义一个事件: 实现一个继承自 ApplicationEvent,并且写相应的构造函数 2)定义一个事件监听者:实现 ApplicationListener 接口,重写 onApplicationEvent() 方法 3)使用事件发布者发布消息: 可以通过 ApplicationEventPublisher 的 publishEvent() 方法发布消息
- 策略设计模式:Spring 框架的资源访问接口就是基于策略设计模式实现的
- 装饰者设计模式:装饰者设计模式可以动态地给对象增加些额外的属性或行为。相比于使用继承,装饰者 模式更加灵活 Spring 中配置DataSource的时候,DataSource可能是不同的数据库和数据源。我们能否根据客户的需求在 少修改原有类的代码下切换不同的数据源?这个时候据需要用到装饰者模式。
- 适配器设计模式:适配器模式使得接口不兼容的那些类可以一起工作,其别名为包装器 在Spring MVC中,DispatcherServlet根据请求信息调用HandlerMapping,解析请求对应的Handler,解析 到对应的Handler(也就是我们常说的Controller控制器)后,开始由HandlerAdapter适配器处理
8. BeanFactory和Context(ApplicationContext)的区别
因为 ApplicationContext 包含 BeanFactory 的所有功能,所以通常建议在普通BeanFactory中使用。
- BeanFactory :延迟注入,相比于ApplicationContext 来说会占用更少的内存,程序启动速度更快。
- ApplicationContext :容器启动的时候,不管你用没用到,一次性创建所有 bean 。
- BeanFactory 仅提供了 最基本的依赖注入支持,ApplicationContext 扩展了 BeanFactory ,除了有BeanFactory的功能还有额外更多 功能,所以一般开发人员使ApplicationContext会更多
9. spring是如何解决循环依赖的?
spring利用singletonObjects, earlySingletonObjects, singletonFactories三级缓存去解决的,所说的缓存其实也就是三个Map。
可以看到三级缓存各自保存的对象,这里重点关注二级缓存earlySingletonObjects和三级缓存singletonFactory,一级缓存可以进行忽略。前面我们讲过先实例化的bean会通过ObjectFactory半成品提前暴露在三级缓存中。
singletonFactory是传入的一个匿名内部类,调用ObjectFactory.getObject()最终会调用getEarlyBeanReference方法。再来看看循环依赖中是怎么拿其它半成品的实例对象的。
我们假设现在有这样的场景AService依赖BService,BService依赖AService
- AService首先实例化,实例化通过ObjectFactory半成品暴露在三级缓存中
- 填充属性BService,发现BService还未进行过加载,就会先去加载BService
- 再加载BService的过程中,实例化,也通过ObjectFactory半成品暴露在三级缓存
- 填充属性AService的时候,这时候能够从三级缓存中拿到半成品的ObjectFactory
拿到ObjectFactory对象后,调用ObjectFactory.getObject()方法最终会调用getEarlyBeanReference()方法,getEarlyBeanReference这个方法主要逻辑大概描述下如果bean被AOP切面代理则返回的是beanProxy对象,如果未被代理则返回的是原bean实例,这时我们会发现能够拿到bean实例(属性未填充),然后从三级缓存移除,放到二级缓存earlySingletonObjects中,而此时B注入的是一个半成品的实例A对象,不过随着B初始化完成后,A会继续进行后续的初始化操作,最终B会注入的是一个完整的A实例,因为在内存中它们是同一个对象。下面是重点,我们发现这个二级缓存好像显得有点多余,好像可以去掉,只需要一级和三级缓存也可以做到解决循环依赖的问题???
只要两个缓存确实可以做到解决循环依赖的问题,但是有一个前提这个bean没被AOP进行切面代理,如果这个bean被AOP进行了切面代理,那么只使用两个缓存是无法解决问题,下面来看一下bean被AOP进行了切面代理的场景。
我们会发现再执行一遍singleFactory.getObject()方法又是一个新的代理对象,这就会有问题了,因为AService是单例的,每次执行singleFactory.getObject()方法又会产生新的代理对象,假设这里只有一级和三级缓存的话,我每次从三级缓存中拿到singleFactory对象,执行getObject()方法又会产生新的代理对象,这是不行的,因为AService是单例的,所有这里我们要借助二级缓存来解决这个问题
10. spring能解决那些循环依赖、不能解决那些循环依赖,为什么?
构造函数循环依赖(无法解决):
首先要解决循环依赖就是要先实例化,然后放入三级缓存暴露出来,那么如果是构造函数这一步循环依赖, 实例化的时候就会产生无限递归创建,所以不能别解决 如果是延迟加载的话可以解决(另当别论)
setter方式的多例的循环依赖(无法解决):
如果是多例的,在容器初始化的时候,不会去创建,所以早期没有放入到三级缓存中暴露出来,所以无法解 决循环依赖,会报错
setter方式的单例循环依赖(A依赖B,B依赖A):
A与B循环依赖,都要被代理的情况 1.A实例化->A放入三级缓存->依赖注入B->B实例化->B放入三级缓存->依赖注入a->去三级缓存拿a的代理 ->把代理的A放入二级->返回a的代理注入到B->b初始化->走后置通知获得代理b->B的代理放入一级缓存、 ->原生a依赖注入b->A初始化->后置通知,不走代理返回原生A->再偷天换日->放入一级缓存
11. 说说对spring AOP的理解和使用?
aop是一种面向切面编程的思想,是对oop思想的补充,是将多个类处理业务逻辑的公共部分抽离出来,达到现复用、简化代码、解耦的目的。主要通过动态生成其代理对象来实现,常见的主流技术有JDK和CGLIB。
spring的AOP是基于JDK和CGLIB两种动态代理技术作为支撑的,默认为JDK代理,可以收到配置强制使用CGLIB,如果代理类没有实现接口也会采用CGLIB进行代理,Spring AOP典型使用为事务、通知等,同时我们也可以用来做请求日志记录、鉴权等操作
12. Spring如何保证线程安全的?
首先spring并未帮我们解决线程安全的问题,在spring中bean的作用域分别为 单例、多列、request、session级别、容器级别goal-session,其中只有多列是线程安全的,其他都会有线程安全问题,因此如果要解决安全问题,可以将bean设置成多列模式,另外基于bean的创建特性,我们的bean如果是无状态bean,也不会有安全问题,因此我们在设置bean的时候尽量不要给bean动态赋值,保证bean为无状态bean,另外还可以给bean赋值或者操作值时加锁,不过性能比较低。
13. Spring事务和Mysql事务有什么区别?
spring是没有事务的,事务是由数据库本身决定的,spring只是管理事务,如果数据库本身不支持,那spring也不会生效。
14. 说说你对spring事务的理解?
分析:从事务的隔离级别、传播机制、spring事务三个方面作答
首先spring是没有事务的,事务是由数据库本身决定的,spring的事务开启方式有两种,一个是声明式事务,一个是编程式事务,声明式事务是通过添加Transaction注解 的方式开启事务,使用spring aop实现,一般加在类或者方法上,事务控制粒度比较大,但使用上比较方便,编程式事务通过 TransactionTemplate,控制事务的粒度小,但是代码侵入性比较强。
Spring事务提供了7中传播机制:
REQUIRED(必须的):是默认的传播机制,如果B方法中调用A,如果B中有事务,则A无论是否开启事务都会用B 的事务,任何地方出现异常A和B都回滚
REQUIRES_NEW(需要新的):每次都会开启一个新的事务,外面事务回滚,里面事务不会回滚
NESTED:当前存在事务,新建一个事务,嵌套在已有事务中做为一个子事务;当前不存在事务,新建一个事务。
SUPPORTS(1): 如果存在一个事务,支持当前事务。如果没有事务,则非事务的执行,完全依赖最外层事务
MANDATORY(强制性的):必须运行在事务里面
NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起
NEVER:以非事务方式执行,如果当前存在事务,则抛出异常 7. NESTED:开启新事务,提交事务依赖于外层事务,如果外层事务回滚,则里面事务也回滚
Spring事务还提供了四种隔离级别:
- DEFAULT(-1):数据库默认的隔离级别
- READ_UNCOMMITTED(1):读未提交ru,会导致脏读
- READ_COMMITTED(2):读已提交 rc 避免脏读,允许不可重复读和幻读
- REPEATABLE_READ(4):可重复读 rr 避免脏读,不可重复读,允许幻读,innodb存储引擎解决了幻读
- SERIALIZABLE:串行化(不会使用这种)
15.@Resource和@Autowired有什么区别
- 提供方不同
- @Autowired 是Spring提供的,@Resource 是J2EE提供的。
- 装配时默认类型不同
- @Autowired只按type装配,@Resource默认是按name装配。
- 使用区别
- @Autowired与@Resource都可以用来装配bean,都可以写在字段或setter方法上
- @Autowired默认按类型装配,默认情况下必须要求依赖对象存在,如果要允许null值,可以设置它的required属性为false。如果想使用名称装配可以结合@Qualifier注解进行使用。
- @Resource,默认按照名称进行装配,名称可以通过name属性进行指定,如果没有指定name属性,当注解写在字段上时,默认取字段名进行名称查找。如果注解写在setter方法上默认取属性名进行装配。当找不到与名称匹配的bean时才按照类型进行装配。但是需要注意的是,如果name属性一旦指定,就只会按照名称进行装配。
16. 说说springbean的作用域?
- singleton
- Spring的scope的默认值是singleton,Spring 只会为每一个bean创建一个实例,并保持bean的引用。
- prototype
- 每次注入或者通过Spring应用上下文获取的时候,都会创建一个新的bean实例;
- Spring一旦将Bean实例交给(注入)调用者,就不再持有这个bean的引用。就无法再执行bean定义的destroy-method。清除prototype作用域的bean对象并释放资源,是调用者的职责。
- request:
- HTTP request表示该针对每一次HTTP请求都会产生一个新的bean,仅适用于WebApplicationContext环境。
- session作用域
- 针对session中的每次HTTP请求都会产生一个新的bean,仅适用于WebApplicationContext环境。
- globalSession作用域:
- 在一个全局的HTTP Session中,一个bean定义对应一个实例。global session类似于HTTP Session作用域,它只有对portlet才有意义。对于Servlet的web应用就相当于session。
二.SpringMVC
1. SpringMVC是什么? 请说出你对它的理解?
SpringMVC是Spring将Web层基于MVC封装后的框架.
在没有SpringMVC之前,Web层的Servlet负责的事情很多,很杂. 例如:接收请求,调用service层处理请求,封装返回结果,响应信息给浏览器. SpringMVC将Servlet负责的事情分门别类,进行具体的划分. M-model: 封装数据 V-View: 封装视图 C-Controller: 处理器方法,用于接收请求
2. SpringMVC的执行流程是什么?
![]()
用户通过浏览器发起 HttpRequest 请求到前端控制器 (DispatcherServlet)。
DispatcherServlet 将用户请求发送给处理器映射器 (HandlerMapping)。
处理器映射器 (HandlerMapping)会根据请求,找到负责处理该请求的处理器,并将其封装为处理器执行链 返回 (HandlerExecutionChain) 给 DispatcherServlet
DispatcherServlet 会根据 处理器执行链 中的处理器,找到能够执行该处理器的处理器适配器(HandlerAdaptor) --注,处理器适配器有多个
处理器适配器 (HandlerAdaptoer) 会调用对应的具体的 Controller
Controller 将处理结果及要跳转的视图封装到一个对象 ModelAndView 中并将其返回给处理器适配器 (HandlerAdaptor)
HandlerAdaptor 直接将 ModelAndView 交给 DispatcherServlet ,至此,业务处理完毕
业务处理完毕后,我们需要将处理结果展示给用户。于是DisptcherServlet 调用 ViewResolver,将 ModelAndView 中的视图名称封装为视图对象
ViewResolver 将封装好的视图 (View) 对象返回给 DIspatcherServlet
DispatcherServlet 调用视图对象,让其自己 (View) 进行渲染(将模型数据填充至视图中),形成响应对象 (HttpResponse)
前端控制器 (DispatcherServlet) 响应 (HttpResponse) 给浏览器,展示在页面上。
3.SpringMVC的核心组件有哪些?作用是什么?
- DispatcherServlet: 总控制器/前端控制器,接收请求,调度 HandlerMapping: 处理器映射器。
- SpringMVC框架加载时,存放请求路径与处理器方法之间的映射关系。
- HandlerAdapter: 处理器适配器,适配处理器的实现方式,实现方式不同调用的适配器类不同。不同的适配器类,可以处理不同的处理器方法。
- ViewResolver: 视图解析器,根据逻辑视图,生成对应的物理视图,并将物理视图返回给总控制器
- Handler: 处理器,我们自己编写的用于处理器业务的方法
4. SpringMVC常用注解有哪些?
@RequestMapping 为处理器绑定浏览器访问的路径 @RequestBody 解析请求携带的json数据,封装到对应的javaBean中 @ResponseBody 将响应结果装换成json并响应给浏览器 @Controller 用于创建Controller层类对象,并将类对象存放到SpringMVC的IOC容器中 @ControllerAdvice 全局异常处理 全局数据绑定 全局数据预处理
5. @Controller注解的作用
用于创建Controller层类对象,并将类对象存放到SpringMVC的IOC容器中
6. @RequestMapping注解的作用
注解书写位置:
- 类上: 用于窄化请求路径,访问此类中的方法时,方法路径前都要加上此路径
- 方法上: 为处理器方法绑定浏览器访问的路径
7. RequestMapping和GetMapping有什么区别?
RequestMapping具有类属性的,可以进行GET,POST,PUT或者其它的注释中具有的请求方法。
GetMapping是GET 请求方法中的一个特例。它只是ResquestMapping的一个延伸,目的是为了提高清晰度。
7. SpringMVC怎么样设定重定向和转发的?
- 请求转发与重定向的区别:
- 请求转发在服务器端完成的;重定向是在客户端完成的。
- 请求转发的速度快;重定向速度慢。
- 请求转发的是同一次请求;重定向是两次不同请求。
- 请求转发不会执行转发后的代码;重定向会执行重定向之后的代码。
- 请求转发地址栏没有变化;重定向地址栏有变化。
- 请求转发必须是在同一台服务器下完成;重定向可以在不同的服务器下完成
- SpringMVC设定请求转发
- 在返回值前面加"forward:"。
- SpringMVC设定重定向
- 在返回值前面加"redirect:"。例如我们在登录的时候,登录失败会重定向到登录页面。
8. SpringMVC中拦截器编写方式?
SpringMVC中的拦截器实现方式有两种:
- 编写一个类实现HandlerInterceptor接口,重写抽象方
- 编写一个类继承HandlerInterceptorAdapter,选择性的重写方法
- 以上两种方式需要在核心配置文件中配置拦截的路径
9. SpringMvc的处理器是不是单例模式?如果是,有什么问题?怎么解决?
是单例模式,在多线程访问的时候有线程安全问题。 解决方案: 在控制器里面不能写可变状态量,如果需要使用这些可变的量,可以使用ThreadLocal解决,为每个线程单独生成一个变量,独立操作,互不影响。
三. Mybatis
1. MyBatis 是否支持延迟加载?延迟加载的原理是什么?
Mybatis仅支持association关联对象和collection关联集合对象的延迟加载,association指的就是一对一,collection指的就是一对多查询。在Mybatis配置文件中,可以配置是否启用延迟加载lazyLoadingEnabled=true|false。
它的原理是,使用CGLIB创建目标对象的代理对象,当调用目标方法时,进入拦截器方法,比如调用a.getB().getName(),拦截器invoke()方法发现a.getB()是null值,那么就会单独发送事先保存好的查询关联B对象的sql,把B查询上来,然后调用a.setB(b),于是a的对象b属性就有值了,接着完成a.getB().getName()方法的调用。这就是延迟加载的基本原理。
2. 说一下 MyBatis 的一级缓存和二级缓存?
一级缓存:Mybatis的一级缓存的作用域是session,当openSession()后,如果执行相同的SQL(相同语句和参数),Mybatis不进行执行SQL,而是从缓存中命中返回。一级缓存默认开启。
二级缓存:Mybatis的二级缓存的作用域是一个mapper的namespace,同一个namespace中查询sql可以从缓存中命中。二级缓存是可以跨session的。
开启二级缓存数据查询流程:二级缓存 -> 一级缓存 -> 数据库。
缓存更新机制:当某一个作用域(一级缓存 Session/二级缓存 Mapper)进行了C/U/D 操作后,默认该作用域下所有 select 中的缓存将被 clear。
3. MyBatis 与 Hibernate 有哪些不同?
- Mybatis 和 hibernate 不同,它不完全是一个 ORM 框架,因为 MyBatis 需要 程序员自己编写 Sql 语句
- Mybatis 直接编写原生态 sql,可以严格控制 sql 执行性能,灵活度高,非常 适合对关系数据模型要求不高的软件开发,因为这类软件需求变化频繁,一但需 求变化要求迅速输出成果。但是灵活的前提是 mybatis 无法做到数据库无关性, 如果需要实现支持多种数据库的软件,则需要自定义多套 sql 映射文件,工作量大。
- Hibernate 对象/关系映射能力强,数据库无关性好,对于关系模型要求高的 软件,如果用 hibernate 开发可以节省很多代码,提高效率。
4. Mybatis是如何将sql执行结果封装为目标对象并返回的?都有哪些映射形式?
- 使用标签,逐一定义数据库列名和对象属性名之间的映射关系。
- 使用 sql 列的别名功能,将列的别名书写为对象属性名。
有了列名与属性名的映射关系后,Mybatis 通过反射创建对象,同时使用反射给对象的属性逐一赋值并返回,那些找不到映射关系的属性,是无法完成赋值的。
5. #{}和${}的区别是什么?
- #{}是预编译处理,${}是字符串替换。
- Mybatis 在处理#{}时,会将 sql 中的#{}替换为?号,调用 PreparedStatement 的 set 方法来赋值;
- Mybatis 在处理{}时,就是把${}替换成变量的值。
- 使用#{}可以有效的防止 SQL 注入,提高系统安全性。
6. MyBatis 分页插件的实现原理是什么?
分页插件的基本原理是使用 MyBatis 提供的插件接口,实现自定义插件,在插件的拦截方法内拦截待执行的 SQL,然后重写 SQL,根据 dialect 方言,添加对应的物理分页语句和物理分页参数。
7. MyBatis 如何实现一对一和一对多查询?
- 一对一:通过在resultMap 里面配置 association 节点配置一对一的类就可以完成;
- 一对多:通过在resultMap 里面的 collection 节点配置一对多的类就可以完成;
8. MyBatis 插入数据如何获取自增得主键?
- 第一种mybatis支持自增主键的写法,使用
useGeneratedKeys和keyProperty属性设置:<insert id="insertSelective" parameterType="请求对象" useGeneratedKeys="true" keyProperty="Id"> ............................. </insert>
- 第二种使用
selectKey,限定于mysql数据库的写法<selectKey keyProperty="id" order="AFTER" resultType="java.lang.Integer"> SELECT LAST_INSERT_ID() </selectKey>
9. 在 mapper 中如何传递多个参数?
- 使用 #{0},#{1} ...
public UserselectUser(String name,String area);对应的 xml,#{0}代表接收的是 dao 层中的第一个参数,#{1}代表 dao 层中第二 参数,更多参数一致往后加即可。
<select id="selectUser"resultMap="BaseResultMap"> select * fromuser_user_t whereuser_name = #{0} anduser_area=#{1} </select>
- 使用 @param 注解
public interface usermapper { user selectuser(@param(“username”) string username,@param(“hashedpassword”) string hashedpassword); }
- :多个参数封装成 map