自学内容网 自学内容网

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

需求: 造⼀辆⻋

传统程序开发

我们的实现思路是这样的:
先设计轮⼦(Tire),然后根据轮⼦的⼤⼩设计底盘(Bottom),接着根据底盘设计⻋⾝(Framework),最后根据⻋⾝设计好整个汽⻋(Car)。这⾥就出现了⼀个"依赖"关系:汽⻋依赖⻋⾝,⻋⾝依赖底盘,底盘依赖轮⼦.
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 是⼀种思想,也是"⽬标", ⽽思想只是⼀种指导原则,最终还是要有可⾏的落地⽅案,⽽ DI 就属于具体的实现。所以也可以说, DI 是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...");
 }
}
接下来我们学习如何从Spring容器中获取对象
@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);
 }
}
读取 bean 的代码:
@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 存储 bean 的代码如下所示:
@Repository
public class UserRepository {
 public void sayHi() {
 System.out.println("Hi, UserRepository~");
 }
}
读取 bean 的代码:

@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 存储 bean 的代码如下所示:
@Component
public class UserComponent {
 public void sayHi() {
 System.out.println("Hi, UserComponent~");
 }
}
读取 bean 的代码:
@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~");
 }
}
读取 bean 的代码:
@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:配置层. 处理项⽬中的⼀些配置信息.

类注解之间的关系

查看 @Controller / @Service / @Repository / @Configuration 等注解的源码发现:
其实这些注解⾥⾯都有⼀个注解 @Component ,说明它们本⾝就是属于 @Component 的"⼦类".
@Component 是⼀个元注解,也就是说可以注解其他类注解,如 @Controller , @Service ,
@Repository 等. 这些注解被称为 @Component 的衍⽣注解.
--------------------------------------------------------------------------------------------------------------------------------

⽅法注解 @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;
 }
}
接下来我们根据名称来获取bean对象
@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 可以针对同⼀个类, 定义多个对象

重命名 Bean

可以通过设置 name 属性给 Bean 对象进⾏重命名操作,如下代码所⽰:

@Bean(name = {"u1","user1"})
public User user1(){
 User user = new User();
 user.setName("zhangsan");
 user.setAge(18);
 return user;
}
此时我们使⽤ u1 就可以获取到 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);
 }
}
name={} 可以省略,如下代码所⽰:
@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 类的实现代码如下:
@Controller
public class UserController {
 //注⼊⽅法1: 属性注⼊
 @Autowired
 private UserService userService;
 public void sayHi(){
 System.out.println("hi,UserController...");
 userService.sayHi();
 }
}
获取 Controller 中的 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)

Setter 注⼊和属性的 Setter ⽅法实现类似,只不过在设置 set ⽅法的时候需要加上 @Autowired 注
解 ,如下代码所⽰:
@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)!