目录
🚩Spring是什么
🎈什么是容器?
🎈什么是 IoC?
📝传统开发思路
📝IOC思想
📝IoC 优势
🎈DI 介绍
🚩IoC 详解
🎈Bean的存储
🔴五大类注解
📝@Controller(控制器存储)
👩🏻💻获取bean对象的方式
📝@Service(服务存储)
📝@Repository(仓库存储)
📝@Component(组件存储)
📝@Configuration(配置存储)
🍭类注解之间的关系
🔴⽅法注解 @Bean
🔴Bean传递参数
🎈SpringBoot特点
🚩DI 详解
🎈属性注⼊
🎈构造方法注入
🎈Setter注入
🔴@Autowired存在问题
📝解决方法一 属性名和bean名称相同
📝解决方法二 @Primary
📝解决方法三 @Qualifier
📝解决方法四 @Resource
🔴五大注解修改名以及Bean修改名
📝Bean修改名
📝类注解修改
我们学习了springBoot和spring mvc的开发,完成了基本的功能开发了,但是真正的spring,spring mvc以及spring boot之间又有什么关系呢?
🚩Spring是什么
spring是一个开源框架,让我们的开发更加的简单,支持广泛的应用场景,有着活跃而庞大的社区,这可能就是spring一直存在的原因吧。
我们⽤⼀句更具体的话来概括Spring, 那就是: Spring 是包含了众多⼯具⽅法的 IoC 容器
🎈什么是容器?
容器是⽤来容纳某种物品的(基本)装置。⸺来⾃:百度百科⽣活中的⽔杯, 垃圾桶, 冰箱等等这些都是容器.我们想想,之前课程我们接触的容器有哪些?• List/Map -> 数据存储容器• Tomcat -> Web 容器
🎈什么是 IoC?
IOC是Spring的核心思想,什么是IOC呢?IOC是一种思想,之前我们在学习spring MVC 中,我们在类上添加@RestController 和 @Controller 注解,就是把这个类对象交给spring管理,spring框架启动时就会加载该类,把对象交给spring管理,就是IOC思想。
IoC: Inversion of Control (控制反转), 也就是说 Spring 是⼀个"控制反转"的容器
- 什么是控制反转?——控制权反转
- 控制权反转具体是体现在哪里呢?
获得依赖对象的过程中被反转了,也就是说,当需要某个对象时 ,传统开发模式都是自己new创建对象,现在不需要自己去创建对象了,把创建的对象的任务交给容器(也就是spring容器),程序中只需要注入(Dependency Injection,DI)就可以了.
- spring是根据什么创建对象的呢?
之前我们在学习spring MVC 中,我们在类上添加@RestController 和 @Controller 注解,就是把这个类对象交给spring管理。——通过注解
IOC思想(在我的理解):通过注解对该对象进行了控制,将其存入到spring容器中,让spring对这个对象进行管理和创建。
相信大家看完上述的描述,对IOC思想了解了很多。下面我们通过一个案例进行加深理解。
📝传统开发思路
我们的实现思路是这样的:先设计轮⼦(Tire),然后根据轮⼦的⼤⼩设计底盘(Bottom),接着根据底盘设计⻋⾝(Framework),最后根据⻋⾝设计好整个汽⻋(Car)。这⾥就出现了⼀个"依赖"关系:汽⻋依赖⻋⾝,⻋⾝依赖底盘,底盘依赖轮⼦.
我们首先在newCarExample中创建car,然后在car类中创建Framework,然后根据车身创建底盘,然后进而创建轮胎。
public class NewCarExample {
public static void main(String[] args) {
Car car = new Car();
car.run();
}
/**
* 汽⻋对象
*/
static class Car {
private Framework framework;
public Car() {
framework = new Framework();
System.out.println("Car init....");
}
public void run(){
System.out.println("Car run...");
}
}
/**
* ⻋⾝类
*/
static class Framework {
private Bottom bottom;
public Framework() {
bottom = new Bottom();
System.out.println("Framework init...");
}
}
/**
* 底盘类
*/
static class Bottom
{
private Tire tire;
public Bottom() {
this.tire = new Tire();
System.out.println("Bottom init...");
}
}
/**
* 轮胎类
*/
static class Tire {
// 尺⼨
private int size;
public Tire(){
this.size = 17;
System.out.println("轮胎尺⼨:" + size);
}
}
}
设计问题:这样的设计看起来没问题,但是可维护性却很低.接下来需求有了变更: 随着对的⻋的需求量越来越⼤, 个性化需求也会越来越多,我们需要加⼯多种尺⼨的轮胎.那这个时候就要对上⾯的程序进⾏修改了,修改后的代码如下所⽰:我们要加上轮胎大小属性,我们要给size传到底盘,然后底盘传到车身,最后传到车子。size这个属性增加了程序的耦合度,让每个对象都要对size进行添加。从以上代码可以看出,以上程序的问题是:当最底层代码改动之后,整个调⽤链上的所有代码都需要修改.程序的耦合度⾮常⾼(修改⼀处代码, 影响其他处的代码修改)
📝IOC思想
解决方案:在上⾯的程序中, 我们是根据轮⼦的尺⼨设计的底盘,轮⼦的尺⼨⼀改,底盘的设计就得修改. 同样因为我们是根据底盘设计的⻋⾝,那么⻋⾝也得改,同理汽⻋设计也得改, 也就是整个设计⼏乎都得改。我们尝试换⼀种思路, 我们先设计汽⻋的⼤概样⼦,然后根据汽⻋的样⼦来设计⻋⾝,根据⻋⾝来设计底盘,最后根据底盘来设计轮⼦. 这时候,依赖关系就倒置过来了:轮⼦依赖底盘, 底盘依赖⻋⾝, ⻋⾝依赖汽⻋。我们可以尝试不在每个类中⾃⼰创建下级类,如果⾃⼰创建下级类就会出现当下级类发⽣改变操作,⾃⼰也要跟着修改. 此时, 我们只需要将原来由⾃⼰创建的下级类,改为传递的⽅式(也就是注⼊的⽅式) ,因为我们不 需要在当前类中创建下级类了,所以下级类即使发⽣变化(创建或减少参数),当前类本⾝也⽆需修 改任何代码,这样就完成了程序的解耦.我们把创建子类的方式,改成注入传递的方式。
public class newCarExample {
public static void main(String[] args) {
Tire tire=new Tire(20);
Bottom bottom=new Bottom(tire);
Framwork framwork=new Framwork(bottom);
Car car=new Car(framwork);
car.run();
}
}
public class Tire {
private int size;
public Tire(int size){
System.out.println("轮胎的尺寸:"+size);
}
}
//轮胎依赖底盘
public class Bottom {
private Tire tire;
public Bottom(Tire tire){
this.tire=tire;
System.out.println("bottom init ..........");
}
}
//底盘依赖机身
public class Framwork {
private Bottom bottom;
public Framwork(Bottom bottom){
this.bottom=bottom;
System.out.println("framwork init....");
}
}
//机身依赖车子
public class Car {
private Framwork framwork;
public Car(Framwork framwork){
this.framwork=framwork;
System.out.println("car init....");
}
public void run(){
System.out.println("car run....");
}
}
📝IoC 优势
在传统的代码中对象创建顺序是:Car -> Framework -> Bottom -> Tire改进之后解耦的代码的对象创建顺序是:Tire -> Bottom -> Framework -> Car
🎈DI 介绍
DI: Dependency Injection(依赖注⼊)容器在运⾏期间, 动态的为应⽤程序提供运⾏时所依赖的资源,称之为依赖注⼊。
程序运⾏时需要某个资源,此时容器就为其提供这个资源.从这点来看, 依赖注⼊(DI)和控制反转(IoC)是从不同的⻆度的描述的同⼀件事情,就是指通过 引⼊ IoC 容器,利⽤依赖关系注⼊的⽅式,实现对象之间的解耦。
IoC 是⼀种思想,也是"⽬标", ⽽思想只是⼀种指导原则,最终还是要有可⾏的落地⽅案,⽽ DI 就属于具体的实现。所以也可以说, DI 是IoC的⼀种实现.我们不用自己创建该依赖对象,我们只需要将依赖对象注入到所需要的对象中,创建对象的事情交给spring来管理。⽐如说我今天⼼情⽐较好,吃⼀顿好的犒劳犒劳⾃⼰,那么"吃⼀顿好的"是思想和⽬标(是IoC),但最后我是吃海底捞还是杨国福?这就是具体的实现,就是 DI。
对IOC和DI有了初步的了解,我们接下来就要学习spring IOC和DI。
既然 Spring 是⼀个 IoC(控制反转)容器,作为容器, 那么它就具备两个最基础的功能:• 存• 取Spring 容器 管理的主要是对象, 这些对象, 我们称之为"Bean". 我们把这些对象交由Spring管理, 由 Spring来负责对象的创建和销毁. 我们程序只需要告诉Spring, 哪些需要存, 以及如何从Spring中取出 对象。
🚩IoC 详解
🎈Bean的存储
共有两类注解类型可以实现:
- 1. 类注解:@Controller、@Service、@Repository、@Component、@Configuration.
- 2. ⽅法注解:@Bean
🔴五大类注解
📝@Controller(控制器存储)
@Controller
public class TestController {
public void doController(){
System.out.println("do controller....");
}
}
ApplicationContext 翻译过来就是: Spring 上下⽂因为对象都交给 Spring 管理了,所以获取对象要从 Spring 中获取,那么就得先得到 Spring 的上下⽂。
为什么TestController对象存入到spring中呢?@Controller注解的作用是什么呢?
NoSuchBeanDefinitionException表示没有注解,那么spring就获取不了该对象bean。所以我们知道了@Controller这个注解就是为了让我们获得该Bean对象存储到spring中,后续我们在主程序中getBean就获取到该对象了,所以我们上面也说了,注解的作用就是让程序运行时,控制这个注解对应的类对象。后续对这个对象进行创建和获取。
👩🏻💻获取bean对象的方式
⽐如
- 类名: UserController, Bean的名称为: userController
- 类名: AccountManager, Bean的名称为: accountManager
- 类名: AccountService, Bean的名称为: accountService
也有⼀些特殊情况, 当有多个字符并且第⼀个和第⼆个字符都是⼤写时, 将保留原始的⼤⼩写. 这些规则 与java.beans.Introspector.decapitalize (Spring在这⾥使⽤的)定义的规则相同.⽐如
- 类名: UController, Bean的名称为: UController
- 类名: AManager, Bean的名称为: AManage
根据这个命名规则, 我们来获取Bean
1》根据类型来获取
2》根据名称来获取
根据名称获取的时候,首字母小写,我们看到类名时TestController,不是前两个字母大写,那么就将大写字母转成小写字母即可。并且类型要转换。
2》根据类型和名称来获取
📝@Service(服务存储)
@Service
public class TestService {
public void doService(){
System.out.println("do service.....");
}
}
public class IocDemoApplication {
public static void main(String[] args) {
ApplicationContext context=SpringApplication.run(IocDemoApplication.class);
TestService testService=context.getBean(TestService.class);
testService.doService();
}
}
📝@Repository(仓库存储)
📝@Component(组件存储)
📝@Configuration(配置存储)
代码形式都是和上述的两个方式相同
- @Controller:控制层, 接收请求, 对请求进⾏处理, 并进⾏响应.
- • @Servie:业务逻辑层, 处理具体的业务逻辑.
- • @Repository:数据访问层,也称为持久层. 负责数据访问操作
- • @Configuration:配置层. 处理项⽬中的⼀些配置信息
🍭类注解之间的关系
类注解之间的关系查看 @Controller / @Service / @Repository / @Configuration 等注解的源码发现:其实这些注解⾥⾯都有⼀个注解 @Component ,说明 它们本⾝就是属于 @Component 的"⼦类". @Component 是⼀个元注解,也就是说可以注解其他类注解,如 @Controller , @Service , @Repository 等. 这些注解被称为 @Component 的衍⽣注解。@Controller , @Service 和 @Repository ⽤于更具体的⽤例(分别在控制层, 业务逻辑层, 持久化层), 在开发过程中, 如果你要在业务逻辑层使⽤ @Component 或@Service,显然@Service是更好的选择。⽐如杯⼦有喝⽔杯, 刷⽛杯等, 但是我们更倾向于在⽇常喝⽔时使⽤⽔杯, 洗漱时使⽤刷⽛杯。不同的场景运用不同的功能,我们在实际开发中,我们实现业务的时候,我们会更倾向于用@Service。
🔴⽅法注解 @Bean
类注解是添加到某个类上的, 但是存在两个问题:
- 1. 使⽤外部包⾥的类, 没办法添加类注解
@Data
public class UserInfo {
private Integer age;
private String name;
private Integer id;
}
@Configuration
public class BeanConfigTest {
@Bean
public UserInfo userInfo(){
UserInfo userInfo=new UserInfo();
userInfo.setName("张老师");
userInfo.setAge(20);
userInfo.setId(1);
return userInfo;
}
}
- 2. ⼀个类, 需要多个对象, ⽐如多个数据源
@Configuration public class BeanConfigTest { @Bean public UserInfo userInfo(){ UserInfo userInfo=new UserInfo(); userInfo.setName("张老师"); userInfo.setAge(20); userInfo.setId(1); return userInfo; } @Bean public UserInfo userInfo2(){ UserInfo userInfo=new UserInfo(); userInfo.setName("chlorine"); userInfo.setAge(19); userInfo.setId(2); return userInfo; } }
我们观察上述代码,是同一个类型,存在多个Bean,我们此时是无法通过类型来获取bean。程序会启动失败。
同一个类型,存在多个Bean,我们此时可以通过名称或者(名称+类型)来获取Bean
🔴Bean传递参数
定义了一个叫name2的String类型的对象,定义了一个叫name的String类型的对象。
我们根据名称去拿,如果对应的对象只有一个时,就直接赋值,如果有多个时,通过名称去匹配,如果名称都不匹配,按照内部逻辑来匹配。
🎈SpringBoot特点
Q: 使⽤前⾯学习的四个注解声明的bean,⼀定会⽣效吗?A: 不⼀定(原因:bean想要⽣效,还需要被Spring扫描)
约定大于配置
比如我们上课,教务处在开学前都会给给定的课表都约定好,等到开学后学生按照课程就直接去哪个教室上课就行了,而配置就是临时老师说调课,就需要老师临时通知,但是肯定是学校提前弄好的课程进行更多,而少数是进行老师进行调课。
springboot也有一个自己的约定,其中之一体现就是:扫描路径。默认的扫描路径是:启动类所在的目录及其子孙目录。
我们创建下项目默认的启动类都是在最大的包中,如果我们调动启动类的位置。将其移到controller包下,我们现在执行Configuration包下的程序,看是否成功。
报错,显示无法找到UserInfo,我们上面说Springboot约定大于配置,约定默认扫描路径是启动类所在的目录及其子孙目录。此时所在的目录和子孙目录,并没有user'Info而在自己的同级目录里,此时报错。
我们就相当于老师突然调课,我们需要给springboot增加配置。添加注解@Component,这个注解可以指定扫描路径,如果没有指定,默认就是该注解所在类的目录及其子孙目录。
我们增加注解,让启动类进行扫描到Configuration包下的类。执行成功。
推荐做法:把启动类放在我们希望扫描的包的路径下, 这样我们定义的bean就都可以被扫描到
🚩DI 详解
依赖注⼊是⼀个过程,是指IoC容器在创建Bean时, 去提供运⾏时所依赖的资源,⽽资源指的就是对象.在上⾯程序案例中,我们使⽤了 @Autowired 这个注解,完成了依赖注⼊的操作.简单来说, 就是把对象取出来放到某个类的属性中.
关于依赖注⼊, Spring也给我们提供了三种⽅式:
- 1. 属性注⼊(Field Injection)
- 2. 构造⽅法注⼊(Constructor Injection)
- 3. Setter 注⼊(Setter Injection)
🎈属性注⼊
@Controller
public class UserController {
@Autowired
private UserInfo userInfo2;
public void doController(){
System.out.println(userInfo2);
System.out.println("do Controller.........");
}
@SpringBootApplication
public class IocDemoApplication {
public static void main(String[] args) {
//1.属性注入
ApplicationContext context=SpringApplication.run(IocDemoApplication.class);
TestController testController=(TestController)context.getBean("testController") ;
testController.doControl();
}
}
属性注入以类型进行匹配,与注入的属性名称无关。但是如果一个类型存在多个对象时,有优先名称匹配,如果名称都匹配不上,那就会报错。
我们看到同一个类型出现了两个对象userInfo和userInfo2,那么此时我们要名称匹配,我定义的时候用的是us,此时匹配匹配不上,报错。
🎈构造方法注入
//二、构造方法注入
private TestService testService;
public TestController(){};
public TestController(UserInfo userInfo){
this.userInfo=userInfo;
}
@Autowired
public TestController(TestService testService){
this.testService=testService;
}
public TestController(UserInfo userInfo,TestService testService){
this.userInfo=userInfo;
this.testService=testService;
}
public void doControl2(){
testService.doService();
System.out.println("do Controller.........");
}
注意:
- 1.如果存在多个构造函数时,需要加@AutoWired注明使用哪个构造函数
- 2.再添加了有参的构造方法中,最好加上无参的构造方式,防止报错
- 3.如果只有一个构造函数,@AutoWired可以省略掉
- 4.明确注入哪个构造函数,否则会造成空指针异常
🎈Setter注入
//三、setter注入
private TestService testService2;
@Autowired
public void setTestService2(TestService testService2) {
this.testService2 = testService2;
}
public void doController3(){
testService2.doService();
System.out.println("do Controller.........");
}
🔴@Autowired存在问题
程序启动失败,发现类型UserInfo存在多个Bean。
📝解决方法一
属性名和你需要使用的对象名保持一致。
📝解决方法二
使用@Primary注解标识默认的对象
📝解决方法三
使用@Qualifier指定哪个对象
📝解决方法四
使用@Resource注解指定ben
上述四个方法,使用率最高的是@Qualifier和@Resource注解
解决方法思路:给bean重命名或者指定名称
🔴五大注解修改名以及Bean修改名
📝Bean修改名
@Bean({"u1","uu"})
@Bean("u1")
一个Bean可以有多个名称,但是一个名称只能对应一个Bean。
📝类注解修改
人生小满胜万全。