Java中调用参数是数组的存储过程
首先,Spring bean的默认加载顺序是怎么控制的
工程中有2个bean,A和B,其中必须先初始化A再初始化B,但是没有depend-on或者Order等方式去保证,只不过恰好刚好这么运行着没出事,但是突然增加了一个C之后,就先初始化B再初始化A导致问题,但是在主干版本上却没问题。
解决这个问题其实很简单,depend-on即可,但是为什么会分支版本上会增加C后就改变AB的初始化顺序?为什么主干版本上同样添加没问题呢?可以看spring的源码 DefaultListableBeanFactory 类中有
private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<String, BeanDefinition>(256);
这个map保存的就是xml中定义的bean结构,spring解析定义的bean之后,保存到这里,其中key为beanid,value为bean的详细信息,根据beanid算出hashCode分别为1505068002,1682286698,从定义可以看到这是一个ConcurrentHashMap,在获取到定义后,spring在初始化的时候是怎么初始化的呢?显然也是从这个定义里取值,为了简化问题,我们理解为是类似如下方式
for(Entry<String,BeanDefinition> entry : beanDefinitionMap)
去遍历bean然后根据BeanDefinition来初始化,考虑分支版本的是用JDK1.6,主干是用JDK1.8,不同ConcurrentHashMap的实现是否会导致取出来的顺序不一样呢?
JDK1.8中,ConcurrentHashMap 的实现是这样的,数组Node<K,V>[]table,每个元素为一个Node,每个元素直接落到Node上,去追加链表或者在红黑树上,总之是一次hash
JDK1.6中,ConcurrentHashMap 的实现是这样的,先Segment<K,V>[]segments来进行一个分段,然后每个Segment里再包含元素 HashEntry<K,V>[] table,即,会对每个元素会有2次hash,第一次定位到Segment,第二次在Segment内部定位到自己的位置userService
可以知道,在JDK1.8中2个bean的位置是固定的(所以主干版本同样添加但是AB初始化顺序不变),但是在JDK1.6中可能会发生这种情况:B在第一个Segment中,A在第二个Segment中,导致取出来的时候先获取到B,后取出来A,所以出现了空指针,同样,也可以解释,为什么新增了1个id为C的bean就导致了问题,但是之前没问题,因为新增bean导致ConcurrentHashMap中部分bean所在的Segment发生变化。
当然,对有依赖关系显然是显式指定出来的好,不然像这样坑后来人就不好了
使用Spring @DependsOn控制bean加载顺序
spring容器载入bean顺序是不确定的,spring框架没有约定特定顺序逻辑规范。但spring保证如果A依赖B(如beanA中有@Autowired B的变量),那么B将先于A被加载。但如果beanA不直接依赖B,我们如何让B仍先加载呢?
控制bean初始化顺序
可能有些场景中,bean A 间接依赖 bean B。如Bean B应该需要更新一些全局缓存,可能通过单例模式实现且没有在spring容器注册,bean A需要使用该缓存;因此,如果bean B没有准备好,bean A无法访问。
另一个场景中,bean A是事件发布者(或JMS发布者),bean B (或一些) 负责监听这些事件,典型的如观察者模式。我们不想B 错过任何事件,那么B需要首先被初始化。
简言之,有很多场景需要bean B应该被先于bean A被初始化,从而避免各种负面影响。我们可以在bean A上使用@DependsOn注解,告诉容器bean B应该先被初始化。下面通过示例来说明。
示例说明
示例通过事件机制说明,发布者和监听者,然后通过spring配置运行。为了方便说明,示例进行了简化。
EventManager.java
事件管理类,维护监听器列表,通过单例方法获取事件管理器,可以增加监听器或发布事件。
- import java.util.ArrayList;
- import java.util.List;
- import java.util.function.Consumer;
- public class EventManager {
- private final List<Consumer<String>> listeners = new ArrayList<>();
- private EventManager() {
- }
- private static class SingletonHolder {
- private static final EventManager INSTANCE = new EventManager();
- }
- public static EventManager getInstance() {
- return SingletonHolder.INSTANCE;
- }
- public void publish(final String message) {
- listeners.forEach(l -> l.accept(message));
- }
- public void addListener(Consumer<String> eventConsumer) {
- listeners.add(eventConsumer);
- }
- }
EventPublisherBean.java
事件发布类,通过EventManager类发布事件。
- import com.logicbig.example.EventManager;
- public class EventPublisherBean {
- public void initialize() {
- System.out.println("EventPublisherBean initializing");
- EventManager.getInstance().publish("event published from EventPublisherBean");
- }
- }
EventListenerBean.java
事件监听者,可以增加监听器。
- import com.logicbig.example.EventManager;
- public class EventListenerBean {
- private void initialize() {
- EventManager.getInstance().
- addListener(s ->
- System.out.println("event received in EventListenerBean : " + s));
- }
- }
AppConfig.java
配置运行类。
- @Configuration
- @ComponentScan("com.logicbig.example")
- public class AppConfig {
- @Bean(initMethod = "initialize")
- @DependsOn("eventListener")
- public EventPublisherBean eventPublisherBean () {
- return new EventPublisherBean();
- }
- @Bean(name = "eventListener", initMethod = "initialize")
- // @Lazy
- public EventListenerBean eventListenerBean () {
- return new EventListenerBean();
- }
- public static void main (String... strings) {
- new AnnotationConfigApplicationContext(AppConfig.class);
- }
- }
运行AppConfig的main方法,输出结果为:
- EventListenerBean initializing
- EventPublisherBean initializing
- event received in EventListenerBean : event published from EventPublisherBean
总结
如果我们注释掉@DependsOn("eventListener"),我们可能不确定获得相同结果。尝试多次运行main方法,偶尔我们将看到EventListenerBean 没有收到事件。为什么是偶尔呢?因为容器启动过程中,spring按任意顺序加载bean。
那么当不使用@DependsOn可以让其100%确定吗?可以使用@Lazy注解放在eventListenerBean ()上。因为EventListenerBean在启动阶段不加载,当其他bean需要其时才加载。这次我们仅EventListenerBean被初始化。
EventPublisherBean initializing
现在从新增加@DependsOn,也不删除@Lazy注解,输出结果和第一次一致,虽然我们使用了@Lazy注解,eventListenerBean在启动时仍然被加载,因为@DependsOn表明需要EventListenerBean。
@Lazy
@Lazy用于指定该Bean是否取消预初始化。主要用于修饰Spring Bean类,用于指定该Bean的预初始化行为,
使用该Annotation时可以指定一个boolean型的value属性,该属性决定是否要预初始化该Bean
- lazy代表延时加载,lazy=false,代表不延时,如果对象A中还有对象B的引用,会在A的xml映射文件中配置b的对象引用,多对一或一对多,不延时代表查询出对象A的时候,会把B对象也查询出来放到A对象的引用中,A对象中的B对象是有值的。
- lazy=true代表延时,查询A对象时,不会把B对象也查询出来,只会在用到A对象中B对象时才会去查询,默认好像是false,你可以看看后台的sql语句的变化就明白了,一般需要优化效率的时候会用到
- @Lazy(true)
- @Component
- public class Chinese implements Person{
- //codes here
- }
@DependsOn用于强制初始化其他Bean。可以修饰Bean类或方法,使用该Annotation时可以指定一个字符串数组作为参数,每个数组元素对应于一个强制初始化的Bean
- @DependsOn({"steelAxe","abc"})
- @Component
- public class Chinese implements Person{
- //codes here
- }
@Order
@Order注解的源码如下,value用例赋值,默认赋值Ordered.LOWEST_PRECEDENCE,即默认优先级最低:
- @Retention(RetentionPolicy.RUNTIME)
- @Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})
- public @interface Order {
- /**
- * The order value. Default is {@link Ordered#LOWEST_PRECEDENCE}.
- * @see Ordered#getOrder()
- */
- int value() default Ordered.LOWEST_PRECEDENCE;
- }
再来看下Order接口的源码如下,一目了然,值越小优先级越高,即被优先加载(precedence即优先级):
- public interface Ordered {
- /**
- * Useful constant for the highest precedence value.
- * @see java.lang.Integer#MIN_VALUE
- */
- int HIGHEST_PRECEDENCE = Integer.MIN_VALUE;//值越小,优先级越高
- /**
- * Useful constant for the lowest precedence value.
- * @see java.lang.Integer#MAX_VALUE
- */
- int LOWEST_PRECEDENCE = Integer.MAX_VALUE;//值越大,优先级越低
- /**
- * Return the order value of this object, with a
- * higher value meaning greater in terms of sorting.
- * <p>Normally starting with 0, with {@code Integer.MAX_VALUE}
- * indicating the greatest value. Same order values will result
- * in arbitrary positions for the affected objects.
- * <p>Higher values can be interpreted as lower priority. As a
- * consequence, the object with the lowest value has highest priority
- * (somewhat analogous to Servlet "load-on-startup" values).
- * @return the order value
- */
- int getOrder();
拓展①:为何在类上加@Order就可以对类(映射到容器中就是bean)有效排序了?
先了解下Java强大的注解功能,可以参考Java注解教程及自定义注解
拓展②:给类@Order注解后spring容器具体怎么给bean排序的呢?
在@Order注解的源码上,作者写了相关信息,感兴趣可以查看下
拓展③:@Order的作用域可以是类、方法、类成员,方法和类成员暂时没有测试,保留。
Springboot的@AutoConfigureAfter注解,手动的指定Bean的实例化顺序
Springboot的@AutoConfigureAfter注解,手动的指定Bean的实例化顺序。了解Spring内Bean的解析,加载和实例化顺序机制有助于我们更好的使用Spring/Springboot,避免手动的去干预Bean的加载过程,搭建更优雅的框架。
Spring容器在实例化时会加载容器内所有非延迟加载的单例类型Bean,看如下源码:
- public abstract class AbstractApplicationContext extends DefaultResourceLoader
- implements ConfigurableApplicationContext, DisposableBean {
- //刷新Spring容器,相当于初始化
- public void refresh() throws BeansException, IllegalStateException {
- ......
- // Instantiate all remaining (non-lazy-init) singletons.
- finishBeanFactoryInitialization(beanFactory);
- }
- }
- public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory
- implements ConfigurableListableBeanFactory, BeanDefinitionRegistry, Serializable {
- /** List of bean definition names, in registration order */
- private volatile List<String> beanDefinitionNames = new ArrayList<String>(256);
- public void preInstantiateSingletons() throws BeansException {
- List<String> beanNames = new ArrayList<String>(this.beanDefinitionNames);
- for (String beanName : beanNames) {
- ......
- getBean(beanName); //实例化Bean
- }
- }
- }
ApplicationContext内置一个BeanFactory对象,作为实际的Bean工厂,和Bean相关业务都交给BeanFactory去处理。
在BeanFactory实例化所有非延迟加载的单例Bean时,遍历beanDefinitionNames 集合,按顺序实例化指定名称的Bean。beanDefinitionNames 属性是Spring在加载Bean Class生成的BeanDefinition时,为这些Bean预先定义好的名称,看如下代码:
- public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory
- implements ConfigurableListableBeanFactory, BeanDefinitionRegistry, Serializable {
- public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
- throws BeanDefinitionStoreException {
- ......
- this.beanDefinitionNames.add(beanName);
- }
- }
BeanFactory在加载一个BeanDefinition(也就是加载Bean Class)时,将相应的beanName存入beanDefinitionNames属性中,在加载完所有的BeanDefinition后,执行Bean实例化工作,此时会依据beanDefinitionNames的顺序来有序实例化Bean,也就是说Spring容器内Bean的加载和实例化是有顺序的,而且近似一致,当然仅是近似。
Spring在初始化容器时,会先解析和加载所有的Bean Class,如果符合要求则通过Class生成BeanDefinition,存入BeanFactory中,在加载完所有Bean Class后,开始有序的通过BeanDefinition实例化Bean。
我们先看加载Bean Class过程,零配置下Spring Bean的加载起始于ConfigurationClassPostProcessor的postProcessBeanDefinitionRegistry(BeanDefinitionRegistry)方法,我总结了下其加载解析Bean Class的流程:
配置类可以是Spring容器的起始配置类,也可以是通过@ComponentScan扫描得到的类,也可以是通过@Import引入的类。如果这个类上含有@Configuration,@Component,@ComponentScan,@Import,@ImportResource注解中的一个,或者内部含有@Bean标识的方法,那么这个类就是一个配置类,Spring就会按照一定流程去解析这个类上的信息。
在解析的第一步会校验当前类是否已经被解析过了,如果是,那么需要按照一定的规则处理(@ComponentScan得到的Bean能覆盖@Import得到的Bean,@Bean定义的优先级最高)。
如果未解析过,那么开始解析:
解析内部类,查看内部类是否应该被定义成一个Bean,如果是,递归解析。
解析@PropertySource,也就是解析被引入的Properties文件。
解析配置类上是否有@ComponentScan注解,如果有则执行扫描动作,通过扫描得到的Bean Class会被立即解析成BeanDefinition,添加进beanDefinitionNames属性中。之后查看扫描到的Bean Class是否是一个配置类(大部分情况是,因为标识@Component注解),如果是则递归解析这个Bean Class。
解析@Import引入的类,如果这个类是一个配置类,则递归解析。
解析@Bean标识的方法,此种形式定义的Bean Class不会被递归解析
解析父类上的@ComponentScan,@Import,@Bean,父类不会被再次实例化,因为其子类能够做父类的工作,不需要额外的Bean了。
在1,3,4,6中都有递归操作,也就是在解析一个Bean Class A时,发现其上能够获取到其他Bean Class B信息,此时会递归的解析Bean Class B,在解析完Bean Class B后再接着解析Bean Class A,可能在解析B时能够获取到C,那么也会先解析C再解析B,就这样不断的递归解析。
在第3步中,通过@ComponentScan扫描直接得到的Bean Class会被立即加载入beanDefinitionNames中,但@Import和@Bean形式定义的Bean Class则不会,也就是说正常情况下面@ComponentScan直接得到的Bean其实例化时机比其他两种形式的要早。
通过@Bean和@Import形式定义的Bean Class不会立即加载,他们会被放入一个ConfigurationClass类中,然后按照解析的顺序有序排列,就是图片上的 “将配置类有序排列“。一个ConfigurationClass代表一个配置类,这个类可能是被@ComponentScan扫描到的,则此类已经被加载过了;也可能是被@Import引入的,则此类还未被加载;此类中可能含有@Bean标识的方法。
Spring在解析完了所有Bean Class后,开始加载ConfigurationClass。如果这个ConfigurationClass是被Import的,也就是说在加载@ComponentScan时其未被加载,那么此时加载ConfigurationClass代表的Bean Class。然后加载ConfigurationClass内的@Bean方法。
顺序总结:@ComponentScan > @Import > @Bean
下面看实际的启动流程:
此图顺序验证小框架:Spring Bean解析,加载及实例化顺序验证小框架
Bean Class的结构图如上所示,A是配置类的入口,通过A能直接或间接的引入一个模块。
此时启动Spring容器,将A引入容器内。
如果A是通过@ComponentScan扫描到的,那么此时的加载顺序是:
A > D > F > B > E > G > C
如果A是通过@Import形式引入的,那么此时的加载顺讯是:
D > F > B > E > G > A > C
当然以上仅仅代表着加载Bean Class的顺序,实际实例化Bean的顺序和加载顺序大体相同,但还是会有一些差别。
Spring在通过getBean(beanName)形式实例化Bean时,会通过BeanDefinition去生成Bean对象。在这个过程中,如果BeanDefinition的DependsOn不为空,从字面理解就是依赖某个什么,其值一般是某个或多个beanName,也就是说依赖于其他Bean,此时Spring会将DependsOn指定的这些名称的Bean先实例化,也就是先调用getBean(dependsOn)方法。我们可以通过在Bean Class或者@Bean的方法上标识@DependsOn注解,来指定当前Bean实例化时需要触发哪些Bean的提前实例化。
当一个Bean A内部通过@Autowired或者@Resource注入Bean B,那么在实例化A时会触发B的提前实例化,此时会注册A>B的dependsOn依赖关系,实质和@DependsOn一样,这个是Spring自动为我们处理好的。
了解Spring Bean的解析,加载及实例化的顺序机制能够加深对Spring的理解,搭建更优雅简介的Spring框架。
Java中调用参数是数组的存储过程的更多相关文章
- Java中调用c/c++语言出现Exception in thread "main" java.lang.UnsatisfiedLinkError: Test.testPrint(Ljava/lang/String;)V...错误
错误: Exception in thread "main" java.lang.UnsatisfiedLinkError: Test.testPrint(Ljava/lang/S ...
- java中调用dll文件的两种方法
一中是用JNA方法,另外是用JNative方法,两种都是转载来的, JNA地址:http://blog.csdn.net/shendl/article/details/3589676 JNativ ...
- Java 中可变参数
可变参数 Java 中可变参数 现在需要编写一个求和的功能,但是不知道有几个参数,在调用的时候才知道有几个参数,请问这如何实现呢? Java 给我们提供了一个 JDK 1.5 的新特性---可变参数 ...
- Java中调用MatLab返回值
当在Java中使用MatLab函数时,由于语言语法的不同,Matlab返回多个数据时,想在Java中获取到并进行使用.查阅了网上资料,翻箱倒柜加上自己实战,得出方法如下: 如MatLab函数返回的是N ...
- 在易语言中调用MS SQL SERVER数据库存储过程方法总结
Microsoft SQL SERVER 数据库存储过程,根据其输入输出数据,笼统的可以分为以下几种情况或其组合:无输入,有一个或多个输入参数,无输出,直接返回(return)一个值,通过output ...
- java中调用js脚本
JDK1.6加入了对Script(JSR223)的支持.这是一个脚本框架,提供了让脚本语言来访问Java内部的方法.你可以在运行的时候找到脚本引擎,然后调用这个引擎去执行脚本.这个脚本API允许你为脚 ...
- 如何在Java中调用Python代码
有时候,我们会碰到这样的问题:与A同学合作写代码,A同学只会写Python,而不会Java, 而你只会写Java并不擅长Python,并且发现难以用Java来重写对方的代码,这时,就不得不想方设法“调 ...
- 在Java中调用Python
写在前面 在微服务架构大行其道的今天,对于将程序进行嵌套调用的做法其实并不可取,甚至显得有些愚蠢.当然,之所以要面对这个问题,或许是因为一些历史原因,或者仅仅是为了简单.恰好我在项目中就遇到了这个问题 ...
- 在Java中调用Python代码
极少数时候,我们会碰到类似这样的问题:与A同学合作写代码, A同学只会写Python,不熟悉Java ,而你只会写Java不擅长Python,并且发现难以用Java来重写对方的代码,这时,就不得不想方 ...
随机推荐
- EXTJS 4.2 资料 Grid嵌套
如图: var ParentContCateId = 0; var start = 0; var limit = 20; DistributionPointForm = function () { E ...
- Mapped Statements collection does not contain value for TaskMapper.selectByPrimaryKey
Mapped Statements collection does not contain value for后面是什么类什么方法之类的: 错误原因有几种: 1.mapper.xml中没有加入name ...
- springMVC+MyBatis+Spring+maven 整合(1)
1.首先第一步.部署mybatis ; 1.1 下载myBatis MyBits前身是iBitis,相对于Hibernate而言,它是半自动化ORM框架.由于老板对性能要求的比较苛刻,不得不放弃我亲爱 ...
- PAT-乙级-1027. 打印沙漏(20)
1027. 打印沙漏(20) 时间限制 200 ms 内存限制 65536 kB 代码长度限制 8000 B 判题程序 Standard 作者 CHEN, Yue 本题要求你写个程序把给定的符号打印成 ...
- The 7th Zhejiang Provincial Collegiate Programming Contest->Problem G:G - Wu Xing
http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=3328 至今未看懂题意,未编译直接提交,然后 A了.莫名AC总感觉怪怪的. ...
- linux服务器初步印象,远程连接mysql数据库,传输文件,启动/关闭tomcat命令
1.连接服务器数据库,以Navicat连接mysql为例 1.1 常规 新建连接,连接名,主机名或ip地址:127.0.0.1 端口:3306用户名:(服务器端)root密码:(服务器端)pwd 1. ...
- mysql语句中把string类型字段转datetime类型
mysql语句中把string类型字段转datetime类型 在mysql里面利用str_to_date()把字符串转换为日期 此处以表h_hotelcontext的Start_time和En ...
- hibernate annotation注解 columnDefinition用法
column注解中的columnDefinition属性用于覆盖数据库DDL中的语句:(MySql) @Column(columnDefinition = "int(11) DEFAULT ...
- spring transactionmanager
Spring配置文件中关于事务配置总是由三个组成部分,分别是DataSource.TransactionManager和代理机制这三部分,无论哪种配置方式,一般变化的只是代理机制这部分. DataSo ...
- QT5.7交叉编译安装到arm(好多系列文章)
以下采用的系统为ubuntu16.04,开发板为迅为iTOP4412,4.3寸屏. 下载qt5.7源码qt-everywhere-opensource-src-5.7.0.tar.xz http:// ...