当前位置: 首页 > news >正文

潍坊百度网站排名wordpress pckr

潍坊百度网站排名,wordpress pckr,网站欣赏公司网站案例,免费域名空间申请本文是 SpringBoot 开发的干货集中营#xff0c;涵盖了日常开发中遇到的诸多问题#xff0c;通篇着重讲解如何快速解决问题#xff0c;部分重点问题会讲解原理#xff0c;以及为什么要这样做。便于大家快速处理实践中经常遇到的小问题#xff0c;既方便自己也方便他人涵盖了日常开发中遇到的诸多问题通篇着重讲解如何快速解决问题部分重点问题会讲解原理以及为什么要这样做。便于大家快速处理实践中经常遇到的小问题既方便自己也方便他人老鸟和新手皆适合值得收藏 1. 哪里可以搜索依赖包的 Maven 坐标和版本 https://mvnrepository.com/ 这个在2023年前使用得最多但目前2024国内访问该网站时经常卡死在人机校验这一步导致无法使用 Maven Central 刚开始我是临时用这个网站来替换前面那个现在它越来越好用就直接使用它了 2. 如何确定 SpringBoot 与 JDK 之间的版本关系 在 Spring官网 可以找到 SpringBoot 对应的 JDK 关系但这种关系说明位于具体版本的参考手册(Reference Doc)中按照以下图示顺序操作即可找到。 重大版本与JDK及Spring基础框架的对应关系表 3. 如何统一处理Web请求的JSON日期格式问题 方式一编程式声明 在 JacksonAutoConfiguration 装配前, 先装配一个 Jackson2ObjectMapperBuilderCustomizer并在这个 Customizer 中设置日期格式。如下所示 Configuration ConditionalOnClass(ObjectMapper.class) AutoConfigureBefore(JacksonAutoConfiguration.class) // 本装配提前于官方的自动装配public class JacksonConfig {Beanpublic Jackson2ObjectMapperBuilderCustomizer myJacksonCustomizer() {return builder - {builder.locale(Locale.CHINA);builder.timeZone(TimeZone.getTimeZone(ZoneId.systemDefault()));builder.simpleDateFormat(yyyy-MM-dd HH:mm:ss);} } 方式二配置式声明 推荐 参考下面的示例代码即可关键之处是要指定 spring.http.converters.preferred-json-mapper 的值为 jackson, 否则配置不生效 spring:jackson:date-format: yyyy-MM-dd HH:mm:sslocale: zh_CNtime-zone: GMT8http:converters:preferred-json-mapper: jackson 4. 如何以静态方式访问Bean容器 写一个实现了 ApplicationContextAware 接口的类通过该接口的 setApplicationContext方法获取 ApplicationContext, 然后用一个静态变量来持有它。之后便可以通过静态方法使用 ApplicationContext 了。Spring 框架在启动完成后会遍历容器中所有实现了该接口的Bean然后调用它们的setApplicationContext()方法将ApplicationContext也就是容器自身作为参数传递过去。下面是示例代码 import org.springframework.context.ApplicationContextAware; import org.springframework.stereotype.Component;Component public class ApplicationContextHolder implements ApplicationContextAware {// 声明一个静态变量来持有 ApplicationContext private static ApplicationContext appContext;Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {ApplicationContextHolder.appContext applicationContext;}public static ApplicationContext getContext() {return ApplicationContextHolder.appContext;}} 5. 如何将工程打包成一个独立的可执行jar包 按以下三步操作即可仅针对maven工程 在 pom.xml 中添加 spring boot 的构建插件 为上一步的插件配置执行目标 在工程目录下命令行执行 maven clean package -Dmaven.test.skiptrue buildpluginsplugingroupIdorg.springframework.boot/groupIdartifactIdspring-boot-maven-plugin/artifactIdversion2.1.6.RELEASE/versionexecutionsexecutiongoalsgoalrepackage/goal/goals/execution/executions/plugin/plugins /build 关于 spring-boot-maven-plugin 插件的版本问题 如果不指定版本默认会去下载最新的这极有可能与代码工程所用的 jdk 版本不兼容导致打包失败。那么应该用哪个版本呢一个简单的办法是先进入到本机的 Maven 仓库目录然后再分别打开以下两个目录 org/springframework/boot/spring-boot org/springframework/boot/spring-boot-maven-plugin 再结合自己工程的spring-boot版本可通过IDE查看选择相同版本或稍低版本的plugin插件 6. 如何从jar包外部读取配置文件 在 Java 启动命令中添加 spring-boot 配置文件相关参数指定配置文件的位置如下所示 java -jar xxxx.jar --spring.config.location{yaml配置文件绝对路径} 指定外部配置文件还有其它一些方式详情参见 SpringBoot项目常见配置 特别说明 --spring.config.location 这个配置项一定要写在 xxxx.jar 之后因为这是一个 SpringApplication 的参数不是 java 命令的参数或选项该参数最终是传递到了 main 方法的 args 变量上因此在 main 方法中构建 SpringApplication 实例时务必要把 args 参数传递过去比如下面这两种写法 /** 样例A */ public static void main(String[] args) {SpringApplication.run(OverSpeedDataInsightMain.class); }/** 样例B */ public static void main(String[] args) {SpringApplication.run(OverSpeedDataInsightMain.class, args); } 样例A由于没有传递args参数因此通过命令行添加的 --spring.config.location 参数不会被SpringBoot实例读取到在运行期间也就不会去读取它指定的配置文件了。 7. 如何同时启用多个数据源 方式一手动创建多个My Batis的SqlSessionFactory 因为国内使用 MyBatis 框架最多因此特别针对此框架单独说明。总体思路是这样的 多个数据源各有各的配置 针对每个数据源单独创建一个 SqlSessionFactory 每个 SqlSession 各自扫描不同数包和目录下的 Mapper.java 和 mapper.xml 指定某个数据源为主数据源强制 样例工程部分代码如下完整源码请访问码云上的工程 mybatis-multi-ds-demo application.yml (点击查看) spring:datasource:primary:driver: org.sqlite.JDBCurl: jdbc:sqlite::resource:biz1.sqlite3?date_string_formatyyyy-MM-dd HH:mm:ssminor:driver: org.sqlite.JDBCurl: jdbc:sqlite::resource:biz2.sqlite3?date_string_formatyyyy-MM-dd HH:mm:ss 主数据源装配 (点击查看) MapperScan(basePackages {cnblogs.guzb.biz1},sqlSessionFactoryRef PrimarySqlSessionFactory ) Configuration public class PrimarySqlSessionFactoryConfig {// 表示这个数据源是默认数据源多数据源情况下必须指定一个主数据源PrimaryBean(name PrimaryDataSource)ConfigurationProperties(prefix spring.datasource.primary)public DataSource getPrimaryDateSource() {// 这个MyBatis内置的无池化的数据源仅仅用于演示实际工程请更换成具体的数据源对象return new UnpooledDataSource();}PrimaryBean(name PrimarySqlSessionFactory)public SqlSessionFactory primarySqlSessionFactory(Qualifier(PrimaryDataSource) DataSource datasource) throws Exception {SqlSessionFactoryBean bean new SqlSessionFactoryBean();bean.setDataSource(datasource);// 主数据源的XML SQL配置资源Resource[] xmlMapperResources new PathMatchingResourcePatternResolver().getResources(classpath:mappers/primary/*.xml);bean.setMapperLocations(xmlMapperResources);return bean.getObject();}PrimaryBean(PrimarySqlSessionTemplate)public SqlSessionTemplate primarySqlSessionTemplate(Qualifier(PrimarySqlSessionFactory) SqlSessionFactory sessionFactory) {return new SqlSessionTemplate(sessionFactory);} } 副数据源装配 (点击查看) Configuration MapperScan(basePackages {cnblogs.guzb.biz2},sqlSessionFactoryRef MinorSqlSessionFactory ) public class MinorSqlSessionFactoryConfig {Bean(name MinorDataSource)ConfigurationProperties(prefix spring.datasource.minor)public DataSource getPrimaryDateSource() {// 这个MyBatis内置的无池化的数据源仅仅用于演示实际工程请更换成具体的数据源对象return new UnpooledDataSource();}Bean(name MinorSqlSessionFactory)public SqlSessionFactory primarySqlSessionFactory(Qualifier(MinorDataSource) DataSource datasource) throws Exception {SqlSessionFactoryBean bean new SqlSessionFactoryBean();bean.setDataSource(datasource);// 主数据源的XML SQL配置资源Resource[] xmlMapperResources new PathMatchingResourcePatternResolver().getResources(classpath:mappers/minor/*.xml);bean.setMapperLocations(xmlMapperResources);return bean.getObject();}Bean(MinorSqlSessionTemplate)public SqlSessionTemplate primarySqlSessionTemplate(Qualifier(MinorSqlSessionFactory) SqlSessionFactory sessionFactory) {return new SqlSessionTemplate(sessionFactory);} } 方式二使用路由式委托数据源 AbstractRoutingDataSource 推荐 上面这种方式粒度比较粗在创建SqlSessionFactory时将一组Mapper与DataSource绑定。如果想粒度更细一些比如在一个Mapper内A方法使用数据源A, B方法使用数据源B则无法做到。 Spring 官方有个 AbstractRoutingDataSource 抽象类, 它提供了以代码方式设置当前要使用的数据源的能力。其实就是把自己作为 DataSource 的一个实现类并将自己作为数据源的集散地代理人在内部维护了一个数据源的池将 getConnection() 方法委托给这个池中对应的数据源。 DynamicDataSource.java public class DynamicDataSource extends AbstractRoutingDataSource {/** 通过 ThreadLocal 来记录当前线程中的数据源名称 */private final ThreadLocalString localDataSourceName new ThreadLocal();public DynamicDataSource(DataSource defaultTargetDataSource, MapObject, Object targetDataSources) {super.setDefaultTargetDataSource(defaultTargetDataSource);super.setTargetDataSources(targetDataSources);}Overrideprotected Object determineCurrentLookupKey() {return localDataSourceName.get();}public void setDataSourceName(String dataSourceName) {localDataSourceName.set(dataSourceName);}public void clearDataSourceName() {localDataSourceName.remove();} } DynamicDataSourceConfig Configuration public class DynamicDataSourceConfig {// 表示这个数据源是默认数据源多数据源情况下必须指定一个主数据源PrimaryBean(name dynamic-data-source)DependsOn(DataSourceName.FIRST)public DynamicDataSource getPrimaryDateSource(Qualifier(DataSourceName.FIRST) DataSource defaultDataSource,Qualifier(DataSourceName.SECOND) Autowired(required false) DataSource secondDataSource) {System.out.println(first defaultDataSource , second secondDataSource);MapObject, Object allTargetDataSources new HashMap();allTargetDataSources.put(DataSourceName.FIRST, defaultDataSource);allTargetDataSources.put(DataSourceName.SECOND, secondDataSource);return new DynamicDataSource(defaultDataSource, allTargetDataSources);}Bean(name DataSourceName.FIRST)ConfigurationProperties(prefix spring.datasource.first)public DataSource createFirstDataSource() {// 这个MyBatis内置的无池化的数据源仅仅用于演示实际工程请更换成具体的数据源对象return new UnpooledDataSource();}Bean(name DataSourceName.SECOND)ConfigurationProperties(prefix spring.datasource.second)public DataSource createSecondDataSource() {// 这个MyBatis内置的无池化的数据源仅仅用于演示实际工程请更换成具体的数据源对象return new UnpooledDataSource();}} SwitchDataSourceTo Retention(RetentionPolicy.RUNTIME) Target(ElementType.METHOD) public interface SwitchDataSourceTo {/** 数据源的名称 */String value() default DataSourceName.FIRST;} SwitchDataSourceAspect Aspect Component public class SwitchDataSourceAspect {AutowiredDynamicDataSource dynamicDataSource;Around(annotation(switchDataSourceTo))public Object around(ProceedingJoinPoint point, SwitchDataSourceTo switchDataSourceTo) throws Throwable {String dataSourceName switchDataSourceTo.value();try {dynamicDataSource.setDataSourceName(dataSourceName);System.out.println(切换到数据源: dataSourceName);return point.proceed();} finally {System.out.println(执行结束准备切换回到主数据源);dynamicDataSource.setDataSourceName(DataSourceName.FIRST);}} } Biz1Mapper Mapper public interface Biz1Mapper {// 未指定数据源即为「默认数据源」Select(select * from user)ListUserEntity listAll();SwitchDataSourceTo(DataSourceName.FIRST)Select(select * from user where id#{id})UserEntity getById(Param(id) Long id); } Biz2Mapper Mapper public interface Biz2Mapper {Select(select * from authority)SwitchDataSourceTo(DataSourceName.SECOND)ListAuthorityEntity listAll();// 本方法没有添加 SwitchDataSourceTo 注解因此会使用默认的数据源即 first// 但 first 数据源中没有这个表。该方法会通过在程序中手动设置数据源名称的方式来切换Select(select count(*) as quantity from authority)Integer totalCount();} 完整源码请访问码云上的工程 mybatis-multi-ds-demo 方式三使用 MyBatisPlus 的 多数据源方案 推荐 MyBatisPlus 增加了对多数据源的支持详细做法请参考 MyBatis多数据源官方手册它的底层原理与方式二一致但特性更多功能出更完善。若有兴趣的话建议将这个多数据源的功能单独做成一个 jar 包或 maven 依赖。以使其可以在非 MyBatis 环境中使用。 多数据源切换引起的事务问题 对于纯查询类非事务性方法上面的多数据源切换工作良好一旦一个Service方法开启了事务且内部调用了多个有不同数据源的Dao层方法则这些数据源切换均会失败。原因为切换数据源发生在openConnection()方法执行时刻但一个事务内只有一个Connection。当开启事务后再次切换数据源时由于已经有connection了此时切换会无效。 因此解决办法为先切换数据源再开启事务。开启事务后不能再切换数据源了。 8. 如何同时启用多个Redis连接 最简单的办法是直接使用 Redis官方的客户端库但这样脱离了本小节的主旨。业务代码中使用spring 的 redis 封装主要是使用 RedisTemplate 类RedisTemplate 封装了常用的业务操作但它并不关注如何获得 redis 的连接。这个工作是交由 RedisConnectionFactory 负责的。因此RedisTemplate 需要指定一个 RedisConnectionFactory。由此可知在工程中创建两个RedisConnectionFactory, 每个连接工厂连接到不同的 redis 服务器即可。以下简易示例代码中两个连接工厂连接的是同一个服务器的不同数据库。 创建两个 RedisConnectionFactory 和两个 RedisTemplate Configuration public class RedisConfiguration {/** * 0号数据库的连接工厂* 本示例没有使用早期的 JedisConnectionFactory, 而是选择了并发性更好的 LettuceConnectionFactory, 下同*/PrimaryBean(redis-connection-factory-db0) // 明确地指定 Bean 名称该实例将作为依赖项传递给相应的 RedisTemplate, 下同public RedisConnectionFactory createLettuceConnectionFactory0() {// 这里使用的是单实例Redis服务器的连接配置类// 哨兵与集群模式的服务器使用对应的配置类设置属性即可。// 另外这里没有演示通过yaml外部配置文件来设置相应的连接参数因为这不是本小节的重点RedisStandaloneConfiguration clientProps new RedisStandaloneConfiguration();clientProps.setHostName(localhost);clientProps.setPort(6379);clientProps.setDatabase(0);return new LettuceConnectionFactory(clientProps);}/** 1号数据库的连接工厂 */Bean(redis-connection-factory-db1)public RedisConnectionFactory createLettuceConnectionFactory1() {RedisStandaloneConfiguration clientProps new RedisStandaloneConfiguration();clientProps.setHostName(localhost);clientProps.setPort(6379);clientProps.setDatabase(1);return new LettuceConnectionFactory(clientProps);}/** * 操作0号数据库的 RedisTemplate, * 创建时直接将0号数据库的 RedisConnectionFactory 实例传递给它*/PrimaryBean(redis-template-db-0)public RedisTemplateString, String createRedisTemplate0(Qualifier(redis-connection-factory-db0) RedisConnectionFactory factory0) {RedisTemplateString, String redisTemplate new RedisTemplate();redisTemplate.setConnectionFactory(factory0);redisTemplate.setKeySerializer(new StringRedisSerializer());redisTemplate.setValueSerializer(new StringRedisSerializer());return redisTemplate;}/** * 操作1号数据库的 RedisTemplate, * 创建时直接将1号数据库的 RedisConnectionFactory 实例传递给它*/Bean(redis-template-db-1)public RedisTemplateString, String createRedisTemplate1(Qualifier(redis-connection-factory-db1) RedisConnectionFactory factory1) {RedisTemplateString, String redisTemplate new RedisTemplate();redisTemplate.setConnectionFactory(factory1);redisTemplate.setKeySerializer(new StringRedisSerializer());redisTemplate.setValueSerializer(new StringRedisSerializer());return redisTemplate;}} 多Redis连接的测试验证代码 Component SpringBootApplication public class MultiRedisAppMain {// 注入操作0号数据库的Redis模板Resource(name redis-template-db-0)RedisTemplate redisTemplate0;// 注入操作1号数据库的Redis模板Resource(name redis-template-db-1)RedisTemplate redisTemplate1;public static void main(String[] args) {SpringApplication.run(MultiRedisAppMain.class, args);}EventListener(ApplicationReadyEvent.class)public void operateBook() {redisTemplate0.opsForValue().set(bookName, 三体);redisTemplate0.opsForValue().set(bookPrice, 102);redisTemplate1.opsForValue().set(bookName, 老人与海);redisTemplate1.opsForValue().set(bookPrice, 95);} } 本小节完整的示例代码已上传到 multi-redis-demo 9. 如何同时消费多个 Kafka Topic 9.1 同时消费同一 Kakfa 服务器的多个topic 这个是最常见的情况同时也是最容易实现的具体操作是为 KafkaListener 指定多个 topic 即可如下所示 点击查看代码 /** 多个topic在一个方法中消费的情况 */ KafkaListener(topics {topic-1, topic-2, topic-3}, groupId group-1) public void consumeTopc1_2_3(String message) {System.out.println(收到消息 kafka message); }/** 不同 topic 在不同方法中消费的情况 */ KafkaListener(topics topic-A, groupId group-1) public void consumeTopicA(String message) {System.out.println(收到消息 kafka message); }/** 不同 topic 在不同方法中消费的情况 */ KafkaListener(topics topic-B, groupId group-1) public void consumeTopicB(String message) {System.out.println(收到消息 kafka message); } 9.2 同时消费不同Kafka服务器的多个topic 这种情况是本小节的重点与 spring 对 redis 的封装不同spring 对 kafka 官方的 client lib 封装比较重引入了以下概念 ConsumerFactroy 消费者工厂该接口能创建一个消费者它将创建与消息系统的网络连接 MessageListenerContainer 消息监听器容器这是 spring 在 Consumer 之上单独封装出来的概念顾名思义该组件的作用是根据监听参数创建一个消息监听器。看上去它似乎与 Consumer 组件要干的事一样但在 spring 的封装结构里consumer 实际上只负责连接到消息系统然后抓取消息抓取后如何消费是其它组件的事MessageLisntener 便是这样的组件而 MessageListenerContainer 是创建 MessageListener 的容器类组件。 KafkaListenerContainerFactory 消息监听器容器的工厂类即这个组件是用来创建 MessageListenerContainer 的而 MessageListenerContainer 又是用来创建 MessageLisntener 的。 看了上面3个重要的组件的介绍你一定会产生个疑问创建一个监听器需要这么复杂吗感觉一堆的工厂类这些工厂类还是三层套娃式的。答案是如果仅仅针对 Kafka不需要这么复杂。spring 的这种封装是要建立一套『事件编程模型』来消费消息。并且还是跨消息中间件的也就是说无论是消费 kafka 还是 rabbitmq , 它们的上层接口都是这种结构。为了应对不同消息系统间的差异才引出了这么多的工厂类。 但不得不说作为一个具体的使用者而言这就相当于到菜单市买一斤五花肉非得强行塞给你二两边角料实得五花肉只有8两不说那二两完全是多余的既浪费又增加负担。spring 官方的这种封装让它们的程序员爽了但使用者的负担却是增加了。我们愿意花大把时间来学习 Spring Framework 和 Spring Boot 的编程思想和源代码因为这两个是非常基础的通用框架。但是对具体产品的过渡封装使用者大多是不喜欢的因为我们可没那么多时间来学习它的复杂设计。毕竟这些只是工具的封装不是一个可部署的产品。业务代码要基于它们来实现功能谁也不想错误堆栈里全是一堆第三访库的类而不是我们自己写的代码。尽管spring 的工具质量很好。但复杂的包装增加了使用难度概念没有理解到位、某个理解不透彻的参数配置不对、某个完全没听说过的默认配置项在自己特定的环境下出错这些因素导致的异常都会让开发者花费巨大的时间成本来解决。因此对于有复杂需求的同仁们建议大家还是直接使用 kafka 官方提供的原生 client lib, 自己进行封装这样可以做到完全可控。 回到主题要实现同时连接多个不同的kafka服务器提供相应服务器的 ConsumerFactory 即可。只是 ConsumerFactory 实例还需要传递给 KafkaListenerContainerFactory最后在 KafkaLisntener 注解中指定要使用的 KafkaListenerContainerFactory 名称即可。 连接多个 Kafka 服务器的组件配置类 Configuration public class KafkaConfiguration {PrimaryBean(consumerFactory)public ConsumerFactory createConsumerFactory() {MapString, Object consumerProperties new HashMap();consumerProperties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, localhost:9092);consumerProperties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());consumerProperties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());return new DefaultKafkaConsumerFactory(consumerProperties);}// 第二个消费工厂为便于实操, 这里依然连接的是同一个 Kafka 服务器Bean(consumerFactory2)public ConsumerFactory createConsumerFactory2() {MapString, Object consumerProperties new HashMap();consumerProperties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, localhost:9092);consumerProperties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());consumerProperties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());return new DefaultKafkaConsumerFactory(consumerProperties);}Primary// 自己创建的监听容器工厂实例中一定要有一个实例的名字叫: kafkaListenerContainerFactory,// 因为 KafkaAnnotationDrivenConfiguration 中也默认配置了一个 KafkaListenerContainerFactory// 这个默认的 KafkaListenerContainerFactory 名称就叫 kafkaListenerContainerFactory// 其装配条件就是当容器中没有名称为 kafkaListenerContainerFactory 的Bean时那个装配就生效// 如果不阻止这个默认的KafkaListenerContainerFactory装备会导致容器中有两个 KafkaListenerContainerFactory这会引入一些初始化问题Bean(kafkaListenerContainerFactory)public KafkaListenerContainerFactoryKafkaMessageListenerContainer createContainerFactory1(ConcurrentKafkaListenerContainerFactoryConfigurer configurer,Qualifier(consumerFactory) ConsumerFactory consumerFactory) {ConcurrentKafkaListenerContainerFactory listenerContainerFactory new ConcurrentKafkaListenerContainerFactory();configurer.configure(listenerContainerFactory, consumerFactory);return listenerContainerFactory;}// 第二个监听器容器工厂Bean(kafkaListenerContainerFactory2)public KafkaListenerContainerFactoryKafkaMessageListenerContainer createContainerFactory2(ConcurrentKafkaListenerContainerFactoryConfigurer configurer,Qualifier(consumerFactory2) ConsumerFactory consumerFactory2) {ConcurrentKafkaListenerContainerFactory listenerContainerFactory new ConcurrentKafkaListenerContainerFactory();configurer.configure(listenerContainerFactory, consumerFactory2);return listenerContainerFactory;} } 连接多 Kafka 服务器的测试主程序 Component EnableKafka SpringBootApplication public class MultiKafkaAppMain {public static void main(String[] args) {SpringApplication.run(MultiKafkaAppMain.class, args);}KafkaListener(topics topic1, groupId g1, containerFactory kafkaListenerContainerFactory)public void consumeKafka1(String message) {System.out.println([KAFKA-1]: 收到消息 message);}KafkaListener(topics topic-2, groupId g1, containerFactory kafkaListenerContainerFactory2)public void consumeKafka2(String message) {System.out.println([KAFKA-2]: 收到消息 message);}EventListener(ApplicationReadyEvent.class)public void init() {System.out.println([MAIN]: 启动成功等待Kakfa消息);} } 本小节完整的示例代码已上传到 multi-kafka-demo 10. 如何查看程序启动后所有的 Properties 方式一遍历Environment对象 Spring Boot 中有个 Environment 接口它记录了当前激活的 profile 和所有的「属性源」下面是一段在 runtime 期间打印所有 properties 的示例代码 PrintAllPropetiesDemo.java点击查看 Component public class PrintAllPropetiesDemo {ResourceEnvironment env;EventListener(ApplicationReadyEvent.class)public void printAllProperties throws Exception {// 打印当前激活的 profileSystem.out.println(Active profiles: Arrays.toString(env.getActiveProfiles()));// 从「环境」对象中获取「属性源」final MutablePropertySources sources ((AbstractEnvironment) env).getPropertySources();// 打印所有的属性包括去重、脱敏StreamSupport.stream(sources.spliterator(), false).filter(ps - ps instanceof EnumerablePropertySource).map(ps - ((EnumerablePropertySource) ps).getPropertyNames()).flatMap(Arrays::stream)// 去除重复的属性名.distinct()// 过滤敏感属性内容.filter(prop - !(prop.contains(credentials) || prop.contains(password))).forEach(prop - System.out.println(prop : env.getProperty(prop)));} } 方式二查看 Spring Acuator 的 /env 监控页面 推荐 先引入 acuator 的依赖 dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-actuator/artifactId /dependency 然后在配置 acuator 的 web 访问 uri Bean public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {return http.authorizeExchange().pathMatchers(/actuator/**).permitAll().anyExchange().authenticated().and().build(); } 假定端口为8080, 则访问 http://localhost:8080/acuator/env 便能看到工程运行起来后所有的 properties 了 11. 如何申明和使用异步方法 在 SpringBoot 中使用异步方法非常简单只要做以下同步 启用异步特性 在要异步执行的方法中添加 Async 注解 下面是一段示例代码 // 启用异步特性 EnableAsync public class BookService {Async // 声明要异步执行的方法public void disableAllExpiredBooks(){....} } 特别说明 以上代码确实可以让 disableAllExpiredBook() 方法异步执行但它的执行方式是: 每次调用此方法时都新创建一个线程然后在新线程中执行这个方法。如果方法调用得不是很频繁这个做法是OK的。但如果方法调用得很频繁就会导致系统频繁地开线程而创建线程的开销是比较大的。Spring 已经考虑到了这个场景只需要为异步执行的方法指定一个执行器就可以了而这个执行器通常都是一个具备线程池功能的执行器。示例代码如下 EnableAsync public class BookService {Async(bookExcutor) // 在注解中指定执行器public void disableAllExpiredBooks(){....} }Configuration public class ExecutorConfiguration {// 装配书籍任务的通用执行器Bean(bookExcutor)public Executor speedingArbitrationExecutor() {ThreadPoolTaskExecutor executor new ThreadPoolTaskExecutor();executor.setCorePoolSize(6);executor.setMaxPoolSize(24);executor.setQueueCapacity(20000;executor.setKeepAliveSeconds(30);executor.setThreadNamePrefix(书籍后台任务线程-);executor.setWaitForTasksToCompleteOnShutdown(true);// 任务队列排满后直接在主线程提交任务的线程执行任务异步执行变同步executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());return executor;} } 12. 如何快速添加 boot 的 maven 依赖项 Spring Boot 是一个以Boot为中心的生态圈当我们指定了boot的版本后如果要使用中生态圈中的组件就不用再指定该组件的版本了。有两种方式可达到此目的。 方式一项目工程直接继承 Boot Starter Parent POM parentgroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-parent/artifactIdversion3.1.5/version /parent 方式二在pom.xml的依赖管理节点下添加 spring-boot-dependencies dependencies!-- ② 这里添加starter依赖但不用指定版本 --dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter/artifactId/dependency /dependencies......dependencyManagementdependencies!-- ① 在这里添加spring-boot的依赖pom --dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-dependencies/artifactIdversion2.7.16/versiontypepom/typescopeimport/scope/dependency/dependencies /dependencyManagement 同理如果要引入 Spring Cloud 生态圈中的相关组件也建议通过「方式二」把 spring-cloud-dependencies 加入到依赖管理节点下 13. 如何以静态方式获取 HttpServletRequest 和 HttpServletResponse 通过 spring-web 组件提供的 RequestContextHolder 中的静态方法来获取 HttpServletRequest 和 HttpServletResponse如下所示 import org.springframework.web.util.WebUtils;import org.springframework.web.context.request.RequestAttributes; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes;public class WebTool extends WebUtils {public static HttpServletRequest getHttpRequest() {RequestAttributes requestAttributes RequestContextHolder.getRequestAttributes();ServletRequestAttributes servletRequestAttributes (ServletRequestAttributes) requestAttributes;return servletRequestAttributes.getRequest();}public static HttpServletResponse getHttpResponse() {RequestAttributes requestAttributes RequestContextHolder.getRequestAttributes();ServletRequestAttributes servletRequestAttributes (ServletRequestAttributes) requestAttributes;return servletRequestAttributes.getResponse();} } 14. 如何解决 ConfigurationProperties 不生效的问题 如果你在自己的 Properties 类上添加了 ConfigurationProperties 注解启动程序后没有效果可参考下面这两种方法来解决: 方式一1. 在启动类添加 EnableConfigurationProperties 注解 2. 在 ConfigurationProperties 标注的类上添加 Component 注解 Service注解也可以 启动类 SpringBootApplication EnableAutoConfiguration EnableConfigurationProperties public class MyBootApp {public static void main(String[] args) {SpringApplication.run(MyBootApp.clss, args);} } 自定义的 Properties 类 ComponentConfigurationProperties(prefixgzub.hdfs)public class HdfsProperties {private String nameNode;private String user;private String password; } 方式二1. 在启动类添加 ConfigurationPropertiesScan 注解并指定要扫描的 package2. 在自定义的 Properties 类上添加 ConfigurationProperties不需要添加 Component 注解 启动类 SpringBootApplication ConfigurationPropertiesScan({vip.guzb})public class MyBootApp {public static void main(String[] args) {SpringApplication.run(MyBootApp.clss, args);} } 自定义的 Properties 类 ConfigurationProperties(prefixgzub.hdfs)public class HdfsProperties {private String nameNode;private String user;private String password; } 15. 如何统一处理异常 编写一个普通的Bean不继承和实现任何类与接口 在该Bean的类级别上添加 RestControllerAdvice 注解向框架声明这是一个可跨 Controller 处理异常、初始绑定和视图模型特性的类 在类中编写处理异常的方法并在方法上添加 ExceptionHandler 注解向框架声明这是一个异常处理方法 编写异常处理方法的要求如下 方法是 public 的 方法必须用 ExceptionHandler 注解修饰 方法的返回值就是最终返给前端的内容通常是JSON文本 方法参数中需指定要处理的异常类型 如果需要对特定异常做特殊的处理则重复第3步 下面是一较完整的示例代码点击查看 import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.http.ResponseEntity import org.springframework.http.HttpStatus;RestControllerAdvice public class MyGlobalExceptionHandlerResolver {/** 处理最外层的异常 */ExceptionHandler(Exception.class)public ResponseEntityErrorResponse handleException(Exception e) {List details new ArrayList();details.add(e.getMesssage());ErrorResponse error new ErrorResponse(e.getMessage, details);return new ResponseEntity(error, HttpStatus.BAD_REQUEST);}/** 处理业务异常这里使用了另外一种方式来设置 http 响应码 */ExceptionHandler(BusinessException.class)ResponseStatus(HttpStatus.HTTP_BAD_REQUEST)public ErrorResponse handleException(BusinessException e) {List details new ArrayList();details.add(e.getBackendMesssage());return new ErrorResponse(e.getFrontendMessage(), details);} }/** 返回给前端的错误内容对象 */ public class ErrorResponse {private String message;private ListString details;...... }/** 业务异常 */ public class BusinessException extends RuntimeException{private String frontendMessage;private String backendMessage;...... } 16. 应该对哪些异常做特殊处理 对于Web开发而言我们应该在全局异常处理类中对以下异常做特殊处理 Exception BusinessExecption HttpRequestMethodNotSupportedException HttpClientErrorException FeignException ConstraintViolationException ValidationException 17. 异常处理组件应该具备的特性 业务异常处理 异常信息中要明确区分出前端展示内容与后端错误内容 后端错误内容可再进一步分为「错误的一般描述信息」和「详细的错误列表」 前后端错误信息中应过滤敏感内容如身份证、密码等且过滤机制提供开关功能以方便开发调试 异常信息中应该包含业务流水号便于调试和排查线上问题时将各个节点的错误内容串联起来 多数情况下业务异常都不应该打印堆栈只需要在日志中输出第一个触发业务异常的代码位置即可 因为业务异常是我们在编码阶段就手动捕获了的也就是说这些异常是可预期的并且是我们自己手动编码抛出的。因此只需要输出该异常的抛出点代码位置异常堆栈是没有意义的它只会增加日志的存储体积 另外多数业务异常都是在检查业务的执行条件时触发的比如商品不存在、库存不足、越权访问、输入数据不合规等。且这类错误会频繁发生若输出其堆栈的话日志中会大量充斥着这样的异常堆栈。它既增加了日志的存储体积也干扰了正常日志内容的查看 异常信息中要详细记录错误内容尽可能把异常现场的信息都输出。这是开发人员最容易给自己和他人挖坑的地方比如一个业务异常的日志输出内容是这样“积分等级不够”。这个异常信息是严重不足的它缺少以下这些重要信息以致极难在线上排查问题 谁的积分等级不足 这个用户当前的积分是多少 他要拥有多少积分和什么样的等级 他在访问什么资源 注意您可能会有疑问把用户账号输出到日志就可以了没必要输出它当前的积分因为积分可以去数据库查。但这样做是不行的因为 生产环境的数据库研发人员是不能直接访问的让运维人员查效率不高还增加运维工作量 数据查询出来的值也不是发生异常当时的值时光荏然你大妈已经不你大妈了 即使是个相对静态变动不频繁的参数运行期代码所使用的值也极有可能与数据库中不一致。比如程序启动时没有从数据库中加载而是使用了默认值又或者是某个处理逻辑将它的值临时改变了 非业务异常 尽可能地捕获所有异常 一定要在日志中输出非业务异常的堆栈重要 尽量不要二次包装非业务异常如果一定要包装「务必」在将包装后的异常 throw 前先输出原始异常的堆栈信息 18. 为什么出错了却没有异常日志 在 WebMVC 程序中通常都有一全局异常处理器如15小节所述)因此有异常一定是会被捕获并输出日志的。不过这个全局异常处理器仅对Web请求有效如果是以下以下情况则需要在代码中手动捕获和输出异常日志 在非WEB请求的线程中运行的代码比如定时任务中的代码所产生的异常。如果没有捕获和输出异常日志那么发生了异常也不知道只能从结果数据上判断可能发生了错误但却无法快速定位。 从Web请求线程中脱离出来的异步线程中的代码这种情况更常见同时也要非常小心。比如异步发送短信异步发邮件等一定要做好异常处理 19. 如何处理异常日志只有一行简短的文本 比如下面这个经典的场景 java.lang.NullPointerException 异常信息只有这么一行没有代码位置没有causedException, 更没有堆栈。这是因为JVM有个快速抛出FastThrow的异常优化如果相同的异常在短时间内集中大量throw则将这些异常都合并为同一个异常对象且没有堆栈。 解决办法为java 启动命令中添加-OmitStackTraceInFastThrow这个JVM选项如 java -XX:-OmitStackTraceInFastThrow -jar xxxx.jar 说明1 JVM只对以下异常做FastThrow优化 NullPointerException ArithmeticException ArrayStoreException ClassCastException ArrayIndexOutOfBoundsException 说明2 出现此问题基本上意味着代码有重大缺陷跟死循环差不多不然不会出现大量相同的常集中抛出。另外开启该选项后若这种场景出现是会刷爆日志存储的。当然相比之下找到问题更重要该选项是否要在生产环境开启就自行决定吧。 20. 如何解决同一实例内部方法调用时,部分事务失效的问题 事务失效示例代码(点击查看) Service public class BookService {ResourceBookDao bookDao;public void changePrice(Long bookId, Double newPrice) {doChangePrice(bookId, newPrice);logOperation();sendMail();}Transactional(rollbackFor Exception.class)public void resetPrice(Long bookId, Double newPrice) {doChangePrice(bookId, newPrice);logOperation();sendMail();}Transactional(rollbackFor Exception.class)public void doChangePrice(Long bookId, Double newPrice) {bookDao.setPrice(bookId, newPrice);}Transactional(rollbackFor Exception.class)public void logOperation(Long bookId, Double newPrice) {.... // 省略记录操作日志的代码}public void sendMail(Long bookId, Double newPrice) {.... // 省略发送邮件的代码} } 上述代码调用 changePrice() 方法时如果 sendMail() 方法在执行时发生了异常则前面的 doChangePrice() 和 logOperation() 所执行的数据库操作均不会回滚。但同样的情形如果发生在 resetPrice() 方法上doChangePrice() 和 logOperation() 均会回滚。 这个例子还可以进行更细化的演进不过核心原因都是一个Spring 对注解事务的实现手段是通过 CGLib 工具库创建一个继承这个业务类的新类捕获原业务类方法执行期间的异常然后执行回滚的。但是对原业务类中方法内部对其它方法的调用这个被调用的方法其上的事务注解则不再生效。如果直接在外部调用这些方法则事务注解是生效的。 以上面的示例代码为准, changePrice() 方法内部分别调用了 doChangePrice()、logOperation()、sendMail() 三个方法但由于 changePrice() 方法本身并没有添加事务注解因此它内部调用的 doChangePrice()、logOperation() 这两个方法的事务注解是不生效的。因此实际上执行过程都没有开启事务。当然如果是从外部直接单独调用 doChangePrice() 和 logOperation()则二者的事务均生效。 解决办法在外部单独调用这些有事务注解的方法。如果需要将这些方法组合在一个方法体内整体完成一个业务逻辑也在其它类中创建方法在该方法中调用这些有事务注解的方法完成逻辑组织。 21. 如何阻止某个第三方组件的自动装配 方法一配置 SpringBootApplication 注解的 exclude 属性 如下代码所示 // 启动时将Spring官方的数据源自动装配排除 SpringBootApplication(exclude {DataSourceAutoConfiguration.class}) public class MyAppMain{public static void main(String[] args) {SpringApplication.run(MyAppMain.class, args);} } 方法二在配置文件中指定 推荐 方法一需要修改代码对于普通的业务系统而言是能不改代码就坚决不改。因此推荐下面这种配置的方式来指定 spring:autoconfigure:# 指定要排除的自动装配类多个类使用英文逗号分隔exclude: org.springframework.cloud.gateway.config.GatewayAutoConfiguration 方法三临时注释掉该组件的 EnableXXX 注解比如常见的 EnableConfigurationProperies 、EnalbeAsync 、EnableJms 等在代码中临时注释掉这些注解即可。但仅适用于提供了这种 Enable 注解方式装配的组件。 未完待续~~~ 文章转载自顾志兵 原文链接https://www.cnblogs.com/guzb/p/spring-boot-common-development-issue-solution-list.html 体验地址引迈 - JNPF快速开发平台_低代码开发平台_零代码开发平台_流程设计器_表单引擎_工作流引擎_软件架构
http://www.zqtcl.cn/news/597468/

相关文章:

  • 国内产品网站w源码1688网站关键词描述字数
  • 网站404 模板wordpress 文字插件下载
  • 河南民基建设工程有限公司网站齐齐哈尔建设局网站首页
  • 响应式网站建设推荐乐云践新三丰云免费云服务器
  • 长沙网站建设模板uc浏览器访问网站
  • 擼擼擼做最好的导航网站陕西政务服务网注册公司流程
  • 怎样做商城网站的推广wordpress用php哪个版本好
  • 网站功能模块建设建设网站考证
  • 网站代码结构成都住建局官网报名入口
  • 吴桥县网站建设房产门户网站模板
  • 标签化网站网络服务类型及其所采用的网络协议
  • 做网站服务器应该怎么配置网页美工设计实践性教案
  • 响应式网站导航栏内容泰安网站营销推广
  • 南通营销网站开发软件开发工具名词解释
  • 吉林企业网站模板建站哪个好wordpress后台新建慢
  • 整合营销的成功案例肇庆seo优化
  • 网站关键字标签合肥高端网站建设设计公司哪家好
  • 大型企业网站设计案例免费在线看片
  • 云南网站开发公司找哪家网站弹出式链接后台怎么做
  • 电商网站的支付模块怎么做企业网站建设招标文件
  • 旅游在线网站开发十八个免费的舆情网站
  • 网站怎么申请百度小程序火车头采集发布wordpress
  • 外贸网站的推广技巧有哪些莱芜网吧
  • 溧阳城乡建设局网站ps中网站页面做多大的
  • sns社交网站 有哪些wordpress开发分类筛选
  • 黄石网站建设教程网上怎样查询企业资质
  • 国内设计师交流网站怎样做自己的网站钻钱
  • 无锡专业网站推广网络营销组合策略
  • 网站建设的安全威胁中国建设银行的网站色彩
  • 中小型企业网站建设与管理潍坊做网站哪家公司最好