在Springboot的启动类中,常常都是这么一块代码
@SpringBootApplication
public class JavaBaseApplication {
public static void main(String[] args) {
SpringApplication.run(JavaBaseApplication.class, args);
}
}
但其实这个run方法是有返回值的,返回的是ConfigurableApplicationContext
ConfigurableApplicationContext context = SpringApplication.run(JavaBaseApplication.class, args);
他就是ApplicationContext的子类
我们可以看一下他的类图,按一下control+alt+u
可以看到其实ApplicationContext 间接的继承了BeanFactory, 主要是对BeanFactory和一些其他父类进行了一个组合封装
比如我们常见的getBean方法,其实就是使用BeanFactory的getBean方法
public Object getBean(String name) throws BeansException {
this.assertBeanFactoryActive();
return this.getBeanFactory().getBean(name);
}
所以BeanFactory才是Spring的核心 !
BeanFactory 表面上有只有getBean()的方法,
其实它还可以进行
- 控制反转,
- 基本的依赖注入,
- 甚至Bean的生命周期管理等各种功能, 都是由他的实现类提供.
其主要实现类是DefaultListableBeanFactory
可以看一下DefaultSingletonBeanRegistry, 单例Bean管理就是在这里面
private final Map<String, Object> singletonObjects = new ConcurrentHashMap(256);
使用了一个map进行管理,key就是bean的名字,value就是这个实例对象.
因为是个私有属性,可以通过反射来查看
@SpringBootApplication
public class JavaBaseApplication {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
ConfigurableApplicationContext context = SpringApplication.run(JavaBaseApplication.class, args);
Field singletonObjects = DefaultSingletonBeanRegistry.class.getDeclaredField("singletonObjects");
singletonObjects.setAccessible(true);
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
Map<String,Object> map= (Map<String, Object>) singletonObjects.get(beanFactory);
map.entrySet().stream().filter(e->e.getKey().startsWith("component")).forEach(e->{
System.out.println(e.getKey()+" "+e.getValue());
});
}
}
我建了两个空的component来看一下,输出的结果如下
看一下 AppicationContext 中常见的一些方法, 因为他不只整合了BeanFactory,还有其他四个类呢! 我们再回顾一下.
MessageSource
context.getMessage()方法就是来自MessageSource类 , 主要是提供了一个翻译的方法, 通过key与value的版本,翻译成各个语言.
因为web应用面向全世界, 可以根据不同国家的语言来进行翻译, 一般会在浏览器的请求头中获取, 我们再进行翻译. 这个用的比较少,就跳过了.
ResourceLoader
context.getResource(String fileName)可以通过文件名获取resources中的文件.
比如我前两天做的那个请求日志打印包,就需要获取包中的自定义日志配置.
如果我早点看到这个源码,我就能一下子就想到如何去获取这部分资源,不用去百度了.
百度的方法是直接通过spring提供的类
Resource resource = new ClassPathResource("log4j-aop.properties");
回过头来,我们看一下这个context.getResource的内部实现,其内部继承了ResourceLoader
public interface ResourcePatternResolver extends ResourceLoader {
String CLASSPATH_ALL_URL_PREFIX = "classpath*:";
Resource[] getResources(String var1) throws IOException;
}
这个就是资源管理了, 还有就是这个前缀 一般看到的是classpath: 加了个*号表示还会去jar包中捞,
比如我们获取META-INF/spring.factories
EnvironmentCapable
还有我们比较常用的获取系统资源, 初级只知道用@Value等注入手段, 但如果你去翻翻源码, 就会发现有很多spring的资源没去用
比如简单的获取配置文件中的端口号
System.out.println(context.getEnvironment().getProperty("server.port"));
可以看到这个方法,接口向上就是EnvironmentCapable
一般用这个方法就是在一些静态方法中无法使用@Value注入时,可以这样做.
但有可能获取配置为空, 具体的问题出现原因,后面往下看到了再补充.
ApplicationEventPublisher
这个主要就是事件的发送与监听,主要用于解耦.
比如component1是一个用户注册类, 注册完后需要做一些其他操作,比如短信通知,邮箱通知,但是都不重要.
因为我主要负责的是让用户注册成功即可, 不管短信通知或者其他操作做与不做,成不成功,都不重要.
这样我代码里就很干净, 我只负责注册, 不用去注入其他操作类.
广播一下我注册好了, 其他人自己去听广播,该做什么自己去安排.
首先需要一个事件类, 一个发送通知的动作, N个接听
如我这是一个用于注册事件类, 需要继续ApplicationEvent
public class UserRegisterEvent extends ApplicationEvent {
public UserRegisterEvent(Object source){
super(source);
}
}
一个推送事件功能,ApplicationContext有几成,但如果你看了源码就知道其实就是集成ApplicationEventPublisher, 所以我们直接注入这个类也是可以的.
@Component
public class Component1 {
@Resource
private ApplicationEventPublisher publisher;
// 这里是一个注册的方法
public void register(){
//注册操作
System.out.println("用户注册");
//完成后进行通知
publisher.publishEvent(new UserRegisterEvent(this));
}
}
然后写个监听这个事件的类,使用注解开启监听,参数就是监听的事件类
@Component
public class Component2 {
@EventListener
public void listener(UserRegisterEvent event){
//因为,上面事件类有构造方法,在组件1中塞入自己, 在这里就可以知道是谁发的事件
System.out.println(event);
//接收到事件后执行需要完成操作
System.out.println("短信通知");
}
}
而且事件不止一个类可以监听, 比如我邮箱通知的Component3, 微信通知的Component4 都可以监听同一个事件, 而不用因为多了一些操作,而去修改Component1中的注册方法, 以此来进行解耦.
这里在启动类偷个懒不注入了,直接getBean()调一下
context.getBean(Component1.class).register();
最后的输出结果如下