一、Spring,Spring Boot和Spring MVC的联系及区别
Spring是另外两个框架的基础,是Java生态系统的核心框架,而SpringMVC是Spring 的子模块,专注于 Web 层开发,基于 MVC 设计模式(模型-视图-控制器)。Spring MVC 依赖 Spring 的 IOC 和 AOP,是 Spring 生态中处理 Web 请求的模块。而Spring Boot是Spring 的“快速启动器”,旨在简化 Spring 应用的配置和开发流程。Spring Boot 基于 Spring,但默认配置了 Spring MVC、Spring Data 等模块,更好上手。
二、什么是IoC
IoC是Spring的核心思想,其实IoC我们已经使用过了,在类上添加@Rest Controller和@Controller注解,就是将这个对象交给Spring管理,Spring框架启动时就会加载该类,把对象交给Spring处理,就是IoC思想。
概念:
三、一个关于IoC的实例
如果实现一辆车,需要以下结构:
它们是相互依赖的,Car依赖底盘,底盘依赖于轮胎。
如果我们按照以前的方式来写,是这样的。
package com.example.ioc;public class NewCar {public static void main(String[] args) {Car car = new Car(20);car.run();}
static class Car{private Bottom bottom;public Car(int size) {this.bottom = new Bottom(size);System.out.println("Car init....");}public void run() {System.out.println("Car run....");}}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);}}
}
这样写的问题是:当代码最底层改动后,整个调用链上的代码都要改动,对于这段代码来说就是轮胎参数改变后,整个代码都要改。
解决方法:
我们可以将所有的配件都外包出去,如果轮胎尺寸更改后,我们只需要向代工厂提要求就行了,自身不需要修改。
此时,我们只需要将自己原本创建的下级类,改为传递的方式,也就是注入的方式,因为不需要在当前的类中创建下级类了,所以即使下级类发生改变,当前类也无需修改,这样就完成了程序的解耦合。
通过IoC思路修改后的代码:
package com.example.ioc;public class NewCar {public static void main(String[] args) {Tire tire = new Tire(20);Bottom bottom = new Bottom(tire);Car car = new Car(bottom);car.run();}
static class Car{private Bottom bottom;public Car(Bottom bottom) {this.bottom = bottom;System.out.println("Car init....");}public void run() {System.out.println("Car run....");}}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优势:
通过IoC思想修改后的代码,类的创建方式是反的,传统代码是Car控制并创建了Bottom,而Bottom控制并创建了Tire,依次往下,而改进后的控制权发生反转不是使用方对象控制并创建依赖对象了,而是将依赖对象注入到当前对象中,依赖对象的控制权不再由当前类控制了。
这样的话,即使依赖类发生任何改变,当前类都是不受影响的,这就是经典的控制反转,也是IoC的实现思想。
四、IoC容器
上述这段代码就是IoC容器做的工作
Tire tire = new Tire(20);Bottom bottom = new Bottom(tire);Car car = new Car(bottom);
从上面可以看出,IoC容器具备以下优点:
资源不由使用资源的双方管理,而是由不使用资源的第三方管理。这样可以带来很多好处。第一:资源集中管理,实现资源的可配置和易于管理。第二:降低了使用资源双方的依赖程度,也就是降低了耦合度。
五、DI介绍
DI,中文翻译为依赖注入。
容器在运行时间,动态的为应用程序提供运行时所依赖的资源,称为依赖注入。
从这方面来看,DI就是IoC思想的一种实现方式。
六、IoC&DI的使用步骤
Spring容器中存储的主要是对象,这些对象,我们称之为“Bean”,我们把这些对象交由Spring管理,我们把这些对象交由Spring管理,由Spring来负责对象的创建和销毁,我们程序只需要告诉Spring,哪些需要存,以及如何从Spring中取出对象。
1、Bean的存储和获取
要将某个对象交给IoC容器管理,需要在类上添加注解@Compent,而Spring框架为了更好的服务web应用程序,提供了更多的注解。
共有两类注解可以实现:
类注解:@Controller、@Service、@Repository、@Component、@Configuration。
方法注解:@Bean
1>@Controller(控制器存储)
使用@Controller存储Bean的代码如下:
@Controller//将对象存储到Spring中public class UserController {public void sayHi(){System.out.println("Hi");}}
如何得知对象已经存储到了Spring容器中呢?
接下来是从Spring容器中获取对象:
@SpringBootApplication
public class IoCApplication {public static void main(String[] args) {ApplicationContext context = SpringApplication.run(IoCApplication.class, args);UserController usercontroler = context.getBean(UserController.class);//从Spring上下文中获取对象usercontroler.sayHi();}}
ApplicationContext翻译过来就是:Spring上下文
因为对象都交给Spring管理了,所以获取对象要从Spring中获取,首先就要先得到Spring的上下文。(这个上下文,就是指当前的运行环境,也可以看作一个容器,容器里存了很多内容,这些内容是当前运行的环境)
获取Bean的其他方式:
Spring容器将会为存入进去的Bean起一个名字,起名字的规则如下:
1.bean名称以小驼峰方式起名:
例如:类名:UserController,Bean名称:userController
类名:AccountManager,Bean名称:accountManager。
2.特殊情况,第一个和第二个都是大写。
例如:UController,Bean名称:UController。
类名:AManager,Bean名称:AManager
根据这个规则来获取Bean:
@SpringBootApplication
public class IoCApplication {public static void main(String[] args) {ApplicationContext context = SpringApplication.run(IoCApplication.class, args);UserController usercontroller1 = context.getBean(UserController.class);//根据Bean的类型获取UserController usercontroller2 = (UserController) context.getBean("userController");//根据Bean名称获取UserController usercontroller3 = context.getBean("userController", UserController.class);//根据Bean名称+类型获取System.out.println(usercontroller1);System.out.println(usercontroller2);System.out.println(usercontroller3);}}
我们看运行结果:
后缀都一样,说明地址相同,地址相同,说明是同一个。
获取bean对象,是父类BeanFactory提供的功能。
ApplicationContext和BeanFactory的联系:
继承关系和功能方面来说:Spring有两个顶级的接口:BeanFactory和ApplicationContext,其中BeanFactory提供了访问容器的基础能力,而ApplicationContext属于BeanFactory的子类,它继承了BeanFactory的所有功能之外,它还有对国际化支持,资源访问支持,以及事件传播等方面的支持。
从性能方面来说:ApplicationContext是一次性加载并初始化所有的Bean对象(空间换时间),而BeanFactory是需要哪个就去加载哪个,因此更轻量化。
2>@Service(服务存储)
使用@Service存储bean的代码如下:
@Service
public class UserService {public void sayHello(String name) {System.out.println("Hello " + name);}
}
读取bean的代码:
@SpringBootApplication
public class IoCApplication {public static void main(String[] args) {ApplicationContext context = SpringApplication.run(IoCApplication.class, args);UserService userService = context.getBean(UserService.class);userService.sayHello("World");}}
当然@Service也可以使用Bean的名称或者名称+类型来获取。
3>@Repository(仓库存储)
使用@Repository存储Bean的代码:
@Repository
public class UserRepository {public void Hello(){System.out.println("Hello");}
}
读取Bean的代码:
@SpringBootApplication
public class IoCApplication {public static void main(String[] args) {ApplicationContext context = SpringApplication.run(IoCApplication.class, args);UserRepository userRepository = context.getBean(UserRepository.class);UserRepository userRepository2 = context.getBean("userRepository", UserRepository.class);userRepository2.Hello();userRepository.Hello();}}
4>@Component(组件存储)
@Component
public class UserComponent {public void sayHello() {System.out.println("Hello");}
}
读取Bean
@SpringBootApplication
public class IoCApplication {public static void main(String[] args) {ApplicationContext context = SpringApplication.run(IoCApplication.class, args);UserComponent userComponent = context.getBean(UserComponent.class);userComponent.sayHello();}}
5>@Configuration(配置存储)
存储Bean
@Configuration
public class UserConfiguration {public void Hello(){System.out.println("Hello");}
}
获取Bean
@SpringBootApplication
public class IoCApplication {public static void main(String[] args) {ApplicationContext context = SpringApplication.run(IoCApplication.class, args);UserConfiguration userConfiguration = context.getBean(UserConfiguration.class);userConfiguration.Hello();}}
2、各个注解的使用场景:
@Controller:控制层,接受请求,对请求进行处理,并进行响应。
@Service:业务逻辑层,处理具体的业务逻辑。
@Repository:数据访问层,也称为持久层,负责数据访问操作。
@Configuration:配置层,处理项目中的一些配置信息。
它们关系图:
查看源码可知,除了Component外,其他四个注解里面都有@Component,说明它们本身就是属于@Component的“子类”,这好比如,@Component是一个普通的杯子,而其他注解则是喝水杯,漱口杯等。
3、方法注解@Bean
类注解是添加到某个类上的,但是存在两个问题:
1.使用外部包的类,没办法添加类注解。
2.一个类,需要多个对象,比如多个数据源。
在这些场景,我们就需要使用方法注解@Bean。
1.使用外部包的类
方法注解的使用方法:
错误使用方法:
public class BeanConfig {@Beanpublic User user() {User user = new User();user.setName("zhangsan");user.setAge(18);return user;}
}
@SpringBootApplication
public class IoCApplication {public static void main(String[] args) {ApplicationContext context = SpringApplication.run(IoCApplication.class, args);User user = context.getBean("user", User.class);System.out.println(user);}}
运行后会报错:
这是因为方法注解要配合类注解使用
在Spring框架的设计中,方法注解@Bean要配合类注解才能将对象正常的存储到Spring容器中,如下代码所示:
@Component
public class BeanConfig {@Beanpublic User user() {User user = new User();user.setName("zhangsan");user.setAge(18);return user;}
}
@SpringBootApplication
public class IoCApplication {public static void main(String[] args) {ApplicationContext context = SpringApplication.run(IoCApplication.class, args);User user = context.getBean("user", User.class);System.out.println(user);}}
这时代码运行就正常了
2.定义多个对象
对于同一个类,如果我们要定义多个对象,就要使用@Bean,因为有时候类是同一个,但是配置不同,指向不同的数据源。
例如下列场景:
@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(19);return user;}
}
但是当定义了多个对象后,根据类型获取对象,获取的是哪个对象呢?
@SpringBootApplication
public class IoCApplication {public static void main(String[] args) {ApplicationContext context = SpringApplication.run(IoCApplication.class, args);User user = context.getBean( User.class);System.out.println(user);}}
这时会报错:
报错信息上显示:期望只有一个匹配,结果发现了两个:user1,user2。
从中可以看出,@Bean注解的bean的名称就是它的方法名。
可以根据名称来获取bean对象:
@SpringBootApplication
public class IoCApplication {public static void main(String[] args) {ApplicationContext context = SpringApplication.run(IoCApplication.class, args);User user1 = (User) context.getBean( "user1");User user2 = (User) context.getBean( "user2");System.out.println(user1);System.out.println(user2);}}
运行结果显示如下:
可以看出,@Bean可以针对一个类来定义多个对象。
3.重命名Bean
可以通过设置name属性给Bean对象进行重命名操作,代码如下:
public User user1() {User user = new User();user.setName("zhangsan");user.setAge(18);return user;}
其中name = 可以省略。
@Bean({"zhangsan","user1"})public User user1() {User user = new User();user.setName("zhangsan");user.setAge(18);return user;}
通过zhangsan来获取User对象:
@SpringBootApplication
public class IoCApplication {public static void main(String[] args) {ApplicationContext context = SpringApplication.run(IoCApplication.class, args);User user1 = (User) context.getBean( "zhangsan");User user2 = (User) context.getBean( "user1");System.out.println(user1);System.out.println(user2);}}
此时”user1“和”zhangsan“都表示user1,如果只需要一个名字,可以删除一个,同时”{}“也可以删除:
@Bean("zhangsan")public User user1() {User user = new User();user.setName("zhangsan");user.setAge(18);return user;}
4.扫描路径
@Bean要想生效,需要被Spring扫描到,如果将Spring类移到某个包中,就会出现错误。
此时再运行就会报错:
意为没有找到bean的名称为zhangsan。
这是因为使用五大注解声明的bean,要想生效,还需要配置扫描路径,让Spring扫描到这些注解,通过@ComponentScan来配置路径。
@ComponentScan({"com.example.ioc"})
@SpringBootApplication
public class IoCApplication {public static void main(String[] args) {ApplicationContext context = SpringApplication.run(IoCApplication.class, args);User user1 = (User) context.getBean( "zhangsan");// User user2 = (User) context.getBean( "user1");System.out.println(user1);// System.out.println(user2);}}
此时就不会报错了,同时”{}“里面可以配置多个包路径。
之前没有配置也可以使用是因为@ComponentScan实际上已经包含在了启动类声明注解中了。
默认的扫描范围是SpringBoot启动类所在包及其子包。