SpringBoot入门-(2) Spring IOC机制
Spring
Spring是一个当前主流的轻量级的框架,发展到形状已经不仅仅是一个框架,而是形成以Spring为基础的生态圈,如(Spring Boot
,Spring Cloud
,Spring Security
等)
Spring 两大核心技术
- 控制反转(IoC)
- 面向切面编程(AOP)
本文先介绍其一
控制反转(IoC/DI)
依赖注入(DI:Dependency Injection ):Spring通过创建容器的方式,来负责对象的创建、对象和对象之间关系的维护等
动机
在面向对象的系统设计中,底层的业务逻辑是由多个对象组成,对象之间通常存在复杂的联系,导致系统的耦合度很高,例如:
public class UserServiceImp implements UserService{private UserDao userdao = new UserDaoImp();...}
上述
UserServiceImp
类实现UserService
接口,其中创建的私有成员变量是通过UserDaoImp
类创建出来的实例。当
UserDaoImp
的业务逻辑产生变化或出现错误,都有可能需要修改UserServiceImp
的代码,所谓产生了"牵一发而动全身"的系统高耦合度。
不难发现,系统越庞大,对象关系越复杂,系统耦合度越高,导致系统维护愈发困难。
因此,Spring横空出世,解决对象之间耦合度过高的问题。后来从产品发展为生态圈。
概念
究竟什么是控制反转?
先看下图:
首先是左图的情况,假设其他一个齿轮需要修改或不转动,都会导致其他的齿轮停止工作,这称为高耦合度。而右图,齿轮之间不存在依赖关系,工作相对独立,不会影响到其他齿轮的正常工作。这表现的就是"控制反转"的基本思想:
借助于“第三方”实现具有依赖关系的对象之间的解耦。
齿轮之间的传动全部依靠“第三方”了, 全部对象的控制权全部上缴给“第三方”IOC容器,主动创建变为了被动注入, 这就是“控制反转”(依赖注入)这个名称的由来
代码
首先创建Maven项目(传送门:SpringBoot入门-(1) Maven【概念+流程】-CSDN博客),pom.xml文件导入相关spring依赖,包括测试依赖:
<dependencies><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-aop</artifactId></dependency><dependency><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter-api</artifactId><scope>test</scope></dependency><dependency><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter-engine</artifactId><scope>test</scope></dependency><dependency><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter</artifactId><version>RELEASE</version><scope>test</scope></dependency></dependencies>
注意,这里没有定义依赖的版本,是通过父项目统一管理版本号。
without Spring
我们先用常规的MVC架构实现一些简单逻辑,后面对比Spring架构就可以明显发现其优势和作用。
首先我们实现一个非常简易的商品存入数据库的逻辑:
部分代码如下:
-
ProductDaoImpl
package com.example.Dao;import com.example.Dao.ProductDao; import com.example.entity.Product;public class ProductDaoImpl implements ProductDao {//这里是模拟商品存入,并没有真正存入数据库@Overridepublic void saveProduct(Product product) {System.out.println("保存商品信息");System.out.println(product.toString());} }
-
ProductServiceImpl
package com.example.Service; import com.example.entity.Product; import com.example.Dao.*;public class ProductServiceImpl implements ProductService{private ProductDao productDao = new ProductDaoImpl();//其他业务逻辑,如检查是否合法等@Overridepublic void saveProduct(Product product) {productDao.saveProduct(product);} }
可以看到在
ProductServiceImpl
中,我们通过new
的方式创建实例对象并赋值给成员变量
测试代码:
在test
目录底下进行测试:
package com.example.Service;import com.example.entity.Product;
import org.junit.jupiter.api.Test;public class ProductServiceTest { //如果 Calculator 类和测试类处于相同的包或者符合 Java 的包访问规则,那么测试类就可以直接访问 ProductServiceImpl 类。@Testpublic void testSaveProduct() {ProductService productService = new ProductServiceImpl();productService.saveProduct(new Product(0, "test",12.7));}
}
可以看到控制台输出信息:
with spring
我们现在使用spring框架实现相同的功能
- 方式一:配置文件
首先新建spring的配置文件,如图:
命名为applicationContext
,可以看到文件中已经存在默认的内容(默认生成的XML文件的NameSpace,<bean>
就是关键的标签。
容器中创建对象,本质就是在文件中配置一个bean
。
在<beans></beans>
中间添加我们想要创建的对象及注入依赖。
<bean id="productDao" class="com.example.Dao.ProductDaoImpl"/><bean id="productService" class="com.example.Service.ProductServiceImpl"><property name="productDao" ref="productDao"/></bean>
容器通过
com.example.Dao.ProductDaoImpl
类创建id为productDao
的对象对象属性的赋值通过
<property>
标签,需要在com.example.Service.ProductServiceImpl
提供对应的setter方法:public void setProductDao(ProductDaoImpl productDao) {this.productDao = productDao;}
下一步,在测试程序中创建容器,并获取指定对象进行测试。
public class ProductServiceTest {//如果 Calculator 类和测试类处于相同的包或者符合 Java 的包访问规则,那么测试类就可以直接访问 ProductServiceImpl 类。@Testpublic void testSaveProduct() {ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");ProductService productService = (ProductService) ac.getBean("productService");productService.saveProduct(new Product(0, "test",12.7));}
}
上述代码中,我们先创建了容器,然后根据xml文件中的配置获取对象,返回的类型是Object,注意转换类型。
观察到上述两种方式,spring将层与层之间的联系解耦,可以比较一下下面两段代码:
//解耦前
private ProductDao productDao = new ProductDaoImpl();//spring
private ProductDao productDao;
//在配置文件中注入依赖
<bean id="productService" class="com.example.Service.ProductServiceImpl"><property name="productDao" ref="productDao"/>
</bean>
这样,我们在"更换齿轮"的时候就不用去修改Service层的代码,修改配置文件即可。(注意,配置文件依赖注入一定要在相应的地方添加setter方法,底层会进行调用)
- 方式二: 注解实现IOC
- 注解+配置
当我们的项目很大的时候,使用配置文件就会出现问题,配置文件信息内容过大让人眼花缭乱,所以spring提供了注解的方式。
根据注解,我们可以把配置文件中的:
<bean id="productDao" class="com.example.Dao.ProductDaoImpl"/>
等效为:
@Repository("productDao")
//如果不给注解起名字,默认的名字为类名(首字母小写)
public class ProductDaoImpl implements ProductDao {//这里是模拟示范@Overridepublic void saveProduct(Product product) {System.out.println("保存商品信息");System.out.println(product.toString());}
}
Spring 默认不使用注解装配 Bean,因此我们需要在 Spring 的 XML 配置中,通过**context:component-scan** 元素开启 Spring Beans的自动扫描功能
<context:component-scan base-package="com.example.*"></context:component-scan>
其中
base-package
是你希望自动扫描的路径。
Bean的自动装配
使用@AutoWired
注解实现自动装配,如:
public class ProductServiceImpl implements ProductService{@Autowiredprivate ProductDao productDao;
- 全注解
不使用spring配置文件,而是进行全注解开发,因此我们需要使用注解写一个配置类实现和annotation.xml
的功能。
在config/目录下创建类文件:
@Configuration
@ComponentScan("com.example")
public class SpringConfig {
}
对应的测试代码:
@Testpublic void testSaveProduct() {ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfig.class);ProductService productService = ac.getBean("productService", ProductService.class);
当然也可以在配置类中使用@Bean标签
:
@Configuration
@ComponentScan("com.example")
public class SpringConfig {@Bean(name = "productService")public ProductServiceImpl creat(){return new ProductServiceImpl();}
}
对应的测试代码修改一句即可:
ProductService productService = ac.getBean(ProductService.class);