黑马程序员Spring视频教程,全面深度讲解spring5底层原理 学习笔记

介绍

为了更加符合我自己的学习习惯,完整详细的呈现学习内容,为了减少工作量,该博客内容以及代码主要参考https://blog.csdn.net/qq_38505969/article/details/123739542 并对该博客内容稍加修改。

跟着黑马满一航老师的spring高级49讲做的学习笔记,本笔记跟视频内容的项目名称和代码略有不同,我将49讲的代码每一讲的代码都拆成了独立的springboot项目,并且项目名称尽量做到了见名知意,都是基于我自己的考量,代码都已经过运行验证过的,仅供参考。

视频教程地址:https://www.bilibili.com/video/BV1P44y1N7QG

代码仓库地址:https://gitee.com/CandyWall/spring-source-study

注:

1. 每一讲对应一个二级标题,每一个三级标题是使用子项目名称命名的,和我代码仓库的项目是一一对应的;

2. 代码里面用到了lombok插件来简化了Bean中的get()、set()方法,以及日志的记录的时候用了lombok的@Slf4j注解。

每个子项目对应的视频链接以及一些重要内容的笔记

第一讲 BeanFactory与ApplicationContext的区别与联系

spring_01_beanfactory_applicationcontext_differences_connections

p1 000-Spring高级49讲-导学

p2 001-第一讲-BeanFactory与ApplicationContext_1

测试代码:

@SpringBootApplication
@Slf4j
public class A01Application {
    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(A01Application.class, args);
        // class org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext
        System.out.println(context.getClass());
    }
}

到底什么是BeanFactory

  • 它是ApplicationContext的父接口

鼠标选中ConfigurableApplicationContext,按Ctrl + Alt + U打开类图,可以看到ApplicationContext的有个父接口是BeanFactory

  • BeanFactory才是 Spring 的核心容器, ApplicationContext 实现并扩展了它的功能

  • 这里的contextgetBean其实是BeanFactory提供的

        ConfigurableApplicationContext context = SpringApplication.run(A01Application.class, args);
        // 按 Ctrl + Alt + B 可以跳转到方法的实现中
         System.out.println(context.getBean(Component1.class));
  // class org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext
        System.out.println(context.getClass());

Ctrl + Alt左点击getBean方法可以看到该方法的实现

可以看出这个getBean() 是通过getBeanFactory()获取一个BeanFactory实现的

再通过追踪这个getBeanFactory()可以看到是由GenericApplicationContext这个类提供的方法实现

GenericApplicationContext这个类里面可以找到beanFactory作为成员变量出现。

按图索骥,AnnotationConfigServletWebServerApplicationContext又间接继承了GenericApplicationContext,在这个类里面可以找到beanFactory作为成员变量出现。

p3 002-第一讲-BeanFactory功能

BeanFactory接口中的方法(已知类名如何快速查找某一类 Ctrl+Shift+Alt+N后输入类名查找)(Ctrl+F12查看所有方法

查看springboot默认的ConfigurableApplicationContext类中的BeanFactory的实际类型

ConfigurableApplicationContext context = SpringApplication.run(A01Application.class, args);
// org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
// 查看实际类型
// class org.springframework.beans.factory.support.DefaultListableBeanFactory
System.out.println(beanFactory.getClass());

从打印结果可以了解到实际类型为DefaultListableBeanFactory,所以这里以BeanFactory的一个实现类DefaultListableBeanFactory作为出发点,进行分析。

这里我们暂且不细看DefaultListableBeanFactory,先看DefaultListableBeanFactory的父类DefaultSingletonBeanRegistry,先选中它,然后按F4,可以跳转到对应的源码,可以看到有个私有的成员变量singletonObjects,这就是放的所有的单例bean了。

先补充一下反射获取某个类的成员变量的步骤:
获取成员变量,步骤如下:
1、获取Class对象
2、获取构造方法
3、通过构造方法,创建对象
4、获取指定的成员变量(私有成员变量,通过setAccessible(boolean flag)方法暴力访问)
5、通过方法,给指定对象的指定成员变量赋值或者获取值
public void set(Object obj, Object value)
​ 在指定对象obj中,将此 Field 对象表示的成员变量设置为指定的新值
​ public Object get(Object obj)
​ 返回指定对象obj中,此 Field 对象表示的成员变量的值

代码如下:

Field singletonObjects = DefaultSingletonBeanRegistry.class.getDeclaredField("singletonObjects");
// 设置私有变量可以被访问
singletonObjects.setAccessible(true);
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
Map<String, Object> map = (Map<String, Object>) singletonObjects.get(beanFactory);
// 查看实际类型
// class org.springframework.beans.factory.support.DefaultListableBeanFactory
System.out.println(beanFactory.getClass());
map.entrySet().stream().filter(entry -> entry.getKey().startsWith("component")).forEach(System.out::println);

这里singletonObjects.get(beanFactory)为什么要传一个ConfigurableListableBeanFactory的变量进去呢?打印了这个beanFactory的实际类型为DefaultListableBeanFactory,查看其类图,可以了解到该类也实现了DefaultSingletonBeanRegistry接口,所以这里反射获取某个类的成员变量的get()方法中可以作为参数传进来。

结论:

BeanFactory表面上只有getBean,实际上控制反转、基本的依赖注入、直至Bean的生命周期的各种功能都有它的实现类提供

p4 003-第一讲-ApplicationContext功能1

ApplicationContextBeanFactory 多点啥?

多实现了四个接口:

  • MessageSource: 国际化功能,支持多种语言

  • ResourcePatternResolver: 通配符匹配资源路径

  • EnvironmentCapable: 环境信息,系统环境变量,*.properties*.application.yml等配置文件中的值

  • ApplicationEventPublisher: 发布事件对象

  1. MessageSource

resources目录下创建四个文件messages.propertesmessages_en.propertiesmessages_ja.propertiesmessages_zh.properties,然后分别在四个文件里面定义同名的key,比如在message_en.properties中定义hi=hello,在messages_ja.propertes中定义hi=こんにちは,在messages_zh中定义hi=你好,这样在代码中就可以根据这个**key hi和不同的语言类型**获取不同的value了。

System.out.println(context.getMessage("hi", null, Locale.CHINA));
System.out.println(context.getMessage("hi", null, Locale.ENGLISH));
System.out.println(context.getMessage("hi", null, Locale.JAPANESE));

运行结果如下:

p5 004-第一讲-ApplicationContext功能2,3

  1. ResourcePatternResolver

例1:获取类路径下的messages开头的配置文件

Resource[] resources = context.getResources("classpath:messages*.properties");
for (Resource resource : resources) {
    System.out.println(resource);
}

例2:获取spring相关jar包中的spring.factories配置文件

resources = context.getResources("classpath*:META-INF/spring.factories");
for (Resource resource : resources) {
    System.out.println(resource);
}
  1. EnvironmentCapable

获取系统环境变量中的java_home和项目的application.yml中的server.port属性

System.out.println(context.getEnvironment().getProperty("java_home"));
System.out.println(context.getEnvironment().getProperty("server.port"));

p6 005-第一讲-ApplicationContext功能4

  1. ApplicationEventPublisher

定义一个用户注册事件类,继承自ApplicationEvent

public class UserRegisteredEvent extends ApplicationEvent {
    public UserRegisteredEvent(Object source) {
        super(source);
    }
}

再定义一个监听器类,用于监听用户注册事件,类头上需要加@Component注解,将该类交给spring管理,定义一个处理事件的方法,参数类型为用户注册事件类的对象,方法头上需要加上@EventListener注解

@Component
@Slf4j
public class UserRegisteredListener {
    @EventListener
    public void userRegist(UserRegisteredEvent event) {
        System.out.println("UserRegisteredEvent...");
        log.debug("{}", event);
    }
}

接着再定义一个用户服务类,里面有个register(String username, String password)方法可以完成用户的注册,注册完毕后发布一下用户注册完毕事件

@Component
@Slf4j
public class UserService {
    @Autowired
    private ApplicationEventPublisher context;
    public void register(String username, String password) {
        log.debug("新用户注册,账号:" + username + ",密码:" + password);
        context.publishEvent(new UserRegisteredEvent(this));
    }
}

最后在Springboot启动类中调用一下UserService里面的register()方法注册一个新用户,UserRegisteredListener中就能处理这个用户注册完毕的事件,实现了UserService类和UserRegisteredListener类的解耦。

UserService userService = context.getBean(UserService.class);
userService.register("张三", "123456");

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值