Spring架构中的核心理念就是IOC和AOP,可以说,Spring的强大离不开这两大特性。
因为spring boot推荐采用注解开发,所以文中主要介绍基于注解的Spring Ioc。
IoC容器简介
Spring IoC 容器是个管理 Bean(在Spring 中把每个需要管理的对象称为 Spring Bean ,简称 Bean ) 的容器,在Spring 定义中,它要求所有的 IoC 容器都需要实现接口 BeanFactory ,可以说,BeanFactory 是IoC容器的基础,下面看下这个接口的源码:
除了图中标记的这个方法之外,还有以下方法(由于篇幅,这里就直接copy出来了,感兴趣的小伙伴可以自己去查看源码)
Object getBean(String name, Object... args) throws BeansException;
<T> T getBean(Class<T> requiredType) throws BeansException;
<T> T getBean(Class<T> requiredType, Object... args) throws BeansException;
......
可以看到这个接口赋予了ioc容器可以使用bean的类型和名称来获取对应的bean。
由于 BeanFactory 的功能还不够强大,因此 Spring 在BeanFactory 的基础 还设计了 一个更为
高级的 接口ApplicationContext 。它是 BeanFactory 的子接口之一,在 Spring 的体系中 BeanFactory和
ApplicationContext 是最为重要的接口设计 ,在现实中我们使用的大部 Spring IoC 容器是
ApplicationContext 接口的实现类 ,它们的关系如图 所示
在图中可以看到, ApplicationContext 接口通过继承上级接口,进而继承 BeanFactory 接口, 但是在
BeanFactory 基础上,扩展了消息国际 化接口(MessageSource )、环境可配置接口EnvironmentCapable )、应用事件发布接口( ApplicationEventPublisher)和 资源模式解析接口(ResourcePatternResolver ),所以它的功能会更为强大。
以它的功能会更为强大。
基于注解的AnnotationConfigApplicationContext 容器基本使用
因为在Spring Boot 当中我们主要是通过注解来装配 Bean到 Spring IoC 容器中,所以这里主要介绍一个基于注解的 IoC 容器,它就是AnnotationConfigApplicationContext
首先我们创建一个User对象,准备将它交给Spring管理
@Data
public class BasicUser {
private Long id;
private String userName;
private String note;
}
然后定义一个配置类,用来向Spring容器声明对象信息:
@Configuration
public class IocBasicAppConfig {
@Bean(name = "basicUser")
public BasicUser initUser() {
BasicUser user = new BasicUser();
user.setId(1L);
user.setUserName("赵子明");
user.setNote("这是一个普通用户");
return user;
}
这样Spring容器就可以知道我们声明的这个“basicUser”的bean了。
下面写一个测试类看一下:
@Slf4j
public class IocTest {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(IocBasicAppConfig.class);
BasicUser user = context.getBean(BasicUser.class);
log.warn(user.toString());
}
}
可以看到代码中在创建容器时,指定了刚刚创建的配置类,“new AnnotationConfigApplicationContext(IocBasicAppConfig.class)”
然后运行main方法,可以如下日志被打印出来,说明成功从容器中拿到bean了
怎么装配自己的bean
上面的示例是容器的基本用法,但是如果有很多个bean需要交由容器管理,如果每个都用@Bean注解方式,那无疑是一件工作量很大的事情,所以spring这里提出了基于扫描机制来将bean交给容器管理。
扫描机制中的重要注解如下:
@ComponentScan 指定扫描规则
@Component 标记可以被扫描到的bean
查看@ComponentScan注解的源码可以发现,该注解支持许多扫描规则的配置,这里以指定扫描包和指定扫描时忽略不加入容器的配置举例。
先定义个配置类,用来配置扫描规则:
@ComponentScan(basePackages = {"com.zzm.iocscan"},
excludeFilters = {@ComponentScan.Filter(classes = {Service.class, IocExclude.class})})
@Configuration
@Slf4j
public class ScanAppConfig {
@Bean(value = "dataSource")
public DataSource getDataSource(){
Properties properties = new Properties();
properties.setProperty("driver","com.mysql.jdbc.Driver");
properties.setProperty("url","jdbc:mysql://zzm.zgj.cn:3306/ZzmSpringBootLearn");
properties.setProperty("username","admin");
properties.setProperty("password","admin");
DataSource dataSource = null;
try {
dataSource = BasicDataSourceFactory.createDataSource(properties);
}catch (Exception e){
log.warn("创建数据源失败:", e);
}
return dataSource;
}
}
这里我们指定了扫描的包是“com.zzm.iocscan”,并且被@Service和@IocExclude(自己定义的注解)这两个注解标记的bean会在扫描时忽略掉,不加入容器中。下面是相关的bean以及自定义注解@IocExclude
/**
* @Author: 赵子明
* @Date: 2023/4/5
* @Description: IocExclude
* @Version: ioc注入排除注解,被该注解标记的bean,将不会自动注入到ioc容器中
*/
public @interface IocExclude {
}
@Component("user")
@Data
public class ScanUser {
@Value(value = "2")
private Long id;
@Value(value = "赵子明")
private String userName;
@Value(value = "这是通过component注解注入的bean")
private String note;
}
@IocExclude
@Slf4j
public class UserIocExcludeService {
public void printUser(ScanUser user){
log.warn("UserIocExcludeService-printUser结果:【{}】",user.toString());
}
}
@Service
@Slf4j
public class UserService {
public void printUser(ScanUser user){
log.warn("UserService-printUser结果:【{}】",user.toString());
}
}
这几个类都放在“com.zzm.iocscan”包下面。按照我们配置的扫描规则,UserService 和UserIocExcludeService 这两个bean是不会加入到spring容器中的,而ScanUser 和dataSource则会被正常扫描;
下面创建一个测试类,代码如下:
@Slf4j
public class IocScanTest {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(ScanAppConfig.class);
userTest(context);
getDataSourceTest(context);
userServiceTest(context);
userIocExcludeServiceTest(context);
}
private static void userTest(ApplicationContext context){
ScanUser user = context.getBean(ScanUser.class);
log.warn(user.toString());
}
private static void userServiceTest(ApplicationContext context){
UserService userService = context.getBean(UserService.class);
userService.printUser(context.getBean(ScanUser.class));
}
private static void userIocExcludeServiceTest(ApplicationContext context){
UserIocExcludeService userService = context.getBean(UserIocExcludeService.class);
userService.printUser(context.getBean(ScanUser.class));
}
private static void getDataSourceTest(ApplicationContext context){
DataSource dataSource = (DataSource) context.getBean("dataSource");
log.warn("从ioc容器中获取到的数据源={}",dataSource.toString());
}
运行main方法,得到日志如下:
可以看到ScanUser 和dataSource可以从容器中获取到,而UserService获取不到,因为UserService用了@Service注解标记,我们的扫描规则中设置了忽略被其标记的类。
下面将获取UserService的方法注释掉,继续运行main方法
可以看到UserIocExcludeService 这个也没有被扫描到容器中,因为UserIocExcludeService 用了@IocExclude注解标记,我们的扫描规则中设置了忽略被其标记的类。
可以有人想问,既然扫描机制这么方便,那为什么还用@Bean注解呢,因为有时候我们需要引入第三方的jar包,如上述的dataSource,此时就可以使用@Bean很方便的将其对象注入到Spring容器中了
@Bean(value = "dataSource")
public DataSource getDataSource(){
Properties properties = new Properties();
properties.setProperty("driver","com.mysql.jdbc.Driver");
properties.setProperty("url","jdbc:mysql://zzm.zgj.cn:3306/ZzmSpringBootLearn");
properties.setProperty("username","admin");
properties.setProperty("password","admin");
DataSource dataSource = null;
try {
dataSource = BasicDataSourceFactory.createDataSource(properties);
}catch (Exception e){
log.warn("创建数据源失败:", e);
}
return dataSource;
}
关于@ComponentScan注解的其他扫描规则的配置,感兴趣的小伙伴可以自行尝试下。