Spring中的控制反转和依赖注入(IoC和DI)以及常见面试题
1.什么是Spring
Spring在不同情景下呢有不同的意思。
(1)比如说你的项目里面使用到了Spring,那这里的Spring指的是Spring家族,其中包含了各种各样的框架;
(2)要是说Spring有什么特点其中一个是IoC那么这里指的就是Spring Core,Spring Framework。(Spring Core 是 Spring Framework 的核心模块,主要负责实现 IoC 和 DI 功能;而 Spring Framework 是一个整合了多个模块的完整开发框架,包含 Spring Core、AOP、Web、Data 等模块)Spring是包含众多工具的IoC容器,它既然是容器里面肯定需要装东西,那Spring存储的是对象也可以称之为Bean,当然只在Spring范围内这么称呼。
Spring ,Spring MVC,Spring Boot的区别?(常见面试题)
Spring MVC,Spring Boot都属于Spring,Spring MVC是基于Spring的基础上开发出来的一种框架,主要用来做网站开发所以也叫Spring WEB MVC;Spring Boot是基于Spring的一套快速开发整合包,主要用来快速搭建框架。
2.什么是IoC?
IoC专业词汇解释是控制反转。意思就是原本我们使用对象的时候需要自己去new进行创建,那有了Spring之后,我们把创建对象的权力交由Spring来做,这就叫控制反转。其实有点像我们要得到某样东西可以选择买或者选择自己做,就比如我想要吃蛋糕,你可以搜教程和买工具然后自己做(这相当于我们自己创建对象);当然也可以直接去蛋糕店买想要什么蛋糕直接向店家描述然后让店家来给你做(这相当于由Spring来创建对象)。
优点:使用控制反转主要是为了解耦。这就牵扯到了软件的设计原则:高内聚,低耦合。
高内聚:一个模块内部的关系,低耦合:各个模块之前的关系。
同时也有节约空间的作用,比如说之前一个对象只能一个人来用,但是现在交给Spring创建大家都可以用,实现资源共享。
3.实现IoC(DI依赖注入实现)
IoC其实只是一种思想,那具体Spring怎么去实现的呢?使用DI也就是依赖注入。依赖注入就是指在容器运行期间,动态的为应用程序提供运行所依赖的资源。就类似于点餐,你告诉厨师想要吃什么这就是声明依赖,厨师给你提供餐食这就是注入依赖。
代码实现:
自己创建对象
private BookDao bookDao = new BookDa0();
声明依赖
@Component
public class BookDao{}
注入依赖(告诉Spring,从容器中取出这个对象,赋值给当前对象属性)
@Autowired
private BookDao bookDao;
4.IoC详解
上面的只是让大家对IoC有个基础的认识,下面会对IoC进行详细的学习
4.1Bean的存储
前面说了Spring是IoC容器,存储的是对象,所以需要在类上加上@Component的注解来表示声明,而Spring框架提供了不止这一种注解。
类注解:@Controller,@Service,@Repository,@Component,@Configuration
方法注解:@Bean
4.1.1@Controller(控制器存储)
声明对象
@Controller
public class UseController {public void doController(){System.out.println("do controller");}
}
@SpringBootApplication
public class SpringTestApplication {public static void main(String[] args) {//Spring上下文,所以这里run方法返回的就是Spring的运行环境ApplicationContext context = SpringApplication.run(SpringTestApplication.class, args);UserController bean = context.getBean(UserController.class);bean.doController();}}
运行结果
ApplicationContext VS BeanFactory(常见面试题,八股文只需要背诵即可)
继承关系和功能方面来说:Spring 容器有两个顶级的接口:BeanFactory和 ApplicationContext。其中BeanFactory 提供了基础的访问容器的能力,而 ApplicationContext 属于 BeanFactory 的子类,它除了继承了 BeanFactory 的所有功能之外,它还拥有独特的特性,还添加了对国际化支持、资源访问支持、以及事件传播等方面的支持。
从性能方面来说:ApplicationContext 是一次性加载并初始化,所有的 Bean 对象,而 BeanFactory 是需要那个才去加载那个,因此更加轻量。(空间换时间)
4.1.2@Service(服务存储)
声明对象
@Service
public class UserService {public void doService(){System.out.println("do Service");}}
@SpringBootApplication
public class SpringTestApplication {public static void main(String[] args) {//Spring上下文,所以这里run方法返回的就是Spring的运行环境ApplicationContext context = SpringApplication.run(SpringTestApplication.class, args);UserController bean = context.getBean(UserController.class);bean.doController();UserService service = context.getBean(UserService.class);service.doService();}}
运行结果:
4.1.3@Repository(仓库存储)
声明对象
@Repository
public class UserRepository {public void doRepository(){System.out.println("do Repository");}}
@SpringBootApplication
public class SpringTestApplication {public static void main(String[] args) {//Spring上下文,所以这里run方法返回的就是Spring的运行环境ApplicationContext context = SpringApplication.run(SpringTestApplication.class, args);UserController bean = context.getBean(UserController.class);bean.doController();UserService service = context.getBean(UserService.class);service.doService();UserRepository repository = context.getBean(UserRepository.class);repository.doRepository();}
}
运行结果:
4.1.4@Component(组件存储)
声明对象:
@Component
public class UserComponent {public void doUserComponent(){System.out.println("do UserComponent");}
}
@SpringBootApplication
public class SpringTestApplication {public static void main(String[] args) {//Spring上下文,所以这里run方法返回的就是Spring的运行环境ApplicationContext context = SpringApplication.run(SpringTestApplication.class, args);UserController bean = context.getBean(UserController.class);bean.doController();UserService service = context.getBean(UserService.class);service.doService();UserRepository repository = context.getBean(UserRepository.class);repository.doRepository();UserComponent userComponent = context.getBean(UserComponent.class);userComponent.doUserComponent();}
}
运行结果:
4.1.5 @Configuration(配置存储)
声明对象:
@Configuration
public class UserConfig {public void doUserConfig(){System.out.println("do UserConfig");}}
@SpringBootApplication
public class SpringTestApplication {public static void main(String[] args) {//Spring上下文,所以这里run方法返回的就是Spring的运行环境ApplicationContext context = SpringApplication.run(SpringTestApplication.class, args);UserController bean = context.getBean(UserController.class);bean.doController();UserService service = context.getBean(UserService.class);service.doService();UserRepository repository = context.getBean(UserRepository.class);repository.doRepository();UserComponent userComponent = context.getBean(UserComponent.class);userComponent.doUserComponent();UserConfig userConfig = context.getBean(UserConfig.class);userConfig.doUserConfig();}
}
运行结果;
以上是通过类名来获取对象
4.1.6通过名称来获取对象
- 若未明确指定名称,Spring 会把类名的首字母小写作为默认名称。
- 当类名以两个大写字母开头时,默认名称会和类名完全一样。
@SpringBootApplication
public class SpringTestApplication {public static void main(String[] args) {//Spring上下文,所以这里run方法返回的就是Spring的运行环境ApplicationContext context = SpringApplication.run(SpringTestApplication.class, args);UserController bean = context.getBean(UserController.class);bean.doController();UserService service = context.getBean(UserService.class);service.doService();UserRepository repository = context.getBean(UserRepository.class);repository.doRepository();UserComponent userComponent = context.getBean(UserComponent.class);userComponent.doUserComponent();UserConfig userConfig = context.getBean(UserConfig.class);userConfig.doUserConfig();System.out.println("------------");//根据名称获取对象,需要进行类型转换UserController controller = (UserController)context.getBean("userController");controller.doController();//根据名称和类名获取对象context.getBean("userService", UserService.class).doService();}
}
4.2为什么要这么多的类注解?
其实就类似于车牌号码一样,各个省份的车牌号开头是不一样的,节约号码的同时还能便于识别车辆的归属地。那这么多类注解的作用是类似的,是代码能够分层然后更好的去维护代码。
几个注解之间的关系:都是Component的衍生类,功能相同,但是规范里面不同的情形下用不同的注解。不过Controller比较特殊,如果外界想要访问,只能通过Controller,程序的入口只能通过Controller。
4.3@Bean
五大注解只能加在类上,并且只能加在自己的代码上。如果使用了第三方的jar包,我们也想要交给Spring管理的话是不能使用五大注解的;同时遇到对于同一个类中需要定义多个对象的时候比如说数据库操作需要定义多个数据源时也是不能使用五大注解的。所以我们需要使用@Bean这个方法注解。
@Bean我们在获取对象的时候往往使用名称来获取,因为使用类名的时候,同时我们又定义了多个对象,这些对象的类型一样就会报错。
数据源:
@Data
public class UserInfo {private int age;private String name;private int id;}
使用BeanConfig这个类来定义和返回UserInfo对象,这里的@Bean需要搭配五大注解使用。因为整个项目里面的文件是非常多的,那在运行的时候需要扫描文件,但是不可能每一个文件都去扫描,那么这个时候搭配五大注解来使用就是告诉Spring运行的时候需要扫描到当前的文件。
@Configuration
public class BeanConfig {@Beanpublic UserInfo userInfo() {UserInfo userInfo = new UserInfo();userInfo.setAge(22);userInfo.setName("张三");userInfo.setId(1);return userInfo;}@Beanpublic UserInfo userInfo2() {UserInfo userInfo = new UserInfo();userInfo.setAge(24);userInfo.setName("李四");userInfo.setId(2);return userInfo;}
}
@SpringBootApplication
public class SpringTestApplication {public static void main(String[] args) {ApplicationContext context = SpringApplication.run(SpringTestApplication.class, args);UserInfo userInfo = context.getBean(UserInfo.class);System.out.println(userInfo);}
}
代码报错,获取到了两个userInfo,因为定义多个对象的时候使用了相同类型。
Exception in thread "main" org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.example.springtest.demos.web.config.UserInfo' available: expected single matching bean but found 2: userInfo,userInfo2at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveNamedBean(DefaultListableBeanFactory.java:1273)at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveBean(DefaultListableBeanFactory.java:494)at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:349)at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:342)at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1172)at com.example.springtest.SpringTestApplication.main(SpringTestApplication.java:37)
@Bean中存储对象的名称就是对象的方法名
@SpringBootApplication
public class SpringTestApplication {public static void main(String[] args) {ApplicationContext context = SpringApplication.run(SpringTestApplication.class, args);UserInfo userInfo =(UserInfo) context.getBean("userInfo");System.out.println(userInfo);}
}
运行结果:
5.DI详解
DI直译是依赖注入,它也有很多其他的名称如“属性装配”“依赖装配”。依赖注入的方式大概分为以下三种
5.1属性注入
属性注入以类型进行匹配,与注入的属性名称无关。
@Controller
public class UserController {@Autowiredprivate UserService us;public void doController(){us.doService();System.out.println("do controller");}
}
@SpringBootApplication
public class SpringTestApplication {public static void main(String[] args) {ApplicationContext context = SpringApplication.run(SpringTestApplication.class, args);UserController controller =(UserController) context.getBean("userController");controller.doController();}
}
但是如果一个类型存在多个对象时,优先名称匹配,如果名称匹配不上,就会报错。而且无法注入一个Final修饰的属性。所以官方不建议使用这种方式来进行依赖注入。
解决办法
(1).属性名和你需要使用的对象名保持一致
(2).使用@Primary注解标识默认的对象
(3).使用@Qualifier
(4).使用@Resource注解
@Autowird 与 @Resource的区别(常见面试题)
(1)@Autowired 是Spring框架提供的注解,而@Resource是JDK提供的注解
(2)@Autowired 默认是按照类型注入,而@Resource是按照名称注入,相比于 @AutoWired 来说,@Resource支持更多的参数设置,例如 name 设置,根据名称获取 Bean。
补充:@Autowired 默认是按照类型注入的,但是同一个类型存在多个对象的时候会按名称匹配,名称也匹配不上则会报错。
5.2构造方法注入
如果存在多个构造函数时,需要加@AutoWired来注明要使用哪一个构造函数,如果只有一个构造函数时@AutoWired可以省略
@Controller
public class UserController {private UserService us;private UserInfo info;public UserController(){}@Autowiredpublic UserController(UserService us){this.us = us;}public UserController(UserService us, UserInfo info){this.us = us;this.info = info;}public void doController(){us.doService();System.out.println("do controller");}
}
@SpringBootApplication
public class SpringTestApplication {public static void main(String[] args) {ApplicationContext context = SpringApplication.run(SpringTestApplication.class, args);UserController controller =(UserController) context.getBean("userController");controller.doController();}
}
5.3Setter方法注入
@Controller
public class UserController {private UserService us;@Autowiredpublic void setUs(UserService us) {this.us = us;}public void doController(){us.doService();System.out.println("do controller");}
}
@SpringBootApplication
public class SpringTestApplication {public static void main(String[] args) {ApplicationContext context = SpringApplication.run(SpringTestApplication.class, args);UserController controller =(UserController) context.getBean("userController");controller.doController();}
}