[Spring]-组件注册
IOC/DI
组件和容器
- 组件: 具有一定功能的对象
- 容器: 管理组件(创建/获取/保存/销毁)
一个常见的容器
组件: Servlet组件
容器: Servlet容器
IOC和DI
- loC: Inversion of Control(控制反转)
- 控制: 资源的控制权(资源的创建、获取、销毁等)
- 反转: 和传统的方式不一样了
- Dl: DependencyInjection(依赖注入)
- 依赖: 组件的依赖关系,如NewsController依赖NewsServices
- 注入: 通过setter方法、构造器、等方式自动的注入(赋值)
注册组件
组件注册的各种方式
搭建测试工程
- 新建maven工程
- 父工程本身不编写业务代码, 删除src目录
- 新建spring模块, next->不需要勾选任何依赖
- 清理没有的模块文件
- 了解程序运行流程
package com.guigu.spring.ioc;
/**
* 主入口文件
*/
@SpringBootApplication
public class Spring01IocApplication {
public static void main(String[] args) {
// 1.跑起一个spring应用
// ConfigurableApplicationContext 继承自 ApplicationContext
// ApplicationContext是spring应用上下文对象, 也就是IOC容器
ConfigurableApplicationContext ioc = SpringApplication.run(Spring01IocApplication.class, args);
System.out.println("ioc=" + ioc);
// 2.获取容器内所有组件的名字
// spring启动会有很多默认组件
String[] names = ioc.getBeanDefinitionNames();
for (String name : names) {
System.out.println("name=" + name);
}
}
}
实验1
通过@Bean注解把自定义组件放入ioc容器中
package com.guigu.spring.ioc.bean;
import lombok.Data;
@Data
public class Person {
private String name;
private Integer age;
private String gender;
}
org.projectlombok
lombok
package com.guigu.spring.ioc;
/**
* 主入口文件
*/
@SpringBootApplication
public class Spring01IocApplication {
public static void main(String[] args) {
// 1.跑起一个spring应用
// ConfigurableApplicationContext 继承自 ApplicationContext
// ApplicationContext是spring应用上下文对象, 也就是IOC容器
ConfigurableApplicationContext ioc = SpringApplication.run(Spring01IocApplication.class, args);
System.out.println("ioc=" + ioc);
// 2.获取容器内所有组件的名字
// spring启动会有很多默认组件
String[] names = ioc.getBeanDefinitionNames();
for (String name : names) {
System.out.println("name=" + name);
}
}
// 3.自己创建一个组件,并放入容器中
// 容器中的每个组件都有名字, 默认名字是方法名
// 也可以在注解中指定组件的名字
@Bean("zhangsan")
public Person person() {
Person p = new Person();
p.setName("张三");
p.setGender("男");
p.setAge(18);
return p;
}
}
如maven项目出现问题, 可以这样排查
- 先检查file->settings中的maven配置 (这里的配置只对当前项目生效)
- 再检查新建项目时的maven配置 (这里的配置在新建项目时生效)
- 清理idea的缓存, 让idea重新下载项目的依赖
实验2
理解组件的获取方式
package com.guigu.spring.ioc;
/**
* 主入口文件
*/
@SpringBootApplication
public class Spring01IocApplication {
public static void main(String[] args) {
// 1.跑起一个spring应用
ConfigurableApplicationContext ioc = SpringApplication.run(Spring01IocApplication.class, args);
// 4.获取容器中的组件对象
// 组件的四大特征: 名字, 类型, 对象, 作用域
// 4.1通过组件的名字获取对象
// 组件必须全局唯一, 如果重复, 最先声明的生效, 后声明的无效
// 如果组件不存在, 会抛异常:NoSuchBeanDefinitionException
// 只要组件存在, 就能精准获取
Person zhsngsan = (Person) ioc.getBean("zhangsan");
System.out.println("对象=" + zhsngsan);
// 4.2按照组件类型获取对象
// 按照类型获取一个, 如果该类型的对象有多个, 会抛异常:NoUniqueBeanDefinitionException
Person bean = ioc.getBean(Person.class);
System.out.println("对象=" + bean);
// 4.3按照组件类型获取所有对象
// 按照类型获取多个: 返回所有组件的List集合
Map type = ioc.getBeansOfType(Person.class);
System.out.println("对象=" + type);
// 4.4按照名称+类型获取对象
// 不需要类型转换, 也不用考虑ben是否唯一
Person iocBean = ioc.getBean("zhangsan", Person.class);
System.out.println("对象=" + iocBean);
}
}
理解组件的创建时机
- 在类中声明构造器, 输出日志
package com.guigu.spring.ioc.bean;
// @Data
public class Person {
private String name;
private Integer age;
private String gender;
public Person(String name, Integer age, String gender) {
System.out.println("Person全参构造执行");
this.name = name;
this.age = age;
this.gender = gender;
}
public Person() {
System.out.println("Person无参构造执行");
}
}
package com.guigu.spring.ioc;
/**
* 主入口文件
*/
@SpringBootApplication
public class Spring01IocApplication {
public static void main(String[] args) {
// 1.跑起一个spring应用
ConfigurableApplicationContext ioc = SpringApplication.run(Spring01IocApplication.class, args);
System.out.println("===ioc容器创建完成===");
}
// 5.组件的创建时机: 容器启动过程中就会创建组件对象
@Bean("zhangsan")
public Person person() {
return new Person();
}
}
理解组件的单实例特性
package com.guigu.spring.ioc;
/**
* 主入口文件
*/
@SpringBootApplication
public class Spring01IocApplication {
public static void main(String[] args) {
// 1.跑起一个spring应用
ConfigurableApplicationContext ioc = SpringApplication.run(Spring01IocApplication.class, args);
// 6重复获取组件
// 单实例特性: 所有组件默认是单例的, 每次获取直接从容器中拿, 容器会提前创建组件
Person bean1 = ioc.getBean(Person.class);
System.out.println("bean1=" + bean1);
Person bean2 = ioc.getBean(Person.class);
System.out.println("bean2=" + bean2);
Person bean3 = ioc.getBean(Person.class);
System.out.println("bean3=" + bean3);
}
@Bean("zhangsan")
public Person person() {
return new Person();
}
}
实验3
理解配置类注解和分层模型注解
- 组件也是框架的底层配置, 有两种方式配置方式:
- 通过配置文件, 指定配置
- 通过配置类, 指定配置
- 对于组件的配置主流是使用配置类进行配置
- 通过配置类可以分类管理组件的配置, 配置类本身也是容器中的组件
- 通过配置类完成组件的注册
package com.guigu.spring.ioc.config;
/**
* 如果把所有的bean都在启动类中进行注册, 是非常臃肿的
* 所以, 实际开发中都是在配置类中, 进行bean的注册
*/
@Configuration // 告诉spring容器, 这是一个配置类
public class PersonConfig {
@Bean("zhangsan")
public Person person() {
return new Person();
}
}
package com.guigu.spring.ioc;
/**
* 主入口文件
*/
@SpringBootApplication
public class Spring01IocApplication {
public static void main(String[] args) {
// 1.跑起一个spring应用
ConfigurableApplicationContext ioc = SpringApplication.run(Spring01IocApplication.class, args);
// 7.获取配置类中注册的组件
Person bean = ioc.getBean(Person.class);
System.out.println("bean=" + bean);
}
}
- MVC分层注解
先模拟下项目中MVC分层代码
未来更好的区分组件的用途, spring为我们提供了MVC分层注解
- 1、@Controller 控制器
- 2、@Service 服务层
- 3、@Repository 持久层
- 4、@Component 三层之外的其他组件
这些注解本质上是没有区别的, 都是注册组件而已, 只是分类使用提高代码可读性
package com.guigu.spring.ioc.controller;
@Controller
public class UserController {
}
package com.guigu.spring.ioc.dao;
@Repository
public class UserMapper {
}
package com.guigu.spring.ioc.service;
@Service
public class UserService {
}
package com.guigu.spring.ioc;
/**
* 主入口文件
*/
@SpringBootApplication
public class Spring01IocApplication {
public static void main(String[] args) {
ConfigurableApplicationContext ioc = SpringApplication.run(Spring01IocApplication.class, args);
// 8.查看MVC分层组件
UserController bean = ioc.getBean(UserController.class);
System.out.println("bean=" + bean);
UserService bean2 = ioc.getBean(UserService.class);
System.out.println("bean2=" + bean2);
}
}
实验4
分层注解能起作用的前提是, 这些组件必须在主程序所在包及其子包下
可以通过@ComponentScan() 注解指定spring组件生效的范围
package com.guigu.spring.ioc;
/**
* 主入口文件
*/
@ComponentScan(basePackages = "com.guigu.spring") // 组件批量扫描注解, 决定组件的生效范围, 默认是启动类所在包及其子包
@SpringBootApplication
public class Spring01IocApplication {
public static void main(String[] args) {
// 1.跑起一个spring应用
ConfigurableApplicationContext ioc = SpringApplication.run(Spring01IocApplication.class, args);
// 8.查看MVC分层组件
UserController bean = ioc.getBean(UserController.class);
System.out.println("bean=" + bean);
UserService bean2 = ioc.getBean(UserService.class);
System.out.println("bean2=" + bean2);
}
}
实验5
第三方依赖的代码都是只读的, 不能通过添加注解的方式注册到容器
第三方组件需要注册到容器中, 有两种方式
package com.guigu.spring.ioc.config;
@Configuration // 告诉spring容器, 这是一个配置类
public class PersonConfig {
//通过@Bean注解, 自己new对象, 注册给容器
@Bean
public CoreConstants coreConstants {
return new CoreConstants();
}
}
package com.guigu.spring.ioc;
/**
* 主入口文件
* 1.通过@Import()注解, 把第三方组件注册到容器中
* 2.该注解可以在启动类中使用, 也可以在任何组件中使用
* 3.使用该注解多个地方重复注册组件, 只会生效一个
*/
@Import(CoreConstants.class)
@SpringBootApplication
public class Spring01IocApplication {
public static void main(String[] args) {
}
}
// 如果大量注册第三方组件, 推荐新建AppConfig配置类进行管理
package com.guigu.spring.ioc.config;
/**
* 统一管理所以组件, 防止启动类臃肿
*/
@Import(CoreConstants.class)
@Import(其他第三方组件)
@Import(其他第三方组件)
@Configuration
public class AppConfig {
}
实验6
使用@Scope注解可以设置组件的作用域
package com.guigu.spring.ioc.config;
@Configuration // 告诉spring容器, 这是一个配置类
public class PersonConfig {
/**
* @Scope 调整组件的作用域
* 1. @Scope("singleton"): 单例模式, 是默认值,
* 容器初始化的时候提前创建组件, 每次获取, 拿到的都是同一个
* 2. @Scope("prototype"): 原型模式, 非单例
* 每次获取组件时, 都会创建一个新的组件,
* 3. @Scope("request"): 同一次请求单实例, 基本不用
* 4. @Scope("session"): 同一次会话创建一个实例, 基本不用
*/
// @Scope("singleton")
@Scope("prototype")
@Bean("zhangsan")
public Person person() {
return new Person();
}
}
package com.guigu.spring.ioc;
/**
* 主入口文件
*/
@SpringBootApplication
public class Spring01IocApplication {
public static void main(String[] args) {
ConfigurableApplicationContext ioc = SpringApplication.run(Spring01IocApplication.class, args);
// 9.理解组件的作用域
Person bean1 = ioc.getBean(Person.class);
System.out.println("bean1=" + bean1);
Person bean2 = ioc.getBean(Person.class);
System.out.println("bean2=" + bean2);
}
}
单例模式下, 组件全局只会创建一个, 多次获取拿到是同一个, 单例组件在容器初始化的时候就会提前创建
非单例模式下, 每次获取, 都会创建一个新的组件
默认情况下, 容器的组件都是单例模式, 并且是饿汉式单例, 使用@Lazy注解改为懒汉单例, 实现组件懒加载
package com.guigu.spring.ioc.config;
@Configuration // 告诉spring容器, 这是一个配置类
public class PersonConfig {
/**
* @Scope 调整组件的作用域
* 1. @Scope("singleton"): 单例模式, 是默认值,
* 容器初始化的时候提前创建组件, 每次获取, 拿到的都是同一个 ==> 饿汉单例
* 可以使用@Lazy注解, 实现懒加载 ==> 懒汉单例
* 2. @Scope("prototype"): 原型模式, 非单例
* 每次获取组件时, 都会创建一个新的组件,
*/
@Lazy // 单例模式下, 可以继续调整为懒加载
@Bean("zhangsan")
public Person person() {
return new Person();
}
}
package com.guigu.spring.ioc;
/**
* 主入口文件
*/
@SpringBootApplication
public class Spring01IocApplication {
public static void main(String[] args) {
ConfigurableApplicationContext ioc = SpringApplication.run(Spring01IocApplication.class, args);
// 9.理解组件的作用域
Person bean1 = ioc.getBean(Person.class);
System.out.println("bean1=" + bean1);
Person bean2 = ioc.getBean(Person.class);
System.out.println("bean2=" + bean2);
}
}
使用的时候再创建组件
实验7
理解FactoryBean工厂Bean
package com.guigu.spring.ioc.factory;
/**
* 场景: 如果制造某些对象比较复杂, 利用工厂方法来制造
*/
@Component
public class BYDFactory implements FactoryBean {
/**
* 调用此方法给容器中制造Bean
* @return
* @throws Exception
*/
@Override
public Car getObject() throws Exception {
System.out.println("BYDFactory 正在制造Car对象...");
Car car = new Car();
return car;
}
/**
* 说明制造的东西的类型 (为了多态)
* @return
*/
@Override
public Class> getObjectType() {
return Car.class;
}
/**
* 是否单例?
* 返回true: 单例
* 返回false: 多例
* @return
*/
@Override
public boolean isSingleton() {
return true;
}
}
package com.guigu.spring.ioc;
/**
* 主入口文件
*/
@SpringBootApplication
public class Spring01IocApplication {
public static void main(String[] args) {
ConfigurableApplicationContext ioc = SpringApplication.run(Spring01IocApplication.class, args);
// 10.理解工程Bean
// 通过工厂类的工厂方法制造bean
// FactoryBean制造的组件的类型, 是接口中泛型指定的类型, 组件名字是工厂自己的名称
// 根据类型获取容器中的bean
Car bean = ioc.getBean(Car.class);
System.out.println("bean=" + bean);
// 获取所有类型为Car的组件, 查看bean的名字
Map beansOfType = ioc.getBeansOfType(Car.class);
System.out.println("beansOfType=" + beansOfType);
}
}
实验8
理解按条件注册 @Conditional 注解
package com.guigu.spring.ioc.config;
@Configuration // 告诉spring容器, 这是一个配置类
public class PersonConfig {
// 需求: 判断当前电脑的操作系统是windows还是mac
// windows系统创建 bill 对象
// mac系统创建 joseph 对象
@Conditional(MacCondition.class)
@Bean("joseph")
public Person joseph() {
return new Person("乔布斯", 18, "男");
}
@Conditional(WindowsCondition.class)
@Bean("bill")
public Person bill() {
return new Person("比尔盖茨", 18, "男");
}
}
package com.guigu.spring.ioc.condition;
public class MacCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
Environment environment = context.getEnvironment();
String property = environment.getProperty("OS");
return property.contains("mac");
}
}
package com.guigu.spring.ioc.condition;
public class WindowsCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
// 获取系统环境对象
Environment environment = context.getEnvironment();
// 从系统环境对象中获取系统名称
String property = environment.getProperty("OS");
// 判断
return property.contains("Windows");
}
}
package com.guigu.spring.ioc;
/**
* 主入口文件
*/
@SpringBootApplication
public class Spring01IocApplication {
public static void main(String[] args) {
ConfigurableApplicationContext ioc = SpringApplication.run(Spring01IocApplication.class, args);
// 11. 按条件注册
// 拿到环境变量
ConfigurableEnvironment environment = ioc.getEnvironment();
String property = environment.getProperty("OS");
System.out.println("系统=" + property);
Map beans = ioc.getBeansOfType(Person.class);
System.out.println("beans=" + beans);
}
}
我的电脑时windows系统, 所以注入的比尔盖茨
修改系统的环境变量, 测试是否根据系统, 注入不同的组件
再次运行代码
实验9
@Conditional 衍生注解
对于一些常见的按需加载条件, spring官方已经提供了很多接口实现, 也就是衍生注解, 解放生产力
- 点进注解源码
- 选中接口 Conditional
- Ctrl+H查看接口的实现类
下面演示几个衍生注解的使用
package com.guigu.spring.ioc.bean;
public class Dog {
}
package com.guigu.spring.ioc.config;
@Configuration
public class DogConfig {
@ConditionalOnResource(resources = "classpath:haha.abc") // 系统中存在资源, 加载dog组件
@ConditionalOnMissingBean(name = "joseph") // 容器中没有joseph组件时, 加载dog组件, 注意: 这里存在类的加载顺序问题, 组件的创建顺序按照类名首字母排序, d在p的前面, 所以Dog创建时永远不存在Person, 就创建狗子
@ConditionalOnBean(value = {Person.class}) // 容器中有Person类型的组件时, 加载dog组件, 注意: 这里存在类的加载顺序问题, 组件的创建顺序按照类名首字母排序, d在p的前面, 所以Dog创建时永远不存在Person, 就不创建狗子
// 上面问题的解决: 有依赖关系的bean要放在一个文件中,并且依赖bean放于被依赖bean的后面
@Bean
public Dog dog() {
return new Dog();
}
}
package com.guigu.spring.ioc;
/**
* 主入口文件
*/
@SpringBootApplication
public class Spring01IocApplication {
public static void main(String[] args) {
// 1.跑起一个spring应用
ConfigurableApplicationContext ioc = SpringApplication.run(Spring01IocApplication.class, args);
// 12. 按条件注册--衍生注解
Map beansOfType = ioc.getBeansOfType(Dog.class);
System.out.println("beans=" + beansOfType);
}
}
运行结果1: 系统中有指定资源, dog组件注入容器, 没有资源就不加载