理解 IoC & DI
Spring 是包含了众多⼯具⽅法的 IoC 容器
• List/Map -> 数据存储容器• Tomcat -> Web 容器
IoC: Inversion of Control (控制反转), 也就是说 Spring 是⼀个"控制反转"的容器控制反转就是控制权反转. 获得依赖对象的过程被反转了, 当需要某个对象时, 传统开发模式中需要⾃⼰通过 new 创建对象, 现在不需要再进⾏创建, 把创建对象的任务交给容器, 程序中只需要依赖注⼊ (Dependency Injection,DI)就可以了这个容器称为:IoC容器. Spring是⼀个IoC容器, 所以有时Spring 也称为Spring 容器
IoC 介绍
开发方式的比较
传统的开发方式:例如现实生活中造车的流程
车>——>...——>框架>——>底座>——>轮胎...
现在以以上四个组分进行编写
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 Framework {private Bottom bottom;public Framework(int size){bottom = new Bottom(size);System.out.println("bottom init...");} } public class Bottom {private Tire tire;public Bottom(int size){tire = new Tire(size);System.out.println("tire init...");} } public class Tire {public Tire(int size){System.out.println("tire size:"+size);} } public class Main {public static void main(String[] args) {Car car1 = new Car(21);car1.run();} }可以理解为:
Car 依赖 Framework ,Framework 依赖 Bottom ,Bottom 依赖 Tire
相当于是根据轮胎造车子
此时代码的耦合度非常高,修改 size 会导致整条调用链上的代码都要修改——>
可以将依赖关系倒置过来:
Tire 依赖 Bottom ,Bottom 依赖 Framework,Framework 依赖 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...");} } public class Framework {private Bottom bottom;public Framework(Bottom bottom){this.bottom = bottom;System.out.println("bottom init...");} } public class Bottom {private Tire tire;public Bottom(Tire tire){this.tire = tire;System.out.println("tire init...");} } public class Tire {private int size;private String color;public Tire(int size,String color){System.out.println("tire size:"+size);} } public class Main {public static void main(String[] args) {Tire tire = new Tire(21,"red");Bottom bottom = new Bottom(tire);Framework framework = new Framework(bottom);Car car = new Car(framework);} }此时注意在每个类当中都没有创建下级类,而是将下级“传递”给当前类
在Main类中,创建给个类的对象,针对各个类的特定需求,在当前类中设置,然后将将当前类进行传递,不需要单独设置
IoC 优势
相较于传统的方式,改进后的代码发生了 控制权翻转 ,不再是使用方创建并控制对象,而是把对象注入到当前对象中,依赖对象的控制权不再由当前对象控制
意味着即使依赖类发生变化,当前对象也不会受影响——>控制反转(IoC)
资源不由使⽤资源的双⽅管理,⽽由不使⽤资源的第三⽅管理,这可以带来很多好处。第⼀,资源集中管理,实现资源的可配置和易管理。第⼆,降低了使⽤资源双⽅的依赖程度,也就是我们说的耦合度1. 资源集中管理: IoC容器会帮我们管理⼀些资源(对象等), 我们需要使⽤时, 只需要从IoC容器中去取就可以了2. 我们在创建实例的时候不需要了解其中的细节, 降低了使⽤资源双⽅的依赖程度, 也就是耦合度Spring 就是⼀种IoC容器, 帮助我们来做了这些资源管理
DI 介绍
程序运⾏时需要某个资源,此时容器就为其提供这个资源从这点来看, 依赖注⼊(DI)和控制反转(IoC)是从不同的⻆度的描述的同⼀件事情,就是指通过引⼊ IoC 容器,利⽤依赖关系注⼊的⽅式,实现对象之间的解耦
IoC & DI 使用
IoC 详解
要把某个对象交给IOC容器管理,需要在类上添加⼀个注解共有两类注解类型可以实现:1. 类注解:@Controller、@Service、@Repository、@Component、@Configuration2. ⽅法注解:@Bean
•@Controller:控制层, 接收请求, 对请求进⾏处理, 并进⾏响应.
• @Service:业务逻辑层, 处理具体的业务逻辑.• @Repository:数据访问层,也称为持久层. 负责数据访问操作• @Configuration:配置层. 处理项⽬中的⼀些配置信息其实这些注解⾥⾯都有⼀个注解 @Component ,说明它们本⾝就是属于 @Component 的"⼦类"@Component 是⼀个元注解,也就是说可以注解其他类注解,如 @Controller , @Service ,@Repository 等. 这些注解被称为 @Component 的衍⽣注解@Controller 表现层@Service 业务逻辑层@Repository 数据层
Bean的存储
使用@Controller存储如下:
@Controller //将对象存储到Spring中 public class HelloController {public void sayHi(){System.out.println("Hello");} } @SpringBootApplication public class DemoApplication {public static void main(String[] args) {//获取 Spring上下文 对象ApplicationContext context = SpringApplication.run(DemoApplication.class, args);//从Spring上下文中获取对象HelloController bean = context.getBean(HelloController.class);//使用对象bean.sayHi();} }
ApplicationContext Spring 上下⽂因为对象都交给 Spring 管理了,所以获取对象要从 Spring 中获取,那么就得先得到 Spring 的上下⽂指当前的运⾏环境, 也可以看作是⼀个容器, 容器⾥存了很多内容, 这些内容是当前运⾏的环境
获取bean对象的方式
@SpringBootApplication public class DemoApplication {public static void main(String[] args) {ApplicationContext context = SpringApplication.run(DemoApplication.class, args);HelloController bean = context.getBean(HelloController.class);System.out.println(bean);bean.sayHi(); //1. 根据 bean 的类型获取 beanHelloController bean2 = (HelloController) context.getBean("helloController");System.out.println(bean2);bean2.sayHi(); //2. 根据 bean 的名称获取 beanHelloController bean3 = (HelloController) context.getBean("helloController",HelloController.class);System.out.println(bean3);bean3.sayHi(); //3. 根据 bean 的名称和类型获取 bean} }
地址⼀样, 说明对象是⼀个
@SpringBootApplication public class DemoApplication {public static void main(String[] args) {ApplicationContext context = SpringApplication.run(DemoApplication.class, args);UController bean4 = (UController) context.getBean("uCtroller");System.out.println(bean4);bean4.say(); }
bean 的名称在spring容器中是唯一的
1. 默认情况下,类名首字母小写表示
2. 如果类名前两位均为大写,bean 的名称就是类名本身
获取bean对象, 是⽗类BeanFactory提供的功能
ApplicationContext VS BeanFactor• 继承关系和功能⽅⾯来说:Spring 容器有两个顶级的接⼝:BeanFactory 和ApplicationContext。其中 BeanFactory 提供了基础的访问容器的能⼒,⽽ ApplicationContext 属于 BeanFactory 的⼦类,它除了继承了 BeanFactory 的所有功能之外,它还拥有独特的特性,还添加了对国际化⽀持、资源访问⽀持、以及事件传播等⽅⾯的⽀持• 从性能⽅⾯来说:ApplicationContext (提前加载)是⼀次性加载并初始化所有的 Bean 对象,⽽ BeanFactory (懒加载)是需要那个才去加载那个,因此更加轻量. (空间换时间)
方法注解 @Bean
方法注解要配合类注解使用
在 Spring 框架的设计中,⽅法注解 @Bean 要配合类注解才能将对象正常的存储到 Spring 容器中
@Component public class UserComponent {@Beanpublic UserInfo userInfo(){return new UserInfo("ming");} } @SpringBootApplication public class DemoApplication {public static void main(String[] args) {ApplicationContext context = SpringApplication.run(DemoApplication.class, args);UserInfo bean = context.getBean(UserInfo.class);System.out.println(bean); }
定义多个对象
@Bean 注解的bean, bean的名称就是它的⽅法名
@Bean 可以针对同⼀个类, 定义多个对象
@Component public class UserComponent {@Beanpublic UserInfo userInfo(){return new UserInfo("ming");}@Beanpublic UserInfo userInfo2(){return new UserInfo("liu");} } @SpringBootApplication public class DemoApplication {public static void main(String[] args) {ApplicationContext context = SpringApplication.run(DemoApplication.class, args);UserInfo bean = (UserInfo) context.getBean("userInfo");System.out.println(bean);UserInfo bean2 = (UserInfo) context.getBean("userInfo2");System.out.println(bean2); }
重命名 Bean
可以通过设置 name 属性给 Bean 对象进⾏重命名操作
@Component public class UserComponent {@Bean({"user","userInfo"})public UserInfo userInfo(){return new UserInfo("ming");} } @SpringBootApplication public class DemoApplication {public static void main(String[] args) {ApplicationContext context = SpringApplication.run(DemoApplication.class, args);UserInfo bean = (UserInfo) context.getBean("user");System.out.println(bean); }
name={} 可以省略只有⼀个名称时, {}也可以省略
扫描路径
bean想要⽣效,还需要被Spring扫描使⽤五⼤注解声明的bean,要想⽣效, 还需要配置扫描路径, 让Spring扫描到这些注解也就是通过 @ComponentScan 来配置扫描路径
将启动类移到 model 包中
@ComponentScan({"demo.component"}) @SpringBootApplication public class DemoApplication {public static void main(String[] args) {ApplicationContext context = SpringApplication.run(DemoApplication.class, args);UserInfo bean = (UserInfo) context.getBean("user");System.out.println(bean); }
那为什么前⾯没有配置 @ComponentScan注解也可以呢?@ComponentScan 注解虽然没有显式配置,但是实际上已经包含在了启动类声明注解@SpringBootApplication 中了默认扫描的范围是SpringBoot启动类所在包及其⼦包在配置类上添加 @ComponentScan 注解, 该注解默认会扫描该类所在的包下所有的配置类把启动类放在我们希望扫描的包的路径下, 这样我们定义的bean就都可以被扫描到
DI 详解
依赖注⼊是⼀个过程,是指IoC容器在创建Bean时, 去提供运⾏时所依赖的资源,资源指是对象
简单来说, 就是把对象取出来放到某个类的属性中
依赖注⼊也被称之为 "对象注⼊", "属性装配", 具体含义需要结合⽂章的上下⽂来理解
依赖注入
属性注入
属性注⼊是使⽤ @Autowired 实现的,将 Service 类注⼊到 Controller 类中
@Service public class UserService {public void sayHi(){System.out.println("Hello Service");} } @Controller public class UserController {@Autowiredprivate UserService userService;public void sayHi(){userService.sayHi();System.out.println("Hello Controller");} } @SpringBootApplication public class DemoApplication {public static void main(String[] args) {ApplicationContext context = SpringApplication.run(DemoApplication.class, args);UserController bean = context.getBean(UserController.class);bean.sayHi();} }
构造方法注入
构造⽅法注⼊是在类的构造⽅法中实现注⼊
@Controller public class UserController {private UserService userService;@Autowiredpublic UserController(UserService userService){this.userService = userService;}public UserController(){}public void sayHi(){userService.sayHi();System.out.println("Hello Controller");} } @SpringBootApplication public class DemoApplication {public static void main(String[] args) {ApplicationContext context = SpringApplication.run(DemoApplication.class, args);UserController bean = context.getBean(UserController.class);bean.sayHi();} }
如果类只有⼀个构造⽅法,那么 @Autowired 注解可以省略;如果类中有多个构造⽅法,那么需要添加上 @Autowired 来明确指定到底使⽤哪个构造⽅法
Setter 注入
Setter 注⼊和属性的 Setter ⽅法实现类似,只不过在设置 set ⽅法的时候需要加上 @Autowired 注解@Controller public class UserController {private UserService userService;@Autowiredpublic void setUserService(UserService userService){this.userService = userService;}public void sayHi(){userService.sayHi();System.out.println("Hello Controller");} } @SpringBootApplication public class DemoApplication {public static void main(String[] args) {ApplicationContext context = SpringApplication.run(DemoApplication.class, args);UserController bean = context.getBean(UserController.class);bean.sayHi();} }
方法的优缺点
• 属性注⼊◦ 优点: 简洁,使⽤⽅便◦ 缺点:▪ 只能⽤于 IoC 容器,如果是⾮ IoC 容器不可⽤,并且只有在使⽤的时候才会出现 NPE(空指针异常)▪ 不能注⼊⼀个Final修饰的属性• 构造函数注⼊(Spring 4.X推荐)◦ 优点:▪ 可以注⼊final修饰的属性▪ 注⼊的对象不会被修改▪ 依赖对象在使⽤前⼀定会被完全初始化,因为依赖是在类的构造⽅法中执⾏的,⽽构造⽅法是在类加载阶段就会执⾏的⽅法.▪ 通⽤性好, 构造⽅法是JDK⽀持的, 所以更换任何框架,他都是适⽤的◦ 缺点:▪ 注⼊多个对象时, 代码会⽐较繁琐• Setter注⼊(Spring 3.X推荐)◦ 优点: ⽅便在类实例之后, 重新对该对象进⾏配置或者注⼊◦ 缺点:▪ 不能注⼊⼀个Final修饰的属性注⼊对象可能会被改变, 因为setter⽅法可能会被多次调⽤, 就有被修改的⻛险.
@Autowired存在问题
当同⼀类型存在多个bean时, 使⽤@Autowired会存在问题
@Service public class UserService {@Beanpublic UserInfo userInfo1(){UserInfo userInfo = new UserInfo();userInfo.setName("ming");userInfo.setAge(18);return userInfo;}@Beanpublic UserInfo userInfo2(){UserInfo userInfo = new UserInfo();userInfo.setName("liu");userInfo.setAge(17);return userInfo;} } @Controller public class UserController {@Autowiredprivate UserService userService;@Autowiredprivate UserInfo userInfo;public void sayHi(){System.out.println(userInfo);System.out.println("Hello");} }@SpringBootApplication public class DemoApplication {public static void main(String[] args) {ApplicationContext context = SpringApplication.run(DemoApplication.class, args);UserController bean = context.getBean(UserController.class);bean.sayHi();} }
报错的原因是,⾮唯⼀的 Bean 对象
@Primary
当存在多个相同类型的Bean注⼊时,加上@Primary注解,来确定默认的实现@Service public class UserService {@Primary //指定该bean为默认的bean@Beanpublic UserInfo userInfo1(){UserInfo userInfo = new UserInfo();userInfo.setName("ming");userInfo.setAge(18);return userInfo;}@Beanpublic UserInfo userInfo2(){UserInfo userInfo = new UserInfo();userInfo.setName("liu");userInfo.setAge(17);return userInfo;} }
@Qualifier
指定当前要注⼊的bean对象。 在@Qualifier的value属性中,指定注⼊的bean的名称@Qualifier注解不能单独使⽤,必须配合@Autowired使⽤@Controller public class UserController {@Autowiredprivate UserService userService;@Qualifier("userInfo1")@Autowiredprivate UserInfo userInfo;public void sayHi(){System.out.println(userInfo);System.out.println("Hello");} }
@Resource
按照bean的名称进⾏注⼊,通过name属性指定要注⼊的bean的名称@Controller public class UserController {@Autowiredprivate UserService userService;@Resource(name = "userInfo2")@Autowiredprivate UserInfo userInfo;public void sayHi(){System.out.println(userInfo);System.out.println("Hello");} }
@Autowird 与 @Resource的区别• @Autowired 是spring框架提供的注解,⽽@Resource是JDK提供的注解• @Autowired 默认是按照类型注⼊,⽽@Resource是按照名称注⼊. 相⽐于 @Autowired 来说,@Resource ⽀持更多的参数设置,例如 name 设置,根据名称获取 Bean