springboot学习笔记
老版的基本xml配置的spring简直就是一场配置灾难.
按官方的说法, 使用xml的好处:
修改配置之后, 不需要重新编译代码. 这样可以方面调整某些参数. (但是如果需要重启服务, 这好像也没有多大的意义, 我通常需要的是在线调整参数).
缺点:
配置太臃肿了, 编辑起来累人.
因此, 个人推荐直接使用注解.
官方文档:
https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#spring-core
@Bean
作用在方法之上, 定义一个bean对象, 这个对象是单例模式的. @Bean所在的class通常需要使用@Configuratoin, @Service, @Component中的一种来注解
@Configuration
public class MyConfig {
@Bean
String hello(){return "hello world";}
}
上例定义了一个name为"hello", 值为"hello world"的String对象.
@Component
当开启了EnableAutoConfiguration, springboot会自动搜索被@Component注解的class, 并将其注册为Bean对象
@Component中@bean注解的方法也会生成bean对象
@Configuration
它相当于加强版的@Component
@Configuration与@Component差别
处理嵌套的bean的方式不同
@Configuration与@Component的区别, 在于处理嵌套的bean时, 方式不一样.
- @Configuration处理嵌套的bean.
下面的例子中, 只会创建一个MyParam对象, "config1"和"config2"都关联的是同一个对象.
@Configuration
public class MyConfig {
private final static Logger log = LogManager.getLogger(MyConfig.class);
@Bean
public MyParam config1(){
return config2();
}
@Bean
public MyParam config2(){
return new MyParam(1);
}
}
- @Component处理嵌套的bean.
同样的例子, Component会创建两个MyParam, "config1"和"config2"关联的不是同一个对象
@Component
public class MyConfig {
private final static Logger log = LogManager.getLogger(MyConfig.class);
@Bean
public MyParam config1(){
return config2();
}
@Bean
public MyParam config2(){
return new MyParam(1);
}
}
CGLIB代理差异
@Configuration注解的class会被CGLIB代理, 生成的Bean对象其实是由代理之后的class生成的, 像这样
MyConfig$$EnhancerBySpringCGLIB$$e6716a4e
@Component不会被CGLib代理, 生成对bean就是原始的class构造的
@Service
等同于@Component, 没有任何差别, 可以相互替代.
通常用于注解业务层的class, 这样代码逻辑更清晰一些.
@Repository
基本等同于@Component, 但是它对DAO的异常做了特殊处理.
通常, 操作数据库时, 可能会抛出Hibernate的HibernateException, 或者是JPA的PersistenceException,
@Repository会将这些异常转换成SpringBoot的异常.
主要是用于区分不同的功能.
@Service通常用于业务层
@Repository通常用于DAO.
都可以使用@Component代替.
@Autowired
自动装配, 可以用于自动构造参数/成员对象.
本例中的@Configuration的作用, 后面再讲.
基本用法
作用于构造方法
@Configuration
public class MyParam {
private final static Logger log = LogManager.getLogger(MyParam.class);
public MyParam(){
log.info("MyParam()");
}
public MyParam(int x){
log.info("MyParam x = {}", x);
}
// 作用于构造方法, 自动装配时, 使用此构造方法
@Autowired
public MyParam(int x, int y){
/** (x, y的值是未知的, 不清楚spring是如何生成x, y的) */
log.info("MyParam x = {}, y = {}", x, y);
}
}
@Configuration
public class MyConfig {
private final static Logger log = LogManager.getLogger(MyConfig.class);
// 此处省略了 @Autowired, 因为此Class只有一个构造方法.
// springboot会查找名为"myParam"的bean, 如果没有, 则会查找类型MyParam
// 的bean.
// MyParam 参数将使用上面的public MyParam(int x, int y)方法来构造
public MyConfig(MyParam myParam){
log.info("construct MyConfig");
}
}
作用于普通方法
@Configuration
public class MyConfig {
private final static Logger log = LogManager.getLogger(MyConfig.class);
public static class MyParam{
int x;
public MyParam(int x){
this.x = x;
}
}
@Bean
MyParam arg1(){
log.info("arg1()");
return new MyParam(1);
}
@Bean
MyParam arg2(){
log.info("arg2()");
return new MyParam(2);
}
//springboot将搜索名为"arg1"的bean.
@Autowired
public void config(MyParam arg1) {
log.info("config");
}
}
作用于成员变量
@Configuration
public class MyConfig {
private final static Logger log = LogManager.getLogger(MyConfig.class);
// springboot将搜索名为"myParam"的对象, 自动赋值.
@Autowired
private MyParam myParam;
}
AutoWired搜索Bean的过程
先根据参数的类型(byType)查找bean. 如果没有找到, 则根据name(byName)来查找
如果定义这两种方式都定义了, 则默认情况下, 都使用byType查找到的bean, 例如
//此处相当于创建了一个type为"MyParam"的bean
@Configuration
public class MyParam {
private final static Logger log = LogManager.getLogger(MyParam.class);
public int x;
public MyParam(int x){
log.info("MyParam x = {}", x);
this.x = x;
}
}
@Configuration
public class MyConfig {
private final static Logger log = LogManager.getLogger(MyConfig.class);
//定义了name为"arg1"的bean
@Bean
MyParam arg1(){
log.info("arg1()");
return new MyParam(1);
}
//定义了name为"arg2"的bean
@Bean
MyParam arg2(){
log.info("arg2()");
return new MyParam(2);
}
//由于springboot context中已经有了类型"MyParam"的bean,
// 所以此处不会使用arg1()创建的bean.
@Autowired
public void config(MyParam arg1) {
log.info("config: {}", arg1.x);
}
}
上例中, springboot context中存在三个类型为MyParam的bean, 按byName搜索成功之后, 不会再往下搜索了.
如果public void config(MyParam arg1)
一定要使用arg1()创建的bean, 可以显式的指定name, 如下
@Autowired
@Qualifier("arg1")
public void config(MyParam arg1) {
log.info("config: {}", arg1.x);
}
使用@Qualifier
注解强制要求使用某个bean.
或者使用
@Configuration
public class MyConfig {
private final static Logger log = LogManager.getLogger(MyConfig.class);
//定义了name为"arg1"的bean
@Bean
@Primary
MyParam arg1(){
log.info("arg1()");
return new MyParam(1);
}
//定义了name为"arg2"的bean
@Bean
MyParam arg2(){
log.info("arg2()");
return new MyParam(2);
}
//由于springboot context中已经有了类型"MyParam"的bean,
// 所以此处不会使用arg1()创建的bean.
@Autowired
public void config(MyParam arg1) {
log.info("config: {}", arg1.x);
}
}
@Primary
上例中, @Configuration注解会byType创建了一个bean, 这个bean具有第一优先级. 使用@Primary可以实现相同的效果(注意: @Primary不会覆盖@Configuration创建的bean)
@Configuration
public class MyConfig {
private final static Logger log = LogManager.getLogger(MyConfig.class);
public static class MyParam {
public int x;
public MyParam(int x){
log.info("MyParam x = {}", x);
this.x = x;
}
}
@Bean
MyParam arg1(){
log.info("arg1()");
return new MyParam(1);
}
@Bean
@Primary
MyParam arg2(){
log.info("arg2()");
return new MyParam(2);
}
/** 装配的是arg2()创建的对象 */
@Autowired
public void config(MyParam arg) {
log.info("config: {}", arg.x);
}
/** 装配的也是arg2()创建的对象 */
@Autowired
public void config2(MyParam arg1) {
log.info("config2: {}", arg1.x);
}
}
Bean的定制化
官方文档
https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#beans-factory-nature
监听bean的创建和销毁事件
@Configuration
public class MyConfig implements InitializingBean, DisposableBean {
private final static Logger log = LogManager.getLogger(MyConfig.class);
@Override
public void afterPropertiesSet() throws Exception {
//MyConfig创建之后调用
log.info("[MyConfig] afterPropertiesSet");
}
@Override
public void destroy() throws Exception {
//MyConfig销毁之前调用
log.info("[MyConfig] destroy()");
}
}
或者使用注解的方式
@Configuration
public class MyConfig{
private final static Logger log = LogManager.getLogger(MyConfig.class);
@PostConstruct
public void afterPropertiesSet() throws Exception {
//MyConfig创建之后调用
log.info("[MyConfig] afterPropertiesSet");
}
@PreDestroy
public void destroy() throws Exception {
//MyConfig销毁之前调用
log.info("[MyConfig] destroy()");
}
}
装配ApplicationContext
bean可能需要操作ApplicationContext, 需要装配ApplicationContext对象. 也有两种方式.
@Configuration
public class MyConfig implements ApplicationContextAware { //方式1, 实现ApplicationContextAware
private final static Logger log = LogManager.getLogger(MyConfig.class);
//方式2, 自动装配
@Autowired
ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
log.info("[MyConfig] setApplicationContext");
}
}
装配Bean Name
springboot会为每个bean对象, 创建一个名称, bean可以通过以下方式获取到springboot为其创建的名称
@Configuration
public class MyConfig implements BeanNameAware {
private final static Logger log = LogManager.getLogger(MyConfig.class);
@Override
public void setBeanName(String name) {
log.info("set bean name: {}", name);
}
}
使用BeanPostProcessor批量定制
@Configuration
public class MyBeanPostProcessor implements BeanPostProcessor {
public static final Logger log = LogManager.getLogger(MyBeanPostProcessor.class);
private ConfigurableListableBeanFactory configurableBeanFactory;
@Autowired
public MyBeanPostProcessor(ConfigurableListableBeanFactory beanFactory) {
this.configurableBeanFactory = beanFactory;
}
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException {
/** 开始定制 bean */
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName)
throws BeansException {
/** 开始定制 bean */
return bean;
}
}
加速扫描过程
默认情况下, springboot会扫描所有依赖的jar包, 会影响启动速度.
spring提供了一个工具, 为所有的Component创建索引
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-indexer</artifactId>
<version>5.1.2.RELEASE</version>
<optional>true</optional>
</dependency>
</dependencies>
生成的jar/war中的META-INF下会出现一个索引文件spring.components
#
com.my.MyConfig2=org.springframework.stereotype.Component