【Spring】IocDI详解(6)
本系列共涉及4个框架:Sping,SpringBoot,Spring MVC,Mybatis。
博客涉及框架的重要知识点,根据序号学习即可。
有什么不懂的都可以问我,看到消息会回复的,可能会不及时,请见谅!!
目录
本系列共涉及4个框架:Sping,SpringBoot,Spring MVC,Mybatis。
博客涉及框架的重要知识点,根据序号学习即可。
1、前言
1.1 Spring具体概念
1.2 什么是容器呢?
1.3 什么是IoC
2、IoC详解
2.1 什么是IoC
2.2 IoC有什么作用
2.3 IoC有什么优势
2.4 IoC的使用
2.5 Bean的存储【非常重要!!!一定要好好理解】
2.5.1 @Controller(控制器存储)
2.5.2 @Service(服务存储)
2.5.3 @Repository(仓库存储)
2.5.4 @Component (组件存储)
2.5.5 @Configuration(配置存储)
2.5.6 @Bean 【方法注解---唯一的】
2.6 注解之间的关系
3、DI详解
3.1 什么是DI
3.2 DI与IoC的联系
3.3 DI的使用
3.4 依赖注入的三种方式
3.4.1 属性注入
3.4.2 构造方法注入
3.4.3 Setter注入(设值注入)
3.5 三种注入的优缺点
3.6 @Autowired存在的问题
1、前言
1.1 Spring具体概念
之前的文章谈及到什么是Spring,但是只给了一个抽象的定义。目前我们知道Spring是一个开源的框架,它让我们的开发更加简单,也支持广泛的应用场景,有着活跃而庞大的社区,这也是Spring长久不衰的原因。由于对Spring有着模糊的了解,现在给出一个更加具体的概念:Spring是一个包含众多工具的Ioc容器【关键词:容器??Ioc??】
1.2 什么是容器呢?
容器是用来容纳某种物品的装置。其实在开发圈子,容器这个词已经用得泛滥了。
之前我们遇到过什么容器呢?
List/Map——>数据存储容器
Tomcat——>web容器
1.3 什么是IoC
Ioc是Spring的核心思想【Spring两大核心思想:Ioc与Aop(后续会写关于Aop的文章)】。
其实IoC在之前将Spring MVC入门的文章已经用过了,在类上面添加@RestController和@Controller注解,就是把这个对象交给Spring管理,Spring框架启动时就会加载该类,把对象交给Spring管理,就是IoC思想。
2、IoC详解
2.1 什么是IoC
(1)IoC:Inversion of Control(控制反转),也就是说Spring是一个"控制反转"的容器。
(2)什么是控制反转呢?也就是控制权反转。什么的控制权发生反转?获得依赖对象的过程被反转了,也就是说,当需要某个对象时,传统开发模式中需要自己new创建对象,现在不需要再进行创建,把创建对象的任务交给容器,程序中只需要依赖注入(Dependency Injection,DI)就可以了。这个容器称为:IoC容器。Spring是一个IoC容器,所以有时Spring也称Spring 容器。
2.2 IoC有什么作用
使用IoC思想是将控制权进行反转,简而言之就是不再是使用方对象创建并控制依赖对象了,而是把依赖对象注入当前对象中,依赖对象的控制权不再由当前类控制了。这样的话,即使依赖类发生任何改变,当前类是不受影响的,这就是典型的控制反转,也是IoC的实现思想。
2.3 IoC有什么优势
资源不由使用资源的双方管理,而是由不使用资源的第三方管理,可以带来一下好处:
(1)资源集中管理:IoC容器会帮助我们管理一些资源(对象等),需要使用时直接从IoC容器中去取
(2)同时在创建实例的时候不需要了解其中的细节,降低了使用资源双方的依赖程度,以达软件设计原则中的“低耦合”
Spring就是一种IoC容器,帮助我们管理资源
2.4 IoC的使用
(1)既然Spring是一个IoC(控制反转)容器,那么作为容器,它的基本功能就是存和取
(2)Spring容器管理的主要是对象,我们称这些对象为“Bean”。我们把这些“Bean”交给Spring管理,由Spring来负责对象的创建和销毁,我们程序只需要告诉Spring,哪些需要存,以及哪些需要从Spring中取出来。Spring创建及管理对象就是Bean的存储【非常重要!!!】
2.5 Bean的存储【非常重要!!!一定要好好理解】
要把某个对象交给IoC容器管理,需要在类之前加上注解@Component,而Spring框架为了更好的服务web应用程序,提供了更丰富的注解。
共有两类注解类型可以使用:
1、类注解:@Controller、@Service、@Repository、@Component、@Configuration
2、方法注解:@Bean
2.5.1 @Controller(控制器存储)
(1)使用@Controller存储Bean的代码如下:
@Controller
public class UserController {public void sayHi(){System.out.println("hi,UserController");}
}
如何观察这个对象已经存在Spring容器当中了呢?
接下来学习如何从Spring容器中获取对象
@SpringBootApplication
public class DemoApplication {public static void main(String[] args) {//获取Spring上下文ApplicationContext context=SpringApplication.run(DemoApplication.class,args);//从Spring上下文中获取对象UserController userController=context.getBean(UserController.class);//使用对象userController.sayHi();}
}
ApplicationContext翻译过来就是Spring上下文。因为对象都交给Spring管理了,所以获取对象要从Spring中取,那么就需要先得到Spring上下文。当前这个上下文是指当前的运行环境,也可以看做是一个容器,容器里面存了很多内容,这个内容是当前的运行环境。
(2)Bean命名约定
程序人员不需要为bean指定名称,就是说如果没有显式的提供名称,Spring容器会为bean生成唯一的名称。
命名约定使用Java标准约定作为实例字段名,也就是说,bean名称使用小驼峰的形式,比如类名为UserController的Bean的名称为userController
当然也会有一些特殊情况就是当多个字符并且第一个和第二个字母都是大写时,将保留原始大小写。比如类名为UController的Bean的名称为UController
(3)获取Bean的其他方式
如果Spring容器中,同一个类型存在多个Bean的话,怎么获取呢?ApplicationContext也提供了其他获取Bean的方式,ApplicationContext获取Bean对象的功能,是父类BeanFactory提供的功能
@SpringBootApplication
public class SpringIocApplication {public static void main(String[] args) {//获取Spring上下文ApplicationContext context=SpringApplication.run(DemoApplication.class,args);//根据bean的类型,从上下文中获取对象UserController userController1=context.getBean(UserController.class);//根据bean的名字,从上下文中获取对象UserController userController2=(UserController) context.getBean("userController");//根据bean的类型+名字,从上下文中获取对象UserController userController3=context.getBean("userController", UserController.class);System.out.println(userController1);System.out.println(userController2);System.out.println(userController3);}
}
地址都一样,说明是同一个对象
2.5.2 @Service(服务存储)
使用@Service存储Bean的代码如下:
@Service
public class UserService {public void sayHi(){System.out.println("hi,UserService");}
}
获取bean的代码
@SpringBootApplication
public class SpringIocApplication {public static void main(String[] args) {//获取Spring上下文ApplicationContext context=SpringApplication.run(DemoApplication.class);//从上下文中获取对象UserService userServicer=context.getBean(UserService.class);//使用对象userService.sayHi();}
}
2.5.3 @Repository(仓库存储)
使用@Repository存储Bean的代码如下:
@Repository //将对象存储到Spring中
public class UserRepository {public void say(){System.out.println("hi,UserRepository");}
}
获取bean的代码
@SpringBootApplication
public class DemoApplication {public static void main(String[] args) {//获取Spring上下文ApplicationContext context=SpringApplication.run(DemoApplication.class,args);//从Spring上下文中获取对象UserRepository userRepository=context.getBean(UserRepository.class);//使用对象userRepository.sayHi();}
}
2.5.4 @Component (组件存储)
使用@Component存储Bean的代码如下:
@Component
public class UserComponent {public void sayHi(){System.out.println("hi,UserComponent");}
}
获取bean的代码
@SpringBootApplication
public class DemoApplication {public static void main(String[] args) {//获取Spring上下文ApplicationContext context=SpringApplication.run(DemoApplication.class,args);//从Spring上下文中获取对象UserComponent userComponent=context.getBean(UserComponent.class);//使用对象userComponent.sayHi();}
}
2.5.5 @Configuration(配置存储)
使用@Configuration存储Bean的代码如下:
@Configuration
public class UserConfiguration {public void sayHi(){System.out.println("hi, UserConfiguration");}
}
获取bean的代码
@SpringBootApplication
public class DemoApplication {public static void main(String[] args) {//获取Spring上下文ApplicationContext context=SpringApplication.run(DemoApplication.class,args);//从Spring上下文中获取对象UserConfiguration userConfiguration=context.getBean( UserConfiguration .class);//使用对象userConfiguration.sayHi();}
}
2.5.6 @Bean 【方法注解---唯一的】
在某些外部包的类无法使用类注解以及某些类需要多个对象,这个时候需要使用方法注解@Bean
(1)方法注解需要配合类注解使用
import lombok.Data;@Data
public class User {private String name;private int age;
}
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;@Component
public class BeanConfig {@Beanpublic User user(){User user=new User();user.setName("zhangsan");user.setAge(18);return user;}
}
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;@SpringBootApplication
public class DemoApplication {public static void main(String[] args) {//获取Spring上下文ApplicationContext context=SpringApplication.run(DemoApplication.class,args);//从Spring上下文中获取对象User user=context.getBean(User.class);//使用对象System.out.println(user);}
}
(2)定义多个对象
①根据类型获取user时,报错显示:期望只有一个匹配,却发现了两个,user1和user2
@Component
public class BeanConfig {@Beanpublic User user1(){User user=new User();user.setName("zhangsan");user.setAge(18);return user;}@Beanpublic User user2(){User user=new User();user.setName("lisi");user.setAge(17);return user;}
}
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;@SpringBootApplication
public class DemoApplication {public static void main(String[] args) {//获取Spring上下文ApplicationContext context=SpringApplication.run(DemoApplication.class,args);//从Spring上下文中获取对象User user=context.getBean(User.class);//使用对象System.out.println(user);}
}
②根据名称来获取bean对象
@SpringBootApplication
public class DemoApplication {public static void main(String[] args) {//获取Spring上下文ApplicationContext context=SpringApplication.run(DemoApplication.class,args);//从Spring上下文中获取对象User user1= (User) context.getBean("user1");User user2= (User) context.getBean("user2");//使用对象System.out.println(user1);System.out.println(user2);}
}
(3)重命名Bean
可以通过设置name属性给Bean对象进行重命名。此时我们使用u1就可以获取到User对象了
@Component
public class BeanConfig {@Bean(name = {"u1","user1"})public User user1(){User user=new User();user.setName("zhangsan");user.setAge(18);return user;}
}
@SpringBootApplication
public class DemoApplication {public static void main(String[] args) {//获取Spring上下文ApplicationContext context=SpringApplication.run(DemoApplication.class,args);//从Spring上下文中获取对象User u1= (User) context.getBean("u1");//使用对象System.out.println(u1);}
}
2.6 注解之间的关系
(1)为什么有这么多类注解
这个也是和应用分层相呼应的,让开发人员看到类注解之后,就能直接了解当前类的用途。
@Controller:控制层,接受请求,对请求进行处理,并进行响应
@Service:业务逻辑层,处理具体的业务逻辑
@Repository:数据访问层,也称辞旧层,负责数据访问操作
@Configuration:配置层。处理项目中的一些配置信息。
(2)类注解之间的关系
查看@Controller/@Service/@Repository/@Configutation 等注解的源码发现,这些注解里面都有一个注解@Component,说明它们本来就是属于@Component的子类。@Component是一个元注解,就是也可注解其他类的注解,@Controller/@Service/@Repository/@Configutation等,这些注解都被称为@Component的衍生注解。
3、DI详解
3.1 什么是DI
DI:Dependency Injection(依赖注入)。容器在运行期间,动态的为应用程序提供运行时所依赖的资源,称之为依赖注入。
3.2 DI与IoC的联系
(1)程序运行时需要某个资源,此时容器就为其提供这个资源。从这点可以看出,依赖注入(DI)与控制反转(IoC)是从不同的角度的描述的同一件事,就是指通过引入IoC容器,利用依赖关系注入的方式,实现对象间的解耦。
(2)IoC是一种思想,也是一种“目标”,而思想只是一种指导原则,最终还是需要有可行的落地方案,而DI就是属于具体的实现,所以,DI是IoC的一种实现
3.3 DI的使用
依赖注入是一个过程,是指IoC容器在创建Bean时,去提供运行时所依赖的资源,而资源指的就是对象。
3.4 依赖注入的三种方式
3.4.1 属性注入
属性注入式使用@Autowired实现的,将Service类注入到Controller类中
Service类的实现代码:
import org.springframework.stereotype.Service;@Service
public class UserService {public void sayHi(){System.out.println("hi,UserService");}
}
Controller类的实现代码:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;@Controller
public class UserController {//1、属性注入@Autowiredprivate UserService userService;public void sayHi(){System.out.println("hi,UserController");userService.sayHi();}
}
获取Controller中的sayHi方法
@SpringBootApplication
public class DemoApplication {public static void main(String[] args) {//获取Spring上下文ApplicationContext context=SpringApplication.run(DemoApplication.class,args);//从Spring上下文中获取对象UserController userController=context.getBean(UserController.class);//使用对象userController.sayHi();}
}
3.4.2 构造方法注入
构造方法是在类的构造方法中实现注入的
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;@Controller
public class UserController2 {//2、构造方法注入private UserService userService;@Autowiredpublic UserController2(UserService userService){this.userService=userService;}public void sayHi(){System.out.println("hi,UserController2");userService.sayHi();}
}
@SpringBootApplication public class DemoApplication {public static void main(String[] args) {//获取Spring上下文ApplicationContext context=SpringApplication.run(DemoApplication.class,args);//从Spring上下文中获取对象UserController2 userController2=context.getBean(UserController2.class);//使用对象userController2.sayHi();} }
PS:如果类只有一个构造方法,那么@Autowired注解可以省略;如果类中又多个构造方法时,那么需要添加上@Autowired来明确指定到底使用哪个构造方法
3.4.3 Setter注入(设值注入)
Setter注入和属性的setter方法实现类似,只不过在设置set方法的时候加上@Autowired注解
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;@Controller
public class UserController3 {//2、Setter注入private UserService userService;@Autowiredpublic void setUserController3(UserService userService){this.userService=userService;}public void sayHi(){System.out.println("hi,UserController3");userService.sayHi();}
}
@SpringBootApplication
public class DemoApplication {public static void main(String[] args) {//获取Spring上下文ApplicationContext context=SpringApplication.run(DemoApplication.class,args);//从Spring上下文中获取对象UserController3 userController3=context.getBean(UserController3.class);//使用对象userController3.sayHi();}
}
3.5 三种注入的优缺点
(1)属性注入
优点:简洁,使用方便
缺点:只能用于IoC容器;不能注入一个final修饰的属性
(2)构造方法
优点:可以注入final修饰的属性;注入的对象不会被修改;依赖对象在使用前一定会被完全初始化,因为依赖是在类的构造方法中执行的,而构造方法是在类的加载阶段就会执行的方法;通用性好,构造方法是JDK支持的,所以更换任何框架,也是适用的。
缺点:注入多个对象时,代码会比较繁琐
(3)setter注入
优点:方便在类的实例之后,重新对该对象进行配置或者注入
缺点:不能注入一个final修饰的属性;注入对象可能会被改变,因为setter方法可能会被多洗调用,就会用被修改的风险
3.6 @Autowired存在的问题
(1)同一类型存在多个bean时,使用@Autowired会报错。报错的原因是,非唯一的Bean对象。上面已经出现过了
(2)解决方法:使用以下注解
@Primary、@Qualifier、@Resource
①使用@Primary注解:当存在多个相同类型的Bean注入时,加上@Primary注解,用来确定默认的实现
@Component
public class BeanConfig {@Primary //指定该bean为默认的bean@Beanpublic User user1(){User user=new User();user.setName("zhangsan");user.setAge(18);return user;}@Beanpublic User user2(){User user=new User();user.setName("lisi");user.setAge(17);return user;}
}
②使用@Qualifier注解,指定当前要注入的bean对象。在@Qualifier的value属性中,指定注入的bean的名称【@Qualifier注解不可以单独使用,必须配合@Autowired使用】
@Controller
public class UserController {@Qualifier("user1") //指定bean的名称@Autowiredprivate User user;public void sayHi(){System.out.println("hi,UserController");System.out.println(user);}
}
③使用@Resource注解,是按照bean的名称进行注入。通过name属性指定要注入的bean的名称
@Controller
public class UserController {@Resource(name = "user1")private User user;public void sayHi(){System.out.println("hi,UserController");System.out.println(user);}
}