目录
1.IoC & DI
在Spring中我们学习了SpringBoot 和 SpringMVC的开发,能够完成一些基本功能模块的开发,本章就让我们来深入Spring,认识Spring框架,了解并学习Spring在我们的开发中可以为我们提供哪些服务.
1.1 Spring介绍
在Spring的学习中,我们知道了Spring是一个Spring的开源框架,他让我们的开发更加简单. 他支持广泛的应用场景, 有着活跃而庞大的社区, 这也是Spring能够长久不衰的原因,Spring框架中包含了很多"IoC"容器,这些IoC容器也是Spring框架的特色之一,哪么什么是IoC,Spring又使用它们做了什么呢?
1.1.1 啥是IoC
IoC 是Spring的核心思想,是为了管理Spring项目中的类,简单来说Spring就相当于一个水桶(容器),而被IoC的类就相当于水桶(容器)里面的水(管理),类(水)是管理在Spring(水桶)中的,我们可以通过Spring的一些注解比如:"RestController"和"Controller"这类的注解将某些需要管理的类的控制权交到Spring进行管控,Spring框架在启动的时候就会去加载这些类并由Spring进行管理,这就是IoC思想,IoC是Inversion of Control的缩写 其本意是控制反转,也就是说Spring是一个"控制反转"的容器.
1.2 IoC介绍
上面我们通过文字的描述展示了Spring和IoC之间的关系,可能会有点模糊,接下来我们通过代码例子来"深入"的介绍IoC(控制反转)中的作用
1.2.1 案例1展示
我们那造车来举例,先是使用传统的方式进行一辆汽车的制造,实现思路是这样的:我们先设计汽车轮胎((Tire)),再根据轮胎的大小去设计汽车的底盘(Bottom),再根据汽车的底盘去设计车身(Framework),最后再去设计好汽车(car),我们可以画出它们之间的关系
如果是按照这样的生成模式去设计代码的话,是这样的:
public class NewCarExample {
public static void main(String[] args) {
Car car = new Car();
car.run();
}
//汽车类
static class Car {
//汽车的设计依赖与车身(Framework)
private Framework framework;
public Car() {
this.framework = new Framework();
System.out.println("Car into...");
}
public void run () {
System.out.println("Car run...");
}
}
//车身类
static class Framework {
//车身依赖与底盘
private Bottom bottom;
public Framework() {
this.bottom = new Bottom();
System.out.println("Framework info.....");
}
}
//底盘类
static class Bottom {
//底盘依赖与轮胎
private Tire tire;
public Bottom () {
this.tire = new Tire();
System.out.println("Bottom info.....");
}
}
//轮胎类
static class Tire {
//轮胎的大小
private int size;
public Tire () {
this.size = 20;
System.out.println("轮胎已经制造好了,轮胎的尺寸为:" + size);
}
}
}
1.2.2 案例1问题分析
从上面的代码我们可以很直观的发现一个问题,就是我们汽车的轮胎尺寸是设置死的,按照上面的代码是无法更改轮胎尺寸的,从而也就无法满足汽车的个性化需求.那如果我们想在上述的代码中更换轮胎的尺寸大小我们需要做出哪些改变的呢,
首当其冲的肯定是在Tire(轮胎)类中要将汽车的尺寸大小设为可变的,那么就要设置有参的构造函数去设置Tire中的size值
//轮胎类
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) {
Scanner scanner = new Scanner(System.in);
int size = scanner.nextInt();
Car car = new Car(size);
car.run();
}
//汽车类
static class Car {
//汽车的设计依赖与车身(Framework)
private Framework framework;
public Car(int size) {
this.framework = new Framework(size);
System.out.println("Car into...");
}
public void run () {
System.out.println("Car run...");
}
}
//车身类
static class Framework {
//车身依赖与底盘
private Bottom bottom;
public Framework(int size) {
this.bottom = new Bottom(size);
System.out.println("Framework info.....");
}
}
//底盘类
static class Bottom {
//底盘依赖与轮胎
private Tire tire;
public Bottom (int size) {
this.tire = new Tire(size);
System.out.println("Bottom info.....");
}
}
//轮胎类
static class Tire {
//轮胎的大小
private int size;
public Tire (int size) {
this.size = size;
System.out.println("轮胎已经制造好了,轮胎的尺寸为:" + size);
}
}
}
此时一个最大的问题出来了,为啥我汽车设计,车身设计,底盘设计为啥来了一个尺寸的参数,底盘的设计还好理解,那么如果底层代码(依赖的关系的右端)有需求需要进行调整的时候,那么是不是我在该类依赖关系的左端的类都需要进行修改,此时的代码耦合性就太高了,不是一个好的代码.
1.2.3 优化方案
我们从需求中分析可以知道,我们一系列设计只是要他的下一层的类实例化对象罢了,那么我们为啥不可以将它们的关系倒置过来,先设计好汽车大概是啥样的,然后去根据前面设计好的汽车的图纸去设计车身,再根据车身去设计对应的底盘,最后再根据底盘去设计好轮胎,此时的依赖关系就倒转过来了.
那么这种思想如何来实现呢:
我们可以尝试不在每个类中自己创建下级类,如果自己创建下级类就会出现当下级类发生改变操作,自己也要跟着修改.此时,我们只需要将原来由自己创建的下级类,改为传递的方式(也就是注入的方式),因为我们不需要在当前类中创建下级类了,所以下级类即使发生变化(创建或减少参数),当前类本身也无需修改任何代码,这样就完成了程序的解耦.
代码如下:
public class NewCarExample {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int size = scanner.nextInt();
Tire tire = new Tire(size);
Bottom bottom = new Bottom(tire);
Framework framework = new Framework(bottom);
Car car = new Car(framework);
}
//汽车类
static class Car {
//汽车的设计依赖与车身(Framework)
private Framework framework;
public Car(Framework framework) {
this.framework = framework;
System.out.println("Car into...");
}
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 info.....");
}
}
//底盘类
static class Bottom {
//底盘依赖与轮胎
private Tire tire;
public Bottom (Tire tire) {
this.tire = tire;
System.out.println("Bottom info.....");
}
}
//轮胎类
static class Tire {
//轮胎的大小
private int size;
public Tire (int size) {
this.size = size;
System.out.println("轮胎已经制造好了,轮胎的尺寸为:" + size);
}
}
}
优化过后的思路相比于第一种就会发现,第二种思路每个类和类之间只需要你这个类下一层依赖的类的实例化,不再像第一种思路的代码出现汽车设计,车身设计,底盘设计的类中需要去传参传轮胎尺寸大小的情况,这就达到解耦合的目的了(软件设计原则:高内聚(一个模块内部的关系),低耦合(各个模块之间的关系)).
1.2.4 控制反转在Spring中的体现
在传统的造车思路的代码实践中,每个类之间的依赖关系是这样的:
在优化后的解耦合的造车代码中,每个类之间的依赖关系是这样的:
在代码的实践中我们发现传统的代码中,Car 控制并创建了Framework,Framework 创建并创建了 Bottom,依次往下.但是依赖关系反转(控制反转)后,不再是使用方对象创建并控制依赖对象了,而是把依赖对象注入将当前对象中,依赖对象的控制权不再由当前类控制了,依赖的对象不在去涉及到被依赖对象的创建,而是直接得到之后使用,这就是IoC的思想的核心地方 ----- 控制反转.
那么在Spring中控制反转又是怎么体现出来的呢,在Spring中我们(程序猿)去创建的类如果没有使用一些特定的注解去进行"管理"的话,此时我们在其他类中去操作这个类(实例化 -> 使用)这就相当于上面造车方法中传统的思路,如果在这个类打上特定的注解,那么这个类就会自动的交给Spring这个大容器进行管理,这就是Spring中的控制反转的方式,因此Spring就是一个IoC容器
1.2.5 IoC的优点
资源不由使用资源的双方管理,而由不使用资源的第三方管理,这可以带来很多好处。第一,资源集中管理,实现资源的可配置和易管理。第二,降低了使用资源双方的依赖程度,也就是我们说的耦合度。
1). 资源集中管理: IoC容器会帮我们管理一些资源(对象等), 我们需要使用时, 只需要从IoC容器中去取
就可以了
2). 我们在创建实例的时候不需要了解其中的细节, 降低了使用资源双方的依赖程度, 也就是耦合度.
Spring 就是一种IoC容器, 帮助我们来做了这些资源管理.
1.3 DI
在上面我们介绍了IoC(控制反转)的思想,说到底这只是一种思想,如果要运用在Spring中肯定是通过某种方法来实现的,DI就是IoC思想在Spring实现的一种方法.
DI的全称为Dependency Injection(依赖注入),容器在运行期间, 动态的为应用程序提供运行时所依赖的资源,称之为依赖注入,依赖对象的创建的控制权交给Spring进行管理,DI就是把依赖对象取出来,并且赋值给该对象的属性。
在上述的造车案例中,使用优化后的方法进行造车,该过程就是依赖注入。
2.IoC&DI使用
DI主要的使用就是存入(Cinterller)和取出(Autowired)两个功能,下面我们通过例子来展示DI的使用方法.
注:在讲述案例之前我们先说明一下一个项目的完成是要有三个层次结构的,Controller 是直接对接前端发生到后端的数据,Conterller对数据进行一下处理和处理一些简单的业务就将处理的数据转交给Service层,Service是项目重要业务处理的地方由Controller 处理过的数据就在这得到对应的响应,Repository层就是对接数据库的地方,主要是负责从数据库中得到对应数据转交至Service层,最后Controller得到Service的数据格式化之后就会返回给前端进行展示.
此处我们模拟一个图书列表展示的功能(数据库部分就省略采用其他方式实现)
图书的属性有:ID,书名,作者,数量.价格,出版社,状态(在数据库中0表示不可借阅,1表示可借阅)
图书类的定义:
@Data
public class BookInfo {
public int Id;// 书籍ID
public String BookName;// 书名
public String Author;// 作者
public int Count;// 数量
public int Price;// 价格
public String Publish;// 出版社
public int Status;// 状态
public String StatusCN;
}
此处的@Data注解并不是将这个类交给Spring进行管理的意思,而是默认给这个类配置一些方法,例如Get,Set和toString方法,所以没有在类里面对get和set方法进行实现
BookDao类由@Component交给Spring进行管理
@Component //把BookDao这个类交给Spring进行管理
public class BookDao {
//从"数据库"中获取图书信息
public List<BookInfo> mockBook() {
List<BookInfo> array = new ArrayList<>();
for (int i = 0; i < 5; i++) {
BookInfo bookInfo = new BookInfo();
bookInfo.setId(i);
bookInfo.setBookName("图书"+i);
bookInfo.setAuthor("作者"+i);
bookInfo.setCount(i*i*3);
bookInfo.setPrice(i*i*i);
bookInfo.setPublish("出版社"+i);
bookInfo.setStatus(1);
array.add(bookInfo);
}
return array;
}
}
Service层Spring定义了专门的注解进行管理,就是@Service
@Service
public class BookService {
@Autowired
private BookDao bookDao;
public List<BookInfo> getBookList() {
List<BookInfo> array = bookDao.mockBook();
for (BookInfo bookInfo : array) {
if(bookInfo.getStatus() == 1) {
bookInfo.setStatusCN("可借阅");
} else {
bookInfo.setStatusCN("不可借阅");
}
}
return array;
}
}
Controller层也有对应的注解进行管理(@RestController)
@RestController
@RequestMapping("/book")
public class BookController {
@Autowired //从容器中取
private BookService bookService;
//图书展示
@RequestMapping("/getBooks")
public List<BookInfo> getBooks() {
List<BookInfo> array = bookService.getBookList();
for (BookInfo bookInfo :array) {
System.out.println(bookInfo.toString());
}
return array;
}
}
然后通过前端进行访问得到返回结果如下(此处没有写前端页面进行前后端交互,就是用PostMan进行模拟前端交互)
在看我编写的代码就会发现,为什么在我的BookService中要使用BookDao类里面的方法的时候和在BookController中要使用BookService类中的方法的时候,没有对定义的变量进行实例化就可以进行使用,按照我们之前的编码方法应该是这样的
@Service
public class BookService {
//@Autowired
private BookDao bookDao = new BookDao();
public List<BookInfo> getBookList() {
List<BookInfo> array = bookDao.mockBook();
for (BookInfo bookInfo : array) {
if(bookInfo.getStatus() == 1) {
bookInfo.setStatusCN("可借阅");
} else {
bookInfo.setStatusCN("不可借阅");
}
}
return array;
}
}
@RestController
@RequestMapping("/book")
public class BookController {
//@Autowired //从容器中取
private BookService bookService = new BookService();
//图书展示
@RequestMapping("/getBooks")
public List<BookInfo> getBooks() {
List<BookInfo> array = bookService.getBookList();
for (BookInfo bookInfo :array) {
System.out.println(bookInfo.toString());
}
return array;
}
}
就会思考怎么没有实例化使用idea还不爆警告到爆的. 这就是IoC做出的优化,将类的控制权交给Spring进行管理(通过特定的注解),在后续如果有其他类要进行使用的话就直接从"容器"里面取出,取出的方法为@Autowired.
3.IoC详解
前面我们提到IoC控制反转,就是将对象的控制权交给Spring的IOC容器,由IOC容器创建及管理对象,也就是Bean的存储。
3.1Bean的存储
在IoC&DI的使用中我们只是用了@Component,@Service,@RestController,这三个类注解,在Spring中一共给我们提供了五大类注解,一个方法注解
1). 类注解:@Controller、@Service、@Repository、@Component、@Configuration.
2). 方法注解:@Bean.
3.1.1注解 @Controller (控制器存储)
在所需要进行管理的类上打上@Controller注解即可
@Controller
public class UserController {
public void sayH () {
System.out.println("userController sayH.....");
}
}
哪么怎么知道,我这个类是被Spring进行管理的呢,去Spring容器中拿出来使用一下不就知道了,
Spring中获取对象的方法:
在创建一个Spring项目的时候是会默认给出一个启动类的
就如上图中的选中部分,在里面的方法是有返回值且可以被接收的
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(DemoApplication.class, args);
}
}
这里去实践的时候要注意了,Spring中是有两个ApplicationContext 类的
我们要选定Springframework包底下的才可以正常使用.
哪么怎么获取对应的被管理类呢,在获取Spring管理的类中我们都是使用到了ApplicationContest中的getBean方法的,因为getBean有多个重载,所以衍生出来多种获取方法.
注:我们先定义一个对应的类方便我们后续的使用,统一都是又有一个SayH方法输出类名和自身是通过那个注解进行管理的.
我们先使用一个简单点的获取方式
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(DemoApplication.class, args);
UserController userController = context.getBean(UserController.class);
userController.sayH();
}
}
这里的对象属性就是类名+.class 获取对象
获取Beaded其他方式:
// 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容器中的代号,Spring可以通过"约定"去得到类的对应bean名称,从而去取出类并进行操作.
那么这个约定是啥呢,这里直接去官方文件去进行查找
这里直接举例:
1.类名: UserController, Bean的名称:userController
2.类名: AccountService, Bean的名称: accountService
注:这里也有特殊情况:当首字母大写且第二个字母也是大写的时候,这个类的Bean名称与类名称一致
例如
类名: UController, Bean的名称: UController
按照上述命名规则去取出一下UserController这个类吧
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(DemoApplication.class, args);
//根据Bean的类型进行获取
UserController userController1 = context.getBean(UserController.class);
System.out.println("userController1:");
userController1.sayH();
System.out.println("userController1的地址: " + userController1);
//根据Bean的名称进行获取
UserController userController2 = (UserController) context.getBean("userController");
System.out.println("userController2:");
userController1.sayH();
System.out.println("userController2的地址: " + userController2);
//根据Bean的类型 + 名称
UserController userController3 = context.getBean("userController",UserController.class);
System.out.println("userController3:");
userController1.sayH();
System.out.println("userController3的地址: " + userController3);
}
}
要注意,如果只是使用Bean的名称取获取类对象的使用需要对类型进行强转,但getBean()参数有Bean类型的时候就不需要了.
那来看一下这段代码的运行结果吧
首先是可以进行正常的获取的,其次这3个对象的地址是同一个,就说明它们本质上还是一个
3.1.2 注解 @Service (服务存储)
注解@Service使用:
@Service
public class UserService {
public void SayH(String name) {
System.out.println("hallo," + name + "这里是元気いっぱいの猫的小屋");
}
}
去DemoApplication下试一下是否可以取出(UserService的Bean名称是userService)
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(DemoApplication.class, args);
UserService userService = context.getBean("userService",UserService.class);
userService.SayH("牛牛");
}
}
运行结果如下:
3.1.3 注解 @Repository(仓库存储)
注解@Repository的使用:
@Repository
public class UserRepository {
public void SayH(String name) {
System.out.println("Hi," + name + "这里是UserRepository");
}
}
去DemoApplication下试一下是否可以取出(UserRepository的Bean名称是userRepository).
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(DemoApplication.class, args);
UserRepository userRepository = context.getBean("userRepository",UserRepository.class);
userRepository.SayH("张某");
}
}
运行结果:
3.1.4 注解@Component(组件存储)
@Component注解使用
去DemoApplication下试一下是否可以取出
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(DemoApplication.class, args);
UComponent uComponent = context.getBean("UComponent",UComponent.class);
uComponent.SayH();
}
}
3.1.5 注解@Configuration(配置存储)
注解@Configuration的使用
@Configuration
public class UserConfiguration {
public void SayH() {
System.out.println("Hi,这里是Configuration的SayH方法");
}
}
去DemoApplication下试一下是否可以取出
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(DemoApplication.class, args);
UserConfiguration userConfiguration = context.getBean("userConfiguration",UserConfiguration.class);
userConfiguration.SayH();
}
}
运行结果:
3.1.6 五大类注解
看完上面的五个注解,看应该会晕,为啥用法和获取方式都一样还要定义这么多"功能一样"的注解呢,在IoC的优化方案板块的时候,我介绍到在开发过程中是可以将代码划分为3个部分的,Cotroller(前端数据处理,调用Service层的方法),Service(项目主要的业务实现区域,可以调用Repository层的方法),Repository层(操纵数据库)是直接对接数据库的,在注解中也是有这样的分层的.
• @Controller:控制层, 接收请求, 对请求进行处理, 并进行响应.
• @Servie:业务逻辑层, 处理具体的业务逻辑.
• @Repository:数据访问层,也称为持久层. 负责数据访问操作
• @Configuration:配置层. 处理项目中的一些配置信息
在项目开发中,每一层都有自己对应的Spring注解.程序的应用分层,调用流程如下
3.2 方法注解 @Bean
在上面我们简介了五大注解类,到这可能会产生一个疑惑:为啥你类有Spring的注解,我方法为啥不能也被Spring进行管理,"我也想进入体制".
Spring也是考虑到了方法可能需要被管理的情况,也是提供了方法 ---- 注解@Bean,那先让我们上手试试看,这里先创建一个User(用户)的model,被Bean注解修饰的方法就对User里面的数据进行修改.
用户类的创建:
@Data
public class UserInfo {
public String name;
public int Age;
}
操作层的创建
public class BeanConfig {
@Bean
public UserInfo UserInfo() {
UserInfo userInfo = new UserInfo();
userInfo.setName("元気いっぱいの猫");
userInfo.setAge(19);
return userInfo;
}
}
调用
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(DemoApplication.class, args);
UserInfo userInfo = context.getBean(UserInfo.class);
System.out.println(userInfo.getName());
System.out.println(userInfo.getAge());
}
}
让我们来看看运行结果
看是你们最喜欢的红色警告.
此处会报警告的原因是在Spring容器中为找到这个UserInfo类型的类,调用不到啊,那如果我们在操作层的类上加上一下五大注解会是啥样的.
@Controller
public class BeanConfig {
@Bean
public UserInfo UserInfo() {
UserInfo userInfo = new UserInfo();
userInfo.setName("元気いっぱいの猫");
userInfo.setAge(19);
return userInfo;
}
}
怎么就运行出来了,从这里我们就可以得到一个结论:方法注解@Bean得搭配着五大类注解一起使用,单干的话是跑不出来的(刚入行还是得有前辈带一下的)
3.2.1 @Bean管理多个方法
在一个类中肯定不只有一个方法需要Spring去管理的吧,那在多个方法需要被管理的情况下 @Bean是如何操作的呢,先来点代码
@Controller
public class BeanConfig {
@Bean
public UserInfo UserInfo1() {
UserInfo userInfo = new UserInfo();
userInfo.setName("元気いっぱいの猫");
userInfo.setAge(19);
return userInfo;
}
@Bean
public UserInfo UserInfo2() {
UserInfo userInfo = new UserInfo();
userInfo.setName("张某某");
userInfo.setAge(20);
return userInfo;
}
}
来无奖竞猜一下,等等运行出来的时候到底会取出那个UserInfo的内容
答案是一个都没取出,还给"对面"干蒙了,两个方法的返回值都是UserInfo 那到底是想取哪一个出来呢,所以此时就不能使用getBean(Bean类型)这个方法了,得换成其他重载的方法.
方法注解@Bean在Spring中获取对象也是可以通过Bean名称进行获取的,但是这块的Bean名称就有些不同的,这块的bean名称就是方法的名称,
例如: 方法名称UserInfo1 Bean名称就是:UserInfo1
那我们通过Bean名称分别去捕获这两个对象试一下:
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(DemoApplication.class, args);
UserInfo userInfo1 = (UserInfo) context.getBean("UserInfo1");
UserInfo userInfo2 = (UserInfo) context.getBean("UserInfo2");
System.out.println(userInfo1.toString());
System.out.println(userInfo2.toString());
}
}
3.2.2 Bean名称的重命名
在上面我们对Spring中的方法进行Bean名称取出的时候就是写入方法的名称就行的,那我们是否可以修改一下Bean的名称使它可以拥有其他代号呢? 这在SpringBoot是被进行实现的,在@Bean的属性中,有一个属性为name就是去对方法的Bean名称进行重命名的.
@Controller
public class BeanConfig {
@Bean (name = {"u1","UserInfo1"})
public UserInfo UserInfo1() {
UserInfo userInfo = new UserInfo();
userInfo.setName("元気いっぱいの猫");
userInfo.setAge(19);
return userInfo;
}
@Bean (name = {"u2","UserInfo2"})
public UserInfo UserInfo2() {
UserInfo userInfo = new UserInfo();
userInfo.setName("张某某");
userInfo.setAge(20);
return userInfo;
}
}
name属性是允许接收字符串集合的,也就是说可以有多个Bean名称指向一个@Bean修饰的方法那我们去使用一下看一下吧.
代码在这里
3.3 扫描路劲
在上述的实例中,应该会产生一个疑惑,如果我在项目中的任何地方使用了五大类注解或者@Bean方法注解之后,都是可以存储到Spring容器中的吗,存储了还是是可以取出的吗.
需要解答这个问题,我们就要去研究研究启动类了,
启动类那个类就是在我们项目启动的时候负责调用项目中的属性,其中启动类中都是默认有一个注 解的,那就是@SpringBootApplication 这个注解就是去负责去扫描和启动类同一级或者下一级的类,将需要Spring管理的类加载到Spring容器中. 注:@SpringBootApplication 注解是不能扫描上一级文件的.
那就是说只要跟@SpringBootApplication 注解的类在文件的同一级或者下一级都是可以被扫描到的,那我们先来实验一下吧.
@Controller
public class BeanConfig {
@Bean (name = {"u1","UserInfo1"})
public UserInfo UserInfo1() {
UserInfo userInfo = new UserInfo();
userInfo.setName("元気いっぱいの猫");
userInfo.setAge(19);
return userInfo;
}
@Bean (name = {"u2","UserInfo2"})
public UserInfo UserInfo2() {
UserInfo userInfo = new UserInfo();
userInfo.setName("张某某");
userInfo.setAge(20);
return userInfo;
}
}
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(DemoApplication.class, args);
UserInfo userInfo1 = (UserInfo) context.getBean("UserInfo1");
System.out.println(userInfo1.toString());
}
}
在当前文件中DemoApplication类是在BeanConfig类的上一级文件中,此时的运行结果为:
是可以被执行出来的,
那么我将DemoApplication放到与BeanConfig不同的文件夹中会是啥样
运行结果:
直接是No bean named 'UserInfo1' available,没有找到Bean名称为UserInfo1的类.
默认扫描的范围是SpringBoot启动类所在包及其子包在配置类上添加 @ComponentScan 注解, 该注解默认会扫描该类所在的包下所有的配置类,
所以在写项目的时候推荐做法:把启动类放在我们希望扫描的包的路径下, 这样我们定义的bean就都可以被扫描到.
4.DI详解
上面我们讲解了控制反转IoC的细节,接下来呢,我们学习依赖注入DI的细节。依赖注入是一个过程,是指IoC容器在创建Bean时, 去提供运行时所依赖的资源,而资源指的就是对象.在上面程序案例中,我们使用了@Autowired 这个注解,完成了依赖注入的操作.简单来说, 就是把对象取出来放到某个类的属性中.
关于依赖注入,Spring其实是给了3种注入方法的:
1)属性注入(Field Injection)
2)构造方法注入(Constructor Injection)
3)Setter注入(Setter Injection)
4.1 属性注入
属性注入其实就是我们在上面一直使用的注解@Autowired,只要是使用了五大类注解或者@Bean注解修饰的方法就可以使用@Autowired注解进行属性注入,那就来段演示
@Service
public class UserService {
public void SayHi() {
System.out.println("Hi,UserService");
}
}
@Controller
public class UserController {
@Autowired
private UserService userService;
public void sayH () {
userService.SayHi();
}
}
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(DemoApplication.class, args);
UserController userController = context.getBean(UserController.class);
userController.sayH();
}
}
运行结果:
是可以正常使用的.
4.2 构造方法注入
构造方法注入是在类的构造方法中实现注入,如下代码所示:
@Controller
public class UserController2 {
private UserService userService;
@Autowired
public UserController2(UserService userService) {
this.userService = userService;
}
public void SayHi() {
userService.SayHi();
System.out.println("Hi,UserInfo2.......");
}
}
使用方法就是在编写构造函数的时候在构造函数上面加上@Aotowired注解,此时这个构造函数时是不需要用户进行任何操作的,Spring会自动的帮我们调用并创建完成
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(DemoApplication.class, args);
UserController2 userController2 = context.getBean(UserController2.class,"userController2");
userController2.SayHi();
}
}
注:如果类只有一个构造方法,那么 @Autowired 注解可以省略;如果类中有多个构造方法,那么需要添加上 @Autowired 来明确指定到底使用哪个构造方法。
4.3Setter注入
Setter 注入和属性的 Setter 方法实现类似,只不过在设置 set 方法的时候需要加上 @Autowired 注解 ,如下代码所示:
@Controller
public class UserController3 {
private UserService userService;
@Autowired
public void setUserService(UserService userService) {
this.userService = userService;
}
public void SayHi() {
System.out.println("Hi,Controller3.....");
userService.SayHi();
}
}
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(DemoApplication.class, args);
UserController3 userController3 = context.getBean(UserController3.class,"userController3");
userController3.SayHi();
}
}
运行结果如下:
演示完毕
4.4 三种注解的优缺点
1)属性注入
优点:
简洁,使用方便;
缺点:
1.只能用于 IoC 容器,如果是非 IoC 容器不可用,并且只有在使用的时候才会出现 NPE(空指针异常)
2. 不能注入一个Final修饰的属性
2)构造函数注入(Spring 4.X推荐)
优点:
1.可以注入final修饰的属性
2.注入的对象不会被修改
3.依赖对象在使用前一定会被完全初始化,因为依赖是在类的构造方法中执行的,而构造方法
是在类加载阶段就会执行的方法.
4.通用性好, 构造方法是JDK支持的, 所以更换任何框架,他都是适用的
缺点:
1.注入多个对象时, 代码会比较繁琐
3) Setter注入(Spring 3.X推荐)
优点:
方便在类实例之后, 重新对该对象进行配置或者注入
缺点:
1.不能注入一个Final修饰的属性
2. 注入对象可能会被改变, 因为setter方法可能会被多次调用, 就有被修改的风险.
更多DI相关内容参考: Dependencies :: Spring Framework
4.5 @Autowired存在问题
当同一类型存在多个bean时, 使用@Autowired会存在问题
先上代码:
@Controller
public class BeanConfig {
@Bean ("u1")
public UserInfo UserInfo1() {
UserInfo userInfo = new UserInfo();
userInfo.setName("元気いっぱいの猫");
userInfo.setAge(19);
return userInfo;
}
@Bean
public UserInfo UserInfo2() {
UserInfo userInfo = new UserInfo();
userInfo.setName("张某某");
userInfo.setAge(20);
return userInfo;
}
}
@RestController
public class UserController {
@Autowired
public UserService userService;
@Autowired
public UserInfo userInfo;
public void SayHi() {
System.out.println("Hi,UserController...");
userService.SayHi();
System.out.println(userInfo);
}
}
@Service
public class UserService {
public void SayHi() {
System.out.println("Hi,UserService");
}
}
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(DemoApplication.class, args);
UserController userController = context.getBean(UserController.class);
userController.SayHi();
}
}
看运行结果:
报错的原因是,非唯一的 Bean 对象。
所以需要指定一下默认调用的是哪一个@Bean注解修饰的方法,Spring提供了3中方法,@Primary
,@Qualifier,@Resource,我们在这里只先演示其中的@Primary注解
使用@Primary注解:当存在多个相同类型的Bean注入时,加上@Primary注解,来确定默认的实现.
@Component
public class BeanConfig {
@Primary
@Bean ("u1")
public UserInfo UserInfo1() {
UserInfo userInfo = new UserInfo();
userInfo.setName("元気いっぱいの猫");
userInfo.setAge(19);
return userInfo;
}
@Bean
public UserInfo UserInfo2() {
UserInfo userInfo = new UserInfo();
userInfo.setName("张某某");
userInfo.setAge(20);
return userInfo;
}
}
此时的运行结果
将@Primary换给另外一个方法玩
@Component
public class BeanConfig {
//@Primary
@Bean ("u1")
public UserInfo UserInfo1() {
UserInfo userInfo = new UserInfo();
userInfo.setName("元気いっぱいの猫");
userInfo.setAge(19);
return userInfo;
}
@Primary
@Bean
public UserInfo UserInfo2() {
UserInfo userInfo = new UserInfo();
userInfo.setName("张某某");
userInfo.setAge(20);
return userInfo;
}
}
只要指定出来给谁是默认值,Spring就不会进行报错(非唯一的 Bean 对象)
Spring IoC&DI 的内容到这里就差不多结束了,大伙一定要上手实现一下才会掌握的下来,那我们在一下篇AOP的文章再见啦!!!