前言:
我们介绍下Spring.
通过前⾯的学习, 我们知道了Spring是⼀个开源框架, 他让我们的开发更加简单. 他⽀持⼴泛的应⽤场景, 有着活跃⽽庞⼤的社区, 这也是Spring能够⻓久不衰的原因.
这么说可能还是很抽象.用一句话概括就是Spring就是一个包含了众多工具和方法的IoC容器.
所谓的容器,现实生活中很多,类似冰箱就是一个容器,里面放了很多食物
在我们之前掌握知识来看:LIst 也是一个容器,里面可以存放很多不同类型的数据.
1.什么是IoC
IoC Inversion of Control (控制反转), 也就是说 Spring 是⼀个"控制反转"的容器.
也就是"控制权"反转.就像是我们在JavaSE中,每一个对象都需要自己来new,但是把创建对象的任务交给了容器,我们只是需要的时候将所需对象注入进目标对象中即可.这个容器就称为"IoC容器",我们将所需对象通过注解加到目标对象中就称为"依赖注入".
"控制反转"是一种思想,而"依赖注入"是这种思想的实现形式.但是不是只有这一种实现方式,Spring用的是依赖注入
1.2IoC介绍
传统的程序开发
就类似造车:
先设计轮⼦(Tire),然后根据轮⼦的⼤⼩设计底盘(Bottom),接着根据底盘设计⻋⾝(Framework),最 后根据⻋⾝设计好整个汽⻋(Car)。这⾥就出现了⼀个"依赖"关系:汽⻋依赖⻋⾝,⻋⾝依赖底盘,底盘依赖轮⼦
代码实现:
java//轮胎 public class Tire { private int size; private String color; public Tire(int size) { System.out.println("tire size:"+size); } } //底盘 public class Bottom { private Tire tire; public Bottom(int size) { tire = new Tire(size); System.out.println("tire init..."); } } //框架 public class Framework { private Bottom bottom; public Framework(int size) { bottom = new Bottom(size); System.out.println("bottom init...."); } } //汽车 public class Car { private Framework framework; public Car(int size) { framework = new Framework(size); System.out.println("framework init..."); } public void run() { System.out.println("car run..."); } } //启动类 public class Main { public static void main(String[] args) { Car car = new Car(10); car.run(); } }
我们通过上述代码就模拟好了造车.
但是如果我们要在上述代码修改一个变量,那么所有的类就会跟着改变
牵一发而动全身
代码的耦合性太高了,根本不利于我们维护代码,可读性也大大降低
解决方案:
我们尝试换⼀种思路, 我们先设计汽⻋的⼤概样⼦,然后根据汽⻋的样⼦来设计⻋⾝,根据⻋⾝来设计 底盘,最后根据底盘来设计轮⼦. 这时候,依赖关系就倒置过来了:轮⼦依赖底盘, 底盘依赖⻋⾝, ⻋⾝依赖汽⻋
这就类似我们打造⼀辆完整的汽⻋, 如果所有的配件都是⾃⼰造,那么当客⼾需求发⽣改变的时候, ⽐如轮胎的尺⼨不再是原来的尺⼨了,那我们要⾃⼰动⼿来改了,但如果我们是把轮胎外包出去,那 么即使是轮胎的尺⼨发⽣变变了,我们只需要向代理⼯⼚下订单就⾏了,我们⾃⾝是不需要出⼒的
如何实现:
改用传递对象的方式,即使下级类修改,也没关系1.2.1IoC实现造车
java//bottom public class Bottom { private Tire tire; public Bottom(Tire tire) { this.tire = tire; System.out.println("tire init..."); } } //Car public class Car { private Framework framework; public Car(Framework framework) { this.framework = framework; System.out.println("framework init..."); } public void run() { System.out.println("car run..."); } } //Framework public class Framework { private Bottom bottom; public Framework(Bottom bottom) { this.bottom = bottom; System.out.println("bottom init...."); } } //Tire public class Tire { private int size; private String color; public Tire(int size, String color) { System.out.println("tire size:"+size+",color:"+color); } } //Main public class Main { public static void main(String[] args) { Tire tire = new Tire(17, "red"); Bottom bottom = new Bottom(tire); Framework framework = new Framework(bottom); Car car = new Car(framework); car.run(); } }
从上面我们可以看到
传统方式是创建Car再去找需求的依赖,是一个从下到上的结构而IOC的方式是将对象依赖注入,是改进之后的控制权反转
这里我们可以看出IOC思维的优势
1.减少了类之间的耦合度
2.便于对象的集中管里
2什么是DI
DI: Dependency Injection(依赖注⼊)
容器在运⾏期间, 动态的为应⽤程序提供运⾏时所依赖的资源,称之为依赖注⼊。
上面IoC方式造车的例子就是"依赖注入"
某个对象想使用某个资源,我们直接注入给他
3.IoC详解
前⾯我们提到IoC控制反转,就是将对象的控制权交给Spring的IOC容器,由IOC容器创建及管理对象。
也就是bean的存储
bean:Spring中的对象称为bean3.1Bean的存储
存储Bean我们需要使用两类注解
类注解:@Controller,@Service,@Component,@Repository,@Configuration
方法注解:@Bean
在学习这些注解之前我们要先学会使用传统方式,来获取Bean.这里我们使用
ApplicationContext来获取
ApplicationContext 翻译过来就是: Spring 上下⽂
因为对象都交给 Spring 管理了,所以获取对象要从 Spring 中获取,那么就得先得到 Spring 的上下文.
关于上下⽂的概念
在计算机领域, 上下⽂这个概念, 咱们最早是在学习线程时了解到过, ⽐如我们应⽤进⾏线程切换的时 候,切换前都会把线程的状态信息暂时储存起来,这⾥的上下⽂就包括了当前线程的信息,等下次该 线程⼜得到CPU时间的时候, 从上下⽂中拿到线程上次运⾏的信息
这个上下⽂, 就是指当前的运⾏环境, 也可以看作是⼀个容器, 容器⾥存了很多内容, 这些内容是当前运⾏的环境
java@SpringBootApplication public class SpringIoc3Application { public static void main(String[] args) { ApplicationContext context=SpringApplication.run(SpringIoc3Application.class, args); } }
获取对象bean的方式:
ApplicationContext 也提供了其他获取bean的⽅式, ApplicationContext 获取bean对象的功能, 是⽗ 类BeanFactory提供的功能.
javapublic 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的命名约定:
在Spring官方文档中指出,如果是类注解以名字来访问,就要注意名字必须和对象名保持一致,并且在getBean()方法中名字的第一个首字母要小写,特殊情况下,对象名前两个字母都是大写的情况下直接使用原名,不需要小写.
使用方法注解的时候.直接使用方法名即可.
⽐如
类名: UserController, Bean的名称为: userController
类名: AccountManager, Bean的名称为: accountManager
类名: AccountService, Bean的名称为: accountService
前两个字母都是大写
⽐如
类名: UController, Bean的名称为: UController
类名: AManager, Bean的名称为: AManager
3.1.1@Controller(控制器存储)
接下来我们使用上述传统方式来获取Bean并观察结果
首先我们要将UserController对象用@Controller注解存放到IoC容器中
这样我们就把这个类放到了Spring中.
接下来我们使用getBean()方法来获取UserController对象
java@SpringBootApplication public class SpringIoc3Application { public static void main(String[] args) { ApplicationContext context=SpringApplication.run(SpringIoc3Application.class, args); UserController bean = context.getBean(UserController.class);//通过类型扫描UserController bean.say(); UserController userController = (UserController) context.getBean("userController"); userController.say(); UserController bean1 = context.getBean("userController", UserController.class); bean1.say(); } }
最后我们看到结果成功输出也就是获取到了UserController对象.
地址一样,说明是一个对象
我们获取对象的功能,是Application的父类BeanFactory的功能.
3.1.2@Service(服务存储)
3.1.3 @Repository(仓库存储)
3.1.4 @Component(组件存储)
3.1.5@Configuration(配置存储)
3.2为什么使用这么多类注解
这个也是和咱们前⾯讲的应⽤分层是呼应的. 让程序员看到类注解之后,就能直接了解当前类的⽤途.
•
@Controller:控制层, 接收请求, 对请求进⾏处理, 并进⾏响应.
•
@Servie:业务逻辑层, 处理具体的业务逻辑.
•
@Repository:数据访问层,也称为持久层. 负责数据访问操作
•
@Configuration:配置层. 处理项⽬中的⼀些配置信息
3.3 ⽅法注解 @Bean
类注解是添加到某个类上的, 但是存在两个问题:
- 使⽤外部包⾥的类, 没办法添加类注解
- ⼀个类, 需要多个对象, ⽐如多个数据源
这种场景, 我们就需要使⽤⽅法注解 @Bean
代码案例1:这里我们将类注解先注释掉.
java//@Configuration public class UserConfig { public void say(){ System.out.println("hi,UserConfig"); } @Bean public User user(){ return new User("张三"); } }
这里出现了报错
错误内容是没有名为"userConfig"的bean可用.
原因是我们没有加上类注解,切记@Bean要搭配类注解使用
加上类注解
java@Configuration public class UserConfig { public void say(){ System.out.println("hi,UserConfig"); } @Bean public User user(){ return new User("张三"); } }
代码案例2:定义多个对象,使用类的类型扫描
这里我们定义两个User对象并且都通过@Bean注解添加到容器中
java@Bean public User user(){ return new User("张三"); } @Bean public User user1(){ return new User("李四"); }
通过类的类型扫描
这里出现了报错,通过类的类型扫描.此时容器中有两个User对象,我们根据类型获取对象,此时Spring不知道你要获取哪个对象,所以报错了.
解决办法:用类的名字扫描
3.4扫描路径
我们把启动类放到其他的目录下面
再次启动程序
为什么会出错呢?
为什么没有找到bean对象呢?
使⽤五⼤注解声明的bean,要想⽣效, 还需要配置扫描路径, 让Spring扫描到这些注解
也就是通过 @ComponentScan 来配置扫描路径.
java@ComponentScan({"com.example.demo"}) @SpringBootApplication public class SpringIocDemoApplication { public static void main(String[] args) { //获取Spring上下⽂对象 ApplicationContext context = SpringApplication.run(SpringIocDemoApplicatio //从Spring上下⽂中获取对象 User u1 = (User) context.getBean("u1"); //使⽤对象 System.out.println(u1); } }
那为什么前⾯没有配置 @ComponentScan注解也可以呢?
@ComponentScan 注解虽然没有显式配置,但是实际上已经包含在了启动类声明注解
@SpringBootApplication 中了.默认扫描的范围是SpringBoot启动类所在包及其⼦包
在配置类上添加 @ComponentScan 注解, 该注解默认会扫描该类所在的包下所有的配置类
ApplicationContext VS BeanFactory(常⻅⾯试题)
•
继承关系和功能⽅⾯来说:Spring 容器有两个顶级的接⼝:BeanFactory 和
ApplicationContext。其中 BeanFactory 提供了基础的访问容器的能⼒,⽽
ApplicationContext 属于 BeanFactory 的⼦类,它除了继承了 BeanFactory 的所有功能之外, 它还拥有独特的特性,还添加了对国际化⽀持、资源访问⽀持、以及事件传播等⽅⾯的⽀持.
•
从性能⽅⾯来说:ApplicationContext 是⼀次性加载并初始化所有的 Bean 对象,⽽
BeanFactory 是需要那个才去加载那个,因此更加轻量. (空间换时间)4.DI(依赖注入)详解
依赖注⼊是⼀个过程,是指IoC容器在创建Bean时, 去提供运⾏时所依赖的资源,⽽资源指的就是对象.
在上⾯程序案例中,我们使⽤了 @Autowired 这个注解,完成了依赖注⼊的操作.
简单来说, 就是把对象取出来放到某个类的属性中.
在⼀些⽂章中, 依赖注⼊也被称之为 "对象注⼊", "属性装配", 具体含义需要结合⽂章的上下⽂来理解.
关于依赖注⼊, Spring也给我们提供了三种⽅式:
- 属性注⼊(Field Injection)
- 构造⽅法注⼊(Constructor Injection)
- Setter 注⼊(Setter Injection
4.1属性注入
4.2构造方法注入
我们可以看到,只有一个构造方法的时候即使不加@Autowired也可以获取数据
但是,我们要是加一个空的构造方法看看效果
我们看下报错信息,也是我们的老朋友了,就是空指针异常.为什么会空指针异常呢?
因为程序启动的时候会首先调用无参数的构造方法,如果没有会调用我们写的,但是两个都有的话就会调用无参数的,此时UserService并没有真正new对象,去调用UserService的say()方法就会出现空指针异常
解决办法:就是在想要注入的构造方法中添加@Autowired注解
4.3Setter注入
java@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三种注入优缺点分析
•
1.属性注⼊
优点: 简洁,使⽤⽅便;
缺点:
只能⽤于 IoC 容器,如果是⾮ IoC 容器不可⽤,并且只有在使⽤的时候才会出现 NPE(空指
针异常)
不能注⼊⼀个Final修饰的属性
2.构造函数注⼊(Spring 4.X推荐)
优点:
可以注⼊final修饰的属性
注⼊的对象不会被修改
依赖对象在使⽤前⼀定会被完全初始化,因为依赖是在类的构造⽅法中执⾏的,⽽构造⽅法
是在类加载阶段就会执⾏的⽅法.
通⽤性好, 构造⽅法是JDK⽀持的, 所以更换任何框架,他都是适⽤的
缺点:
注⼊多个对象时, 代码会⽐较繁琐
3.Setter注⼊(Spring 3.X推荐)
优点: ⽅便在类实例之后, 重新对该对象进⾏配置或者注⼊
缺点:
不能注⼊⼀个Final修饰的属性
注⼊对象可能会被改变, 因为setter⽅法可能会被多次调⽤, 就有被修改的⻛险4.5@Autowired存在的问题
当我们new两个User对象的时候,我们使用@Autowired注入User对象会出现报错.
如果注入对象的名和创建的对象名字不一样就会报错.
解决办法:
1.使用@Primary
在有两个对象的时候可以在其中一个对象上加上@Primary.作用是默认选择,如果有两个对象并且对象名不同会使用加上@Primary的默认对象
2.使用 @Qualifie
在注解后边括号指定当前要注⼊的bean对象。 在@Qualifier的value属性中,指定注⼊的bean的名称
3.@Resource
使⽤@Resource注解:是按照bean的名称进⾏注⼊。通过name属性指定要注⼊的bean的名称
@Autowird 与 @Resource的区别(面试题)
@Autowired 是spring框架提供的注解,⽽@Resource是JDK提供的注解
@Autowired 默认是按照类型注⼊,⽽@Resource是按照名称注⼊. 相⽐于 @Autowired 来说, @Resource ⽀持更多的参数设置,例如 name 设置,根据名称获取 Bean。