《JavaEE进阶》----11.<SpringIOCDI【Spring容器+IOC详解+DI介绍】>
本篇博客会详细讲解什么是Spring。
SpringIOC
SpringID
五个类注解:@Controller、@Service、@Repository、@Component、@Configuration
一个方法注解:@Bean
什么是Spring
IOC容器
Spring 是包含众多工具的IOC容器。能装东西的容器。
1.容器
如我们之前学的
TomCat就是Web容器
list/map:数据容器
学校:学生的容器。
Spring容器:是一个装对象的容器
2.IOC(Inversion of Control):控制翻转(控制权翻转)。指的是对象的控制权,对象交给Spring控制。指的是获取依赖对象的权利/过程。
IOC在之前的博客SpringMVC项目实践中已经用到了。就是我们在类上面添加注解。
@RestController和@Controller注解。
就是把这个对象交给Spring管理。Spring框架启动时就会加载该类。把对象交给Spring管理,就是IoC思想.
传统开发中:
对象谁使用谁控制
当我们想要获取依赖对象。我们需要new一个对象。这个对象定义在方法中 或者 定义在外面。这个对象的控制权都是谁使用谁控制。
现代开发中
对象交给Spring控制
现在我们已经不需要自己去创建这个对象。而是把创建对象的任务交给Spring容器(也就是Spring、SpringIOC容器)。我们只需要在程序中通过依赖注入(DI)(Dependeny Injection)就可以了。
控制反转思想在生活中的体现。
如开车。(驾驶权控制反转)
传统驾驶方式驾驶控制权是驾驶员的。
现在自动驾驶,驾驶权交给了自动驾驶系统。
如招聘,
企业的员工招聘,入职。解雇等控制权,从老板控制。
现在转交给HR(human resources)(人力资源)来处理
经典面试题:
1.Spring,SpringBoot,SpringMVC之间的区别和联系,你是如何理解的?
我的理解
1.Spring 是一个框架
2.SpringBoot 使我们在创建项目的时候可以直接添加一些依赖。并且内置web服务器、提供许多注解方便我们书写代码。对项目进行更多的监控指标,更好的了解项目的运行情况。简化我们的开发。
3.SpringMVC 是一个框架。是针对Web开发的一种MVC的思想的实现。也被称作Spring Web MVC(Spring Web)。在创建项目时,我们添加的依赖Spring Web实际上引的就是SpringMVC。可以认为Spring给我们提供的Web功能就叫做SpringMVC。
假如把Spring看作火车。(而做项目相当于坐火车) 但是它买票不方便。
因此就可以把SpringBoot看作是12306。而12306不仅可以订票还可以订酒店。打的等等。让我们坐火车(做项目更加的方便)
而SpringMVC 可以认为是火车里面提供的一些功能。比如买票,改签,插座等等。(注解/Cookie&Session)
2.Spring两大核心思想IOC和AOP
待续
3.常见面试题:ApplicationContext VS BeanFactory
1.继承关系和功能方面来说:
Spring 容器有两个顶级的接口:BeanFactory和 ApplicationContext。其中BeanFactory提供了基础的访问容器的能力,而ApplicationContext 属于BeanFactory的⼦类,它除了继承了BeanFactory的所有功能之外, 它还拥有独特的特性,还添加了对国际化支持、资源访问支持、以及事件传播等方面的支持.
2.从性能方面来说:
ApplicationContext 是⼀次性加载并初始化所有的 Bean 对象,而BeanFactory 是需要那个才去加载那个,因此更加轻量. (空间换时间)
一、IOC介绍+代码实践
通过一些代码,更清楚的理解Spring中的IOC。
通用程序的实现代码,类的创建顺序是反的。
总结:
传统代码
是Car控制并创建了Framework。Framework创建并创建了Bottom,依次往下
IOC思想
是把依赖对象注⼊将当前对象中,依赖对象的控制权不再由当前类控制了.这样的话,即使依赖类发生任何改变,当前类都是不受影响的,这就是典型的控制反转,也就是 IoC 的实现思想。
这部分代码就是IOC容器做的工作。
IOC容器的优点
资源不由使用资源的双方管理,而由不适用资源的第三方管理。
1.资源集中管理:IOC容器会帮我们管理一些资源(对象等)。我们使用时,从IOC容器取就可以了。
2.在创建实例的时候不需要了解其中的细节。降低了使用资源双方的依赖程度。也就是耦合度。
Spring就是一种IOC容器。帮助我们来做了这些资源。
1.1传统方式代码造车
public class TraditionNewCarExample {
/**
* 传统方式造车
* 1.先造轮胎
* 2.再造底盘
* 3.再造车身
* 4.汽车出品
* 这样设计可维护性很低。比如当需要加工多种尺寸的轮胎。
* 这时候就需要对程序进行修改。我们会发现牵一发而动全身。
* 我们需要去传入参数了。
*
* 创建对象的方式是:new car -> new Framework -> new bottom -> new tire
*/
static class Tire{
private int size;
public Tire(){
this.size = 20;
System.out.println("轮胎尺寸:"+size);
}
}
static class Bottom{
private Tire tire;
public Bottom(){
this.tire = new Tire();
System.out.println("Bottom init...");
}
}
static class Framework{
private Bottom bottom;
public Framework(){
this.bottom = new Bottom();
System.out.println("Bottom init..");
}
}
static class Car{
private Framework framework;
public Car(){
this.framework = new Framework();
System.out.println("car init...");
}
public void run(){
System.out.println("Car run");
}
}
public static void main(String[] args) {
Car car = new Car();
car.run();
}
}
1.2 更新维护传统方式的代码
public class RenewTraditionNewCar {
/**
* 传统方式造车
* 1.先造轮胎
* 2.再造底盘
* 3.再造车身
* 4.汽车出品
* 这样设计可维护性很低。比如当需要加工多种尺寸的轮胎。
* 这时候就需要对程序进行修改。我们会发现牵一发而动全身。
* 我们需要去传入参数了。
* 下面是对传统新建汽车的修改
* 可以看出,当最底层的代码改动之后。整个调用链上的所有代码都需要修改。
* 因此代码耦合度非常高。
*/
static class Tire{
private int size;
public Tire(int size){
this.size = size;
System.out.println("轮胎尺寸:"+size);
}
}
static class Bottom{
private Tire tire;
public Bottom(int size){
this.tire = new Tire(size);
System.out.println("Bottom init...");
}
}
static class Framework{
private Bottom bottom;
public Framework(int size){
this.bottom = new Bottom(size);
System.out.println("Bottom init..");
}
}
static class Car{
private Framework framework;
public Car(int size){
this.framework = new Framework(size);
System.out.println("car init...");
}
public void run(){
System.out.println("Car run");
}
}
public static void main(String[] args) {
Car car = new Car(15);
car.run();
}
}
1.3IOC思想代码造车
/**
* IOC模式造车
* 把创建子类的方式改为注入传递的方式。
*
* 创建对象的方式是:new tire -> new bottom -> new Framework -> new tire
*/
public class IOCNewCar {
public static void main(String[] args) {
Tire tire = new Tire(20);
Bottom bottom = new Bottom(tire);
Framework framework = new Framework(bottom);
Car car = new Car(framework);
car.run();
}
static class Tire{
private int size;
public Tire(int size){
this.size = size;
System.out.println("轮胎尺寸:"+size);
}
}
static class Bottom{
private Tire tire;
public Bottom(Tire tire){
this.tire = tire;
System.out.println("Bottom init...");
}
}
static class Framework{
private Bottom bottom;
public Framework(Bottom bottom){
this.bottom = bottom;
System.out.println("Framework init..");
}
}
static class Car{
private Framework framework;
public Car(Framework framework){
this.framework = framework;
System.out.println("Car init...");
}
public void run(){
System.out.println("Car run...");
}
}
}
二、DI介绍
什么是DI呢?
Dependency Injection(依赖注入)
容器在运行期间,动态的为应用程序提供运行时所依赖的资源。称为依赖注入
依赖注入(DI)和控制翻转(IOC)是从不同的角度描述同一件事。
就是指通过引入IOC容器。利用依赖关系注入的方式。实现对象之间的解耦合。
IoC是一种思想。
IoC是对象的控制反转,主要是用来创建对象的。实现把创建的对象的控制权交给Spring容器。
IOC是对依赖对象的创建。依赖对象的控制权交给Spring。管理依赖对象,对应存。
DI就是具体的实现。也就是DI是IoC的一种实现。
依赖注入,对IoC创建的依赖进行对象注入。可以认为依赖注入是如何拿到和使用IoC创建的依赖对象。对应取。
就像MVC是一种思想,而SpringMVC是具体的实现。
上面IOC思想代码造车的案例就是通过构造函数的方式,把依赖对象注入到需要使用的对象中
三、Spring IoC 和 DI的基本操作
上面是初步了解。接下来具体学习SpringIoC和DI的代码实现。我们还会用到许多的注解。
Spring是IOC容器。那么容器就有最基础的两个功能:
- 存对象(@Component)加上这个注解相当于我们把这个对象交给Spring管理了
- 取对象(@AutoWired)加上这个注销相当于我们把这个对象从Spring拿出来了
Spring容器 管理的主要是对象,这些对象我们称之为 “Bean”。我们把这些对象交给Spring管理。
由Spring负责对象的创建和销毁。
我们写的程序只需要告诉Spring哪些需要存。以及如何从Spring中取出对象。
我们下面以写图书管理系统为例。将Controller层、Service层、Dao层的解耦。
3.1把BookDao 交给Spring管理,由Spring来管理对象
Dao层
@Component
public class BookDao {
/**
* 1.把BookDao交给Spring管理,由Spring来管理对象。
* 数据Mock 获取图书信息
*/
public List<BookInfo> mockData() {
List<BookInfo> books = new ArrayList<>();
for (int i = 0; i < 5; i++) {
BookInfo book = new BookInfo();
book.setId(i);
book.setBookName("书籍"+i);
book.setAuthor("作者"+i);
book.setCount(i*5+3);
book.setPrice(new BigDecimal(new Random().nextInt(100)));
/**
* new Random().nextInt(100):
* 这一部分代码使用java.util.Random类生成一个随机整数。
* nextInt(100)会生成一个范围在0(含)到100(不含)之间的随机整数。
*/
book.setPublish("出版社"+i);
book.setStatus(1);
books.add(book);
}
return books;
}
}
3.2把BookService交给Spring管理,由Spring来管理对象
Service层
@Component
public class BookService {
private BookDao bookDao = new BookDao();
public List<BookInfo> getBookList(){
List<BookInfo> books = bookDao.mockData();
for (BookInfo book:books){
if(book.getStatus() == 1){
book.setStateCN("可借阅");
}else {
book.setStateCN("不可借阅");
}
}
return books;
}
}
3.3把BookController交给Spring管理,由Spring来管理对象
@RestController
@RequestMapping("/book")
public class BookController {
@Autowired
private BookService bookService;
@RequestMapping("/getList")
public List<BookInfo> getList(){
List<BookInfo> books = bookService.getBookList();
return books;
}
}
四、IoC详细用法
五个(类注解)和一个(方法注解)
存对象:
五大类注解@Controller,@Service,@repository,@component,@configuration
一个方法注解@bean.
@RestController不是,因为@RestController = @ResponseBody+@Controller
它之所以可以帮我们存对象,是里面Controller的原因,而不是其本身。
加了注解Spring会帮我们管理。没有加注解,Spring不会帮我们管理。
4.1 五个类注解
4.1.1五个注解的使用
五个注解属于类注解。他们用法相同。
之所以有五个注解,用法相同是为了分类使用。在存储这块的效果是一样的,
一点点差别
在请求入口上,(有@RequestMapping注解)只能使用@Controller企业规范。
功能上除了Controller都一样。
更多的不同是从概念上赋予了不同的含义。
1.表现层:@Controller
2.业务逻辑层:@Service
3.数据层:Dao,使用的注解是@Repository
除了这些层,还会遇到别的组件如redis等 用@component注解
相关的一些配置 用@configuration注解
分这些注解为了让我们更方便的找到相关代码
在详细介绍五个类注解之前,我们先了解一下启动类。
@SpringBootApplication启动类
被这个注解标识的叫做启动类
启动类注解。当
ApplicationContext context = SpringApplication.run(IocDomeApplication.class, args);
.run这个方法在运行的时候,就会帮我们创建对象了。创建完对象,有一个返回结果
返回类型为ConfigurableApplicationContext。它的父类是ApplicationContext
ApplicationContext可以认为是spring的一个上下文。可以理解为记录执行内容顺序的存放之处。
可以认为是一个spring的运行环境,spring是一个IOC容器,它的运行环境里面包含了很多个对象,帮我们管理这些对象
这些对象就存放在ApplicationContext。当通过@Controller注解存入了这个对象到Spring容器。我们取的时候,其实具体就是在ApplicationContext中取的。
常用的三种获取bean(对象)的方式。(附带六个注解使用示例)
注意:
1.获取bean的方式用下面的哪个注解都可以。这是只是演示他们是可以存对象到Spring容器的。
2.获取Bean的功能是BeanFeactory提供的。
注:这五个注解的用法是通用的,这里只是讲解获取bean的三种最常用方式。注解无所谓。
bean:Spring管理的对象都称之为bean。
1.根据类型来获取bean(对象)。
①@Controller注解(控制存储)
1.首先使用@Controller注解存储UserController对象
@Controller("bean") //创建对象 //括号中是对bean进行重命名 如果没有指定名称spring帮我们指定 public class UserController { public void say(){ System.out.println("Hi Controller!!!"); } }
2.从ApplicationContext取出UserController的对象
3.getBean方法就是获取对象
ApplicationContext context = SpringApplication.run(IocDomeApplication.class, args);
UserController bean1 = context.getBean(UserController.class); bean1.say();
2.根据名称来获取bean(对象)
②Service注解(服务存储)
1.首先使用@Service注解存储UserService对象
@Service public class UserService { public void say(){ System.out.println("Hi UserService!!!"); } }
bean名称的命名规则
bean这个名称为类名的小驼峰形式。
参考如下打印。l
例外:如果前两位都是大写字母,bean的名称不变,否则是小驼峰形式。
decapitalize("UserService")方法是bean名称的命名规则。
System.out.println(Introspector.decapitalize("UserService")); //userService
2.从Spring上下文。ApplicationContext类中获取对象
3.可以根据类型来获取对象,但我们换一种根据对象名称(名称被自动从UserService转换为userService)去拿。
而这种方式需要我们根据类型进行强转。这是因为返回类型为Object
ApplicationContext context = SpringApplication.run(IocDomeApplication.class, args);
UserService userService =(UserService) context.getBean("userService"); userService.say();
3.根据名称和类获取bean。
③Component注解
从ApplicationContext获取对象
1.首先使用@Component注解存储UserComponent对象
@Component public class UserComponent { public void say(){ System.out.println("Hi UserComponent!!!"); } }
2.从Spring上下文。ApplicationContext类中获取对象
ApplicationContext context = SpringApplication.run(IocDomeApplication.class, args);
根据名称和类获取bean。
UserComponent userComponent = context.getBean("userComponent", UserComponent.class); userComponent.say();
④@Repository注解
存
@Repository public class UserRepository { public void say(){ System.out.println("Hi Repository!!!"); } }
取
//4.通过Repository注解 UserRepository userRepository = (UserRepository) context.getBean("userRepository"); userRepository.say();
⑤@configuration注解(配置存储)
存
@Configuration public class UserConfiguration { public void say(){ System.out.println("Hi Configuration!!!"); } }
取
//5.通过configuration注解 UserConfiguration userConfiguration = context.getBean(UserConfiguration.class); userComponent.say();
PS:Spring是一个服务,服务启动。程序运行完。不会像学语法时那样直接结束。而是会一直提供服务。直到我们结束它。
上下文:
以CPU执行线程为例。CPU一秒执行上亿次。执行速度非常快来实现并发执行。当执行线程1到第二次执行线程1时。CPU怎么知道它执行到哪里了,接着哪里继续执行。这就是通过上下文记录的。Spring的上下文同理。
获取bean对象,父类BeanFactory提供的功能
4.1.2五大注解之间的关系
我们发现五个类注解中都有@component注解。
我们可以认为,其他是个注解是@component的衍生类。@component也可以称作父类 *
@component是一个元注解。也就是说可以注解其他类注解。
下面三个注解用于更具体的例
@Controller:控制层
@Service:业务逻辑层
@Repository:持久化层
单从功能上看,Controller除了具备让spring管理的功能外,接口的第一层入口必须为Controller,其他怎么调用都行
4.2一个方法注解@Bean
类注解是添加到某个类上的,但是存在两个问题:
1.使用外包部的类,没有办法添加注解
2.一个类,需要多个对象,比如多个数据源。
这种场景我们就需要使用方法注解
⑥@Bean注解 的使用
@Bean注解的方法。通过使用@Bean注解,Spring 容器会将这个方法的返回值注册为一个 Bean(对象),从而使其可以被应用程序的其他部分注入和使用。
为了更方便找到@Bean注解,应该告诉Spring哪个类中有@Bean。此时通过@Configuration来根据这个注解生成这个对象
@Bean需要配合五大注解一起使用。
@Bean注解定义的对象,默认名称为方法名
@Bean注解定义的对象,重命名,也是@Bean()在括号中写新名字
4.2.1定义多个对象
定义了多个对象的话,我们根据类型获取对象,获取的是哪个对象呢?
此时会报错哟:期望只有⼀个匹配,结果发现了多个
我们需要
@Bean注解的bean,bean的名称就是它的方法名
接下来我们根据名称来获取bean对象
存
@Configuration public class BeanConfig { @Bean public UserInfo userInfo(){ UserInfo userInfo = new UserInfo(); userInfo.setId(120); userInfo.setName("张三"); userInfo.setAge(18); return userInfo; } @Bean public UserInfo userInfo2(){ UserInfo userInfo2 = new UserInfo(); userInfo2.setId(121); userInfo2.setName("李四"); userInfo2.setAge(28); return userInfo2; } }
取(方法名)
UserInfo userInfo = (UserInfo) context.getBean("userInfo"); UserInfo userInfo2 = (UserInfo) context.getBean("userInfo2"); System.out.println(userInfo); System.out.println(userInfo2);
4.2.2bean的重命名
重命名名字也可以使用数组形式分别命名与之对应的下面的方法。例如:@Bean({"u1","u2"})
bean可以针对同一个类,定义多个对象
存
@Configuration public class BeanConfig { @Bean({"u1","u2"}) public UserInfo userInfo(){ UserInfo userInfo = new UserInfo(); userInfo.setId(120); userInfo.setName("张三"); userInfo.setAge(18); return userInfo; } @Bean public UserInfo userInfo2(){ UserInfo userInfo2 = new UserInfo(); userInfo2.setId(121); userInfo2.setName("李四"); userInfo2.setAge(28); return userInfo2; } }
取(重命名+类名)
//6.通过@Bean方法注解 UserInfo userInfo = context.getBean("u1",UserInfo.class); UserInfo userInfo2 = context.getBean("u2",UserInfo.class); System.out.println(userInfo); System.out.println(userInfo2);
隐藏条件:
这五大注解必须在Spring的扫描路径下才会生效。
扫描路径默认为:启动类所在的路径。
是被@componentScan标识的当前类所在的路径。@SpringBootApplication包含了@componentScan。因此也是被@SpringBootApplication标识的类
由于本篇博客内容已经很多了,下一篇文章,我们会持续SpringIoC&ID的创作
会详细讲到扫描路径及关于其剩下的知识内容。