面试准备:Spring/Spring MVC常见面试题汇总

1.Spring框架有什么优点?

  1. 轻量:低侵入式设计,代码污染极低
  2. Spring的DI机制和容器实现了对象的管理和装配,提高了组件之间的解耦,方便集成,通过配置和简单的对象注入即可集成其他框架,如 Mybatis、Hibernate、Shiro…
  3. Spring的AOP支持允许将一些通用任务如安全、事务、日志等进行集中式管理,从而提供了更好的复用
  4. Spring的ORM和DAO提供了与第三方持久层框架的良好整合,并简化了底层的数据库访问
  5. Spring并不强制应用完全依赖于Spring,开发者可自由选用Spring框架的部分或全部

2.什么是AOP?

AOP:Aspect Oriented Programming,面向切面编程。
"切面"就是将那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块间的耦合度,利于可操作性和可维护性。

3. 实现AOP的方式(AOP织入的三种时期)?Spring AOP是怎么实现的?

  • 静态AOP:

在编译期,切面直接以字节码的形式编译到目标字节码文件中。 在编译的时候将AOP逻辑织入到代码中,需要专有的编译器和织入器。

优点:被织入的类性能不受影响。
缺点:不够灵活

  • 动态AOP(JDK动态代理):

在运行期,目标类加载后,为接口动态生成代理类,将切面植入到代理类中。Java从1.3引入动态代理。实现原理是为被代理的业务接口生成代理类,将AOP逻辑写入到代理类中,在运行时动态织入AOP,使用反射执行织入的逻辑。
主要实现方式依赖java.lang.reflect包下的InvocationHandler和Proxy类。

优点:Java标准库原生支持,使用简单,无需引用额外的包。相对于静态AOP更灵活。
缺点:带代理的类必须是接口,灵活性受到一些限制;使用反射会影响一些性能。

  • 动态代码字节生成(CGLib):

在运行期,目标类加载后,动态构建字节码文件生成目标类的子类,将切面逻辑加入到子类中。
CGLib是动态代码字节生成的实现,它封装字节码生成工具Asm,原理是在运行期间目标字节码加载后,生成目标类的子类,将切面逻辑加入到子类中,所以使用Cglib实现AOP 不需要基于接口
CGLIB动态代理主要用到拦截方法的技术,主要涉及的类:Enhancer和MethodInceptor接口

优点:没有接口也可以织入,灵活性高。
缺点:扩展类的实例方法为final时,则无法进行织入


Spring AOP 中的代理使用逻辑:

  • 如果目标对象实现了接口,默认情况下会采用 JDK 的动态代理实现 AOP;
  • 如果目标对象没有实现接口,则采用 CGLIB 库,Spring 会自动在 JDK 动态代理和 CGLIB 动态代理之间转换。

4.动态代理实现方式?

就是通过让target类和代理类实现同一接口,代理类通过反射的方式持有target对象,然后调用target对象的方法,并在方法前后实现织入的逻辑,来达到方法拦截的作用。

参考:Java设计模式——代理模式

为什么动态代理仅支持接口,是因为Java不支持多继承。
具体可以查看Proxy类的实现方式

那些动态生成的代理类的继承关系图,它们已经注定有一个共同的父类叫Proxy。Java的继承机制注定了这些动态代理类们无法实现对class的动态代理,原因是多继承在Java中本质上就行不通。

5.PageHelper实现方式?

参考结合源码理解PageHelper

6.什么是IoC?什么是DI?

IoC,Inversion of Control(控制反转)。

控制反转(IOC)是一种设计原则,把传统上由程序代码直接操控的对象的调用权交给容器,通过容器来实现对象组件的装配和管理。这个控制权从从程序代码本身转移到了外部容器。

这样对象与对象之间是松耦合、便于测试、功能可复用(减少对象的创建和内存消耗),使得程序的整个体系结构可维护性、灵活性、扩展性变高。
优点:

  1. 依赖注入把应用的代码量降低。
  2. 它使应用容易测试,单元测试不再需要单例和JNDI查找机制。
  3. 最小的代价和最小的侵入性使松散耦合得以实现。
  4. IOC容器支持加载服务时的饿汉式初始化和懒加载。
  • spring 提供了三种主要的方式来配置 IoC 容器中的 bean
    基于 XML 文件配置
    基于注解配置
    基于注解 + java 代码显式配置

DI依赖注入,是 IoC 实现的一种方式(还有一种是依赖查找)。通过依赖注入机制,简单的配置即可注入需要的资源,完成自身的业务逻辑,不需要关心资源的出处和具体实现。

DI注入的方式:
spring支持setter注入和构造器注入,通常是由构造器注入来注入必须的依赖关系,对于可选的依赖关系,则setter注入是更好的选择。setter注入需要类提供无参构造器或者无参的静态工厂来创建对象。

7.IoC实现原理?

参考Java架构直通车——@SpringApplication自动装配原理这节。

IoC的实现原理就是工厂模式加反射机制

8.spring有哪些主要模块?

Spring框架的七大模块

1.Spring Core
框架的最基础部分,提供 IoC 容器,对 bean 进行管理。

2.Spring Context
基于 bean,提供上下文信息,扩展出JNDI、EJB、电子邮件、国际化、校验和调度等功能。

3.Spring DAO
提供了JDBC的抽象层,它可消除冗长的JDBC编码和解析数据库厂商特有的错误代码,还提供了声明性事务管理方法。

4.Spring ORM
提供了常用的“对象/关系”映射APIs的集成层。 其中包括JPA、JDO、Hibernate、MyBatis 等。

5.Spring AOP
提供了符合AOP Alliance规范的面向方面的编程实现。

6.Spring Web
提供了基础的 Web 开发的上下文信息,可与其他 web 进行集成。

7.Spring Web MVC
提供了 Web 应用的 Model-View-Controller 全功能实现。

9.spring中的bean是线程安全的吗?

Spring 不保证 bean 的线程安全。
默认 spring 容器中的 bean 是单例的。当单例中存在竞态条件,即有线程安全问题。

10.spring支持几种bean的作用域?

  • singleton:单例模式,在整个Spring IoC容器中,使用 singleton 定义的 bean 只有一个实例
  • prototype:原型模式,每次通过容器的getbean方法获取 prototype 定义的 bean 时(将其注入到另一个bean中,或者以程序的方式调用容器的getBean()方法),都产生一个新的 bean 实例

只有在 Web 应用中使用Spring时,request、session、global-session 作用域才有效

  • request:对于每次 HTTP 请求,使用 request 定义的 bean 都将产生一个新实例,即每次 HTTP 请求将会产生不同的 bean 实例。
  • session:同一个 Session 共享一个 bean 实例。
  • global-session:同 session 作用域不同的是,所有的Session共享一个Bean实例。

什么时候使用prototype,什么时候使用singleton,主要看是看bean是有状态bean还是无状态bean:

有状态bean,如果配置为singleton,会出现线程安全问题,比如下面的代码:

package com.test;    
public class TestServiceImpl implements TestService{  
   private User user;
   public void test1(User u) throws Exception {  
       this.user = u;                          //1  
       test2();  
   }  
   
   public void test2() throws Exception {  
       System.out.println(user.getId());       //2 
   }     
}

如果该Bean配置为singleton,在并发访问下如果某一个线程修改了user的值,那么另外的线程可能会出错的。
实际应该是这个例子不应该用实例变量,这样就使得这个Bean由无状态变成了有状态Bean。
如果用有状态的bean,就要用prototype模式,每次在注入的时候就重新创建一个bean,在多线程中互不影响。

11.spring mvc运行流程?

参考:DispatchServlet处理流程
参考:Rest处理流程

12.@RequestMapping的作用是什么?

@RequestMapping 是一个注解,用来标识 http 请求地址与 Controller 类的方法之间的映射。

13.@Autowired的作用是什么?

@Autowired 是一个注解,它可以对类成员变量、方法及构造函数进行标注,让 spring 完成 bean 自动装配的工作。
@Autowired 默认是按照类去匹配,配合 @Qualifier 指定按照名称去装配 bean。

14.什么是spring boot?为什么要用?

spring boot 基于 spring 框架的快速开发整合包。
好处:

编码变得简单
配置变得简单
部署变得简单
监控变得简单

15.spring boot核心配置文件是什么?

Spring Boot 有两种类型的配置文件,application 和 bootstrap 文件
Spring Boot会自动加载classpath目前下的这两个文件,文件格式为 properties 或 yml 格式

*.properties 文件是 key=value 的形式
*.yml 是 key: value 的形式
*.yml 加载的属性是有顺序的,但不支持 @PropertySource 注解来导入配置,一般推荐用yml文件,看下来更加形象

bootstrap 配置文件是系统级别的,用来加载外部配置,如配置中心的配置信息,也可以用来定义系统不会变化的属性.bootstatp 文件的加载先于application文件
application 配置文件是应用级别的,是当前应用的配置文件

16.spring boot有哪些方式可以实现热部署?

以后再详解。

17.什么是Spring的内部bean?

当一个bean仅被用作另一个bean的属性时,它能被声明为一个内部bean,内部bean通常是匿名的,它们的Scope一般是prototype。

如:

	<!-- 利用setter注入Dog -->  
    <bean id="d1" class="com.yang.Dog">  
        <property name="name" value="lala"></property>  
        <property name="age" value="9"></property>  
        <property name="style" value="Hashiqi"></property>  
    </bean>  
    <!--利用构造器注入person-->  
    <bean id="d2" class="com.yang.person">  
        <constructor-arg index="0" value="andreny"></constructor-arg>  
        <constructor-arg index="1" value="15454"></constructor-arg>  
        <constructor-arg index="2" value="teacher"></constructor-arg>  
    </bean>  
    
    <bean id="m1" class="com.yang.myshow">  
        <!--内部bean-->
        <property name="dog" ref="d1"></property>  
        <!--内部bean-->
        <property name="p" ref="d2"></property>  
    </bean>  

18.描述Spring框架中bean的生命周期?

1、Spring容器 从XML 文件中读取bean的定义,并实例化bean。
2、Spring根据bean的定义填充所有的属性。
3、如果bean实现了BeanNameAware 接口,Spring 传递bean 的ID 到 setBeanName方法。
4、如果Bean 实现了 BeanFactoryAware 接口, Spring传递beanfactory 给setBeanFactory 方法。
5、如果有任何与bean相关联的BeanPostProcessors,Spring会在postProcesserBeforeInitialization()方法内调用它们。
6、如果bean实现IntializingBean了,调用它的afterPropertySet方法,如果bean声明了初始化方法,调用此初始化方法。
7、如果有BeanPostProcessors 和bean 关联,这些bean的postProcessAfterInitialization() 方法将被调用。
8、如果bean实现了 DisposableBean,它将调用destroy()方法。

19.spring中如何注入数组/集合?

Spring提供以下几种集合的配置元素:
<list>类型用于注入一列值,允许有相同的值。
<set>类型用于注入一组值,不允许有相同的值。
<map> 类型用于注入一组键值对,键和值都可以为任意类型。
<props>类型用于注入一组键值对,键和值都只能为String类型。

<beans>
  <bean id="at" class="test.array.ArrayTest">
    <property name="names">
      <list>
        <value>aaa</value>
        <value>bbb</value>
      </list>
    </property>
  </bean>
</beans>

也可以通过注解的方式注入:

public interface InjectService extends InitializingBean{
    public void inject();
}

@Service
public class InjectServiceImpl implements InjectService {@Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("begin class:" + this);
    }@Override
    public void inject() {
        System.out.println("this is inject service");
    }
}

@Controller
public class InjectController {@Autowired
    private List<InjectService> list;
    @Autowired
    private Map<String, InjectService> map;
    @Autowired
    private Set<InjectService> set;@RequestMapping("inject.html")
    public void injectUrl() {
        for (InjectService service : list) {
            System.out.println("inject list service class:" + service);
        }
        for (String key : map.keySet()) {
            System.out.println("inject map service class:" + map.get(key));
        }
        for (InjectService service : set) {
            System.out.println("inject map service class:" + service);
        }
    }
}

对于list、set填入的是注入类型Spring管理的实例,对于map,Spring会将service的名字作为key,对象作为value封装进入Map。

20.Spring中有哪几种方法获取HttpSession对象?

	@RequestMapping("/session")
    public Map test(HttpSession session, String otherParam) {
        ...
    }

或者

    @RequestMapping("/session")
    public String setSession(HttpServletRequest request, HttpServletResponse response){
        HttpSession session = request.getSession();
        ...
   }

21.ApplicationContext有哪几种实现方式?

有以下3种实现方式:

1、FileSystemXmlApplicationContext :从文件系统加载IoC配置文件
2、ClassPathXmlApplicationContext:从文件系统加载IoC配置文件
3、WebXmlApplicationContext:通过WebApplicationContextUtils获取

        //加载单个xml文件
        ApplicationContext ctx = new FileSystemXmlApplicationContext("bean.xml");
        //加载多个xml文件
        String[] locations = new String[]{"bean1.xml", "bean2.xml", "bean3.xml"};
        ctx = new FileSystemXmlApplicationContext(locations);
 
        //加载单个xml文件
        ctx = new ClassPathXmlApplicationContext("bean.xml");
        //加载多个xml文件
        locations = new String[]{"bean1.xml", "bean2.xml", "bean3.xml"};
        ctx = new ClassPathXmlApplicationContext(locations);
 
        
        ServletContext servletContext = request.getSession().getServletContext();
        ctx = WebApplicationContextUtils.getWebApplicationContext(servletContext);

22.Spring里面用了哪些设计模式?

工厂模式、单例模式、代理模式、适配器模式、观察者模式…
参考:https://www.cnblogs.com/hwaggLee/p/4510687.html

23.列举几种spring的自动装配方式?

有五种自动装配的方式,可以用来指导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方式。

24.Spring的初始化过程?

参考:Java架构直通车——SpringApplication的初始化过程

25.如何理解理解Spring容器、BeanFactory和ApplicationContext?

spring容器可以理解为对于对象的管理和装配的地方,它负责了对象的整个生命周期–创建、装配、销毁。而这里对象的创建管理的控制权都交给了Spring容器,所以这是一种控制权的反转,称为IOC容器。

BeanFactory和ApplicationContext是Spring的两大核心接口,而其中ApplicationContext是BeanFactory的子接口。它们都可以当做Spring的容器,Spring容器是生成Bean实例的工厂,并管理容器中的Bean。

BeanFactory和ApplicationContext之间的关系:

  1. BeanFactory。
    这是最简单的容器,只能提供基本的DI功能和对象生命周期的管理,所以不太常用。
  2. ApplicationContext
    继承自BeanFactory,它能提供更多企业级的服务,比如AOP,事件监听机制等。

另外,BeanFactory使用的是懒加载。而ApplicationContext默认是初始化的时候加载所有的Singleton Bean,在系统创建前期会有较大的系统开销,之后性能会有较好的改善。

26.Spring中的事务传播级别

1)@Transactional(propagation=Propagation.REQUIRED):默认的spring事务传播级别,使用该级别的特点是,如果上下文中已经存在事务,那么就加入到事务中执行,如果当前上下文中不存在事务,则新建事务执行,所以这个级别通常能满足处理大多数的业务场景。

2)@Transactional(propagation=PROPAGATION.SUPPORTS):从字面意思就知道,supports(支持),该传播级别的特点是,如果上下文存在事务,则支持当前事务,加入到事务执行,如果没有事务,则使用非事务的方式执行。所以说,并非所有的包在transactionTemplate.execute中的代码都会有事务支持。这个通常是用来处理那些并非原子性的非核心业务逻辑操作,应用场景较少。

3)@Transactional(propagation=PROPAGATION.MANDATORY):该级别的事务要求上下文中必须要存在事务,否则就会抛出异常!配置该方式的传播级别是有效的控制上下文调用代码遗漏添加事务控制的保证手段。比如一段代码不能单独被调用执行,但是一旦被调用,就必须有事务包含的情况,就可以使用这个传播级别。

4)@Transactional(propagation=PROPAGATION.REQUIRES_NEW):从字面即可知道,每次都要一个新的事务,该传播级别的特点是,每次都会新建一个事务,并且同时将上下文中的事务挂起,当新建事务执行完成以后,上下文事务再恢复执行。

这是一个很有用的传播级别,举一个应用场景:现在有一个发送100个红包的操作,在发送之前,要做一些系统的初始化、验证、数据记录操作,然后发送100封红包,然后再记录发送日志,发送日志要求100%的准确,如果日志不准确,那么整个父事务逻辑需要回滚。
怎么处理整个业务需求呢?就是通过这个PROPAGATION.REQUIRES_NEW 级别的事务传播控制就可以完成。发送红包的子事务不会直接影响到父事务的提交和回滚。

5)@Transactional(propagation=PROPAGATION.NOT_SUPPORTED) :这个也可以从字面得知,not supported(不支持),当前级别的特点是,如果上下文中存在事务,
则挂起事务,执行当前逻辑,结束后恢复上下文的事务。

这个级别有什么好处?可以帮助你将事务极可能的缩小。我们知道一个事务越大,它存在的风险也就越多。所以在处理事务的过程中,要保证尽可能的缩小范围。比如一段代码,是每次逻辑操作都必须调用的,比如循环1000次的某个非核心业务逻辑操作。这样的代码如果包在事务中,势必造成事务太大,导致出现一些难以考虑周全的异常情况。所以这个事务这个级别的传播级别就派上用场了,用当前级别的事务模板抱起来就可以了。

6)@Transactional(propagation=PROPAGATION.NEVER):该事务更严格,上面一个事务传播级别只是不支持而已,有事务就挂起,而PROPAGATION_NEVER传播级别要求上下文中不能存在事务,一旦有事务,就抛出runtime异常,强制停止执行!

7)@Transactional(propagation=PROPAGATION.NESTED):字面也可知道,nested,嵌套级别事务。该传播级别特征是,如果上下文中存在事务,则嵌套事务执行,如果不存在事务,则新建事务。

PROPAGATION_NESTED 开始一个 “嵌套的” 事务, 它是已经存在事务的一个真正的子事务. 嵌套事务开始执行时, 它将取得一个 savepoint。如果这个嵌套事务失败, 我们将回滚到此 savepoint。嵌套事务是外部事务的一部分, 只有外部事务结束后它才会被提交.

27.Spring的@Transactional如何实现的?

实现@Transactional原理是基于spring aop的。

  1. 自动提交
    默认情况下,数据库处于自动提交模式。每一条语句处于一个单独的事务中,在这条语句执行完毕时,如果执行成功则隐式的提交事务,如果执行失败则隐式的回滚事务。
    事务管理,是一组相关的操作处于一个事务之中,因此必须关闭数据库的自动提交模式。这点,Spring会在org/springframework/jdbc/datasource/DataSourceTransactionManager.java中将底层连接的自动提交特性设置为false
// switch to manual commit if necessary。 this is very expensive in some jdbc drivers,
// so we don't want to do it unnecessarily (for example if we've explicitly
// configured the connection pool to set it already)。if (con。getautocommit()) 
{
    txobject.setmustrestoreautocommit(true);
    if (logger.isdebugenabled()) 
    {
        logger.debug("switching jdbc connection [" + con + "] to manual commit");
    }
    //首先将自动提交属性改为false
    con.setautocommit(false);
}
  1. spring事务回滚规则
    Spring事务管理器回滚一个事务的推荐方法是在当前事务的上下文内抛出异常。Spring事务管理器会捕捉任何未处理的异常,然后依据规则决定是否回滚抛出异常的事务。
    默认配置下,Spring只有在抛出的异常为运行时unchecked异常时才回滚该事务,也就是抛出的异常为RuntimeException的子类(Errors也会导致事务回滚)。而抛出checked异常则不会导致事务回滚。
    Spring也支持明确的配置在抛出哪些异常时回滚事务,包括checked异常。也可以明确定义哪些异常抛出时不回滚事务。
    还可以编程性的通过setRollbackOnly()方法来指示一个事务必须回滚,在调用完setRollbackOnly()后你所能执行的唯一操作就是回滚。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章