properties 依赖 1 2 3 4 5 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-configuration-processor</artifactId > <optional > true</optional > </dependency >
spring-boot-configuration-processor说白了就是给自定义的配置类生成元数据信息的,因为spring也不知道你有哪些配置类,所以搞了这个方便大家自定义
元数据, 这些数据并不是在项目运行中有什么作用. 而是在开发期间能够通过ide的处理给我们更多的便捷提示.
SpringBoot之spring-boot-configuration-processor
配置文件
1 2 3 4 5 6 7 server: port: 8080 servlet: context-path: /demo spring: profiles: active: dev
配置分区
1 2 3 4 5 6 7 8 application: name: dev环境 @artifactId@ version: dev环境 @version@ developer: name: dev环境 xkcoding website: dev环境 http://xkcoding.com qq: dev环境 237497819 phone-number: dev环境 17326075631
1 2 3 4 5 6 7 8 application: name: prod环境 @artifactId@ version: prod环境 @version@ developer: name: prod环境 xkcoding website: prod环境 http://xkcoding.com qq: prod环境 237497819 phone-number: prod环境 17326075631
spring.profiles.active来区分配置
spring boot允许你通过命名约定按照一定的格式
(application-{profile}.properties)来定义多个配置文件根据springboot的配置文件命名约定,结合active可在不同环境引用不同的properties外部配置
如:spring.profiles.active=dev ,dev正好匹配下面配置中的application-dev.properties 配置文件,所以app启动时,项目会先从application-dev.properties加载配置。再从application.properties配置文件加载配置,如果有重复的配置,则以application.properties的配置为准。
如此,我们就不用为了不同的运行环境而去更改大量的环境配置了(此处,dev、pro、test分别为:开发、生产、测试环境配置)
1 2 3 4 5 6 public class ApplicationProperty { @Value("${application.name}") private String name; @Value("${application.version}") private String version; }
在此种场景下,当Bean
被实例化时,@ConfigurationProperties
会将对应前缀的后面的属性与Bean
对象的属性匹配。符合条件则进行赋值。
1 2 3 4 5 6 7 8 @ConfigurationProperties(prefix = "developer") @Component public class DeveloperProperty { private String name; private String website; private String qq; private String phoneNumber; }
@ConfigurationProperties使用详解
actuator actuator是spring boot提供的对应用系统的自省和监控的集成功能,可以对应用系统进行配置查看、相关功能统计等。
三步为你的Springboot集成Actuator监控功能
依赖 1 2 3 4 5 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-actuator</artifactId > </dependency >
配置文件 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 server: port: 8080 servlet: context-path: /demo spring: security: user: name: xkcoding password: 123456 management: server: port: 8090 servlet: context-path: /sys endpoint: health: show-details: always httptrace: endpoints: web: exposure: include: '*'
SpringBootAdmin
Spring Boot Admin用于管理和监控一个或多个Spring Boot服务 ,其分为Server端和Client端,Server端相当于一个注册中心,Client端通过Http请求向Server端进行注册,也可以结合Eureka 、Nacos等注册中心实现服务注册。
SpringBootAdmin
是一个针对 Spring Boot 的 Actuator 接口进行 UI 美化封装的监控工具
它可以在列表中浏览所有被监控 spring-boot 项目的基本信息
详细的 Health 信息、内存 信息、JVM 信息、垃圾回收信息、各种配置信息(比如数据源、缓存列表和命中率)等。
可分为服务端(spring-boot-admin-server
)和客户端(spring-boot-admin-client
)
服务端和客户端之间采用http通讯方式实现数据交互。服务端server需要单独启动一个服务,而客户端client只需要集成到各个微服务中。
SpringBoot快速集成SpringBootAdmin管控台监控服务
SpringBoot整合Spring Boot Admin实现服务监控
注意在服务端开启@EnableAdminServer
依赖
1 2 3 4 <dependency > <groupId > de.codecentric</groupId > <artifactId > spring-boot-admin-starter-client</artifactId > </dependency >
1 2 3 4 <dependency > <groupId > de.codecentric</groupId > <artifactId > spring-boot-admin-starter-server</artifactId > </dependency >
配置文件 客户端:
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 server: port: 8080 servlet: context-path: /demo spring: application: name: spring-boot-demo-admin-client boot: admin: client: url: "http://localhost:8000/" instance: metadata: user.name: ${spring.security.user.name} user.password: ${spring.security.user.password} security: user: name: xkcoding password: 123456 management: endpoint: health: show-details: always endpoints: web: exposure: include: "*"
日志 Java系统中常用日志框架
异常统一处理
handler相当于是平常业务代码中每个请求对应的的controller类以及方法信息.
@ControllerAdvice注解
首先,ControllerAdvice
本质上是一个Component
,因此也会被当成组建扫描
这个类是为那些声明了(@ExceptionHandler
、@InitBinder
或 @ModelAttribute
注解修饰的)方法的类而提供的专业化的@Component
, 以供多个 Controller
类所共享。
可对controller中被 @RequestMapping注解的方法加一些逻辑处理。最常用的就是
说白了,就是aop思想的一种实现,你告诉我需要拦截规则,我帮你把他们拦下来,具体你想做更细致的拦截筛选和拦截之后的处理 ,你自己通过@ExceptionHandler
、@InitBinder
或 @ModelAttribute
这三个注解以及被其注解的方法来自定义
@ControllerAdvice就是@Controller 的增强版。@ControllerAdvice主要用来进行全局的处理
@ExceptionHandler注解 该注解作用对象为方法,并且在运行时有效,value()可以指定异常类。由该注解注释的方法可以具有灵活的输入参数
异常类的自定义
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @Data @EqualsAndHashCode(callSuper = true) public class BaseException extends RuntimeException { private Integer code; private String message; public BaseException (Status status) { super (status.getMessage()); this .code = status.getCode(); this .message = status.getMessage(); } public BaseException (Integer code, String message) { super (message); this .code = code; this .message = message; } }
1 2 3 4 5 6 7 8 9 10 public class JsonException extends BaseException { public JsonException (Status status) { super (status); } public JsonException (Integer code, String message) { super (code, message); } }
1 2 3 4 5 6 7 8 9 10 public class PageException extends BaseException { public PageException (Status status) { super (status); } public PageException (Integer code, String message) { super (code, message); } }
callSuper = true,根据子类自身的字段值和从父类继承的字段值 来生成hashcode,当两个子类对象比较时,只有子类对象的本身的字段值和继承父类的字段值都相同,equals方法的返回值是true。
callSuper = false,根据子类自身的字段值 来生成hashcode, 当两个子类对象比较时,只有子类对象的本身的字段值相同,父类字段值可以不同,equals方法的返回值是true。
异常统一处理 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 @ControllerAdvice @Slf4j public class DemoExceptionHandler { private static final String DEFAULT_ERROR_VIEW = "error" ; @ExceptionHandler(value = JsonException.class) @ResponseBody public ApiResponse jsonErrorHandler (JsonException exception) { log.error("【JsonException】:{}" , exception.getMessage()); return ApiResponse.ofException(exception); } @ExceptionHandler(value = PageException.class) public ModelAndView pageErrorHandler (PageException exception) { log.error("【DemoPageException】:{}" , exception.getMessage()); ModelAndView view = new ModelAndView (); view.addObject("message" , exception.getMessage()); view.setViewName(DEFAULT_ERROR_VIEW); return view; } }
@ControllerAdvice 的介绍及三种用法
@ModelAttribute运用详解
集成mybatis/plus 依赖 1 2 3 4 5 6 7 8 9 10 <dependency > <groupId > org.mybatis.spring.boot</groupId > <artifactId > mybatis-spring-boot-starter</artifactId > <version > ${mybatis.version}</version > </dependency > <dependency > <groupId > mysql</groupId > <artifactId > mysql-connector-java</artifactId > </dependency >
配置文件 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 spring: datasource: url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8&useSSL=false&autoReconnect=true&failOverReadOnly=false&serverTimezone=GMT%2B8 username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver type: com.zaxxer.hikari.HikariDataSource hikari: minimum-idle: 5 connection-test-query: SELECT 1 FROM DUAL maximum-pool-size: 20 auto-commit: true idle-timeout: 30000 pool-name: SpringBootDemoHikariCP max-lifetime: 60000 connection-timeout: 30000 logging: level: com.xkcoding: debug com.xkcoding.orm.mybatis.mapper: trace mybatis: configuration: map-underscore-to-camel-case: true mapper-locations: classpath:mappers/*.xml type-aliases-package: com.xkcoding.orm.mybatis.entity
Lombok中@Builder 注释为你的类生成相对略微复杂的构建器,可以让你以下面显示的那样调用你的代码来初始化你的实例对象:
1 2 3 4 5 6 7 Student.builder() .sno( "001" ) .sname( "admin" ) .sage( 18 ) .sphone( "110" ) .build();123456
mybatisplus集成 依赖 1 2 3 4 5 6 7 8 9 <dependency > <groupId > com.baomidou</groupId > <artifactId > mybatis-plus-boot-starter</artifactId > <version > ${mybatis.plus.version}</version > </dependency > <dependency > <groupId > mysql</groupId > <artifactId > mysql-connector-java</artifactId > </dependency >
配置 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 spring: datasource: url: jdbc:mysql://127.0.0.1:3306/spring-boot-demo?useUnicode=true&characterEncoding=UTF-8&useSSL=false&autoReconnect=true&failOverReadOnly=false&serverTimezone=GMT%2B8 username: root password: root driver-class-name: com.mysql.cj.jdbc.Driver type: com.zaxxer.hikari.HikariDataSource initialization-mode: always continue-on-error: true schema: - "classpath:db/schema.sql" data: - "classpath:db/data.sql" hikari: minimum-idle: 5 connection-test-query: SELECT 1 FROM DUAL maximum-pool-size: 20 auto-commit: true idle-timeout: 30000 pool-name: SpringBootDemoHikariCP max-lifetime: 60000 connection-timeout: 30000 logging: level: com.xkcoding: debug com.xkcoding.orm.mybatis.plus.mapper: trace mybatis-plus: mapper-locations: classpath:mappers/*.xml typeAliasesPackage: com.xkcoding.orm.mybatis.plus.entity global-config: db-config: id-type: auto field-strategy: not_empty table-underline: true db-type: mysql refresh: true configuration: map-underscore-to-camel-case: true cache-enabled: true
文件上传 依赖 1 2 3 4 5 6 <dependency > <groupId > com.qiniu</groupId > <artifactId > qiniu-java-sdk</artifactId > <version > [7.2.0, 7.2.99]</version > </dependency >
配置文件 1 2 3 4 5 6 7 8 9 10 11 12 13 server: port: 8080 servlet: context-path: /demo spring: servlet: multipart: enabled: true file-size-threshold: 0 location: D:\\temp max-file-size: 1MB max-request-size: 10MB resolve-lazily: false
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 @Configuration @ConditionalOnClass({Servlet.class, StandardServletMultipartResolver.class, MultipartConfigElement.class}) @ConditionalOnProperty(prefix = "spring.http.multipart", name = "enabled", matchIfMissing = true) @EnableConfigurationProperties(MultipartProperties.class) public class UploadConfig { private final MultipartProperties multipartProperties; @Autowired public UploadConfig (MultipartProperties multipartProperties) { this .multipartProperties = multipartProperties; } @Bean @ConditionalOnMissingBean public MultipartConfigElement multipartConfigElement () { return this .multipartProperties.createMultipartConfig(); } @Bean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME) @ConditionalOnMissingBean(MultipartResolver.class) public StandardServletMultipartResolver multipartResolver () { StandardServletMultipartResolver multipartResolver = new StandardServletMultipartResolver (); multipartResolver.setResolveLazily(this .multipartProperties.isResolveLazily()); return multipartResolver; } }
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 @RestController @Slf4j @RequestMapping("/upload") public class UploadController { @Value("${spring.servlet.multipart.location}") private String fileTempPath; @PostMapping(value = "/local", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) public Dict local (@RequestParam("file") MultipartFile file) { if (file.isEmpty()) { return Dict.create().set("code" , 400 ).set("message" , "文件内容为空" ); } String fileName = file.getOriginalFilename(); String rawFileName = StrUtil.subBefore(fileName, "." , true ); String fileType = StrUtil.subAfter(fileName, "." , true ); String localFilePath = StrUtil.appendIfMissing(fileTempPath, "/" ) + rawFileName + "-" + DateUtil.current(false ) + "." + fileType; try { file.transferTo(new File (localFilePath)); } catch (IOException e) { log.error("【文件上传至本地】失败,绝对路径:{}" , localFilePath); return Dict.create() .set("code" , 500 ) .set("message" , "文件上传失败" ); } log.info("【文件上传至本地】绝对路径:{}" , localFilePath); return Dict.create() .set("code" , 200 ) .set("message" , "上传成功" ) .set("data" , Dict.create() .set("fileName" , fileName) .set("filePath" , localFilePath)); } }
下载见mvc
缓存集成Redis 依赖 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-data-redis</artifactId > </dependency > <dependency > <groupId > org.apache.commons</groupId > <artifactId > commons-pool2</artifactId > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-json</artifactId > </dependency >
配置文件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 spring: redis: host: localhost timeout: 10000ms lettuce: pool: max-active: 8 max-wait: -1ms max-idle: 8 min-idle: 0 cache: type: redis
配置类
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 @Configuration @AutoConfigureAfter(RedisAutoConfiguration.class) @EnableCaching public class RedisConfig { @Bean public RedisTemplate<String, Serializable> redisCacheTemplate (LettuceConnectionFactory redisConnectionFactory) { RedisTemplate<String, Serializable> template = new RedisTemplate <>(); template.setKeySerializer(new StringRedisSerializer ()); template.setValueSerializer(new GenericJackson2JsonRedisSerializer ()); template.setConnectionFactory(redisConnectionFactory); return template; } @Bean public CacheManager cacheManager (RedisConnectionFactory factory) { RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig(); RedisCacheConfiguration redisCacheConfiguration = config. serializeKeysWith(RedisSerializationContext. SerializationPair.fromSerializer(new StringRedisSerializer ())) .serializeValuesWith(RedisSerializationContext. SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer ())); return RedisCacheManager.builder(factory).cacheDefaults(redisCacheConfiguration).build(); } }
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 @Service @Slf4j public class UserServiceImpl implements UserService { private static final Map<Long, User> DATABASES = Maps.newConcurrentMap(); static { DATABASES.put(1L , new User (1L , "user1" )); DATABASES.put(2L , new User (2L , "user2" )); DATABASES.put(3L , new User (3L , "user3" )); } @CachePut(value = "user", key = "#user.id") @Override public User saveOrUpdate (User user) { DATABASES.put(user.getId(), user); log.info("保存用户【user】= {}" , user); return user; } @Cacheable(value = "user", key = "#id") @Override public User get (Long id) { log.info("查询用户【id】= {}" , id); return DATABASES.get(id); } @CacheEvict(value = "user", key = "#id") @Override public void delete (Long id) { DATABASES.remove(id); log.info("删除用户【id】= {}" , id); } }
tips
1 2 3 4 5 6 7 8 9 @Override @Cacheable(value = {"menuById"}, key = "'hash' + #menu.hashCode()") public Menu findByHash (Menu menu) { return menu; }
定时任务 配置类 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 @Configuration @EnableScheduling @ComponentScan(basePackages = {"com.xkcoding.task.job"}) public class TaskConfig implements SchedulingConfigurer { @Override public void configureTasks (ScheduledTaskRegistrar taskRegistrar) { taskRegistrar.setScheduler(taskExecutor()); } @Bean public Executor taskExecutor () { return new ScheduledThreadPoolExecutor (20 , new BasicThreadFactory .Builder() .namingPattern("Job-Thread-%d" ) .build() ); } }
相关执行的类
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 @Component @Slf4j public class TaskJob { @Scheduled(cron = "0/10 * * * * ?") public void job1 () { log.info("【job1】开始执行:{}" , DateUtil.formatDateTime(new Date ())); } @Scheduled(fixedRate = 2000) public void job2 () { log.info("【job2】开始执行:{}" , DateUtil.formatDateTime(new Date ())); } @Scheduled(fixedDelay = 4000, initialDelay = 5000) public void job3 () { log.info("【job3】开始执行:{}" , DateUtil.formatDateTime(new Date ())); } }
在需要定时执行的方法上加入@Scheduled
@Scheduled(fixedDelay = 5000)
延迟执行。任务在上个任务完成后达到设置的延时时间就执行。
@Scheduled(fixedRate = 5000)
定时执行。任务间隔规定时间即执行。
@Scheduled(cron = “0 0 2 * * ?”)
CRON表达式详解