Java面试题
- 1、面向对象与面向过程
- 2、Spring源码分析(可能比较复杂,但是看下去可能会找到让你顿悟的字眼)
- 2.1、Spring创建Bean对象
- 2.2 实例化(推断构造方法)
1、面向对象与面向过程
- 封装:封装在于明确标识出允许外部使用的所有成员函数和数据项,内部细节对外部调用透明,外部调用无需修改或者关系内部实现
private String name;
public void setName(String name){
this.name = "tuling_"+name;
}
//name有自己的命名规则,明显不能由外部直接赋值
- 继承:继承基类的方法,并做出自己的改变或扩展,但是无法调用子类特有的功能
- 多态:基于对象所属类的不同,外部对同一个方法的调用,实际执行的逻辑不同。需要三个条件:继承、方法重写、父类引用指向子类对象
public class peoper{
public void sayName();
}
public class Tom{
public void sayName(){
System.out.println("Tom!");
}
public class John{
public void sayName(){
System.out.println("John!");
}
}
2、Spring源码分析(可能比较复杂,但是看下去可能会找到让你顿悟的字眼)
2.1、Spring创建Bean对象
先看一段代码
@mapper
public class UserService{
@Autowired
private OrderService orderService;
public void test(){
System.out.println(orderService);
}
}
上面这段代码中@mapper,实际上执行了Spring创建Bean对象并放入Spring容器的过程,如下
- UserService实例化(无参构造方法),生成普通对象
- 对普通对象依赖注入- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -【根据@Autowired判断是否对属性注入依赖】
- 初始化- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -【执行所有方法】
- 初始化后(AOP),生成代理对象 - - - - - - - - - - - - - - - - - - - - - - - - - - - - -【切面编程】
- 将普通对象或者代理对象放入,Map<beanName,Bean对象> - - - - - - - - -【即注册Bean对象】
下面对上述过程一一讲解
- UserService实例化,生成普通对象
这里的UserService实例化,可不是说让你自己在main()方法里面手动做一个UserService userService的实例化。而是说你在public class UserService“头顶”上加了注解@Mapper的时候,Spring“背地里”进行了第一步:将UserService实例化了,也就是UserService userService = new UserService();
- 使用@Autowired,对普通对象的属性进行依赖注入
public class UserService{
/*
在UserService对象中有一个属性OrderService,
Spring也对该属性进行了实例化OrderService orderService
但是实例化出来的普通对象orderService是没有值的,
通过@Autowired的依赖注入,普通对象orderService就有了值,
*/
@Autowired
private OrderService orderService;
public void test(){
System.out.println(orderService);
}
}
@Autowired依赖注入的伪代码
//1. 先创建普通对象
UserService userService = new UserService();
//2. 寻找在userService对象中,被@Autowired标记的属性进行依赖注入
for (Field field:UserService.getClass().getDeclareFields()){ //依次获取属性
if(field.isAnnotationPresent(Autowired.class)){ //判断是否需要依赖注入
field.set(userService,??); //依赖注入
}
}
- 初始化(执行UserService中的所有方法)
//初始化,执行UserService中的所有方法,全都成功后才能将Bean对象创建并放入Map
if (userService instanceof InitializingBean){
((InitializingBean) userService).afterPropertiesSet();
}
为什么要执行初始化这个步骤呢?换句话说,为什么要执行所有方法呢?
//1. 通过执行方法来为创建的实例的属性admin注入值为xxx
@Override
public void afterPropertiesSet() throws Exception{
this.admin = "xxx";
}
//2. 通过执行方法来检验创建的实例是否满足
public void afterPropertiesSet() throws Exception{
if(sqlSession == null){
throw new NullPointerException();
}
}
/*
3. 个人项目经历:每个属性都要get/set方法,若是缺失了某个属性的get/set方法,
Spring启动就会报错,这也说明了初始化执行了Bean对象的所有方法
*/
- 初始化后(AOP),创建代理对象
AOP就是对注入依赖后的普通对象进行切面编程,从而生成代理对象。至于什么是切面编程AOP,读者就自行搜索一下吧,因为我也只是一知半解。
- 将普通对象或者代理对象放入,Map<beanName,Bean对象>
- 我们先看到回到上面Spring创建Bean对象的过程,会发现有普通对象和代理对象。两者的区别,大家可以认为是普通对象经过了“加工”,变成了更多功能的代理对象。
- 现在我们要将普通对象或者代理对象放入Map并将代理对象放入Map<beanName,Bean对象>中。
- 如果没有进行AOP生成代理对象,那么这时候就是将普通对象放入Map中,普通对象成为Bean对象;如果进行了AOP生成代理对象,那么这时候就是将代理对象放入Map中,代理对象成为Bean对象;
- 将普通对象或者代理对象放入Map这个动作完成了,才是真正意义上生成了Bean对象,也就是注册了Bean
2.2 实例化(推断构造方法)
首先我们给出一段代码,这个后面讲解有参构造器要用,这里先告诉读者存在这个类
@Component
public class OrderService{
}
- 没有写出构造器,默认存在一个无参构造器
@Service
public class UserService{
private OrderService orderService;
public void test(){
System.out.println(orderService);
}
}
执行UserService userService = (UserService) applicationContext.getBean(“UserService”);
输出:null
- 写一个有参构造器,那么构造器就只有这个有参构造器
@Service
public class UserService{
private OrderService orderService;
public UserService(OrderService orderService){
this.orderService = orderService;
System.out.println(1);
}
public void test(){
System.out.println(orderService);
}
}
执行UserService userService = (UserService) applicationContext.getBean(“UserService”);
输出:1、#{orderService指针编号}
- 写一个无参构造器和一个有参构造器,那么就存在两个构造器
@Service
public class UserService{
private OrderService orderService;
public UserService(){
System.out.println(0);
}
public UserService(OrderService orderService){
this.orderService = orderService;
System.out.println(1);
}
public void test(){
System.out.println(orderService);
}
}
执行UserService userService = (UserService) applicationContext.getBean(“UserService”);
输出:0、null
- 编写一个有参构造器,那么就存在一个有参构造器
@Service
public class UserService{
private OrderService orderService;
public UserService(OrderService orderService1,OrderService orderService2){
this.orderService = orderService;
System.out.println(2);
}
public void test(){
System.out.println(orderService);
}
}
执行UserService userService = (UserService) applicationContext.getBean(“UserService”);
输出:2、#{orderService指针编号}
- 编写如下两个有参构造器
@Service
public class UserService{
private OrderService orderService;
public UserService(OrderService orderService){
this.orderService = orderService;
System.out.println(1);
}
public UserService(OrderService orderService1,OrderService orderService2){
this.orderService = orderService;
System.out.println(2);
}
public void test(){
System.out.println(orderService);
}
}
执行UserService userService = (UserService) applicationContext.getBean(“UserService”);
报错!Caused by:com.zhouyu.service.UserService.< init >()
总结
- 如果没有重写构造方法,那么创建Bean对象就会用无参构造方法;
- 如果重写了唯一 一个有参构造方法,那么创建Bean对象就会用该有参构造方法;
- 如果重写了两个有参构造方法,那么创建Bean对象不知道用哪个有参构造方法,就会去寻找无参构造方法,如果没有无参构造方法就报错。如果存在无参构造方法就执行无参构造方法。
- 那么有没有办法使用指定的构造方法?通过@Autowired实现
@Service
public class UserService{
private OrderService orderService;
@Autowired //指定使用该构造方法,即使存在无参构造器,也要使用这个有参构造器
public UserService(OrderService orderService){
this.orderService = orderService;
System.out.println(1);
}
public UserService(OrderService orderService1,OrderService orderService2){
this.orderService = orderService;
System.out.println(2);
}
public void test(){
System.out.println(orderService);
}
}
执行UserService userService = (UserService) applicationContext.getBean(“UserService”);
输出:1、#{orderService指针编号}
- 有参构造器public UserService(OrderService orderService)中,参数orderService经过test打印出来不是空的,那么是谁将值传给orderService的呢?
- 当我们创建Bean对象userService时,需要用到有参构造方法,Spring就会在Map中寻找Bean对象OrderService,并且注入到构造方法的参数orderService中。
- 当然这个Bean对象OrderService早就在 2.2 开头的那段代码给出了,@Component就把OrderService注册成了Bean对象。
- 如果将@Component去掉,那么OrderService就无法成为Bean对象,那么Spring也拿不出Bean对象来传给UserService的有参构造方法的参数orderService,那么orderService就为空。但是Spring规定了不能向有参构造方法的参数传null。所以这种情况运行就会出现下面结果:
报错:No qualifying bean of type 'com.zhouyu.service.OrderService'
- 那么Spring怎样从Map<beanName,Bean对象>中,找到合适的Bean对象传入有参构造器的参数中?因为可能同一个类注册了多个bean。
- 如果通过参数的类OrderService找出beanName唯一,那就直接将Bean对象注入orderService。
- 如果通过参数的类OrderService找出beanName不唯一,那就通过参数名orderService找出唯一的beanName,然后将Bean对象注入orderService参数
- 如果这样都找不到,那就报错!
- 如下这三个orderService,orderService1,orderService2分别是可选的beanName,将构造器public UserService(OrderService orderService)的参数orderService改成上述之一即可不报错。
expected single matching bean but found 3:orderService,orderService1,orderService2
- 循环依赖(了解)
@Component
public class OrderService{
private UserService userService;
public OrderService(UserService userService){
this.userService = userService;
}
}
@Service
public class UserService{
private OrderService orderService;
public UserService(OrderService orderService){
this.orderService = orderService;
}
}
报错:Is there an unresolvable circular reference?
我们来看一下过程:当我要将UserService创建为Bean对象时,那么就需要传入OrderService的Bean对象给orderService。那么OrderService要提前被Spring创建为Bean对象是不是?那我们看看OrderService要提前被Spring创建为Bean对象也是用到有参构造器,需要传入UserService的Bean对象,那么UserService要提前被Spring创建为Bean对象…这样就陷入了循环了(circular reference)