1.如何理解控制反转IOC(inversion of control)和依赖注入DI(dependcy Injection)
先说结论:控制反转是一种思想,而依赖注入是其表现形式。好比说IOC是接口,DI是其实现类。IOC就是关于对象的创建。由程序员写程序创建的对象(new一个对象)这种就是正常创建。使用容器创建的对象(一般是DI的形式)就是IOC了。
控制反转(Inversion of Control,IoC)是一种软件设计模式,它通过将控制流程的权利从程序自身转移到外部容器或框架,实现了程序的松耦合。在Spring框架中,IoC是通过依赖注入(Dependency Injection)来实现的,其中最常见的是通过构造函数注入或者Setter方法注入。
ModelDao modelDao = new ModelDao();
@Autowired
private ModelDao modelDao;
因为这里的modelDao,并不是我们new出来的对象,而是Spring框架创建出来的对象。
在Spring应用程序启动时,Spring容器会扫描指定的包路径(通常是主应用程序类所在的包及其子包)以找到被@Component及其衍生注解(如@Service、@Repository等)标记的类。
对于扫描到的被标记的类,Spring容器会实例化它们,然后将这些实例化的对象称为Bean。
这些被实例化的Bean会被注册到Spring容器中。在容器中,每个Bean都有一个唯一的标识符(通常是类名的首字母小写形式)。
一旦所有Bean都被创建并注册到容器中,Spring容器就会完成初始化过程,应用程序就可以运行了。
如果在这些Bean中存在被@Autowired等注解标记的字段、构造函数或方法,Spring容器会负责解决这些依赖关系,将相应的Bean注入到对应的地方。
扫描组件的时候,所有被@Component及其派生的注解都会被扫描到。这里简单看一下@service这个注解
被箭头指着的这句注解的意思,就类似于Java中的派生,代表这两个注解是等价的。所以@Service也可以被Spring的容器扫描到。其他的几个注解也是一样的。具体的注解后面会详细说。关于这个注解上面的这些元注解,在自定义注解这个篇章会详细说。现在先按下不表。
是Spring容器的基础接口,提供了基本的IoC功能。它延迟加载对象,即在需要使用对象时才进行实例化。
是BeanFactory的扩展,提供了更多的功能,如事件传播、AOP支持、国际化等。ApplicationContext在容器启动时就实例化了所有的Bean,因此在应用程序运行期间,可以直接从ApplicationContext获取Bean实例。
因为ApplicationContext是BeanFactory的子接口。所以BeanFactory的基础功能ApplicationContext都有,所以我们平常接触的都是ApplicationContext。就好比Object和其派生类一样,Object很重要,但我们平时不用它
在Spring Boot应用程序中,实际上是使用 ApplicationContext 接口的具体实现类,通常是 AnnotationConfigApplicationContext(基于注解配置)或 ClassPathXmlApplicationContext(基于XML配置)等。这些实现类继承了 BeanFactory 接口,因此在使用Spring Boot时,你实际上是得到了一个ApplicationContext,而ApplicationContext本身就包含了BeanFactory的功能。
在典型的Spring Boot应用中,你通常不直接使用 BeanFactory 接口,而是使用 ApplicationContext,因为它提供了更多的功能,如自动配置、组件扫描、AOP等。
Spring Boot会在启动时自动创建一个ApplicationContext实例,其中包含了应用程序中所有需要管理的Bean的定义。这个ApplicationContext实例负责实例化和管理这些Bean。
Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled。
Spring容器(对于Springboot而言就是ApplicationContext)它对于每一个类,只会实例化一个对象
当Spring容器扫描到一个类时,它会根据配置信息创建一个Bean的实例,并将这个实例注册到容器中。之后,如果在应用程序的其他地方需要引用这个类,Spring容器会直接提供之前创建的Bean实例,而不会重新实例化新的对象。
这种单例模式是Spring默认的作用域(scope)。Spring框架提供了不同的作用域,包括默认的单例(Singleton)作用域,以及其他如原型(Prototype)、请求(Request)、会话(Session)等作用域。但是,大多数情况下,Bean都是以单例模式存在的。
需要注意的是,虽然大多数Bean是单例的,但这并不是绝对的。可以通过@Scope注解或在XML配置中明确指定Bean的作用域,来改变Bean的创建和管理方式。
`@Scope`: 用于指定Bean的作用域,如"singleton"(默认)、"prototype"等。
(冷知识,上面两个单词,一个是单例模式中的单例,一个是原型模式中的原型)
如果选择singleton,那就和没写一样。但如果写了prototype。那么每一次的@Resource或者是@Autowired,都会重新实例化一个对象
而且ApplicationContext其实是懒加载,并不是程序一开始就把所有被标注的类全部都实例化的,而是会等,等到这个bean被用到的时候才会实例化并进行注册。注意,当程序第一次调用bean的时候会进行实例化,这个过程需要用到Java的反射机制(Java的反射机制后面会单独开一期).
这里简单说一下关于bean和Javabean的区别。Javabean是一种规范(专题里面有),是一个抽象的概念,遵守对应规则写成的Class就是JavaBean。而bean单独指的是被spring容器实例化的对象。是具体的。
还有一点提前先说下,后面注解哪里还会强调。这些Class的实例化对象其实也都是有名称的。
@Component("model")
public class MyModel {
// ...
}
其实就类似于 Mymodel model = new Mymode();
如果没有写名字的话,就会默认给一个myModel的变量名(类名的小写)
@Autowired是通过类型来进行导入的,也就是上例中的MyModel(毕竟每一个MyModel类型在当前包只有一个,所以可以锁定)
@Resource则是通过名称来导入的,也就是model。最开始我有点误区,我以为
这个被红箭头指向的是对象名。其实不是的,它只是引用的别名。这个对象本身的名字在它的注解里面标明,这里只是引用时会用到的别名。
@Resource(name = "myBean123")
private MyBean myBean;
@Resource
private MyBean myBean;
那就回去查找名称为myBean的bean类,如果没有找到的话就会报异常了
Spring将@Resource注解的name属性解析为bean的名字,type属性则解析为bean的类型。所以如果使用name属性,则使用byName的自动注入策略,而使用type属性则使用byType自动注入策略。如果既不指定name也不指定type属性,这时将通过反射机制使用byName自动注入策略。
(1)如果同时指定了name和type,则从Spring上下文中找到唯一匹配的bean进行装配,找不到则抛出异常
(2)如果指定了name,则从Spring上下文中查找名称(id)匹配的bean进行装配,找不到则抛出异常
(3)如果指定了type,则从Spring上下文中找到类型匹配的唯一bean进行装配,找不到或找到多个,都抛出异常
(4)如果既没指定name,也没指定type,则自动按照byName方式进行装配。如果没有匹配,则回退为一个原始类型进行匹配,如果匹配则自动装配。
@Resource的作用相当于@Autowired,只不过@Autowired按byType自动注入。
再多说一句,bean,也就是IOC创建的对象是单例的(一般来说是),也就是说依赖注入的对像有且只有一个,且它是在程序启动时创建,程序结束时销毁的。它的存在贯穿整个程序运行始终。所以如果修改了它的值,那么其他地方引用他的时候也会修改它的值。这其实也是IOC和DI的特点之一,实现数据的实时共享(不过这也是需要注意的点,这也是为什么一般都是只有方法没有成员变量的类作为bean存在)。
类A需要使用类B的一个对象,但是我们不需要初始化这个类B。就可以直接用。代码完全不会受到影响。只要在最外层注入他,那什么时候想用了,直接拿来用就可以了。有点像静态变量,一次声明,一直使用。其他的什么都不用管。这耦合度不就低了嘛。
bean对象是在springboot启动的时候就创建了的。所以本质上bean对象的创建和当前用它的类没有任何关系。(再次强调一下,DI的过程相当于只是给了个引用地址)
因为不需要一直初始化,我们知道,每次初始化一个对象的时候,都会赐予它工作空间。所以如果我们只是使用它的方法的话,大可以直接DI它。只用了一个对象自然不会浪费工作空间了。
极其类似于静态资源(静态资源部分有专题),如果在某一个时候改变了这个bean对象的值,那么在程序进行的过程中任何时候调用它得到的都是修改后的值。而且这种DI导入的类容易分不清(不想静态资源有诸多限制和提示)所以建议是需要用到数据共享的时候,创建一个新的静态资源。IOC一般只用于没有成员变量,只有成员方法的类型(因为方法只能被调用,无法被修改,所以一般被DI的都是这种类型)。
1、一类是使用Bean,即是把已经在xml文件中配置好的Bean拿来用,完成属性、方法的组装;比如@Autowired , @Resource,可以通过byTYPE(@Autowired)、byNAME(@Resource)的方式获取Bean;
2、一类是注册Bean,@Component , @Repository , @ Controller , @Service , @Configration这些注解都是把你要实例化的对象转化成一个Bean,放在IoC容器中,等你要用的时候,它会和上面的@Autowired , @Resource配合到一起,把对象、属性、方法完美组装。
在Spring Boot中,用于实现控制反转(IoC)和依赖注入的注解有很多,每个注解都有不同的用途。以下是一些常用的IoC相关注解:
1. `@Component`: 基本的注解,用于标识一个类为Spring组件,可以被Spring容器扫描并管理。
2. `@Service`: 用于标识服务层组件,通常用于服务类。
3. `@Repository`: 用于标识数据访问组件,通常用于DAO(Data Access Object)类。
4. `@Controller`: 用于标识控制器组件,通常用于Spring MVC中的控制器类。
5. `@RestController`: 类似于`@Controller`,但专门用于RESTful风格的控制器。
6. `@Configuration`: 用于标识配置类,通常用于定义Spring Bean的配置。
7. `@Bean`: 用于定义一个Spring Bean,通常在`@Configuration`类中使用。
8. `@Autowired`: 用于依赖注入,可以标记在字段、构造函数、Setter方法上,以将其他Bean注入到当前Bean中。
9. `@Qualifier`: 用于指定依赖注入时的具体Bean名称,当存在多个候选Bean时使用。
11. `@Lazy`: 延迟初始化,用于标记Bean为延迟加载,只有在首次使用时才会被初始化。
12. `@Scope`: 用于指定Bean的作用域,如"singleton"(默认)、"prototype"等。
13. `@Primary`: 用于标记首选的Bean,当多个Bean满足条件时,被标记为`@Primary`的Bean将被优先选择。
这些注解允许你在Spring Boot应用程序中轻松配置和管理Bean,以实现IoC和依赖注入。根据不同的用途和需求,你可以选择合适的注解来标记你的类和组件。
其余的都比较了解,@Qualifier见的少,这里着重的用代码解释一下
public interface Animal {
void makeSound();
}
@Component("dog")
public class Dog implements Animal {
@Override
public void makeSound() {
System.out.println("Woof Woof!");
}
}
@Component("cat")
public class Cat implements Animal {
@Override
public void makeSound() {
System.out.println("Meow Meow!");
}
}
@Component
public class Zoo {
private final Animal animal;
// 使用@Qualifier注解指定要注入的bean的名称
@Autowired
public Zoo(@Qualifier("dog") Animal animal) {
this.animal = animal;
}
public void perform() {
animal.makeSound();
}
}
能看到吧,有多个可选的Bean存在时,可以通过名称来指定是哪一个。不过上述这种情况其实可以用@Resource来解决的。其他的比如作为参数对象传入的话就不太能用@Resource来替代了
而且其实一般来讲,除了像上述这种,因为父类出现导致问题外一般不会有其他的情况出现,毕竟类名对于一个程序包而言是唯一的
public Mybean2 car(@Qualifier("myBean123")MyBean myBean)
而这种写法往往出现在哎@Bean使用的情景下,@Bean和@Configuration经常连用,这里需要好好解释一下为什么。
首先需要介绍一下@Bean是什么。@Bean注解可以用于方法上,该方法返回一个对象,该对象将被Spring容器管理和提供给其他程序组件使用。
@Configuration
public class MyConfig {
@Bean
public MyBean myBean() {
return new MyBean();
}
}
这样的话,就会返回一个MyBean的实例对象,且这个实例对象也会放在Spring容器当中。就跟使用@Component这种情况得到的结果是等价的
不过@Component通常更适用于类级别的声明,而@Bean更适用于方法级别的声明。而且在这个方法里面是可以对这个Bean做各种处理的。
@Bean可以单独用,不过要求限制很多。而且大多数时候都和@Configure连用。其实有一点是比较好理解的,容器创建实例的过程理论上来说是先于其他程序的。所以如果用@Bean创建bean的话,应该是在其他程序之前,所以需要一个@Component的派生注解比如说专门和@Bean搭配的@Configuration。
@Slf4j
@Configuration
public class WebSocketConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter() {
log.info("webSocket配置已加载");
return new ServerEndpointExporter();
}
前段时间梳理webSocket的时候,有一个配置文件。其实这个的作用也就是把ServerEndpointExporter,变成一个Spring容器的Bean。其实把bean放入容器不仅仅是供我们@Resource(DI)的,它们还相当于变成了官方的公共的工具类。可以完成一些内部的工作(这些不是我们能看到的了)。理解这个的本质就好了
首先创建一个Javabean(对象)里面的成员变量,属性方法等都是比较全的
@Configuration
public class MyConfig {
@Bean
public MyBean myBean() {
MyBean myBean = new MyBean(10, "JYT");
return myBean;
}
}
这个时候,相当于myBean,已经是一个bean放入容器了。然后使用它:
@Resource
private MyBean myBean;
/**
原因很简单:类似@Component , @Repository , @ Controller , @Service 这些注册Bean的注解存在局限性,只能局限作用于自己编写的类,如果是一个jar包第三方库要加入IOC容器的话,这些注解就手无缚鸡之力了,是的,@Bean注解就可以做到这一点!当然除了@Bean注解能做到还有@Import也能把第三方库中的类实例交给spring管理,而且@Import更加方便快捷,只是@Import注解并不在本篇范围内,这里就不再概述。
使用@Bean注解的另一个好处就是能够动态获取一个Bean对象,能够根据环境不同得到不同的Bean对象。
@Configuration
public class MyConfig {
@Bean (name = "myBean123")
public MyBean myBean() {
MyBean myBean = new MyBean(10, "JYT");
return myBean;
}
/**
* @Bean注解标注的方法参数位置:应用启动时,会前往Spring的IOC容器中寻找该类型的bean,并注入到方法的参数上,当然也可以结合@Qualifier注解完成指定bean的注入
* 另外,如果获取配置文件中的参数值,可以结合@Value注解
* @return
*/
@Bean
// public Car car(@Qualifier("phone1") Phone phone, @Value("${data.username}") String name){
public Mybean2 car(@Qualifier("myBean123")MyBean myBean){
Mybean2 mybean2 = new Mybean2();
mybean2.setAge(myBean.getAge()+1000);
mybean2.setName(myBean.getName());
return mybean2;
}
}
说几个比较关键的点,这里涉及到了一个写名字的地方,@bean也可以作为参数参与,这里就不用导入了。注意这种写法
@Resource
private MyBean myBean;
@Resource
private Mybean2 myBean2;
/**
* 测试输出结果
*/
@RequestMapping(value = "", method = RequestMethod.POST)
public ResponseResult<Void> test() {
System.out.println(myBean.getName());
System.out.println(myBean.getAge());
System.out.println(myBean2.getName());
System.out.println(myBean2.getAge());
return new ResponseResult<>(SUCCESS);
}
注意,如果@Bean没有设置名字的话,默认的名字是它的方法名。
详情请见springboot特性及其启动器注解(就在本专题中,详细介绍了springboot的几大特性)可以看得出来,springboot的几大特性多多少少都是在自动配置的基础上才得以完成的。而自动配置则是IOC最彻底的体现
有两个类,简称为A类和B类吧。现在呢,我在A类里面使用@Resource导入了一个C对象。然后再A类中写了一个方法D,方法D使用了C对象。然后我现在在B中想直接用A类的D方法,我当前的做法是直接在B类的程序中new了一个A对象,然后调用了A对象的D方法,运行的时候发现C对象报空了。
问题的根本原因是,当你直接在B类中使用new A()创建A类的对象时,这个对象并不由Spring容器管理,因此其中的依赖关系(如通过@Resource导入的C对象)没有得到正确的注入。所以,当你调用A类的方法D时,涉及到的C对象为null,导致空指针异常。
要解决这个问题,你可以让Spring容器来管理A类的对象,确保依赖关系得到正确注入。
@Component
public class B {@Component
public class A {
@Resource
private C c;
public void methodD() {
// 使用C对象的逻辑
}
}
@Autowired
private A a;
public void someMethod() {
// 调用A类的方法D
a.methodD();
}
}
核心只有一个,如果一个类中用到了DI的方法注入了Bean,那么最好也用DI的方式来处理这个类
标签:容器,依赖,Resource,Spring,Bean,反转,注解,public From: https://www.cnblogs.com/052160fh/p/18084432