Spring IoC&DI
IoC & DI ⼊⻔
Spring 是包含了众多⼯具⽅法的 IoC 容器
什么是 IoC?
IoC 是Spring的核⼼思想
在类上⾯添加 @RestController 和 @Controller 注解, 就是把这个对象交给Spring管理, Spring 框架启动时就会加载该类. 把对象交给Spring管理, 就是IoC思想.
IoC: Inversion of Control (控制反转), 也就是说 Spring 是⼀个"控制反转"的容器.
什么是控制反转呢? 也就是控制权反转. 什么的控制权发⽣了反转? 获得依赖对象的过程被反转了
也就是说, 当需要某个对象时, 传统开发模式中需要⾃⼰通过 new 创建对象, 现在不需要再进⾏创
建, 把创建对象的任务交给容器, 程序中只需要依赖注⼊ (Dependency Injection,DI)就可以了.
这个容器称为:IoC容器. Spring是⼀个IoC容器, 所以有时Spring 也称为Spring 容器
IoC 介绍
我们通过案例来了解⼀下什么是IoC
需求: 造⼀辆⻋
传统程序开发
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);
}
}
}
IoC程序开发
试换⼀种思路, 我们先设计汽⻋的⼤概样⼦,然后根据汽⻋的样⼦来设计⻋⾝,根据⻋⾝来设计
底盘,最后根据底盘来设计轮⼦. 这时候,依赖关系就倒置过来了:轮⼦依赖底盘, 底盘依赖⻋
⾝,⻋⾝依赖汽⻋.
如何来实现呢:
我们可以尝试不在每个类中⾃⼰创建下级类,如果⾃⼰创建下级类就会出现当下级类发⽣改变操作,⾃⼰也要跟着修改.
此时,我们只需要将原来由⾃⼰创建的下级类,改为传递的⽅式(也就是注⼊的⽅式),因为我们不需要在当前类中创建下级类了,所以下级类即使发⽣变化(创建或减少参数),当前类本⾝也⽆需修改任何代码,这样就完成了程序的解耦.
public class IocCarExample {
public static void main(String[] args) {
Tire tire = new Tire(20);
Bottom bottom = new Bottom(tire);
Framework framework = new Framework(bottom);
Car car = new Car(framework);
car.run();
}
static class Car {
private Framework framework;
public Car(Framework framework) {
this.framework = framework;
System.out.println("Car init....");
}
public void run() {
System.out.println("Car run...");
}
}
static class Framework {
private Bottom bottom;
public Framework(Bottom bottom) {
this.bottom = bottom;
System.out.println("Framework init...");
}
}
static class Bottom {
private Tire tire;
public Bottom(Tire tire) {
this.tire = tire;
System.out.println("Bottom init...");
}
}
static class Tire {
private int size;
public Tire(int size) {
this.size = size;
System.out.println("轮胎尺⼨:" + size);
}
}
}
IoC 优势
资源不由使⽤资源的双⽅管理,⽽由不使⽤资源的第三⽅管理,这可以带来很多好处。第⼀,资源集中管理,实现资源的可配置和易管理。第⼆,降低了使⽤资源双⽅的依赖程度,也就是我们说的耦合度。
1. 资源集中管理: IoC容器会帮我们管理⼀些资源(对象等), 我们需要使⽤时, 只需要从IoC容器中去取就可以了
2. 我们在创建实例的时候不需要了解其中的细节, 降低了使⽤资源双⽅的依赖程度, 也就是耦合度.
DI介绍
DI: Dependency Injection(依赖注⼊)
容器在运⾏期间, 动态的为应⽤程序提供运⾏时所依赖的资源,称之为依赖注⼊。
程序运⾏时需要某个资源,此时容器就为其提供这个资源.
从这点来看, 依赖注⼊(DI)和控制反转(IoC)是从不同的⻆度的描述的同⼀件事情,依赖注⼊是
从应⽤程序的⻆度来描述, 就是指通过引⼊ IoC 容器,利⽤依赖关系注⼊的⽅式,实现对象之间的解耦.
上述代码中, 是通过构造函数的⽅式, 把依赖对象注⼊到需要使⽤的对象中的.
IOC详解
前⾯我们提到IoC控制反转,就是将对象的控制权交给Spring的IOC容器,由IOC容器创建及管理对
象。
也就是bean的存储.
Bean的存储
要把某个对象交给IOC容器管理,需要在类上添加⼀个注解: @Component
⽽Spring框架为了更好的服务web应⽤程序, 提供了更丰富的注解.
共有两类注解类型可以实现:
1. 类注解:@Controller、@Service、@Repository、@Component、@Configuration.
2. ⽅法注解:@Bean.
@Controller(控制器存储)
@Controller // 将对象存储到 Spring 中
public class UserController {
public void sayHi(){
System.out.println("hi,UserController...");
}
}
@SpringBootApplication
public class SpringIocDemoApplication {
public static void main(String[] args) {
//获取Spring上下⽂对象
ApplicationContext context =
SpringApplication.run(SpringIocDemoApplication.class, args);
//从Spring上下⽂中获取对象
UserController userController = context.getBean(UserController.class);
//使⽤对象
userController.sayHi();
}
}
ApplicationContext 翻译过来就是: Spring 上下⽂
因为对象都交给 Spring 管理了,所以获取对象要从 Spring 中获取,那么就得先得到 Spring 的上下⽂
关于上下⽂的概念
就是指当前的运⾏环境, 也可以看作是⼀个容器, 容器⾥存了很多内容, 这些内容是当前
运⾏的环境
获取bean对象的其他⽅式
ApplicationContext 也提供了其他获取bean的⽅式, ApplicationContext 获取bean对象的功能, 是⽗
类BeanFactory提供的功能.
public interface BeanFactory {
//以上省略...
// 1. 根据bean名称获取bean
Object getBean(String var1) throws BeansException;
// 2. 根据bean名称和类型获取bean
<T> T getBean(String var1, Class<T> var2) throws BeansException;
// 3. 按bean名称和构造函数参数动态创建bean,只适⽤于具有原型(prototype)作⽤域的bean
Object getBean(String var1, Object... var2) throws BeansException;
// 4. 根据类型获取bean
<T> T getBean(Class<T> var1) throws BeansException;
// 5. 按bean类型和构造函数参数动态创建bean, 只适⽤于具有原型(prototype)作⽤域的
bean
<T> T getBean(Class<T> var1, Object... var2) throws BeansException;
//以下省略...
}
常⽤的是上述1,2,4种, 这三种⽅式,获取到的bean是⼀样的
其中1,2种都涉及到根据名称来获取对象. bean的名称是什么呢?
Spring bean是Spring框架在运⾏时管理的对象, Spring会给管理的对象起⼀个名字.
⽐如学校管理学⽣, 会给每个学⽣分配⼀个学号, 根据学号, 就可以找到对应的学⽣.
Spring也是如此, 给每个对象起⼀个名字, 根据Bean的名称(BeanId)就可以获取到对应的对象
Spring中获取bean对象实现了单例模式.
--------------------------------------------------------------------------------------------------------------------------------
bean的命名规则
命名约定使⽤Java标准约定作为实例字段名. 也就是说,bean名称以⼩写字⺟开头,然后使⽤驼峰式⼤⼩写.
⽐如
类名: UserController, Bean的名称为: userController
类名: AccountManager, Bean的名称为: accountManager
类名: AccountService, Bean的名称为: accountService
也有⼀些特殊情况, 当有多个字符并且第⼀个和第⼆个字符都是⼤写时, 将保留原始的⼤⼩写. 这些规则与java.beans.Introspector.decapitalize (Spring在这⾥使⽤的)定义的规则相同.
⽐如
类名: UController, Bean的名称为: UController
类名: AManager, Bean的名称为: AManager
获取bean对象, 是⽗类BeanFactory提供的功能
ApplicationContext VS BeanFactory(常⻅⾯试题)
继承关系和功能⽅⾯来说:Spring 容器有两个顶级的接⼝:BeanFactory 和
ApplicationContext。其中 BeanFactory 提供了基础的访问容器的能⼒,⽽
ApplicationContext 属于 BeanFactory 的⼦类,它除了继承了 BeanFactory 的所有功能之外,
它还拥有独特的特性,还添加了对国际化⽀持、资源访问⽀持、以及事件传播等⽅⾯的⽀持.
从性能⽅⾯来说:ApplicationContext 是⼀次性加载并初始化所有的 Bean 对象,⽽
BeanFactory 是需要那个才去加载那个,因此更加轻量. (空间换时间)
--------------------------------------------------------------------------------------------------------------------------------
@Service(服务存储)
@Service
public class UserService {
public void sayHi(String name) {
System.out.println("Hi," + name);
}
}
@SpringBootApplication
public class SpringIocDemoApplication {
public static void main(String[] args) {
//获取Spring上下⽂对象
ApplicationContext context =
SpringApplication.run(SpringIocDemoApplication.class, args);
//从Spring中获取UserService对象
UserService userService = context.getBean(UserService.class);
//使⽤对象
userService.sayHi();
}
}
--------------------------------------------------------------------------------------------------------------------------------
@Repository(仓库存储)
@Repository
public class UserRepository {
public void sayHi() {
System.out.println("Hi, UserRepository~");
}
}
@SpringBootApplication
public class SpringIocDemoApplication {
public static void main(String[] args) {
//获取Spring上下⽂对象
ApplicationContext context =
SpringApplication.run(SpringIocDemoApplication.class, args);
//从Spring上下⽂中获取对象
UserRepository userRepository = context.getBean(UserRepository.class);
//使⽤对象
userRepository.sayHi();
}
}
@Component(组件存储)
@Component
public class UserComponent {
public void sayHi() {
System.out.println("Hi, UserComponent~");
}
}
@SpringBootApplication
public class SpringIocDemoApplication {
public static void main(String[] args) {
//获取Spring上下⽂对象
ApplicationContext context =
SpringApplication.run(SpringIocDemoApplication.class, args);
//从Spring上下⽂中获取对象
UserComponent userComponent = context.getBean(UserComponent.class);
//使⽤对象
userComponent.sayHi();
}
}
@Configuration(配置存储)
使⽤ @Configuration 存储 bean 的代码如下所⽰:
@Configuration
public class UserConfiguration {
public void sayHi() {
System.out.println("Hi,UserConfiguration~");
}
}
@SpringBootApplication
public class SpringIocDemoApplication {
public static void main(String[] args) {
//获取Spring上下⽂对象
ApplicationContext context =
SpringApplication.run(SpringIocDemoApplication.class, args);
//从Spring上下⽂中获取对象
UserConfiguration userConfiguration =
context.getBean(UserConfiguration.class);
//使⽤对象
userConfiguration.sayHi();
}
}
为什么要这么多类注解?
这个也是和咱们前⾯讲的应⽤分层是呼应的. 让程序员看到类注解之后,就能直接了解当前类的⽤途.
@Controller:控制层, 接收请求, 对请求进⾏处理, 并进⾏响应.
@Service:业务逻辑层, 处理具体的业务逻辑.
@Repository:数据访问层,也称为持久层. 负责数据访问操作
@Configuration:配置层. 处理项⽬中的⼀些配置信息.
类注解之间的关系
⽅法注解 @Bean
类注解是添加到某个类上的, 但是存在两个问题:
1. 使⽤外部包⾥的类, 没办法添加类注解
2. ⼀个类, 需要多个对象, ⽐如多个数据源
⽅法注解要配合类注解使⽤
@Component
public class BeanConfig {
@Bean
public User user(){
User user = new User();
user.setName("zhangsan");
user.setAge(18);
return user;
}
}
定义多个对象
@Component
public class BeanConfig {
@Bean
public User user1(){
User user = new User();
user.setName("zhangsan");
user.setAge(18);
return user;
}
@Bean
public User user2(){
User user = new User();
user.setName("lisi");
user.setAge(19);
return user;
}
}
@SpringBootApplication
public class SpringIocDemoApplication {
public static void main(String[] args) {
//获取Spring上下⽂对象
ApplicationContext context =
SpringApplication.run(SpringIocDemoApplication.class, args);
//根据bean名称, 从Spring上下⽂中获取对象
User user1 = (User) context.getBean("user1");
User user2 = (User) context.getBean("user2");
System.out.println(user1);
System.out.println(user2);
}
}
重命名 Bean
可以通过设置 name 属性给 Bean 对象进⾏重命名操作,如下代码所⽰:
@Bean(name = {"u1","user1"})
public User user1(){
User user = new User();
user.setName("zhangsan");
user.setAge(18);
return user;
}
@SpringBootApplication
public class SpringIocDemoApplication {
public static void main(String[] args) {
//获取Spring上下⽂对象
ApplicationContext context =
SpringApplication.run(SpringIocDemoApplication.class, args);
//从Spring上下⽂中获取对象
User u1 = (User) context.getBean("u1");
//使⽤对象
System.out.println(u1);
}
}
@Bean({"u1","user1"})
public User user1(){
User user = new User();
user.setName("zhangsan");
user.setAge(18);
return user;
}
@Bean("u1")
public User user1(){
User user = new User();
user.setName("zhangsan");
user.setAge(18);
return user;
}
DI 详解
关于依赖注⼊, Spring也给我们提供了三种⽅式:
1. 属性注⼊(Field Injection)
属性注⼊是使⽤ @Autowired 实现的,将 Service 类注⼊到 Controller 类中.
Service 类的实现代码如下:
import org.springframework.stereotype.Service;
@Service
public class UserService {
public void sayHi() {
System.out.println("Hi,UserService");
}
}
@Controller
public class UserController {
//注⼊⽅法1: 属性注⼊
@Autowired
private UserService userService;
public void sayHi(){
System.out.println("hi,UserController...");
userService.sayHi();
}
}
@SpringBootApplication
public class SpringIocDemoApplication {
public static void main(String[] args) {
//获取Spring上下⽂对象
ApplicationContext context =
SpringApplication.run(SpringIocDemoApplication.class, args);
//从Spring上下⽂中获取对象
UserController userController = (UserController)
context.getBean("userController");
//使⽤对象
userController.sayHi();
}
}
2. 构造⽅法注⼊(Constructor Injection)
构造⽅法注⼊是在类的构造⽅法中实现注⼊,如下代码所⽰:
@Controller
public class UserController2 {
//注⼊⽅法2: 构造⽅法
private UserService userService;
@Autowired
public UserController2(UserService userService) {
this.userService = userService;
}
public void sayHi(){
System.out.println("hi,UserController2...");
userService.sayHi();
}
}
注意事项:如果类只有⼀个构造⽅法,那么 @Autowired 注解可以省略;如果类中有多个构造⽅法, 那么需要添加上 @Autowired 来明确指定到底使⽤哪个构造⽅法。
3. Setter 注⼊(Setter Injection)
@Controller
public class UserController3 {
//注⼊⽅法3: Setter⽅法注⼊
private UserService userService;
@Autowired
public void setUserService(UserService userService) {
this.userService = userService;
}
public void sayHi(){
System.out.println("hi,UserController3...");
userService.sayHi();
}
}
三种方法比较
属性注⼊
优点: 简洁,使⽤⽅便;
缺点:
只能⽤于 IoC 容器,如果是⾮ IoC 容器不可⽤,并且只有在使⽤的时候才会出现 NPE(空指
针异常)
不能注⼊⼀个Final修饰的属性
构造函数注⼊(Spring 4.X推荐)
优点:
可以注⼊final修饰的属性
注⼊的对象不会被修改
依赖对象在使⽤前⼀定会被完全初始化,因为依赖是在类的构造⽅法中执⾏的,⽽构造⽅法
是在类加载阶段就会执⾏的⽅法.
通⽤性好, 构造⽅法是JDK⽀持的, 所以更换任何框架,他都是适⽤的
缺点:
注⼊多个对象时, 代码会⽐较繁琐
Setter注⼊(Spring 3.X推荐)
优点:
⽅便在类实例之后, 重新对该对象进⾏配置或者注⼊
缺点:
不能注⼊⼀个Final修饰的属性
注⼊对象可能会被改变, 因为setter⽅法可能会被多次调⽤, 就有被修改的⻛险
@Autowired存在问题
@Component
public class BeanConfig {
@Bean("u1")
public User user1(){
User user = new User();
user.setName("zhangsan");
user.setAge(18);
return user;
}
@Bean
public User user2() {
User user = new User();
user.setName("lisi");
user.setAge(19);
return user;
}
}
@Controller
public class UserController {
@Autowired
private UserService userService;
//注⼊user
@Autowired
private User user;
public void sayHi(){
System.out.println("hi,UserController...");
userService.sayHi();
System.out.println(user);
}
}
当同⼀类型存在多个bean时, 使⽤@Autowired会存在问题;
Spring提供了以下⼏种解决⽅案:
@Primary
使⽤@Primary注解:当存在多个相同类型的Bean注⼊时,加上@Primary注解,来确定默认的实现.
@Component
public class BeanConfig {
@Primary //指定该bean为默认bean的实现
@Bean("u1")
public User user1(){
User user = new User();
user.setName("zhangsan");
user.setAge(18);
return user;
}
@Bean
public User user2() {
User user = new User();
user.setName("lisi");
user.setAge(19);
return user;
}
}
@Qualifier
使⽤@Qualifier注解:指定当前要注⼊的bean对象。 在@Qualifier的value属性中,指定注⼊的bean的名称。
@Qualifier注解不能单独使⽤,必须配合@Autowired使⽤
@Controller
public class UserController {
@Qualifier("user2") //指定bean名称
@Autowired
private User user;
public void sayHi(){
System.out.println("hi,UserController...");
System.out.println(user);
}
}
@Resource
使⽤@Resource注解:是按照bean的名称进⾏注⼊。通过name属性指定要注⼊的bean的名称。
@Controller
public class UserController {
@Resource(name = "user2")
private User user;
public void sayHi(){
System.out.println("hi,UserController...");
System.out.println(user);
}
}
@Autowird 与 @Resource的区别
@Autowired 是spring框架提供的注解,⽽@Resource是JDK提供的注解
@Autowired 默认是按照类型注⼊,⽽@Resource是按照名称注⼊. 相⽐于 @Autowired 来说,
@Resource ⽀持更多的参数设置,例如 name 设置,根据名称获取 Bean.
Autowired装配顺序
Spring, Spring Boot 和Spring MVC的关系以及区别
Spring: 简单来说, Spring 是⼀个开发应⽤框架,什么样的框架呢,有这么⼏个标签:轻量级、⼀
站式、模块化,其⽬的是⽤于简化企业级应⽤程序开发.
Spring的主要功能: 管理对象,以及对象之间的依赖关系, ⾯向切⾯编程, 数据库事务管理, 数据访
问, web框架⽀持等.
但是Spring具备⾼度可开放性, 并不强制依赖Spring, 开发者可以⾃由选择Spring的部分或者全
部, Spring可以⽆缝继承第三⽅框架, ⽐如数据访问框架(Hibernate 、JPA), web框架(如Struts、
JSF)
Spring MVC: Spring MVC是Spring的⼀个⼦框架, Spring诞⽣之后, ⼤家觉得很好⽤, 于是按照MVC
模式设计了⼀个 MVC框架(⼀些⽤Spring 解耦的组件), 主要⽤于开发WEB应⽤和⽹络接⼝,所以,
Spring MVC 是⼀个Web框架.
Spring MVC基于Spring进⾏开发的, 天⽣的与Spring框架集成. 可以让我们更简洁的进⾏Web层
开发, ⽀持灵活的 URL 到⻚⾯控制器的映射, 提供了强⼤的约定⼤于配置的契约式编程⽀持, ⾮常
容易与其他视图框架集成,如 Velocity、FreeMarker等
Spring Boot: Spring Boot是对Spring的⼀个封装, 为了简化Spring应⽤的开发⽽出现的,中⼩型
企业,没有成本研究⾃⼰的框架, 使⽤Spring Boot 可以更加快速的搭建框架, 降级开发成本, 让开发
⼈员更加专注于Spring应⽤的开发,⽽⽆需过多关注XML的配置和⼀些底层的实现.
Spring Boot 是个脚⼿架, 插拔式搭建项⽬, 可以快速的集成其他框架进来.
⽐如想使⽤SpringBoot开发Web项⽬, 只需要引⼊Spring MVC框架即可, Web开发的⼯作是
SpringMVC完成的, ⽽不是SpringBoot, 想完成数据访问, 只需要引⼊Mybatis框架即可.
Spring Boot只是辅助简化项⽬开发的, 让开发变得更加简单, 甚⾄不需要额外的web服务器, 直接
⽣成jar包执⾏即可.
最后⼀句话总结: Spring MVC和Spring Boot都属于Spring,Spring MVC 是基于Spring的⼀个
MVC 框架,⽽Spring Boot 是基于Spring的⼀套快速开发整合包.
原文地址:https://blog.csdn.net/yonpiguxiao/article/details/143861088
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!