Spring面试题

Spring面试题

1-10

1.使用 Spring 框架的好处是什么?

Spring是一个轻量级控制反转面向切面容器框架,用来解决企业项目开发的复杂度问题—解耦

  • 轻量级:体积小,对代码没有侵入性(基本的版本大约 2MB)

  • 控制反转:IoC(Inverse of Control),把创建对象的工作交由Spring完成,Spring在创建对象的时候同时可以完成对象属性赋值(DI)

  • 面向切面:AOP(Aspect Oriented Programming)面向切面编程,可以在不改变原有业务逻辑的情况下实现对业务的增强

  • 容器:实例的容器,管理创建的对象

2.解释下什么是 AOP?

Aspect Oriented Programming 面向切面编程,是一种利用“横切”的技术(底层实现就是动态代理),对原有的业务逻辑进行拦截,并且可以在这个拦截的横切面上添加特定的业务逻辑,对原有的业务进行增强。

基于动态代理实现在不改变原有业务的情况下对业务逻辑进行增强

3.AOP 的代理有哪几种方式?

AOP 思想的实现一般都是基于代理模式 ,在 Java 中一般采用 JDK 动态代理模式,但是我们都知道,JDK 动态代理模式只能代理接口而不能代理类。因此,Spring AOP 会按照下面两种情况进行切换,因为 Spring AOP 同时支持 CGLIB、ASPECTJ、JDK 动态代理。

  1. 如果目标对象的实现类实现了接口,Spring AOP 将会采用 JDK 动态代理来生成 AOP 代理类;

  2. 如果目标对象的实现类没有实现接口,Spring AOP 将会采用 CGLIB 来生成 AOP 代理类。不过这个选择过程对开发者完全透明、开发者也无需关心。

4.怎么实现 JDK 动态代理?

静态代理,代理类只能够为特定的类生产代理对象,不能代理任意类

使用代理的好处

1.被代理类中只用关注核心业务的实现,将通用的管理型逻辑(事务管理、日志管理)和业务逻辑分离

2.将通用的代码放在代理类中实现,提供了代码的复用性

3.通过在代理类添加业务逻辑,实现对原有业务逻辑的扩展(增强)

动态代理

动态代理,几乎可以为所有的类产生代理对象

动态代理的实现方式有2种:

  • JDK动态代理

  • CGLib动态大力

JDK动态代理

  • JDK动态代理类实现:

/***
 * JDK动态代理:是通过被代理对象实现的接口产生其代理对象的
 * 1.创建一个类,实现InvocationHandler接口,重写invoke方法
 * 2.在类种定义一个Object类型的变量,并提供这个变量的有参构造器,用于将被代理对象传递进来
 * 3.定义getProxy方法,用于创建并返回代理对象
 */
public class JDKDynamicProxy implements InvocationHandler {
    //被代理对象
    private Object obj;
    public JDKDynamicProxy(Object obj) {
        this.obj = obj;
    }
    //产生代理对象,返回代理对象
    public Object getProxy(){
        //1.获取被代理对象的类加载器
        ClassLoader classLoader = obj.getClass().getClassLoader();
        //2.获取被代理对象的类实现的接口
        Class<?>[] interfaces = obj.getClass().getInterfaces();
        //3.产生代理对象(通过被代理对象的类加载器及实现的接口)
        //第一个参数:被代理对象的类加载器
        //第二个参数:被代理对象实现的接口
        //第三个参数:使用产生代理对象调用方法时,用于拦截方法执行的处理器
        Object proxy = Proxy.newProxyInstance(classLoader, interfaces,this);
        return proxy;
    }
​
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        begin();
        Object returnValue = method.invoke(obj,args);  //执行method方法(insert)
        commit();
        return returnValue;
    }
​
    public void begin(){
        System.out.println("----------开启事务");
    }
​
    public void commit(){
        System.out.println("----------提交事务");
    }
}
  • 测试

 //创建被代理对象
BookDAOImpl bookDAO = new BookDAOImpl();
StudentDAOImpl studentDAO = new StudentDAOImpl();
​
//创建动态代理类对象,并将被代理对象传递到代理类中赋值给obj
JDKDynamicProxy jdkDynamicProxy = new JDKDynamicProxy(studentDAO);
​
//proxy就是产生的代理对象:产生的代理对象可以强转成被代理对象实现的接口类型
GenaralDAO proxy = (GenaralDAO)jdkDynamicProxy.getProxy();
​
//使用代理对象调用方法,并不会执行调用的方法,而是进入到创建代理对象时指定的InvocationHandler类种的invoke方法
//调用的方法作为一个Method参数,传递给了invoke方法
proxy.insert(student);

CGLib动态代理

由于JDK动态代理是通过被代理类实现的接口来创建代理对象的,因此JDK动态代理只能代理实现了接口的类的对象。如果一个类没有实现任何接口,该如何产生代理对象呢?

CGLib动态代理,是通过创建被代理类的子类来创建代理对象的,因此即使没有实现任何接口的类也可以通过CGLib产生代理对象

CGLib动态代理不能为final类创建代理对象

  • 添加CGLib的依赖

<!-- https://mvnrepository.com/artifact/cglib/cglib -->
<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</version>
</dependency>
  • CGLib动态代理实现:

/**
 * 1.添加cglib依赖
 * 2.创建一个类,实现MethodInterceptor接口,同时实现接口中的intercept方法
 * 3.在类中定义一个Object类型的变量,并提供这个变量的有参构造器,用于传递被代理对象
 * 4.定义getProxy方法创建并返回代理对象(代理对象是通过创建被代理类的子类来创建的)
 */
public class CGLibDynamicProxy implements MethodInterceptor {
​
    private Object obj;
    public CGLibDynamicProxy(Object obj) {
        this.obj = obj;
    }
​
    public Object getProxy(){
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(obj.getClass());
        enhancer.setCallback(this);
        Object proxy = enhancer.create();
        return proxy;
    }
​
​
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        begin();
        Object returnValue = method.invoke(obj,objects); //通过反射调用被代理类的方法
        commit();
        return returnValue;
    }
​
    public void begin(){
        System.out.println("----------开启事务");
    }
​
    public void commit(){
        System.out.println("----------提交事务");
    }
}
  • 测试

//创建被代理对象
BookDAOImpl bookDAO = new BookDAOImpl();
StudentDAOImpl studentDAO = new StudentDAOImpl();
​
//通过cglib动态代理类创建代理对象
CGLibDynamicProxy cgLibDynamicProxy = new CGLibDynamicProxy(bookDAO);
//代理对象实际上是被代理对象子类,因此代理对象可直接强转为被代理类类型
BookDAOImpl proxy = (BookDAOImpl) cgLibDynamicProxy.getProxy();
​
//使用对象调用方法,实际上并没有执行这个方法,而是执行了代理类中的intercept方法,将当前调用的方法以及方法中的参数传递到intercept方法
proxy.update();

5.AOP 的基本概念:切面、连接点、切入点等?

连接点(Join Point)和切入点(Pointcut)不是同一个概念。

连接点是程序执行过程中的特定点,它可以是方法调用、异常抛出、对属性的访问等。每个连接点都对应着一个特定的位置,如某个类的某个方法的调用。

切入点是一组连接点的集合,它定义了在哪些连接点上应用切面。切入点使用表达式来描述连接点的匹配条件,例如某个包下的所有方法、以某个前缀开头的方法等。切入点确定了切面将被织入的具体位置。

简而言之,连接点描述的是程序执行的具体点,而切入点描述的是在哪些连接点上应用切面。连接点是实际存在的,而切入点是通过切入点表达式进行匹配得到的。连接点是切入点的子集,切入点通过匹配条件从连接点中筛选出来。

6.通知类型(Advice)有哪些?

  1. 前置通知(Before advice):在某连接点(JoinPoint)之前执行的通知,但这个通知不能阻止连接点前的执行。ApplicationContext 中在 < aop:aspect > 里面使用 < aop:before > 元素进行声明;

  2. 后置通知(After advice):当某连接点退出的时候执行的通知(不论是正常返回还是异常退出)。ApplicationContext 中在 < aop:aspect > 里面使用 < aop:after > 元素进行声明。

  3. 返回后通知(After return advice :在某连接点正常完成后执行的通知,不包括抛出异常的情况。ApplicationContext 中在 < aop:aspect > 里面使用 << after-returning >> 元素进行声明。

  4. 环绕通知(Around advice):包围一个连接点的通知,类似 Web 中 Servlet规范中的 Filter 的 doFilter 方法。可以在方法的调用前后完成自定义的行为,也可以选择不执行。ApplicationContext 中在 < aop:aspect > 里面使用 < aop:around > 元素进行声明。

  5. 抛出异常后通知(After throwing advice):在方法抛出异常退出时执行的通知。ApplicationContext 中在 < aop:aspect > 里面使用 < aop:after-throwing > 元素进行声明。

7.谈谈你对 IOC 的理解?

  • Spring IoC 容器组件,可以完成对象的创建、对象属性赋值、对象管理

  • IoC (Inverse of Control) 控制反转,通过Spring对象工厂完成对象的创建

  • DI (Dependency Injection)依赖注入,在Spring完成对象创建的同时依赖Spring容器完成对象属性的赋值

8.Bean 的生命周期?

Bean的声明周期方法

在bean标签中通过init-method属性指定当前bean的初始化方法,初始化方法在构造器执行之后执行,通过destroy-method属性指定当前bean的销毁方法,销毁方法在对象销毁之前执行

  • Bean类

    public class Book {
    ​
        private int bookId;
        private String bookName;
    ​
         //初始化方法:在创建当前类对象时调用的方法,进行一些资源准备工作
        public void init(){
            System.out.println("-------init");
        }
    ​
        //销毁方法:在Spring容器销毁对象时调用此方法,进行一些资源回收性的操作
        public void destory(){
            System.out.println("-------destory");
        }
    }
  • Spring配置文件

    <bean id="book" class="com.qfedu.ioc.bean.Book" scope="prototype"init-method="init" destroy-method="destory" ></bean>

在Java中,Bean的生命周期包括以下几个阶段:

  1. 实例化(Instantiation):在这个阶段,容器会根据Bean的定义实例化一个对象。实例化可以通过构造方法进行,也可以通过工厂方法、反射等方式完成。

  2. 属性赋值(Property Assignment):在实例化后,容器会对Bean的属性进行赋值。属性可以通过构造函数参数、Setter方法、直接赋值等方式设置。

  3. 初始化回调(Initialization Callback):当Bean的属性赋值完成后,容器会调用Bean的初始化方法(如果有)。这个方法可以由开发者自定义,并在Bean初始化时执行一些额外的逻辑操作。

  4. 使用(Bean in Use):初始化完成后,Bean就可以被容器或其他对象使用了。在这个阶段,Bean可能会被注入到其他对象中,或者在容器中被管理和维护。

  5. 销毁回调(Destruction Callback):当Bean不再被使用时(例如容器关闭或者显式销毁),容器会调用Bean的销毁方法(如果有)。这个方法可以由开发者自定义,并在Bean销毁前执行一些清理资源的操作。

需要注意的是,Bean的生命周期可能会受到具体容器的管理策略和配置方式的影响。常见的容器如Spring容器,提供了灵活的配置选项来定义Bean的生命周期行为,例如通过配置初始化方法、销毁方法,或者实现InitializingBean和DisposableBean接口来定义回调方法。

9.Bean 的作用域?

Bean的作用域

在bean标签可以通过scope属性指定对象的的作用域

  • scope="singleton" 表示当前bean是单例模式(默认饿汉模式,Spring容器初始化阶段就会完成此对象的创建;当在bean标签中设置 lazy-init="true"变为懒汉模式)

  • scope="prototype" 表示当前bean为非单例模式,每次通过Spring容器获取此bean的对象时都会创建一个新的对象

  • 单例

<bean id="book" class="com.qfedu.ioc.bean.Book" scope="singleton" lazy-init="true"></bean>
  • 多例

<bean id="book" class="com.qfedu.ioc.bean.Book" scope="prototype"></bean>

在Java中,Bean的作用域定义了Bean对象的创建和存在方式,以及在应用程序中的可见范围。常见的Bean作用域包括以下几种:

  1. 单例(Singleton):单例作用域是默认的作用域,表示在整个应用程序中只有一个Bean实例存在。无论何时请求该Bean,容器都返回同一个实例。

  2. 原型(Prototype):原型作用域表示每次请求该Bean时,容器都会创建一个新的Bean实例。每个请求得到的Bean实例都是独立的,没有共享状态。

  3. 会话(Session):会话作用域适用于Web应用程序。它表示在用户会话期间,即用户与应用程序保持连接期间,Bean的实例是唯一的。不同用户之间的会话Bean实例是隔离的。

  4. 请求(Request):请求作用域也适用于Web应用程序。它表示在每次HTTP请求时,容器都会创建一个新的Bean实例。每个请求得到的Bean实例都是独立的。

  5. 其他作用域:除了以上常见的作用域外,还有一些特定场景下使用的作用域,如应用程序(Application)、线程(Thread)等。这些作用域的具体行为取决于所使用的容器和框架。

选择适当的作用域取决于应用程序的需求和设计。使用单例作用域可以实现对象的共享和状态的维护,而原型作用域适用于需要每次访问都得到一个新实例的情况。会话和请求作用域则主要用于Web应用程序中,以满足不同用户和每个HTTP请求的独立性要求。

10.Spring 中的单例 Bean 的线程安全问题了解吗?

大部分时候我们并没有在系统中使用多线程,所以很少有人会关注这个问题。单例 bean 存在线程问题,主要是因为:当多个线程操作同一个对象的时候,对这个对象的非静态成员变量的写操作会存在线程安全问题。常见的有两种解决办法:

  1. 在 Bean 对象中尽量避免定义可变的成员变量(不太现实)。

  2. 在类中定义一个 ThreadLocal 成员变量,将需要的可变成员变量保存在 ThreadLocal 中(推荐的一种方式)。

11-20

11.谈谈你对 Spring 中的事务的理解?

事务是逻辑上的一组操作,要么都执行,要么都不执行。

事务特性

原子性:事务是最小的执行单位,不允许分割。事务的原子性确保动作要么全部完成,要么完全不起作用;

一致性:执行事务前后,数据保持一致;

隔离性:并发访问数据库时,一个用户的事物不被其他事物所干扰,各并发事务之间数据库是独立的;

持久性: 一个事务被提交之后。它对数据库中数据的改变是持久的,即使数据库发生故障也不应该对其有任何影响。

Spring 事务管理接口

  1. PlatformTransactionManager:(平台)事务管理器;

  2. TransactionDefinition:事务定义信息(事务隔离级别、传播行为、超时、只读、回滚规则);

  3. TransactionStatus:事务运行状态;

所谓事务管理,其实就是“按照给定的事务规则来执行提交或者回滚操作”。

12.Spring 中的事务隔离级别?

读未提交(read uncommitted)脏读,一个事务读取到了另一个事务中未提交的数据

读已提交(read committed)T2只能读取T1已经提交的数据;避免了脏读,但可能会导致不可重复度(虚读): 在同一个事务中,两次查询操作读取到数据不一致

可重复读(repeatable read)避免了(虚读),但可能会导致幻读。修改的时候遇到添加,以为修改成功了,读出不一样

串行化(serializable) 同时只允许一个事务对数据表进行操作;避免了脏读、虚读、幻读问题

13.Spring 常用的注入方式有哪些?

  1. 构造器依赖注入:构造器依赖注入通过容器触发一个类的构造器来实现的,该类有一系列参数,每个参数代表一个对其他类的依赖。

  2. Setter 方法注入:Setter 方法注入是容器通过调用无参构造器或无参 static 工厂方法实例化 bean 之后,调用该 bean 的 Setter 方法,即实现了基于 Setter 的依赖注入。

  3. 基于注解的注入:最好的解决方案是用构造器参数实现强制依赖,Setter 方法实现可选依赖。

14.Spring 框架中用到了哪些设计模式?

  1. 工厂设计模式 : Spring 使用工厂模式通过 BeanFactory、ApplicationContext 创建 bean 对象;

  2. 代理设计模式 : Spring AOP 功能的实现;

  3. 单例设计模式 : Spring 中的 Bean 默认都是单例的;

  4. 模板方法模式 : Spring 中 jdbcTemplate、hibernateTemplate 等以 Template 结尾的对数据库操作的类,它们就使用到了模板模式;

  5. 包装器设计模式 : 我们的项目需要连接多个数据库,而且不同的客户在每次访问中根据需要会去访问不同的数据库。这种模式让我们可以根据客户的需求能够动态切换不同的数据源;

  6. 观察者模式:Spring 事件驱动模型就是观察者模式很经典的一个应用;

  7. 适配器模式:Spring AOP 的增强或通知(Advice)使用到了适配器模式、SpringMVC 中也是用到了适配器模式适配 Controller。

15.ApplicationContext 通常的实现有哪些?

  1. FileSystemXmlApplicationContext:此容器从一个 XML 文件中加载beans 的定义,XML Bean 配置文件的全路径名必须提供给它的构造函数。

  2. ClassPathXmlApplicationContext:此容器也从一个 XML 文件中加载beans 的定义,这里,你需要正确设置 classpath 因为这个容器将在 classpath 里找 bean 配置。

  3. WebXmlApplicationContext:此容器加载一个 XML 文件,此文件定义了一个 Web 应用的所有 bean。

//1.初始化Spring容器,加载Spring配置文件
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
//2.通过Spring容器获取Student对象
Student student2 = (Student) context.getBean("stu");

  1. FileSystemXmlApplicationContext: FileSystemXmlApplicationContext是基于文件系统路径的XML配置文件的应用程序上下文。它从一个XML文件中加载bean的定义,并且需要提供XML配置文件的全路径名给它的构造函数。这个容器适用于非Web环境的应用程序。

示例代码:

ApplicationContext context = new FileSystemXmlApplicationContext("/path/to/applicationContext.xml");

在上面的示例中,"/path/to/applicationContext.xml"是XML配置文件的路径。

  1. ClassPathXmlApplicationContext: ClassPathXmlApplicationContext是基于类路径(classpath)的XML配置文件的应用程序上下文。它也从一个XML文件中加载bean的定义,但是需要正确设置classpath,以便容器能够在类路径里找到bean的配置信息。

示例代码:

ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");

在上面的示例中,"classpath:applicationContext.xml"是相对于类路径的XML配置文件路径。

  1. WebXmlApplicationContext: WebXmlApplicationContext是一个加载Web应用程序中所有bean定义的XML文件的应用程序上下文。它通常在Web应用程序的上下文环境中使用,可以加载Web应用程序的所有bean配置。

示例代码:

ApplicationContext context = new WebXmlApplicationContext("/WEB-INF/applicationContext.xml");

在上面的示例中,"/WEB-INF/applicationContext.xml"是Web应用程序中的XML配置文件路径。

需要注意的是,示例中的文件路径和XML配置文件的名称根据实际情况进行修改。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

猿人啊兴

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值