欢迎关注个人主页:逸狼
创造不易,可以点点赞吗
如有错误,欢迎指出~
Spring两大核心思想: IoC 和AOP
Spring,SpringBoot和SpringMVC⼜有什么关系
什么是spring
Spring是⼀个开源框架,他让我们的开发更加简单.他⽀持⼴泛的应⽤场 景,有着活跃⽽庞⼤的社区,具体来说: Spring是包含了众多⼯具⽅法的IoC容器
Ioc 控制反转
IoC: InversionofControl (控制反转),也就是说Spring是⼀个"控制反转"的容器.
控制反转 即 控制权反转: 当需要某个对象时,
- 传统开发模式中需要⾃⼰通过new创建对象,
- 运用Ioc思想就不需要再进⾏创 建,把创建对象的任务交给容器,程序中只需要依赖注⼊(DependencyInjection,DI)就可以了.
这个容器称为:IoC容器. Spring是⼀个IoC容器,所以有时Spring也称为Spring容器.
IoC是Spring的核⼼思想,在类上⾯添加@RestController 和 @Controller 注解,就是把这个对象交给Spring管理,Spring框架启动时就会加载该类.把对象交 给Spring管理,就是IoC思想(之前是对象的使用方创建对象,对象的控制权在对象的使用方手里)
举例: 如果要设计一辆车
在传统的代码中对象创建顺序是:Car->Framework->Bottom->Tire
先设计轮⼦(Tire),然后根据轮⼦的⼤⼩设计底盘(Bottom),接着根据底盘设计⻋⾝(Framework),最 后根据⻋⾝设计好整个汽⻋(Car)。这⾥就出现了⼀个"依赖"关系:汽⻋依赖⻋⾝,⻋⾝依赖底盘,底 盘依赖轮⼦.
package com.example.demo.demoTradition;
public class CarTradition {
public static void main(String[] args) {
Car car = new Car(88);
}
static class Car{
private Framework framework;
public Car(int size){
this.framework = new Framework(size);
System.out.println("car init....");
}
}
static class Framework{
private Bottom bottom;
public Framework(int size){
this.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 tire;
public Tire(int size){
System.out.println("tire init...."+size);
}
}
}
以上程序的问题是:当最底层代码改动之后,整个调⽤链上的所有代码都需要 修改. 程序的耦合度⾮常⾼(修改⼀处代码,影响其他处的代码修改) 比如要添加新的变量时,如颜色等等,那car到tire的底层代码都要修改
在上⾯的程序中,我们是根据轮⼦的尺⼨设计的底盘,轮⼦的尺⼨⼀改,底盘的设计就得修改.同样因 为我们是根据底盘设计的⻋⾝,那么⻋⾝也得改,同理汽⻋设计也得改,也就是整个设计⼏乎都得改
我们尝试换⼀种思路,我们先设计汽⻋的⼤概样⼦,然后根据汽⻋的样⼦来设计⻋⾝,根据⻋⾝来设计 底盘,最后根据底盘来设计轮⼦.这时候,依赖关系就倒置过来了:轮⼦依赖底盘,底盘依赖⻋⾝, ⻋⾝依赖汽⻋
这就类似我们打造⼀辆完整的汽⻋,如果所有的配件都是⾃⼰造,那么当客⼾需求发⽣改变的时候, ⽐如轮胎的尺⼨不再是原来的尺⼨了,那我们要⾃⼰动⼿来改了,但如果我们是把轮胎外包出去,那 么即使是轮胎的尺⼨发⽣变变了,我们只需要向代理⼯⼚下订单就⾏了,我们⾃⾝是不需要出⼒的.
改用Ioc思想
改进之后解耦的代码的对象创建顺序是:Tire->Bottom->Framework->Car
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);
}
}
}
代码经过以上调整,⽆论底层类如何变化,整个调⽤链是不⽤做任何改变的,这样就完成了代码之间的解耦,从⽽实现了更加灵活、通⽤的程序设计了。 (比如如果要增加新的变量"颜色",只需要修改构造Tire的代码,其他都不用动)
通过对比发现,通⽤程序的实现代码,类的创建顺序是反的,传统代码是Car控制并创建了 Framework,Framework创建并创建了Bottom,依次往下,⽽改进之后的控制权发⽣的反转,不再 是使⽤⽅对象创建并控制依赖对象了,⽽是把依赖对象注⼊将当前对象中,依赖对象的控制权不再由 当前类控制了. 这样的话,即使依赖类发⽣任何改变,当前类都是不受影响的,这就是典型的控制反转,也就是IoC的 实现思想。
Spring管理的对象称为bean, Ioc之前,对象的控制权在使用方,Ioc之后,对象的控制权交给了spring
IoC控制反转,就是将对象的控制权交给Spring的IOC容器,由IOC容器创建及管理对 象。 也就是bean的存储
IoC优势
IoC容器具备以下优点: 资源不由使⽤资源的双⽅管理,⽽由不使⽤资源的第三⽅管理,这可以带来很多好处。
第⼀,资源集 中管理,实现资源的可配置和易管理。
第⼆,降低了使⽤资源双⽅的依赖程度,也就是我们说的耦合 度。
- 1. 资源集中管理: IoC容器会帮我们管理⼀些资源(对象等),我们需要使⽤时,只需要从IoC容器中去取 就可以了
- 2. 我们在创建实例的时候不需要了解其中的细节,降低了使⽤资源双⽅的依赖程度,也就是耦合度. Spring就是⼀种IoC容器,帮助我们来做了这些资源管理.
DI依赖注入
DI:DependencyInjection(依赖注⼊) 容器在运⾏期间,动态的为应⽤程序提供运⾏时所依赖的资源,称之为依赖注⼊。
上述代码中,是通过构造函数的⽅式,把依赖对象注⼊到需要使⽤的对象中的
IoC是⼀种思想,也是"⽬标",⽽思想只是⼀种指导原则,最终还是要有可⾏的落地⽅案,⽽DI就属于 具体的实现。所以也可以说,DI是IoC的⼀种实现.
实际应用
⽬标:把BookDao,BookService交给Spring管理,完成Controller层,Service层,Dao层的解耦
- 交给spring管理使用注解:@Component (比如在BookDao类上面加上@Component注解,就表示把BookDao交给Spring管理,Spring会自动new BookDao类的对象 )
- 注⼊运⾏时依赖的对象:使⽤注解@Autowired(比如如下代码中bookDao就是要注入使用的对象,不用new,而是在注入的对象上面添加@Autowired注解即可)
@Component//表示将BookService的对象交给Spring管理了(Spring帮你创建了该对象放到容器中了)
public class BookService {
@Autowired //表示从spring中取到对象放到要用的地方
private BookDao bookDao;
public List<BookInfo> getList(){
// BookDao bookDao = new BookDao();
List<BookInfo> bookInfos = bookDao.mockData();
for(BookInfo bookInfo : bookInfos){
if(bookInfo.getStatus() == 1){
bookInfo.setStatusCN("可借阅");
}else{
bookInfo.setStatusCN("不可借阅");
}
}
return bookInfos;
}
}
@RestController
@RequestMapping("/book")
public class BookController {
@Autowired//将BookService对象注入
private BookService bookService;
@PostMapping("/getList")
public List<BookInfo> getList(){
// BookService bookService = new BookService();
List<BookInfo> bookInfos = bookService.getBookList();
return bookInfos;
}
}
@Component //将BookDao对象交给spring管理
public class BookDao {
.....
}
Bean的存储
将对象的控制权交给Spring的IOC容器就是 bean的存储
Spring框架为了更好的服务web应⽤程序,提供了更丰富的注解.(下面的类注解作用都差不多,只是名称不同,用于不同的模块)共有两类注解类型可以实现:
1. 类注解:@Controller(控制存储)、@Service(服务存储)、@Repository(仓库存储)、@Component(组件存储)、@Configuration(配置存储).
2. ⽅法注解:@Bean.(要配合类注解使用)
类注解
为什么要有多个不同的类注解?
这个也是和咱们前⾯讲的应⽤分层是呼应的.让程序员看到类注解之后,就能直接了解当前类的⽤途.
• @Controller:控制层,接收请求,对请求进⾏处理,并进⾏响应.
• @Servie:业务逻辑层,处理具体的业务逻辑.
• @Repository:数据访问层,也称为持久层.负责数据访问操作
• @Configuration:配置层.处理项⽬中的⼀些配置信息.
程序的应⽤分层,调⽤流程如下:
类注解之间的关系 查看@Controller /@Service /@Repository /@Configuration 等注解的源码发 现:
其实这些注解⾥⾯都有⼀个注解@Component ,说明它们本⾝就是属于@Component 的"⼦类". @Component 是⼀个元注解,也就是说可以注解其他类注解,如 @Controller ,@Service , @Repository 等.这些注解被称为 @Component 的衍⽣注解.
@Controller ,@Service 和@Repository ⽤于更具体的⽤例(分别在控制层,业务逻辑层,持 久化层),在开发过程中,如果你要在业务逻辑层使⽤ @Component 或@Service,显然@Service是更 好的选择
使用@Contreller存储bean
将UserController交给spring管理
@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对象的其他⽅式
上述代码是根据类型来查找对象,如果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种这三种⽅式(根据1.名称; 2.名称和类型; 4.根据类型 ),获取到的bean是⼀样的
bean的名称
程序开发⼈员不需要为bean指定名称(BeanId),如果没有显式的提供名称(BeanId),Spring容器将为该 bean⽣成唯⼀的名称.
- 命名约定使⽤Java标准约定作为实例字段名.也就是说,bean名称以⼩写字⺟开头,然后使⽤驼峰式 ⼤⼩写. ⽐如 类名:UserController,Bean的名称为:userController
- 也有⼀些特殊情况,当有多个字符并且第⼀个和第⼆个字符都是⼤写时,将保留原始的⼤⼩写.这些规则 与java.beans.Introspector.decapitalize(Spring在这⾥使⽤的)定义的规则相同. ⽐如名 :UController,Bean的名称为:UController
@SpringBootApplication
public class SpringIocDemoApplication {
public static void main(String[] args) {
//获取Spring上下⽂对象
ApplicationContext context =
SpringApplication.run(SpringIocDemoApplication.class, args);
//从Spring上下⽂中获取对象
//1.根据bean类型, 从Spring上下⽂中获取对象 使用于该类只有一个对象
UserController userController1 = context.getBean(UserController.class);
//2.根据bean名称, 从Spring上下⽂中获取对象 要进行类型的强转
UserController userController2 = (UserController)
context.getBean("userController");
//3.根据bean类型+名称, 从Spring上下⽂中获取对象
UserController userController3 =
context.getBean("userController",UserController.class);
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是需要哪个才去加载哪个,因此更加轻量.(空间换时间)
⽅法注解@Bean
@Bean主要用于解决以下两个问题
- 使⽤外部包⾥的类,没办法添加类注解
- ⼀个类,需要多个对象,⽐如多个数据源 这种场景
@Bean的使用
在Spring框架的设计中,⽅法注解 @Bean 要配合类注解才能将对象正常的存储到Spring容器中
package com.example.demo;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
@Component//@Bean要配合类注解使用
public class BeanConfig {
@Bean
public User user(){
User user = new User();
user.setName("zhangsan");
user.setAge(18);
return user;
}
}
package com.example.demo;
import com.example.demo.Controller.UserController;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
@SpringBootApplication
public class Demo5Application {
public static void main(String[] args) {
ApplicationContext context= SpringApplication.run(Demo5Application.class, args);
User bean = context.getBean(User.class);
System.out.println(bean);
}
}
定义多个对象
对于同⼀个类,如何定义多个对象呢? ⽐如多数据源的场景,类是同⼀个,但是配置不同,指向不同的数据源.
package com.example.demo;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
@Component//@Bean要配合类注解使用
public class BeanConfig {
@Bean
public User user1(){
User user = new User();
user.setName("zhangsan");
user.setAge(18);
user.setGender("男");
return user;
}
@Bean
public User user2(){
User user = new User();
user.setName("lisi");
user.setAge(81);
user.setGender("女");
return user;
}
}
所以要通过名称来获取bean
package com.example.demo;
import com.example.demo.Controller.UserController;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
@SpringBootApplication
public class Demo5Application {
public static void main(String[] args) {
ApplicationContext context= SpringApplication.run(Demo5Application.class, args);
User user1 = (User) context.getBean("user1");
User user2 = (User) context.getBean("user2");
System.out.println(user1);
System.out.println(user2);
}
}
Bean的重命名
@Component//@Bean要配合类注解使用
public class BeanConfig {
@Bean(name = {"u1", "user1"})//可以指定多个名称
// @Bean({"u1","user1"})//name=可以省略
// @Bean("u1")//只用一个名称时name=和{}都可以省略
public User user1(){
User user = new User();
user.setName("zhangsan");
user.setAge(18);
user.setGender("男");
return user;
}
扫描路径
Q:使⽤前⾯学习的四个注解声明的bean,⼀定会⽣效吗?
A:不⼀定(原因:bean想要⽣效,还需要被Spring扫描)
举例:修改项⽬⼯程的⽬录结构,来测试bean对象是否⽣效:
运行项目,发现没有找到u1
解释: 使⽤五⼤注解声明的bean,要想⽣效,还需要配置扫描路径,让Spring扫描到这些注解 也就是通过 @ComponentScan 来配置扫描路径.
推荐做法: 把启动类放在我们希望扫描的包的路径下,这样我们定义的bean就都可以被扫描到