目录
一、IOC介绍
1、什么是IOC
2、通过案例来了解IoC
2.1 传统程序开发
2.2 问题分析
2.3 解决方案
2.4 IoC程序开发
2.5 IoC 优势
二、DI介绍
三、IOC 详解
3.1 Bean的存储
3.1.1 @Controller(控制器存储)
3.1.2 @Service(服务存储)
3.1.3 @Repository(仓库存储)
3.1.4 @Component(组件存储)
3.1.5 @Configuration(配置存储)
3.2 为什么要这么多类注解?
3.3 方法注解 @Bean
3.3.1 方法注解要配合类注解使用
3.3.2 定义多个对象
3.3.3 重命名 Bean
四、DI 详解
4.1 属性注入
4.2 构造方法注入
4.3 Setter 注入
4.4 @Autowired存在问题
一、IOC介绍
1、什么是IOC
什么是控制反转呢? 也就是控制权反转。 什么的控制权发生了反转? 获得依赖对象的过程被反转了。也就是说, 当需要某个对象时, 传统开发模式中需要自己通过 new 创建对象, 现在不需要再进行创 建, 把创建对象的任务交给容器, 程序中只需要依赖注入 (Dependency Injection,DI)就可以了.这个容器称为:IoC容器. Spring是一个IoC容器, 所以有时Spring 也称为Spring 容器。
2、通过案例来了解IoC
案例:造一辆车。
2.1 传统程序开发
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);
}
}
}
2.2 问题分析
/**
* 轮胎类
*/
static class Tire {
// 尺⼨
private int size;
public Tire(int size){
this.size = size;
System.out.println("轮胎尺⼨:" + size);
}
}
修改之后, 底盘类就会报错:
那么我们将底盘类的构造方法也传一个参数后,底盘类方法不再报错。但其他调用程序又会出现错误,所以我们都要进行修改。
修改后完整代码:
public class NewCarExample {
public static void main(String[] args) {
Car car = new Car(20);
car.run();
}
/**
* 汽⻋对象
*/
static class Car {
private Framework framework;
public Car(int size) {
framework = new Framework(size);
System.out.println("Car init....");
}
public void run(){
System.out.println("Car run...");
}
}
/**
* ⻋⾝类
*/
static class Framework {
private Bottom bottom;
public Framework(int size) {
bottom = new Bottom(size);
System.out.println("Framework init...");
}
}
/**
* 底盘类
*/
static class Bottom {
private Tire tire;
public Bottom(int size) {
this.tire = new Tire(size);
System.out.println("Bottom init...");
}
}
/**
* 轮胎类
*/
static class Tire {
// 尺⼨
private int size;
public Tire(int size){
this.size = size;
System.out.println("轮胎尺⼨:" + size);
}
}
}
2.3 解决方案
我们可以这样理解:当我们造一辆汽车时,如果我们不自己制作轮胎,而是把轮胎外包出去,那 么即使当用户的需求发生改变,需要改变车轮尺寸大小时,我们只需要向代理工厂下订单就行了,我们自身是不需要出力的。
2.4 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);
}
}
}
2.5 IoC 优势
- 资源集中管理: IoC容器会帮我们管理一些资源(对象等), 我们需要使用时, 只需要从IoC容器中去取就可以了
- 我们在创建实例的时候不需要了解其中的细节, 降低了使用资源双方的依赖程度, 也就是耦合度.
Spring 就是一种IoC容器, 帮助我们来做了这些资源管理。
二、DI介绍
三、IOC 详解
既然 Spring 是一个 IoC(控制反转)容器,作为容器, 那么它就具备两个最基础的功能:
- 存
- 取
Spring 容器 管理的主要是对象, 这些对象, 我们称之为"Bean". 我们把这些对象交由Spring管理, 由 Spring来负责对象的创建和销毁。我们程序只需要告诉Spring, 哪些需要存, 以及如何从Spring中取出 对象。
3.1 Bean的存储
- 类注解:@Controller、@Service、@Repository、@Component、@Configuration.
- 方法注解:@Bean.
3.1.1 @Controller(控制器存储)
使用 @Controller 存储 bean 的代码如下:
@Controller // 将对象存储到 Spring 中
public class UserController {
public void sayHi(){
System.out.println("hi,UserController...");
}
}
然后我们来验证一下Spring容器中是否已经有了该对象:
从Spring 容器中获取对象(bean):
@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();
}
}
获取bean对象的其他方式:上述代码是根据类型来查找对象, 如果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; //以下省略... }
其中第一、二、四种方式,是常见的。其中有通过 bean 的名称来获取 bean。
Spring bean是Spring框架在运行时管理的对象, Spring会给管理的对象起一个名字. 给每个对象起一个名字, 根据Bean的名称(BeanId)就可以获取到对应的对象.
Bean 命名约定
比如:
类名: UserController, Bean的名称为: userController类名: AccountManager, Bean的名称为: accountManager类名: AccountService, Bean的名称为: accountService
也有一些特殊情况, 当有多个字符并且第一个和第二个字符都是大写时, 将保留原始的大小写。
比如:类名: UController, Bean的名称为: UController类名: AManager, Bean的名称为: AManager
在知道 bean 的名称约定之后,我们来看看以多种方式获取 bean 对象是如何实现的。
示例代码:
@SpringBootApplication
public class SpringIocDemoApplication {
public static void main(String[] args) {
//获取Spring上下⽂对象
ApplicationContext context = SpringApplication.run(SpringIocDemoApplication.class, args);
//从Spring上下⽂中获取对象
//根据bean类型, 从Spring上下⽂中获取对象
UserController userController1 = context.getBean(UserController.class);
//根据bean名称, 从Spring上下⽂中获取对象
UserController userController2 = (UserController)context.getBean("userController");
//根据bean类型+名称, 从Spring上下⽂中获取对象
UserController userController3 =
context.getBean("userController",UserController.class);
System.out.println(userController1);
System.out.println(userController2);
System.out.println(userController3);
}
}
运行结果:
我们可以看到,地址一样,说明对象是同一个。
3.1.2 @Service(服务存储)
使用 @Service 存储 bean 的代码如下所示:
@Service
public class UserService {
public void sayHi(String name) {
System.out.println("Hi," + name);
}
}
获取 bean 的代码同 @Controller 部分的代码,我们可以获取 bean ,让 bean 对象执行 sayHi 方法,看打印结果,来判断是否获取到了bean。
此处,若把 @Service 注解去掉,会出现 与去掉 @Controller 注解 一样的错误。
3.1.3 @Repository(仓库存储)
使用@Repository 存储 bean 的代码如下所示:
@Repository
public class UserRepository {
public void sayHi() {
System.out.println("Hi, UserRepository~");
}
}
3.1.4 @Component(组件存储)
使用@Component 存储 bean 的代码如下所示:
@Component
public class UserComponent {
public void sayHi() {
System.out.println("Hi, UserComponent~");
}
}
3.1.5 @Configuration(配置存储)
@Configuration
public class UserConfiguration {
public void sayHi() {
System.out.println("Hi,UserConfiguration~");
}
}
3.2 为什么要这么多类注解?
- @Controller:控制层, 接收请求, 对请求进行处理, 并进行响应.
- @Servie:业务逻辑层, 处理具体的业务逻辑.
- @Repository:数据访问层,也称为持久层. 负责数据访问操作
- @Configuration:配置层. 处理项目中的一些配置信息.
3.3 方法注解 @Bean
- 使用外部包里的类, 没办法添加类注解
- 一个类, 需要多个对象, 比如多个数据源
3.3.1 方法注解要配合类注解使用
@Component
public class BeanConfig {
@Bean
public User user(){
User user = new User();
user.setName("zhangsan");
user.setAge(18);
return user;
}
}
来获取 bean 对象:
@SpringBootApplication
public class SpringIocDemoApplication {
public static void main(String[] args) {
//获取Spring上下⽂对象
ApplicationContext context =
SpringApplication.run(SpringIocDemoApplication.class, args);
//从Spring上下⽂中获取对象
User user = context.getBean(User.class);
//使⽤对象
System.out.println(user);
}
}
执行代码,可以看到正确结果:
3.3.2 定义多个对象
示例代码:
@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;
}
}
在上述代码,我们一个类型(User),定义了多个对象。
@SpringBootApplication
public class SpringIocDemoApplication {
public static void main(String[] args) {
//获取Spring上下⽂对象
ApplicationContext context =
SpringApplication.run(SpringIocDemoApplication.class, args);
//从Spring上下⽂中获取对象
User user = context.getBean(User.class);
//使⽤对象
System.out.println(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);
}
}
运行结果:
3.3.3 重命名 Bean
我们可以通过设置 name 属性给 Bean 对象进行重命名操作,如下代码所示:
@Bean(name = {"u1","user1"})
public User user1(){
User user = new User();
user.setName("zhangsan");
user.setAge(18);
return user;
}
代码中,name={} 可以省略,直接写为:@Bean({"u1","user1"})
此时我们使用名字 u1 就可以获取到 User 对象了。
注意:
使用五大注解声明的bean,不一定生效,要想生效,就必须让Spring扫描到这些注解。
启动类默认扫描的范围是 SpringBoot启动类所在包及其子包。若想让 Spring 扫描到默认范围以外的地方,就需要通过 @ComponentScan 来配置扫描路径。
四、DI 详解
- 属性注入
- 构造方法注入
- Setter 注入
4.1 属性注入
属性注入是使用 @Autowired 实现的。
我们以 Service 类注入到 Controller 类中 为例:
@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();
}
}
如果不加 @Autowired 注解,也就是userService对象没有注入进来,当执行到 userService.sayHi() 时,会报空指针异常。
4.2 构造方法注入
@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();
}
}
4.3 Setter 注入
@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();
}
}
4.4 @Autowired存在问题
当同一类型存在多个bean时, 使用@Autowired会存在问题:
示例代码:
@Component
public class BeanConfig {
@Bean("u1") //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 {
//注⼊user
@Autowired
private User user;
public void sayHi(){
System.out.println("hi,UserController...");
System.out.println(user);
}
}
运行代码发现会报错,报错的原因是,非唯一的 Bean 对象。
- @Primary
- @Qualifier
- @Resource
使用@Primary注解:当存在多个相同类型的Bean注入时,加上@Primary注解,来确定默认的实现.
@Component
public class BeanConfig {
@Primary //指定该bean为默认bean的实现
@Bean("u1") //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 {
@Qualifier("user2") //指定bean名称
@Autowired
private User user;
public void sayHi(){
System.out.println("hi,UserController...");
System.out.println(user);
}
}
@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。
关于 Spring IOC(控制权反转)、DI(依赖注入)就先介绍到这里了,希望可以给你带来帮助呀!