概述
在此记录spring的学习内容。spring官网:https://spring.io/
概念故事
从前,在Java的大森林中,有一片神奇的土地,名叫"Spring"。这片土地上生长着各种美丽而强大的植物,它们分别象征着Spring框架中的各种功能和特性。
在这片土地上,有一位智慧而善良的园丁,名叫"Rod Johnson"。Rod是这片土地上的守护者,他有着非凡的见识和智慧。他注意到这片土地原本虽然生机盎然,但由于管理混乱、依赖杂乱等问题而日渐失去活力。于是,他开始策划着一场变革之旅。
Rod明白,为了让这片土地重焕生机,他需要一种全新的方式来管理这里那些繁杂的植物。于是,他提出了"控制反转"的理念,使得每一株植物可以有自己的生长空间,而不再依赖于别的植物。
随后,他又强调"依赖注入"这一概念,让每株植物可以从土地中获取所需的养分,而不用亲自去寻找。这样设计使得这些植物的生长变得更为高效。
Rod还发明了一种神奇的"面向切面编程"技术,一种能够让植物们自由组合、互相辅助的方式,令整片土地都焕发出一种特殊的生机。
渐渐地,这片土地上充满了生机与活力。每一株植物都在互相配合下茁壮成长,形成了一片绚丽而蓬勃的景象。园丁Rod Johnson因其智慧和勇气,被人们誉为这片土地上的英雄。
从那以后,人们便将这片土地上的新秩序称为"Spring",这个充满魔力的名字也因此广为传颂。而这位园丁Rod Johnson,则被尊称为Spring框架的缔造者和守护者。
就这样,Spring框架成为了Java世界中最重要的框架之一,为开发者们带来了许多便利,也为Java企业级应用开发带来了一场新的春天。
原文链接:https://blog.csdn.net/qq_55482652/article/details/137440957
控制反转IOC
控制反转
控制反转(Inversion of Control,IoC)是一种思想,是Spring框架的核心概念之一,它的主要作用 是通过将对象的创建和依赖关系的管理交给Spring容器来实现更松耦合的设计,而不是由程序员显式地进行操作。
一句话理解IOC、IOC容器、依赖注入:IOC是一种思想。通过IOC容器管理对象。编码时,通过依赖注入的方式从IOC容器中获取依赖的对象。
IOC容器->创建对象
DI->给对象的属性赋值
对比-传统开发
传统的开发模式中,应用程序本身负责创建和管理对象,并且通过直接调用其他对象的方法来完成业务逻辑。这种方式下,各个对象之间紧耦合,修改其中一个对象的实现会影响到其他所有相关的组件,导致代码的可维护性、可扩展性和可重用性都很差。
java
public class ServiceA {
private ServiceB serviceB = new ServiceB();
}
这种方式中,ServiceA直接依赖于ServiceB的具体实现。
对比-控制反转IOC
Spring采用控制反转的方式解决了这个问题。它将对象的创建和管理职责从应用程序代码转移到了IOC容器。这样,对象不再自行创建依赖对象,而是从IOC容器中获取。在IoC容器中,对象的创建、依赖注入和生命周期的管理都由容器自动完成,使得组件之间的关系变得非常简单和清晰。具体来说,控制反转有两种主要方式:
- 依赖注入(Dependency Injection, DI):通过构造器、setter方法或者字段注入的方式,将依赖对象注入到需要的对象中。
- 依赖查找(Dependency Lookup):对象在运行时从容器中主动获取它所需要的依赖对象。
通过控制反转,Spring可以轻松实现面向对象设计中的依赖注入,即将对象所需的依赖关系从对象内部转移到外部(有点类似组合),从而消除了对象之间的硬编码关系,提高了代码的复用性和灵活性。同时,使得项目的结构更加清晰,易于维护。
对比总结
在传统的开发模式中,我们的程序主要由业务逻辑和控制逻辑组成。而在这种模式中,构建应用程序的过程通常是这样的:当需要一个对象时,我们会直接使用 new 运算符等方式来手动创建这个对象,并将其注入到其他对象中使用。这种过程中,创建和维护对象的职责完全由程序员负责。
而在IOC容器的设计思想下,控制权被完全反转了。即对象的创建和维护都交由外部容器管理,而程序员只需关注于定义依赖关系以及编写具体的业务逻辑(程序员只要确定需要什么依赖,并且关注业务逻辑即可,不用关心依赖对象的创建和维护)。实现这种控制反转的核心技术就是依赖注入、依赖查找。在Spring框架中,DI 通常通过三种方式进行:构造函数注入、 Setter 方法注入和字段注入。
总的来说,控制反转的目的是减少程序员对对象的手动创建和维护工作,实现代码重用、灵活性和可维护性。同时,依赖注入也使得程序的结构更加清晰,易于扩展和测试。因此,IOC容器和依赖注入已经成为了现代编程语言和框架中不可或缺的一部分。
IOC容器
IOC控制反转是一种思想,而在Spring框架中,负责创建、管理和注入依赖对象的容器就叫IOC容器(也可以叫做Bean容器)。
具体来说,当使用IOC容器时,我们将需要依赖的对象交给容器管理,而不是自行手动创建和维护这些对象。IOC容器会自动扫描项目中所有Bean定义,根据配置文件或者注解等方式实例化Bean对象,并将其注册到容器中。当我们需要使用某个依赖项时,只需要从容器中获取即可,而无需关心对象如何创建和维护。这种方式下,我们只需要关注业务逻辑的实现,从而使代码更加简洁、灵活和易于维护。
Spring框架中常用的IOC容器有两种:BeanFactory 和ApplicationContext。其中,BeanFactory 是Spring框架最基本的IOC容器,提供了最基本的IOC能力。ApplicationContext 是在BeanFactory 的基础上进行了扩展,提供了更多的特性和功能,例如国际化支持、AOP、事件机制等,因此是Spring框架中常用的IOC容器。
问题:IOC容器是如何管理依赖对象的?
依赖注入
依赖注入(Dependency Injection,DI)是一种应用程序设计模式,它将类之间的依赖关系从代码内部提取出来,转而通过外部容器进行管理。通过使用依赖注入,对象不再创建和维护它所依赖的其他对象,而是由 IOC 容器负责创建这些对象,并将其注入到需要使用它们的地方。依赖注入的核心思想是松耦合(loose coupling),也就是减少模块之间的依赖关系,使得系统更加灵活、易于扩展和维护。同时,依赖注入也有助于提高代码的可测试性,因为我们可以使用模拟对象来替代真实的依赖对象进行单元测试。
(说人话就是,依赖注入是给对象的属性赋值,也是从IOC容器中找东西)
实现依赖注入DI有3种方法:
- 构造器注入
- Setter方法注入
- 字段注入
构造器注入
构造器注入是通过构造函数将依赖注入到目标对象中的。这种方式确保了依赖对象在目标对象创建时即被提供,适用于强制性依赖。
示例:
java
@Component
public class ServiceA {
private final ServiceB serviceB;
@Autowired
public ServiceA(ServiceB serviceB) {
this.serviceB = serviceB;
}
}
优点:
确保依赖关系在对象创建时即被注入,不会出现未初始化的问题。
对于必须的依赖来说,构造器注入更加明确和安全。
缺点:
当依赖项较多时,构造函数的参数列表会变得很长,不太优雅。
Setter方法注入
Setter注入是通过setter方法将依赖注入到目标对象中的。适用于可选的依赖,或在对象创建之后进行依赖注入。
示例:
java
@Component
public class ServiceA {
private ServiceB serviceB;
@Autowired
public void setServiceB(ServiceB serviceB) {
this.serviceB = serviceB;
}
}
优点:
灵活性高,可以在对象创建之后再进行依赖注入。
适用于可选依赖和配置复杂对象。
缺点:
可能导致对象处于不完全初始化状态,增加了调试和维护的复杂性。
字段注入
字段注入是通过直接在字段上使用注解将依赖注入到目标对象中的。这种方式简单直观,但不推荐使用,因为违反了依赖倒置原则。
示例:
java
@Component
public class ServiceA {
@Autowired
private ServiceB serviceB;
}
优点:
实现简单,代码简洁。
缺点:
使单元测试变得困难,因为依赖关系在对象创建之后无法更改。
违反了依赖倒置原则(Dependence Inversion Principle),使得类的设计不够优雅。
依赖注入方式总结
选择依赖注入方式的考虑
- 构造器注入适用于强制性依赖,确保对象在创建时即被完全初始化。
- Setter注入适用于可选依赖和需要在对象创建之后进行配置的情况。
- 字段注入由于违反设计原则,通常不推荐使用,除非是在非常简单的场景中。
总结来说,选择合适的依赖注入方式应根据具体的需求和设计原则来决定。构造器注入和Setter注入是更推荐的方式,而字段注入应尽量避免。
代码比较:
假设我们有一个名为UserService的类,它需要依赖于UserDao类来进行数据存储。那么在传统的编程方式中,我们可能会这样写代码:
java
public class UserService {
private UserDao userDao;
public UserService() {
this.userDao = new UserDao();
}
public void addUser(User user) {
// 进行业务逻辑处理
userDao.save(user);
}
// 其他方法省略...
}
在上述代码中,我们通过创建UserDao对象来满足UserService对依赖的需求。然而,这种方式会导致UserService和UserDao紧密耦合在一起,不利于代码的维护和扩展,并且使得UserService难以进行单元测试。
而如果使用依赖注入的话,我们可以将UserDao对象的创建和管理交给外部容器(如Spring容器)来完成,从而解耦和UserService与UserDao之间的关系,代码如下所示:
java
public class UserService {
private UserDao userDao;
public UserService(UserDao userDao) {
this.userDao = userDao;
}
public void addUser(User user) {
// 进行业务逻辑处理
userDao.save(user);
}
// 其他方法省略...
}
在上述代码中,我们通过构造函数注入的方式将UserDao对象注入到了UserService类中,而UserDao对象则是由外部容器负责创建和管理的。这样一来,UserService与UserDao之间的耦合就被消除了,同时也提高了代码的可测试性和可维护性。
总的来说,依赖注入能够大大简化代码的编写和维护工作,使代码更加灵活、易于扩展和测试。
那这个UserDao是什么时候注入到UserService类中的呢?
UserDao
对象是在创建UserService
实例时通过依赖注入的方式注入到UserService
类中的。具体来说,在使用Spring框架时,可以通过在配置文件中配置UserDao
的实现类和UserService
的构造函数参数来实现自动注入;或者使用@Autowired
注解标记UserService
类中的UserDao
字段,让Spring容器自动完成注入。这样一来,当需要使用UserService
对象时,Spring容器会自动创建一个UserDao
对象并将其注入到UserService
对象中。这样就实现了UserService
和UserDao
之间的解耦合。
那这个创建的UserDao是单例吗?
在Spring容器中,默认情况下,所有的Bean都是单例的。也就是说,当使用依赖注入的方式创建UserDao
对象时,默认情况下会创建一个单例对象,并且在整个应用程序的生命周期内都可以使用这个对象。如果需要创建多个实例,则需要在配置文件中进行相应的设置。可以使用@Scope
注解来指定Bean的作用域,常见的作用域有单例(Singleton)、原型(Prototype)、请求(Request)、会话(Session)等。例如,可以使用@Scope("prototype")
注解来将UserDao
配置为原型作用域,这样每次从Spring容器中获取UserDao
对象时,都会创建一个新的实例。
默认情况下会创建一个单例对象,并且在整个应用程序的生命周期内都可以使用这个对象。如果容器中已经创建了UserDao对象,则后续获取UserDao对象时不会再次创建新的对象,而是直接返回之前已经创建好的对象。
需要注意的是,在某些情况下,例如多线程环境下,使用单例模式可能会存在线程安全问题,此时建议将Bean的作用域配置为原型,这样可以每次都创建一个新的对象实例来避免线程安全问题。
IOC实现原理
spring提供两种方式创建Bean,一种是xml,一种是注解。
基于XML的原理
读取解析xml配置文件->反射实例化对象->存储在Map集合中
1 读xml
读取配置文件(spring.xml)->xml解析->获取bean的id和类的全路径名
xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="empDao" class="com.zou.learnspring.dao.impl.EmpDaoImpl2"/>
</beans>
补充
bean标签的属性: 点击跳转
2 实例化对象
通过反射实例化对象->全路径名获取字节码对象->通过字节码实例化->放到容器中(当作一个Map集合)
Class clazz = Class.forName("com.zou.learnspring.dao.impl.EmpDaoImpl2")
Object obj = clazz.newInstance();
map.put("empDao", obj);
3 getBean获取对象
工厂模式返回Bean对象
BeanFactory接口定义的getBean方法,该方法底层就是map.get("name");
接口的继承ApplicationContext -> ... -> BeanFactory
ApplicationContext的实现类:ClassPathXmlApplicationContext。其中间也是间接实现了ApplicationContext接口。
在idea中,在ApplicationContext接口中,通过Ctrl+H 可以查看接口的实现类
面向切面编程
作用:事务控制、日志记录、性能检测、权限控制