在当下的Java服务开发中,Spring框架占有着主要的地位。比如Web服务开发,游戏服务开发。使用Spring可以更好的管理代码,减少代码之间的耦合,利用Spring现成的功能,减少某些功能的开发量,使框架设计更加优雅。所以了解和学习Spring框架是非常有必要的。
1. BeanDefinition
在Spring 中,一个Bean的信息被定义在 BeanDefinition
。
2. Spring 中自动生成的Bean的名字规则
一般来说,都是使用类的名字,并且把首字母变小写,比如UserService的Bean名字是userService,但是如果类的名字前两个都是大写字母,那么它的bean名字和类的名字一样,比如URL,它的bean名字也是URL。Bean名字的生成在java.beans.Introspector.decapitalize
的方法中,如下所示:
/**
* Utility method to take a string and convert it to normal Java variable
* name capitalization. This normally means converting the first
* character from upper case to lower case, but in the (unusual) special
* case when there is more than one character and both the first and
* second characters are upper case, we leave it alone.
*
* Thus "FooBah" becomes "fooBah" and "X" becomes "x", but "URL" stays
* as "URL".
*
* @param name The string to be decapitalized.
* @return The decapitalized version of the string.
*/
public static String decapitalize(String name) {
if (name == null || name.length() == 0) {
return name;
}
if (name.length() > 1 && Character.isUpperCase(name.charAt(1)) &&
Character.isUpperCase(name.charAt(0))){
return name;
}
char chars[] = name.toCharArray();
chars[0] = Character.toLowerCase(chars[0]);
return new String(chars);
}
如果在使用注解标记类的时候,比如@Service,@Component,它们都有一个name的属性,如果这个name不为空,将直接使用name的值做为名字。
也可以自定义bean名字自成策略,如下面所示:
@Configuration
@ComponentScan(basePackages = "org.example", nameGenerator = MyNameGenerator.class)
public class AppConfig {
...
}
3. Spring 循环依赖
如果两个类都使用构造方法注入,并且它们之间相互依赖,就会形成循环依赖,比如类 A和B,在A的构造方法中需要B,在B的构造方法中需要A,它们之间就会因为循环依赖,导致注入失败,如果Spring 发现了循环依赖,会抛出异常:BeanCurrentlyInCreationException
。解决循环依赖的方法是不使用构造方法注入,应该使用Setter方式注入。
4. BeanPostProcessor
在一个Bean被创建成功之后,在调用初化方法之前,会调用此类中的postProcessBeforeInitialization
方法,在调用初始化方法之后,会调用postProcessAfterInitialization
方法。所谓的初始化方法,即Bean中被@PostConstruct
注解标记的方法,或在xml配置中指定的init-method,它属于bean生命期的一部分。注意,这些方法对所有的bean都生效,即每一个bean被创建之后,都会调用此接口的方法。
5. 服务启动与停止:SmartLifecycle
有时候,在服务启动之后或者停止的时候,会执行一些额外的操作。比如服务启动成功之后,启动消息队列的监听接口或发布一下启动成功的消息,在停止服务的时候,需要刷新缓存数据到数据库,清空等待执行的任务等。这个时间,实现一个SmartLifecycle接口即可。如下面代码所示:
@Service
public class SmartLifeCyleImpl implements SmartLifecycle{
private boolean running =false;
@Override
public void start() {
System.out.println("===服务启动成功===");
running = true;
}
@Override
public void stop() {
System.out.println("===服务停止成功===");
}
@Override
public boolean isRunning() {
return running;
}
@Override
public int getPhase() {
//如果有多个类实现了此接口,使用此方法返回的值可以指定执行的顺序。默认值是Integer.MAX_VALUE
//在执行start方法时,值越小,越优化执行。执行stop方法时,值越大越先执行
return SmartLifecycle.super.getPhase();
}
}
- 注意,想要stop生效,不能使用kill -9 命令杀死进程,可以使用kill -15 命令。
- 在stop中异常执行某些任务
如果想要在stop中异步执行某些操作,需要重写stop(Runnable callback)方法,在执行完异步任务之后,调用 callback.run()方法。比如游戏开发中,异步保存数据或异步发送消息队列等。 - 修改stop方法等待的超时时间
其实stop方法类似jvm的回调钩子,让服务在停止的时候执行一些收尾操作,比如释放某些资源,保存数据等。但是它不能无限等待stop执行,必须有一个超时时间。因为stop方法是在DefaultLifecycleProcessor
类中调用的,所以这个超时间可以在这个类中配置timeoutPerShutdownPhase
,默认是30秒。
6. CommandLineRunner
在Spring Boot中,可以实现这个接口,在服务启动之后,会调用这个接口的run方法。
7. 单个Bean的初始化与销毁
在单个Bean中,可以使用@PostConstruct
注解在Bean创建完成之后完成一些初始化的工作,比如从数据库加载一些数据,缓存在内存中等。在Bean销毁时,可以使用@PreDestroy
注解,在Bean销毁之前做一些收尾工作,比如把缓存中的数据刷新到数据库等。
8. Bean的初始化方法调用顺序
- 调用被
@PostConstruct
标记的方法,它会在此Bean所有的依赖都注入成功之后调用。 - 调用
InitializingBean
接口实现的afterPropertiesSet
方法,它会在所有的Bean都完成初始化之后调用。 - 调用自定义的init()方法。
9. Bean的销毁方法调用顺序
- 调用被
@PreDestroy
注解标记的方法。如果使用kill -15 pid的方法关闭服务,被此注解标记的方法就会被调用。 - 调用
DisposableBean
接口实现的destroy
方法。 - 调用自定义的destroy方法
9. 如果是非web服务,需要注册jvm关闭钩子
在web服务中,web服务实现的ApplicatonContext接口中已经实现了自动注册jvm关闭钩子。如果是非web服务,需要开发者手动注册关闭钩子,如下面代码所示:
public final class Boot {
public static void main(final String[] args) throws Exception {
ConfigurableApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
// add a shutdown hook for the above context...
ctx.registerShutdownHook();
// app runs here...
// main method exits, hook is called prior to the app shutting down...
}
}
InitializingBean
所有Bean的BeanFactory类将Bean的属性都初始化完成之后,会调此接口的方法,这个接口只有一个方法afterPropertiesSet
。
在这个方法内,可以对Bean的实例进行一些检测,比如某个属性是否配置正确。但是Spring并不建议你使用这个接口,因为它会和Spring内存的代码耦合在一起,而是推荐使用@PostConstruct
注解。
PropertyPlaceholderConfigurer
它在Spring初始化配置的时候,用来修改配置中的占位符,把真实的数据赋值到占位符的字段上面。比如数据库的配置:
PropertyPlaceholderConfigurer
如果在配置的locations位置中找不到配置的文件,它默认会从SystemProperties中找查,可以通过修改systemPropertiesMode的值,来自定义它的加载模式。它有三种情况:
- never(0):不检测 system properties属性。
- fallback(1):如果在指定的文件中找不到某个属性的配置,再从system properties中查找,这是默认的方式
- override(2):先检查system properties中是否有某个属性的配置,如果有的话,覆盖掉指定配置文件中的属性配置。
FactoryBean
FactoryBean
是一个工厂的Bean,这个工厂用来创建和管理bean。它提供了三人方法:
- Object getObject(): Returns an instance of the object this factory creates. The instance can possibly be shared, depending on whether this factory returns singletons or prototypes.
- boolean isSingleton(): Returns true if this FactoryBean returns singletons or false otherwise.
- Class getObjectType(): Returns the object type returned by the getObject() method or null if the type is not known in advance.
如果想从ApplicationContext中获取某个FactoryBean自己的实例,需要在名字前面添加&。
@Primary 优先选择使用Bean
当一个接口有多种实现的时候,在使用@Autowired注解的时候就不知道选择哪一个实现作为此类型的Bean了。这个时候,可以使用@Primary注解标记优先使用某个Bean,如下面代码所示:
@Configuration
public class MovieConfiguration {
@Bean
@Primary
public MovieCatalog firstMovieCatalog() { ... }
@Bean
public MovieCatalog secondMovieCatalog() { ... }
// ...
}
在使用的时候,就会选择firstMovieCatalog。
public class MovieRecommender {
@Autowired
private MovieCatalog movieCatalog;
// ...
}
@Resource 根据Bean名字注入Bean
如果一个接口有多个Bean的实现,最好的方式是使用@Resource加bean名字进行注入。它也支持一些集合类型,比如Map,List,Set,但是@Resource只能应用在类的成员变量上面和类的单参数setter方法上面。
Spring的注解可以组合,或做为新注解的元注解
比如@Service注解,就是用@Compoent做为元注解。元注解的好处是你不用再重复实现这些注解的功能了,只需要扩展就可以了。比如这样一个场景,我们想获取项目中某些特定的类的class或bean,就可以自定义一个注解,然后使用@Component做为元注解。等项目启动完成之后,被自定义注解标记的类的bean就可以在ApplicationContext中获取了。