目录
一控制反转(思想)
1.什么是IOC
2.控制反转的好处
二依赖注入(实现)
1.依赖注入(Dependency Injection)是什么
2.依赖注入
3.Bean的存储
3.1类注解
3.1.1类注解的用法
3.1.2Bean的命名规则
3.1.3ApplicationContext与BeanFactory
3.2方法注解
3.2.1方法注解解决的问题
3.2.2@Bean的使用
4:Bean的取出(注入)
4.1Bean取出的三种方式
4.2三种方法各有利弊
4.3@Autowired注解的一些坑
4.3.1@Resource,@Primary和@Qualifier
三总结
一控制反转(思想)
1.什么是IOC
IOC的全称是Inversion of Control,意思是控制反转,那么什么是控制反转,在编程领域来说,控制反转的意思就是将对象的创建和依赖关系的控制权从程序员手中,反转到容器手中(Spring)
举个例子:
当你想要吃一个菜的时候,如果在没有饭店(Spring)的时候你需要自己买菜,切菜,炒菜,还要自己盛汤刷碗(自己new一个菜),如果想要换一个菜吃,还要重新买菜,切菜,炒菜,这对现在一天要干十几个小时的牛马来说时间成本是没法接受的。
但是有了饭店那就不一样了,如果你想要吃一个菜,只需要对着菜单点,那么饭店(Spring),就会替你把所有的事情都干了(Spirng替你管理对象创建与依赖关系),你只需要关注一会怎么去吃这道菜就行了(更关注业务逻辑),同时如果你想吃另外的菜,就只需要再点就行了。
2.控制反转的好处
上面和标题其实已经说到,IOC其实就是一种解耦的艺术,将你的那些对象的创建,销毁,环境切换,全部外包给Spring,让它帮助你做,这样你就能够更加专注于业务的实现了,无疑是对生产力的一个大解放,那么插个题外话,假如人工智能能够直接帮助我们做一个大型的应用,并能够自动部署上线,维护的时候,可能我们就只需要关注客户的业务需求是什么就好了,往好的想也是对生产力的大解放吧。
二依赖注入(实现)
1.依赖注入(Dependency Injection)是什么
新闻上总是说以......思想(IOC)为指导,贯彻落实(DI)进一步全面深化改革、......现代化......
IOC终归是一个思想方法,那么这个方法该怎么实现呢?Spring突然想到那就用依赖注入(DI)的方式实现吧.
2.依赖注入
先说概念,容器在运行期间,动态的为应用程序提供运行时所依赖的资源,我们称之为依赖注入。
众所周知,Spring其实就可以理解为是一个IOC容器,我们可以把它想象成是一个工具箱,这个工具箱(Spring)可以根据我的需要给我提供各种各样的工具(依赖资源),那么问题来了,它既然是个箱子,那它就得有两个功能,存和取,依赖注入是取的过程,那么在这之前,我们是怎么存的呢?
3.Bean的存储
Spring管理的主要就是一些对象,我们把这些对象交给它来管理,这些被管理的对象有个名字叫做Bean,由Spring来管理这些Bean的创建和销毁,那么我们该怎么来交给它管理呢?
3.1类注解
我们之前肯定是听说过Spring的五大注解@Controller、@Service、@Repository,@Component、@Configuration
它们都是将可以将类交给Spring管理的注解,其中@Component是里面最基本的注解,其他的注解都是它的衍生,目的就是为了便于开发者理解和维护没啥其他意思,就比如开发这看见了@Controller就知道,哦~这个是控制器类 ,看见了@Service就知道,哦~这个是业务逻辑类。
此时你的同事看完可能会这样 如果所有的都用@Component你同事看完可能是这样
3.1.1类注解的用法
用法很简单,只要将注解加到类上就行了,其他的类似,就不赘述了
@RestController//直接加注解
public class ScopeController {
public void sayHi(){System.out.println("hi,我是ScopeController,我竟然被取出来了啊啊啊啊啊");
}
}
好了现在我们已经把ScopeController这个类交给Spring管理了,那么我们该怎么获取到被管理的Bean呢?我们可以通过ApplicationContext,也就是Spring上下文来获取。
可以看到图中getBean有很多个重载方法,这些重载方法都是BeanFactory来提供的
BeanFactory的部分源码,我加了点注释
public interface BeanFactory {//1.按照bean的名称来获取Object getBean(String var1) throws BeansException;
//2。按照Bean的名称和类型来获取Bean<T> T getBean(String var1, Class<T> var2) throws BeansException;
// 3. 按bean名称和构造函数参数动态创建bean,只适⽤于具有原型(prototype)作⽤域的beanObject getBean(String var1, Object... var2) throws BeansException;
//4.按照Bean类型来获取bean<T> T getBean(Class<T> var1) throws BeansException;
// 5. 按bean类型和构造函数参数动态创建bean,只适⽤于具有原型(prototype)作⽤域的bean<T> T getBean(Class<T> var1, Object... var2) throws BeansException;
}
我们先用类型来获取一下Bean,下面是我用类型来获取ScopeController对象,并且调用它里面的sayHi方法的例子
@SpringBootApplication
public class SpringPrincipleApplication {public static void main(String[] args) {ApplicationContext context = SpringApplication.run(SpringPrincipleApplication.class, args);ScopeController scopeController = context.getBean(ScopeController.class);//取出beanscopeController.sayHi();//调用方法}}
输出结果
3.1.2Bean的命名规则
除了按照类型取bean还有按照bean的名称来取bean的,那么Spring是怎么取名字的呢?
这是Spring官方文档中的对Bean命名规则的概述,就是会将Bean名称按照小驼峰方式命名,但是 如果你命名的非常的”不常见“,那就会保留原始的大小写
比如:
知道了Bean的名字我们再来按照名称访问一次Bean
@SpringBootApplication
public class SpringPrincipleApplication {public static void main(String[] args) {ApplicationContext context = SpringApplication.run(SpringPrincipleApplication.class, args);ScopeController scopeController = context.getBean(ScopeController.class);//按类型ScopeController scopeController1 = (ScopeController) context.getBean("scopeController");//按名称ScopeController scopeController2 = context.getBean("scopeController", ScopeController.class);//按名称和类型System.out.println(scopeController);System.out.println(scopeController1);System.out.println(scopeController2);}}
输出结果是同一个说明取到的是同一个Bean
3.1.3ApplicationContext与BeanFactory
既然谈到了它俩,那我们就捋一捋他俩的关系
1.首先ApplicationContext和BeanFactory是Spring容器的两个顶级接口,其中ApplicationContext是BeanFactory的子类,下面代码是Application接口的原码,可以看到Application除了继承了BeanFactory(ListableBeanFactory, HierarchicalBeanFactory,是BeanFactory的子类)还继承了国际化,资源访问,时间传播方面的支持( EnvironmentCapable, MessageSource, ApplicationEventPublisher, ResourcePatternResolver)
2.从性能方面来说ApplicationContext是一次性加载并初始化所有的Bean对象,但是BeanFactory是需要哪个对象才去加载哪个对象,因此更加的轻量
public interface ApplicationContext extends EnvironmentCapable, ListableBeanFactory, HierarchicalBeanFactory, MessageSource, ApplicationEventPublisher, ResourcePatternResolver {@NullableString getId();String getApplicationName();String getDisplayName();long getStartupDate();@NullableApplicationContext getParent();AutowireCapableBeanFactory getAutowireCapableBeanFactory() throws IllegalStateException;
}
3.2方法注解
类注解说完了,现在我们来说一说方法注解@Bean
3.2.1方法注解解决的问题
那为什么有方法注解呢?存在就是为了解决类注解解决不了的问题,那么是什么问题呢?
3.2.2@Bean的使用
我们先直接使用一下@Bean
方法注解需要搭配类注解来使用,单独的@Bean是没法交给Spring管理的
@Configuration
public class BeanConfig {@Beanpublic Dog dog1(){Dog dog = new Dog("张三");return dog;}@Beanpublic Dog dog2(){Dog dog = new Dog("张三");return dog;}}
使用类型获取一下Dog类的对象
@SpringBootApplication
public class SpringPrincipleApplication {public static void main(String[] args) {ApplicationContext context = SpringApplication.run(SpringPrincipleApplication.class, args);Dog dog = context.getBean(Dog.class);System.out.println(dog.toString());}}
结果运行失败了,为什么呢?我们看一下报错日志,说bean需要是唯一的,但是我却找到了俩,
那Spring该取哪个它不知道了啊
事情就变成了,两个人都说我是真的了 ,刚刚我们是按照类型取的,所以肯定就有两个对象(因为我们存了两个dog对象),如果我们想取dog1,那我们就给他取一个唯一的名字不就行了
毕竟虽然这俩是真假美猴王,但毕竟一个叫孙悟空,一个叫六耳猕猴。
修改后的BeanConfig类给dog1取了两个名字,孙悟空和齐天大圣,给dog2取了一个名字叫六耳猕猴
@Configuration
public class BeanConfig {@Bean(name = {"孙悟空","齐天大圣"})public Dog dog1(){Dog dog = new Dog("张三");return dog;}@Bean(name = {"六耳猕猴"})public Dog dog2(){Dog dog = new Dog("李四");return dog;}
}
修改后按照名称获取Bean
@SpringBootApplication
public class DemoApplication {public static void main(String[] args) {ApplicationContext context = SpringApplication.run(DemoApplication.class, args);Dog dog1 = (Dog) context.getBean("孙悟空");Dog dog2 = (Dog) context.getBean("齐天大圣");Dog dog3 =(Dog) context.getBean("六耳猕猴");System.out.println(dog1.getName());System.out.println(dog2.getName());System.out.println(dog3.getName());}}
获取Bean结果成功
4:Bean的取出(注入)
好上面我们已经把Bean的存储给讲完了,现在我们开始讲一下Bean的使用,也就是把Spring中容器的工具取出来用的过程
4.1Bean取出的三种方式
我们可以有三种方法取出来
@RestController
public class UserController {@Autowiredprivate UserService userService;}
@RestController
public class UserController {private UserService userService;public UserController(UserService userService) {this.userService = userService;}}
@RestController
public class UserController {private UserService userService;@Autowiredpublic void setUserService(UserService userService) {this.userService = userService;}}
4.2三种方法各有利弊
属性注入:
@Autowied使用简洁。
但是只能⽤于 IoC 容器,如果是⾮ IoC 容器不能⽤,并且只有在使⽤的时候才会出现 NPE(空指针异常) ,也不能注⼊⼀个Final修饰的属性
构造方法注入:
4.3@Autowired注解的一些坑
@RestController
public class UserController {@Autowiredprivate Dog dog;//z注入Dog类public void sayHi() {System.out.println(dog.getName());}}
@SpringBootApplication
public class DemoApplication {public static void main(String[] args) {ApplicationContext context = SpringApplication.run(DemoApplication.class, args);UserController userController = context.getBean(UserController.class);//获取BeanuserController.sayHi();//调用方法}}
又是我们熟悉的不知道该取哪个方法的报错了,所以我们就明白,@Autowired是按照类型来注入Bean的,那么我们该怎么解决呢?
4.3.1@Resource,@Primary和@Qualifier
在上面真假美猴王那,我们是给Bean取名字,然后按照名字来取的,那么有没有按照名字来取Bean的注解呢?,还真有@Resource
此时我们可以使用按照名称来注入的@Resource来指定用哪个Bean
既然说到了@Resource那就顺便提一下它和@Autowired的区别吧
2:使⽤@Qualifier注解:指定当前要注⼊的bean对象。 在@Qualifier的value属性中,指定注⼊的bean的名称,就指定是哪个类型,相当于也是按照名称注入了
三总结
你问我写了什么?
1:什么是IOC,以及它的好处知道了吧
2:依赖注入是什么知道了吧
3:Bean可以怎么存储的(类注解和方法注解),其中方法注解使用时候需要注意的问题知道了吧
4:Bean可以怎么取出(注入)的三种方法知道了吧
5:@Autowired和@Resource区别知道了吧
6:怎么解决@Autowired按类型注入时不知道选哪个Bean的情况知道了吧