前言:
我们介绍下Spring.
1.什么是IoC
IoC Inversion of Control (控制反转), 也就是说 Spring 是⼀个"控制反转"的容器.
也就是"控制权"反转.就像是我们在JavaSE中,每一个对象都需要自己来new,但是把创建对象的任务交给了容器,我们只是需要的时候将所需对象注入进目标对象中即可.这个容器就称为"IoC容器",我们将所需对象通过注解加到目标对象中就称为"依赖注入".
"控制反转"是一种思想,而"依赖注入"是这种思想的实现形式.但是不是只有这一种实现方式,Spring用的是依赖注入
1.2IoC介绍
传统的程序开发
就类似造车:
先设计轮⼦(Tire),然后根据轮⼦的⼤⼩设计底盘(Bottom),接着根据底盘设计⻋⾝(Framework),最 后根据⻋⾝设计好整个汽⻋(Car)。这⾥就出现了⼀个"依赖"关系:汽⻋依赖⻋⾝,⻋⾝依赖底盘,底盘依赖轮⼦
代码实现:
//轮胎 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实现造车
//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时间的时候, 从上下⽂中拿到线程上次运⾏的信息这个上下⽂, 就是指当前的运⾏环境, 也可以看作是⼀个容器, 容器⾥存了很多内容, 这些内容是当前运⾏的环境@SpringBootApplication public class SpringIoc3Application {public static void main(String[] args) {ApplicationContext context=SpringApplication.run(SpringIoc3Application.class, args);}}
获取对象bean的方式:
ApplicationContext 也提供了其他获取bean的⽅式, ApplicationContext 获取bean对象的功能, 是⽗ 类BeanFactory提供的功能.public interface BeanFactory {//以上省略...// 1. 根据bean名称获取beanObject getBean(String var1) throws BeansException;// 2. 根据bean名称和类型获取bean<T> T getBean(String var1, Class<T> var2) throws BeansException;// 3. 按bean名称和构造函数参数动态创建bean,只适⽤于具有原型(prototype)作⽤域的beanObject 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对象
@SpringBootApplication public class SpringIoc3Application {public static void main(String[] args) {ApplicationContext context=SpringApplication.run(SpringIoc3Application.class, args);UserController bean = context.getBean(UserController.class);//通过类型扫描UserControllerbean.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
类注解是添加到某个类上的, 但是存在两个问题:1. 使⽤外部包⾥的类, 没办法添加类注解2. ⼀个类, 需要多个对象, ⽐如多个数据源这种场景, 我们就需要使⽤⽅法注解 @Bean代码案例1:这里我们将类注解先注释掉.//@Configuration public class UserConfig {public void say(){System.out.println("hi,UserConfig");}@Beanpublic User user(){return new User("张三");} }
这里出现了报错
错误内容是没有名为“userConfig”的bean可用.
原因是我们没有加上类注解,切记@Bean要搭配类注解使用
加上类注解
@Configuration public class UserConfig {public void say(){System.out.println("hi,UserConfig");}@Beanpublic User user(){return new User("张三");} }
代码案例2:定义多个对象,使用类的类型扫描
这里我们定义两个User对象并且都通过@Bean注解添加到容器中
@Beanpublic User user(){return new User("张三");}@Beanpublic User user1(){return new User("李四");}
通过类的类型扫描
这里出现了报错,通过类的类型扫描.此时容器中有两个User对象,我们根据类型获取对象,此时Spring不知道你要获取哪个对象,所以报错了.
解决办法:用类的名字扫描
3.4扫描路径
我们把启动类放到其他的目录下面
再次启动程序
为什么会出错呢?
为什么没有找到bean对象呢?使⽤五⼤注解声明的bean,要想⽣效, 还需要配置扫描路径, 让Spring扫描到这些注解也就是通过 @ComponentScan 来配置扫描路径.@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也给我们提供了三种⽅式:1. 属性注⼊(Field Injection)2. 构造⽅法注⼊(Constructor Injection)3. Setter 注⼊(Setter Injection4.1属性注入
4.2构造方法注入
我们可以看到,只有一个构造方法的时候即使不加@Autowired也可以获取数据
但是,我们要是加一个空的构造方法看看效果
我们看下报错信息,也是我们的老朋友了,就是空指针异常.为什么会空指针异常呢?
因为程序启动的时候会首先调用无参数的构造方法,如果没有会调用我们写的,但是两个都有的话就会调用无参数的,此时UserService并没有真正new对象,去调用UserService的say()方法就会出现空指针异常
解决办法:就是在想要注入的构造方法中添加@Autowired注解
4.3Setter注入
@Controller public class UserController3 {//注⼊⽅法3: Setter⽅法注⼊private UserService userService;@Autowiredpublic 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。