前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >大厂必问的Spring面试题

大厂必问的Spring面试题

原创
作者头像
程序员大彬
发布2022-01-04 22:22:24
5780
发布2022-01-04 22:22:24
举报
文章被收录于专栏:Java note

大家好,我是大彬~

最近抽空将Spring常见的面试题总结了一下,分享给大家~(找工作的小伙伴赶紧刷起来!)

文章目录:

  • AOP有哪些实现方式?
  • JDK动态代理和CGLIB动态代理的区别?
  • Bean注入容器有哪些方式?
  • @Autowired和@Resource的区别?
  • @Qualifier 注解有什么作用
  • @Bean和@Component有什么区别?
  • @Component、@Controller、@Repositor和@Service 的区别?
  • Spring 事务实现方式有哪些?
  • 有哪些事务传播行为?
  • Spring怎么解决循环依赖的问题?
  • Spring 的单例 Bean 是否有线程安全问题?
  • Spring 用到了哪些设计模式?

AOP有哪些实现方式?

AOP有两种实现方式:静态代理和动态代理。

静态代理

静态代理:代理类在编译阶段生成,在编译阶段将通知织入Java字节码中,也称编译时增强。AspectJ使用的是静态代理。

缺点:代理对象需要与目标对象实现一样的接口,并且实现接口的方法,会有冗余代码。同时,一旦接口增加方法,目标对象与代理对象都要维护。

动态代理

动态代理:代理类在程序运行时创建,AOP框架不会去修改字节码,而是在内存中临时生成一个代理对象,在运行期间对业务方法进行增强,不会生成新类。

JDK动态代理和CGLIB动态代理的区别?

Spring AOP中的动态代理主要有两种方式:JDK动态代理和CGLIB动态代理。

JDK动态代理

如果目标类实现了接口,Spring AOP会选择使用JDK动态代理目标类。代理类根据目标类实现的接口动态生成,不需要自己编写,生成的动态代理类和目标类都实现相同的接口。JDK动态代理的核心是InvocationHandler接口和Proxy类。

缺点:目标类必须有实现的接口。如果某个类没有实现接口,那么这个类就不能用JDK动态代理。

CGLIB来动态代理

通过继承实现。如果目标类没有实现接口,那么Spring AOP会选择使用CGLIB来动态代理目标类。CGLIB(Code Generation Library)可以在运行时动态生成类的字节码,动态创建目标类的子类对象,在子类对象中增强目标类。

CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的。

优点:目标类不需要实现特定的接口,更加灵活。

两者的区别

  1. jdk动态代理使用jdk中的类Proxy来创建代理对象,它使用反射技术来实现,不需要导入其他依赖。cglib需要引入相关依赖:asm.jar,它使用字节码增强技术来实现。
  2. 当目标类实现了接口的时候,Spring Aop默认使用jdk动态代理方式来增强方法,没有实现接口的时候使用cglib动态代理方式增强方法。

Bean注入容器有哪些方式?

将普通类交给Spring容器管理,通常有以下方法:

1、使用@Configuration@Bean注解;

2、使用@Controller@Service@Repository@Component 注解标注该类,然后启用@ComponentScan自动扫描;

3、使用@Import 方法。使用@Import注解把Bean导入到容器中,代码如下:

代码语言:txt
复制
@ComponentScan
@Import({Demo.class})
public class App {
    public static void main(String[] args) throws Exception {
        ConfigurableApplicationContext context = SpringApplication.run(App.class, args);
        System.out.println(context.getBean(Demo.class));
        context.close();
    }
}

@Autowired和@Resource的区别?

1、@Autowired注解是按照类型byType装配依赖对象的,但是如果存在多个类型⼀致的Bean,⽆法通过byType注⼊时,就会使⽤byName根据名称来注⼊。byName是将bean的名字与被注入的成员变量的名称匹配,而不是与被注入的成员变量的类型匹配。以下代码中,App 类的成员变量 userService1 会使用名字为 userService1 的 Bean,成员变量 userService2 会使用名字为 userService2 的 Bean。

代码语言:txt
复制
@Service
class UserService1 implements UserService {...}
@Service
class UserService2 implements UserService {...}

public class App {
    @Autowired
    private UserService userService1;
    @Autowired
    private UserService userService2;

    @Test
    public void test() {
        System.out.println(userService1.getClass().toString());
        System.out.println(userService2.getClass().toString());
    }
}

如果⽆法判断注⼊哪个Bean则会抛出UnsatisfiedDependencyException

2、@Resource会⾸先按照名称byName来装配,如果找不到Bean,会⾃动byType再找⼀次。

@Qualifier 注解有什么作用

当需要创建多个相同类型的 Bean 并希望仅使用属性装配其中一个 Bean 时,可以使用@Qualifier注解和 @Autowired 通过指定应该装配哪个 Bean 来消除歧义。

代码语言:txt
复制
@Component("bird")
public class Bird implements Fly {...}

@Component("plane")
public class Plane implements Fly {...}

@Component
public class FooService {
    @Autowired
    @Qualifier("bird")
    private Fly fly;
}

@Bean和@Component有什么区别?

都是使用注解定义 Bean。两者的区别:

1、@Bean 是使用 Java 代码装配 Bean,@Component 是自动装配 Bean。

2、@Component 注解用在类上,表明一个类会作为组件类,并告知Spring要为这个类创建Bean,每个类对应一个 Bean。而@Bean 注解用在方法上,表示这个方法会返回一个 Bean。@Bean 需要在配置类中使用,即类上需要加上@Configuration注解。

代码语言:txt
复制
@Component
public class Student {...}

@Configuration
public class WebSocketConfig {
    @Bean
    public Student student(){
        return new Student();
    }
}

@Bean 注解更加灵活。当需要将第三方类装配到 Spring 容器中,因为没办法源代码上添加@Component注解,只能使用@Bean注解的方式,当然也可以使用 xml 的方式。

@Component、@Controller、@Repositor和@Service 的区别?

@Component:最普通的组件,可以被注入到spring容器进行管理。

@Controller:将类标记为 Spring Web MVC 控制器。

@Service:业务层组件。

@Repository:数据访问组件,即DAO组件。

Spring 事务实现方式有哪些?

事务就是一系列的操作原子执行。Spring事务机制主要包括声明式事务和编程式事务。

  • 编程式事务:通过编程的方式管理事务,这种方式带来了很大的灵活性,但很难维护。
  • 声明式事务:将事务管理代码从业务方法中分离出来,通过aop进行封装。Spring声明式事务使得我们无需要去处理获得连接、关闭连接、事务提交和回滚等这些操作。使用 @Transactional 注解开启声明式事务。

@Transactional相关属性如下:

属性

类型

描述

value

String

可选的限定描述符,指定使用的事务管理器

propagation

enum: Propagation

可选的事务传播行为设置

isolation

enum: Isolation

可选的事务隔离级别设置

readOnly

boolean

读写或只读事务,默认读写

timeout

int (in seconds granularity)

事务超时时间设置

rollbackFor

Class对象数组,必须继承自Throwable

导致事务回滚的异常类数组

rollbackForClassName

类名数组,必须继承自Throwable

导致事务回滚的异常类名字数组

noRollbackFor

Class对象数组,必须继承自Throwable

不会导致事务回滚的异常类数组

noRollbackForClassName

类名数组,必须继承自Throwable

不会导致事务回滚的异常类名字数组

有哪些事务传播行为?

在TransactionDefinition接口中定义了七个事务传播行为:

  1. PROPAGATION_REQUIRED如果存在一个事务,则支持当前事务。如果没有事务则开启一个新的事务。如果嵌套调用的两个方法都加了事务注解,并且运行在相同线程中,则这两个方法使用相同的事务中。如果运行在不同线程中,则会开启新的事务。
  2. PROPAGATION_SUPPORTS 如果存在一个事务,支持当前事务。如果没有事务,则非事务的执行。
  3. PROPAGATION_MANDATORY 如果已经存在一个事务,支持当前事务。如果不存在事务,则抛出异常IllegalTransactionStateException
  4. PROPAGATION_REQUIRES_NEW 总是开启一个新的事务。需要使用JtaTransactionManager作为事务管理器。
  5. PROPAGATION_NOT_SUPPORTED 总是非事务地执行,并挂起任何存在的事务。需要使用JtaTransactionManager作为事务管理器。
  6. PROPAGATION_NEVER 总是非事务地执行,如果存在一个活动事务,则抛出异常。
  7. PROPAGATION_NESTED 如果一个活动的事务存在,则运行在一个嵌套的事务中。如果没有活动事务, 则按PROPAGATION_REQUIRED 属性执行。

PROPAGATION_NESTED 与PROPAGATION_REQUIRES_NEW的区别:

使用PROPAGATION_REQUIRES_NEW时,内层事务与外层事务是两个独立的事务。一旦内层事务进行了提交后,外层事务不能对其进行回滚。两个事务互不影响。

使用PROPAGATION_NESTED时,外层事务的回滚可以引起内层事务的回滚。而内层事务的异常并不会导致外层事务的回滚,它是一个真正的嵌套事务。

Spring怎么解决循环依赖的问题?

首先,Spring单例对象的初始化大略分为三步:

  1. createBeanInstance:实例化Bean,使用构造方法创建对象,为对象分配内存。
  2. populateBean:进行依赖注入。
  3. initializeBean:初始化Bean。

Spring为了解决单例的循环依赖问题,使用了三级缓存:

singletonObjects:完成了初始化的单例对象map

earlySingletonObjects:完成实例化未初始化的单例对象map

singletonFactories: 单例对象工厂map,单例对象实例化完成之后会加入singletonFactories

在调用createBeanInstance进行实例化之后,会调用addSingletonFactory,将单例对象放到singletonFactories中。addSingletonFactory源码如下:

代码语言:txt
复制
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
    Assert.notNull(singletonFactory, "Singleton factory must not be null");
    synchronized (this.singletonObjects) {
        if (!this.singletonObjects.containsKey(beanName)) {
            this.singletonFactories.put(beanName, singletonFactory);
            this.earlySingletonObjects.remove(beanName);
            this.registeredSingletons.add(beanName);
        }
    }
}

假如A依赖了B的实例对象,同时B也依赖A的实例对象。

  1. A首先完成了实例化,并且将自己添加到singletonFactories
  2. 接着进行依赖注入,发现自己依赖对象B,此时就尝试去get(B)
  3. 发现B还没有被实例化,对B进行实例化
  4. 然后B在初始化的时候发现自己依赖了对象A,于是尝试get(A),尝试一级缓存singletonObjects和二级缓存earlySingletonObjects没找到,尝试三级缓存singletonFactories,由于A初始化时将自己添加到了singletonFactories,所以B可以拿到A对象,然后将A从三级缓存中移到二级缓存中
  5. B拿到A对象后顺利完成了初始化,然后将自己放入到一级缓存singletonObjects
  6. 此时返回A中,A此时能拿到B的对象顺利完成自己的初始化

总结:1、缓存已经遍历过的节点; 2、提前设置对象地址,后完善对象。

Spring 的单例 Bean 是否有线程安全问题?

当多个用户同时请求一个服务时,容器会给每一个请求分配一个线程,这时多个线程会并发执行该请求对应的业务逻辑,如果业务逻辑有对单例状态的修改(单例的成员属性),则必须考虑线程安全问题。

若每个线程中对全局变量、静态变量只有读操作,而无写操作,那么不会有线程安全问题;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则就可能影响线程安全。

无状态Bean和有状态Bean

  • 有实例变量的Bean,可以保存数据,是非线程安全的。
  • 没有实例变量的对象。不能保存数据,是线程安全的。

在Spring中无状态的Bean适合用单例模式,这样可以共享实例提高性能。有状态的Bean在多线程环境下不安全,可以用设置Bean为Prototype模式或者使用ThreadLocal解决线程安全问题。

Spring 用到了哪些设计模式?

1、简单工厂模式BeanFactory就是简单工厂模式的体现,根据传入一个唯一标识来获得 Bean 对象。

代码语言:txt
复制
@Override
public Object getBean(String name) throws BeansException {
    assertBeanFactoryActive();
    return getBeanFactory().getBean(name);
}

2、工厂方法模式FactoryBean就是典型的工厂方法模式。spring在使用getBean()调用获得该Bean时,会自动调用该Bean的getObject()方法。每个 Bean 都会对应一个 FactoryBean,如 SqlSessionFactory 对应 SqlSessionFactoryBean

3、单例模式:一个类仅有一个实例。Spring 创建 Bean 实例默认是单例的。

4、适配器模式:SpringMVC中的适配器HandlerAdatper。由于应用会有多个Controller实现,如果需要直接调用Controller方法,那么需要先判断是由哪一个Controller处理请求,然后调用相应的方法。当增加新的 Controller,需要修改原来的逻辑,违反了开闭原则(对修改关闭,对扩展开放)。

为此,Spring提供了一个适配器接口,每一种 Controller 对应一种 HandlerAdapter 实现类,当请求过来,SpringMVC会调用getHandler()获取相应的Controller,然后获取该Controller对应的 HandlerAdapter,最后调用HandlerAdapterhandle()方法处理请求,实际上调用的是Controller的handleRequest()。每次添加新的 Controller 时,只需要增加一个适配器类就可以,无需修改原有的逻辑。源码如下:

代码语言:txt
复制
// Determine handler for the current request.
mappedHandler = getHandler(processedRequest);

HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

public class HttpRequestHandlerAdapter implements HandlerAdapter {

    @Override
    public boolean supports(Object handler) {//handler是被适配的对象,这里使用的是对象的适配器模式
        return (handler instanceof HttpRequestHandler);
    }

    @Override
    @Nullable
    public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
        throws Exception {

        ((HttpRequestHandler) handler).handleRequest(request, response);
        return null;
    }
}

5、代理模式:spring 的 aop 使用了动态代理,有两种方式JdkDynamicAopProxyCglib2AopProxy

6、模板模式: Spring 中 jdbcTemplatehibernateTemplate 等使用了模板模式。

对你有用的话,点个赞,支持一下~

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • AOP有哪些实现方式?
  • JDK动态代理和CGLIB动态代理的区别?
  • Bean注入容器有哪些方式?
  • @Autowired和@Resource的区别?
  • @Qualifier 注解有什么作用
  • @Bean和@Component有什么区别?
  • @Component、@Controller、@Repositor和@Service 的区别?
  • Spring 事务实现方式有哪些?
  • 有哪些事务传播行为?
  • Spring怎么解决循环依赖的问题?
  • Spring 的单例 Bean 是否有线程安全问题?
  • Spring 用到了哪些设计模式?
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档