Spring常见面试题

Spring常见面试题

Posted by bulingfeng on March 2, 2021

简介

Spring是java程序员面试的必然问题,而如何进行回答就需要详细总结下,并且还要理解的有深度,否则就与他人拉不开差距,而每一次的的精心准备其实都是在给自己博取一些优质的筹码。

依赖注入

依赖注入顾名思义其实就是依赖一个对象,然后把这个对象中所依赖的属性都给赋值相对应的值。

类的注入顺序如下

TestService类–>推断构造方法—>创建对象(这里只是对对象实例化一个动作,对象并不是一个可用状态)—>依赖注入–>初始化前(@PostConstuct)—>初始化(实现InitializingBean接口)—>初始化后(AOP)—>Bean对象(进行了AOP的话,这个对象就是代理对象)

而其中的依赖注入,其实就是获取到创建的对象之后,然后通过反射获取到对应的注解,看注解是否有@Autowire@Resource等注解,然后通过反射把这些属性的值给赋值上,从而完成bean对象的创建。

默认情况下Spring创建bean的是单例模式,通过Map的形式来进行存储Bean,key=bean的名称,value=bean实例。

而如果把Spring改成多例的模式以后则无需进程存储,因为每次都是新的,存储也没有用。

其实从上面的信息来看,依赖注入的对象其实就是相当于通过new的形式来创建一个对象,然后把对应的对象复制给对象的属性。

假如是像从外界获取一些信息,比如从mysql数据库之类的获取到一些信息,然后组装成一个对象,这个时候应该怎么做呢?方案有多种。

方案1:

我们就需要在依赖注入的时候,并且进行初始化之前的时候,来组装一个bean。比如可以使用@PostConstruct注解,来给对象的属性赋值。

1
2
3
4
5
6
7
8
9
10
		private String name;
    public void test(){
        System.out.println("test....,name:"+name);
    }
		
		// 其实就是在依赖注入的时候,判断是否有方法带有@PostConstruct注解,如果带有就优先进行注入。
    @PostConstruct
    public void applyName(){
        name="bulingfeng";
    }

方案2:

可以通过@Bean的注解,通过mysql数据库把数据读取到,然后再组装成bean,而注入的bean中只需要使用@Autowire注解注入对象即可。

方案3:

把这个赋值属性的操作给放到初始化的时候,即实现InitializingBean接口来进行实现。

推断构造方法

给属性赋值的时候会进行构造方法推断,如果只有一个构造方法,那么直接用可以了。如果有无参构造,那么直接使用无参构造。有多个构造的方法的话,但是没有无参构造报错。

为何会出现循环依赖

Spring在依赖注入的时候,比如通过构造器的方式来依赖注入,比如如下代码

1
2
3
4
5
6
7
8
9
10
11
12
@Service
public class TestService {
    private ConfigTest helloController;

    @Autowired
    public TestService(ConfigTest helloController) {
        this.helloController = helloController;
    }
    public void test(){
        System.out.println(helloController);
    }
}

因为构造函数中会有入参helloController,也就是说当Spring使用反射的时候是需要传过来一个对象的。那么这个时候就需要去容器中查找是否有ConfigTest对象。

  • 如果有那么就把这个对象传入进来。
  • 如果没有那么就去创建ConfigTest对象,而这个时候,如果ConfigTest中还引用了TestService对象,那么就出现ConfigTest->TestService->ConfigTest,这样的情况,所以循环依赖就产生了。

byType和byName查找原则

1
2
3
4
5
6
7
8
9
10
11
12
@Service
public class TestService {
    private ConfigTest helloController;

    @Autowired
    public TestService(ConfigTest helloController) {
        this.helloController = helloController;
    }
    public void test(){
        System.out.println(helloController);
    }
}

比如上面的构造器代码中,假如有多个ConfigTest呢?那么构造器如何进行注入bean呢?

先通过byType去map容器中查找,如果只有一个bean对象,那么直接注入;

当通过byType去map容器中查找,发现有多个bean对象,那么就按照入参的参数名称即helloController这个名字作为bean的名字来进行查找;

1
2
3
4
5
6
7
8
9
10
@ComponentScan("org.example.service.test")
@Service
public class TestService {
    @Autowired
    private ConfigTest helloController1111;

    public void test(){
        System.out.println(helloController1111);
    }
}

如果是上述的代码呢? 答案是:只能通过byType的形式去查找

1
2
3
4
5
6
7
8
9
10
@ComponentScan("org.example.service.test")
@Service
public class TestService {
    @Resource
    private ConfigTest helloController1111;

    public void test(){
        System.out.println(helloController1111);
    }
}

如果是换成@Resource注解呢?

答:和构造器的方式是一样的,先通过byType进行查找,如果有直接注入。

当通过byType查询到多个bean对象的时候,开始根据变量的名字进行查找,如果没有查找到则报错。

AOP

1
2
3
4
5
6
7
8
9
10
11
12
@ComponentScan("org.example.service.test")
@Service
@EnableAspectJAutoProxy
public class TestService {

    @Autowired
    private ConfigTest helloController1111;

    public void test(){
        System.out.println(helloController1111);
    }
}
1
2
3
4
5
6
7
8
9
10
@Component
@Aspect
public class AspectTest {

    @Before("execution((*)org.example.service.TestService.test())")
    public void takeSeats() {
        System.out.println("Taking seats");
    }

}
1
2
3
4
5
6
7
8
public static void main(String[] args) {
    AnnotationConfigApplicationContext context=new AnnotationConfigApplicationContext(TestService.class);


    TestService bean = context.getBean("testService",TestService.class);

    bean.test();
}

上面的代码会通过CGLIB来实现动态代理。当我们得到bean对象的时候,发现bean对象是一个代理类。并且这个代理类中的ConfigTest对象为null。下面是动态代理的伪代码

1
2
3
4
5
6
7
8
9
10
public class TestServiceProxy extends TestService{
		TestService testService
		
		public void test(){
				//1.执行aop的切面
				//2.执行testService中的test方法
				 testService.test();
				//3.执行aop的切面
    }
}