Java Spring Boot 基础

Java Spring Boot 基础与应用,其设计目的是用来简化新Spring应用的初始搭建以及开发过程

一个项目有多套前端目录配置

在原先staticstemplates目录前面加一级目录,默认为default,后面其他套前端页面根据需求增加比如default1default2等,很容易因为开发工具出现问题导致不生效需要注意。最好重新maven clean,maven install一下

freemarker的路径修改,key修改配置文件,也可以修改Java配置文件,只能选择一种。我选择的是修改Java配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Configuration
public class FreemarkerConfig {

@Bean
public FreeMarkerConfigurer freeMarkerConfigurer(ShiroTag shiroTag){
FreeMarkerConfigurer configurer = new FreeMarkerConfigurer();
configurer.setTemplateLoaderPath("classpath:/default/templates");
Map<String, Object> variables = new HashMap<>(1);
variables.put("shiro", shiroTag);
configurer.setFreemarkerVariables(variables);

Properties settings = new Properties();
settings.setProperty("default_encoding", "utf-8");
settings.setProperty("number_format", "0.##");
configurer.setFreemarkerSettings(settings);
return configurer;
}

}

修改静态文件的映射配置

1
2
3
4
5
6
7
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/statics/**").addResourceLocations("classpath:/default/statics/");
}
}

还有一种是对resources文件进行改名的,不建议使用,这个是改maven配置文件

1
2
3
4
5
6
7
8
<resources>
<resource>
<directory>src/main/res</directory>
<includes>
<include>**/*</include>
</includes>
</resource>
</resources>

多数据源配置

使用自定义数据源配置,取消默认数据源配置

通过切面对DRUID进行多数据源控制。还需要配置使用哪个配置,如果没有这么配置启动的时候会报循环引用错误org.springframework.boot.autoconfigure.jdbc.DataSourceInitializerInvoker

1
2
3
4
5
6
7
8
sqlSessionFactory defined in class path resource [com/baomidou/mybatisplus/autoconfigure/MybatisPlusAutoConfiguration.class]
┌─────┐
| dataSource defined in class path resource [vip/infotech/test/common/datasource/DynamicDataSourceConfig.class]
↑ ↓
| firstDataSource defined in class path resource [vip/infotech/test/common/datasource/DynamicDataSourceConfig.class]
↑ ↓
| org.springframework.boot.autoconfigure.jdbc.DataSourceInitializerInvoker
└─────┘

auto-configuration通过@EnableAutoConfiguration注解@SpringBootApplication包含该注解。@EnableAutoConfiguration注解开启了spring ApplicationContext的自动配置功能,它通过扫描classpath下的组件,满足不同Conditions的bean注册到容器中。当AutoConfiguration实现类打上@Configuration标签,可以作为spring配置类,当AutoConfiguration实现类打上@EnableConfigurationProperties标签,可以绑定自定义属性或者更多Conditional bean注册方法。在DataSourceAutoConfiguration中:

  1. DataSourceAutoConfiguration打上了@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class })标签,这意味着只有当DataSource.class和EmbeddedDatabaseType.class出现在classpath时,DataSourceAutoConfiguration内的自动配置bean才可能被注册。
  2. DataSourceAutoConfiguration打上了@EnableConfigurationProperties(DataSourceProperties.class)标签,意味着配置文件中的属性和DataSourceProperties类自动绑定了。
    配置文件中spring:datasource开头的属性将自动绑定到DataSourceProperties对象上,其他注解,如@ConditionalOnMissingBean, @ConditionalOnClass and @ConditionalOnProperty等,标识只要条件满足,bean definition将注册到ApplicationContext中。

spring boot 会默认加载org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
DataSourceAutoConfiguration类使用了@Configuration注解向spring注入了一个dataSource bean。可能是使用的时候又去创建了一个dataSource,dataSource创建,需要依赖DataSourceInitializerInvoker,DataSourceInitializerInvoker创建需要依赖dataSource发现dataSource正在创建,循环依赖。

1
2
3
4
5
6
DataSourceInitializerInvoker(ObjectProvider<DataSource> dataSource, DataSourceProperties properties,
ApplicationContext applicationContext) {
this.dataSource = dataSource;
this.properties = properties;
this.applicationContext = applicationContext;
}

找到这个DataSourceInitializerInvoker是什么时候注入到IOC容器中的,因此我们找到了DataSourceAutoConfiguration,继而找到了DataSourceInitializationConfiguration这个配置类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
/*
可以看到在这里通过EnableConfigurationProperties将DataSourceProperties注入到了Bean容器中
然后通过@Import将前面DataSourceConfiguration的内容去扫描添加到容器中
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class })
@ConditionalOnMissingBean(type = "io.r2dbc.spi.ConnectionFactory")
@EnableConfigurationProperties(DataSourceProperties.class)
@Import({ DataSourcePoolMetadataProvidersConfiguration.class, DataSourceInitializationConfiguration.class })
public class DataSourceAutoConfiguration {

@Configuration(proxyBeanMethods = false)
// EmbeddedDatabaseConfiguration生效条件1
@Conditional(EmbeddedDatabaseCondition.class)
// EmbeddedDatabaseConfiguration生效条件2,还没有生成DataSource、XADataSource类型的实例。XADataSource分布式事务的数据源
@ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
// 上面两个condition都满足的话,EmbeddedDataSourceConfiguration被加载
@Import(EmbeddedDataSourceConfiguration.class)
// 内置数据源配置类,加载H2,DERBY,HSQL这3种默认数据源
protected static class EmbeddedDatabaseConfiguration {

}

@Configuration(proxyBeanMethods = false)
@Conditional(PooledDataSourceCondition.class)
@ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
// 上面两个condition都满足的话,import以下配置类
@Import({ DataSourceConfiguration.Hikari.class, DataSourceConfiguration.Tomcat.class,
DataSourceConfiguration.Dbcp2.class, DataSourceConfiguration.Generic.class,
DataSourceJmxConfiguration.class })
// 加载池化的数据源,Hikari、Tomcat、Dbcp2、Generic都代表一种数据源
protected static class PooledDataSourceConfiguration {

}
}

abstract class DataSourceConfiguration {
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(HikariDataSource.class)
@ConditionalOnMissingBean(DataSource.class)
@ConditionalOnProperty(name = "spring.datasource.type", havingValue = "com.zaxxer.hikari.HikariDataSource",
matchIfMissing = true)
static class Hikari {
@Bean
@ConfigurationProperties(prefix = "spring.datasource.hikari")
HikariDataSource dataSource(DataSourceProperties properties) {
HikariDataSource dataSource = createDataSource(properties, HikariDataSource.class);
if (StringUtils.hasText(properties.getName())) {
dataSource.setPoolName(properties.getName());
}
return dataSource;
}

}
}

static class EmbeddedDatabaseCondition extends SpringBootCondition {

private static final String DATASOURCE_URL_PROPERTY = "spring.datasource.url";

private final SpringBootCondition pooledCondition = new PooledDataSourceCondition();


public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
ConditionMessage.Builder message = ConditionMessage.forCondition("EmbeddedDataSource");
if (hasDataSourceUrlProperty(context)) {
return ConditionOutcome.noMatch(message.because(DATASOURCE_URL_PROPERTY + " is set"));
}
//PooledDataSourceConditions是否满足条件
if (anyMatches(context, metadata, this.pooledCondition)) {
return ConditionOutcome.noMatch(message.foundExactly("supported pooled data source"));
}
// com.zaxxer.hikari.HikariDataSource,org.apache.commons.dbcp2.BasicDataSource其中一个就表示满足条件
// classpath下有没有H2、DERBY、HSQL这3种类,如果有就返回
EmbeddedDatabaseType type = EmbeddedDatabaseConnection.get(context.getClassLoader()).getType();
if (type == null) {
return ConditionOutcome.noMatch(message.didNotFind("embedded database").atAll());
}
// 表示加载到了H2、DERBY、HSQL中的一种
return ConditionOutcome.match(message.found("embedded database").items(type));
}

private boolean hasDataSourceUrlProperty(ConditionContext context) {
Environment environment = context.getEnvironment();
if (environment.containsProperty(DATASOURCE_URL_PROPERTY)) {
try {
return StringUtils.hasText(environment.getProperty(DATASOURCE_URL_PROPERTY));
}
catch (IllegalArgumentException ex) {
// Ignore unresolvable placeholder errors
}
}
return false;
}

}


@Configuration(proxyBeanMethods = false)
@Import({ DataSourceInitializerInvoker.class, DataSourceInitializationConfiguration.Registrar.class })
class DataSourceInitializationConfiguration {

/**
* {@link ImportBeanDefinitionRegistrar} to register the
* {@link DataSourceInitializerPostProcessor} without causing early bean instantiation
* issues.
*/
static class Registrar implements ImportBeanDefinitionRegistrar {

private static final String BEAN_NAME = "dataSourceInitializerPostProcessor";

@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
BeanDefinitionRegistry registry) {
if (!registry.containsBeanDefinition(BEAN_NAME)) {
GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
beanDefinition.setBeanClass(DataSourceInitializerPostProcessor.class);
beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
// We don't need this one to be post processed otherwise it can cause a
// cascade of bean instantiation that we would rather avoid.
beanDefinition.setSynthetic(true);
registry.registerBeanDefinition(BEAN_NAME, beanDefinition);
}
}

}

}

@ConditionalOnMissingBean({ DataSource.class, XADataSource.class })。 不会执行里面代码,通过注解去掉DataSourceAutoConfiguration,启动时直接初始化加载用户定义的数据源。

解决方法:
可以dataSource脱离Spring控制也可以启动的时候排除DataSourceAutoConfiguration:

1
2
3
4
5
6
7
8
9
10
11
12
13
@Import({DynamicDataSourceConfig.class})
@SpringBootApplication(exclude= {DataSourceAutoConfiguration.class})
public class TaskApplication extends SpringBootServletInitializer {

public static void main(String[] args) {
SpringApplication.run(TaskApplication.class, args);
}

@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(AdminApplication.class);
}
}

JSON key 大写处理

处理Jason字符串中key为大写的数据,给实体类加上Json注解

1
2
@JsonProperty(value = "Code")
private int code;

表单验证

@Valid 注解

说明

注解 @Valid 的主要作用是用于数据效验,可以在定义的实体中的属性上,添加不同的注解来完成不同的校验规则

使用步骤

  1. Maven 引入相关依赖
  2. 自定义个异常类
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public class ParamErrorException extends RuntimeException {

    public ParamErrorException() {
    }

    public ParamErrorException(String msg) {
    super(msg);
    }

    }
  3. 自定义响应枚举类
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    public enum ResultEnum {

    SUCCESS(1000, "请求成功"), // 0
    PARAMETER_ERROR(1001, "请求参数有误!"),
    UNKNOWN_ERROR(9999, "未知的错误!");

    private Integer code;
    private String msg;

    ResultEnum(Integer code, String msg) {
    this.code = code;
    this.msg = msg;
    }

    public Integer getCode() {
    return code;
    }

    public String getMsg() {
    return msg;
    }
    }
  4. 自定义响应对象类
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    @Data
    public class ResponseResult {
    private Integer code;
    private String msg;

    public ResponseResult(){
    }

    public ResponseResult(ResultEnum resultEnum){
    this.code = resultEnum.getCode();
    this.msg = resultEnum.getMsg();
    }

    public ResponseResult(Integer code, String msg) {
    this.code = code;
    this.msg = msg;
    }
    }
  5. 实体类中添加 @Valid 相关注解
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    @Data
    public class User {
    @NotBlank(message = "姓名不为空")
    private String username;
    @NotBlank(message = "密码不为空")
    private String password;
    @Valid
    @NotNull(message = "用户信息不能为空")
    private UserInfo userInfo;
    }
  6. Controller 中添加 @Valid 注解
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    @RestController
    public class TestController {
    @Validated
    @GetMapping("/user/{username}")
    public ResponseResult findUserInfo(@PathVariable String username) {
    if (username == null || "".equals(username)) {
    throw new ParamErrorException("username 不能为空");
    }
    return new ResponseResult(ResultEnum.SUCCESS);
    }

    @PostMapping("/user")
    public ResponseResult save(@Valid @RequestBody User user) {
    return new ResponseResult(ResultEnum.SUCCESS);
    }
    }
  7. 全局异常处理类中处理 @Valid 抛出的异常
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    @Slf4j
    @RestControllerAdvice("xxx.xxx.xxx")
    public class GlobalExceptionHandler {

    /**
    * 忽略参数异常处理器
    *
    * @param e 忽略参数异常
    * @return ResponseResult
    */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(MissingServletRequestParameterException.class)
    public ResponseResult parameterMissingExceptionHandler(MissingServletRequestParameterException e) {
    log.error("", e);
    return new ResponseResult(ResultEnum.PARAMETER_ERROR.getCode(), "请求参数 " + e.getParameterName() + " 不能为空");
    }

    /**
    * 缺少请求体异常处理器
    *
    * @param e 缺少请求体异常
    * @return ResponseResult
    */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(HttpMessageNotReadableException.class)
    public ResponseResult parameterBodyMissingExceptionHandler(HttpMessageNotReadableException e) {
    log.error("", e);
    return new ResponseResult(ResultEnum.PARAMETER_ERROR.getCode(), "参数体不能为空");
    }

    /**
    * 参数效验异常处理器
    *
    * @param e 参数验证异常
    * @return ResponseInfo
    */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseResult parameterExceptionHandler(MethodArgumentNotValidException e) {
    log.error("", e);
    // 获取异常信息
    BindingResult exceptions = e.getBindingResult();
    // 判断异常中是否有错误信息,如果存在就使用异常中的消息,否则使用默认消息
    if (exceptions.hasErrors()) {
    List<ObjectError> errors = exceptions.getAllErrors();
    if (!errors.isEmpty()) {
    // 这里列出了全部错误参数,按正常逻辑,只需要第一条错误即可
    FieldError fieldError = (FieldError) errors.get(0);
    return new ResponseResult(ResultEnum.PARAMETER_ERROR.getCode(), fieldError.getDefaultMessage());
    }
    }
    return new ResponseResult(ResultEnum.PARAMETER_ERROR);
    }

    /**
    * 自定义参数错误异常处理器
    *
    * @param e 自定义参数
    * @return ResponseInfo
    */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler({ParamaErrorException.class})
    public ResponseResult paramExceptionHandler(ParamErrorException e) {
    log.error("", e);
    // 判断异常中是否有错误信息,如果存在就使用异常中的消息,否则使用默认消息
    if (!StringUtils.isEmpty(e.getMessage())) {
    return new ResponseResult(ResultEnum.PARAMETER_ERROR.getCode(), e.getMessage());
    }
    return new ResponseResult(ResultEnum.PARAMETER_ERROR);
    }

    }
  8. 启动类
  9. 测试

可能出现问题

The valid characters are defined in RFC 7230 and RFC 3986

1)使用Tomcat7.0.69之前的版本
2)对url的特殊字符进行转义(推荐)。浏览器端处理encodeURI()可以用于整个路径或者encodeURIComponent()用于参数因为会把/也进行转码
3)修改tomcat配置文件

JSON parse error: Invalid UTF-8 start byte 0xa1; nested exception is com.fasterxml.jackson.core.JsonParseException: Invalid UTF-8 start byte 0xa1

Unrecognized token 'c': was expecting ('true', 'false' or 'null') 格式出错了
对于特殊字符前端需要进行转义,避免直接执行html,或者js脚本。

解决方法

  1. 避免自己写的过滤器filter xss html sql等过滤器导致编码错误。或者把utf-8编码头给截取掉了。或者过滤截取的时候把标准json格式截取掉了一部分导致错误
  2. yml加上运行使用使用转义字符,与对应配置
    1
    2
    3
    4
    5
    6
    7
    8
    spring:
    profiles:
    active: dev
    jackson:
    date-format: yyyy-MM-dd HH:mm:ss
    time-zone: GMT+8
    parser:
    allow_unquoted_control_chars: true
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    @Configuration
    public class JacksonConfig {
    @Bean
    @Primary
    @ConditionalOnMissingBean(ObjectMapper.class)
    public ObjectMapper getObjectMapper(Jackson2ObjectMapperBuilder builder) {
    ObjectMapper om = builder.build();
    return om;
    }
    }
  3. java配置
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    @Bean
    @Primary
    @ConditionalOnMissingBean(ObjectMapper.class)
    public ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder)
    {
    ObjectMapper objectMapper = builder.createXmlMapper(false).build();

    // 通过该方法对mapper对象进行设置,所有序列化的对象都将按改规则进行系列化
    // Include.Include.ALWAYS 默认
    // Include.NON_DEFAULT 属性为默认值不序列化
    // Include.NON_EMPTY 属性为 空("") 或者为 NULL 都不序列化,则返回的json是没有这个字段的。这样对移动端会更省流量
    // Include.NON_NULL 属性为NULL 不序列化
    objectMapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY);
    objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
    // 允许出现特殊字符和转义符
    objectMapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_CONTROL_CHARS, true);
    // 允许出现单引号
    objectMapper.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true);
    // 字段保留,将null值转为""
    objectMapper.getSerializerProvider().setNullValueSerializer(new JsonSerializer<Object>()
    {
    @Override
    public void serialize(Object o, JsonGenerator jsonGenerator,
    SerializerProvider serializerProvider)
    throws IOException
    {
    jsonGenerator.writeString("");
    }
    });
    return objectMapper;
    }
  4. 测试demo
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    @Data
    public class Tmp {
    @JsonProperty("tmp:id")
    private String id;

    private String name;
    }
    public class Test {
    public static void main(String args[]) {
    ObjectMapper mapper = new ObjectMapper();
    mapper.disable(SerializationFeature.WRITE_NULL_MAP_VALUES);
    mapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
    mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
    mapper.enable(MapperFeature.REQUIRE_SETTERS_FOR_GETTERS);

    try {
    Tmp tmp = mapper.readValue("{\"tmp:id\":\"ab>c\",\"name\":null}", Tmp.class);
    System.out.println(tmp.getId());
    System.out.println(tmp.getName());
    } catch (IOException e) {
    e.printStackTrace();
    }
    }
    }

错误位置代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
public abstract class AbstractJackson2HttpMessageConverter extends AbstractGenericHttpMessageConverter<Object> {
...
this.objectMapper.readValue(inputMessage.getBody(), javaType);
...
}
...
public final class ByteSourceJsonBootstrapper {
public JsonEncoding detectEncoding() throws IOException {
boolean foundEncoding = false;
int quad;
if (this.ensureLoaded(4)) {
quad = this._inputBuffer[this._inputPtr] << 24 | (this._inputBuffer[this._inputPtr + 1] & 255) << 16 | (this._inputBuffer[this._inputPtr + 2] & 255) << 8 | this._inputBuffer[this._inputPtr + 3] & 255;
if (this.handleBOM(quad)) {
foundEncoding = true;
} else if (this.checkUTF32(quad)) {
foundEncoding = true;
} else if (this.checkUTF16(quad >>> 16)) {
foundEncoding = true;
}
} else if (this.ensureLoaded(2)) {
quad = (this._inputBuffer[this._inputPtr] & 255) << 8 | this._inputBuffer[this._inputPtr + 1] & 255;
if (this.checkUTF16(quad)) {
foundEncoding = true;
}
}

JsonEncoding enc;
if (!foundEncoding) {
enc = JsonEncoding.UTF8; // 正常是会走到这一句,使用默认UTF-8
} else {
switch(this._bytesPerChar) {
case 1:
enc = JsonEncoding.UTF8;
break;
case 2:
enc = this._bigEndian ? JsonEncoding.UTF16_BE : JsonEncoding.UTF16_LE;
break;
case 3:
default:
throw new RuntimeException("Internal error");
case 4:
enc = this._bigEndian ? JsonEncoding.UTF32_BE : JsonEncoding.UTF32_LE;
}
}

this._context.setEncoding(enc);
return enc;
}
}

JSON解析报错

  • 请求头与返回头:使用application/json(标准写法)而不是text/json(解析会报错)

应用

启动的时候加载字典信息到内存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
@Component
public class CodeCache {
public static Map<String, CityEntity> cityMap = new HashMap<String, CityEntity>();

@Autowired
private CityDao cityDao;

public void load() {
List<CityEntity> cityList = cityDao.select();
for (CityEntity city : cityList) {
cityMap.put(city.getCode(), city);
}
}

@PostConstruct
public void init() {
// 启动加载cityMap
load()
}

@PreDestroy
public void destroy() {
//系统运行结束
}

@Scheduled(cron = "0 0 0/2 * * ?")
public void testOne() {
// 每2小时执行一次缓存
load();
}
}

使用
@Autowired
Map<String, CityEntity> cityMap;
// 或者
Map<String, CityEntity> cityMap = CodeCache.cityMap;

@Autowired

@Autowired(required=true):当使用@Autowired注解的时候,其实默认就是@Autowired(required=true),表示注入的时候,该bean必须存在,否则就会注入失败
@Autowired(required=false):表示忽略当前要注入的bean,如果有直接注入,没有跳过,不会报错
在注入的过程中,扫描到公共方法中要注入的bean,并未找到,强行注入就会注入失败。我们又不能单独的去除改方法,所以我们采取的思想就是有bean就注入,没有就不注入。解决办法就是@Autowired(required=false)。
容器的启动顺序:
先加载父容器(spring),后加载子容器(springmvc)。所以在Controller里面注入service时,父容器中的bean已经初始化完毕,所以正常注入。
在父子容器中,父容器元素对子容器可见,子容器对父容器的元素不可见。所以父容器中不能获取子容器的元素,但是子容器可以获取父容器的元素。
当前容器均可获取当前容器中的元素,也就是说在service中可以注入其他service。但是,当前容器不可以注入自己。

参考