SpringBoot 常用功能和封装代码小计


一. Spring框架

通过静态方式获取上下文对象(ApplicationContext)

创建一个名为ApplicationContextProvider的类,实现ApplicationContextAware接口。

实现这个接口后,Spring会在ApplicationContext初始化时调用setApplicationContext方法,在这个方法中将ApplicationContext赋值给静态变量context

然后即可在任何地方通过ApplicationContextProvider类的静态方法getApplicationContext()来获取ApplicationContext对象

import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

@Component
public class ApplicationContextProvider implements ApplicationContextAware {

    private static ApplicationContext context;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) {
        context = applicationContext;
    }

    public static ApplicationContext getApplicationContext() {
        return context;
    }
}
获取示例:
ApplicationContext context = ApplicationContextProvider.getApplicationContext();

通过静态方式获取请求对象(HttpServletRequest)

SpringBoot 项目中, 可以在非Controller类中使用RequestContextHolder来获取当前请求的HttpServletRequest对象。

但是请注意,这种方式不是推荐的做法,因为它会使代码与Spring框架耦合度较高。

示例

import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

public class MyService {

    public void doSomething() {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        
        // 使用request对象获取请求信息
        String userAgent = request.getHeader("User-Agent");
        System.out.println("User-Agent: " + userAgent);
    }
}

SpringBoot 项目打包为可执行 Jar 包

  1. 添加POM依赖

    <build>
      <plugins>
        <plugin>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-maven-plugin</artifactId>
          <!-- 版本号使用默认版本号, 或者根据实际情况调整 -->
          <!-- <version>2.5.2</version> -->
          <configuration>
            <executable>true</executable>
          </configuration>
        </plugin>
      </plugins>
    </build>
  2. 执行打包命令

    mvn clean package
  3. 测试是否正常运行

    java -jar app.jar

SpringBoot 项目加载外部配置文件

  1. 通过命令行参数指定配置文件路径

    在启动应用程序时,可以使用--spring.config.location参数来指定配置文件的路径,例如:
    java -jar app.jar --spring.config.location=file:/custom.properties
  2. 通过配置文件配置

    application.propertiesapplication.yml中指定配置文件路径:

    application.properties文件中添加如下配置:

    spring.config.location=file:/custom.properties
    或者在application.yml文件中添加如下配置:
    spring:
      config:
        location: file:/custom.properties

二. SpringMVC

全局统一响应对象封装

  1. 创建 Result<T>

    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    import lombok.experimental.Accessors;
    
    /**
     * 全局统一响应对象
     * @author TheEnd
     */
    @Data
    @Accessors(chain = true)
    @NoArgsConstructor
    @AllArgsConstructor
    public class Result<T> implements Serializable {
    
     /**
      * 响应码
      */
     private Integer code;
     /**
      * 消息
      */
     private String msg;
     /**
      * 数据
      */
     private T data;
    
     public Result(Integer code) {
         this.code = code;
     }
    
     public Result(String msg) {
         this.msg = msg;
     }
    
     public Result(Integer code, String msg) {
         this.code = code;
         this.msg = msg;
     }
    
     public Result(T data) {
         this.data = data;
     }
    
     public static Result<String> success() {
         return new Result<>(Code.SUCCESS);
     }
    
     public static <T> Result<T> success(T t) {
         return new Result<>(Code.SUCCESS, "", t);
     }
    
     public static Result<String> fail() {
         return new Result<>(Code.FAIL, "发生异常");
     }
    
     public static Result<String> fail(Integer code) {
         return new Result<>(code, "发生异常");
     }
    
     public static Result<String> fail(String msg) {
         return new Result<>(Code.FAIL, msg);
     }
    
     public static Result<String> fail(Integer code, String msg) {
         return new Result<>(code, msg);
     }
    
     public static Result<String> authFail() {
         return new Result<>(Code.AUTH, "权限不足");
     }
    
     public static Result<String> businessFail() {
         return new Result<>(Code.BUSINESS, "业务异常");
     }
    
    
     public static Result<String> remoteFail() {
         return new Result<>(Code.REMOTE, "业务异常");
     }
    
     public static class Code {
         public static final Integer SUCCESS = 200;
         public static final Integer FAIL = 0;
         public static final Integer AUTH = 400;
         public static final Integer BUSINESS = 500;
         public static final Integer REMOTE = 600;
     }
    
    }

全局统一异常处理

  1. 创建自定义异常

    默认异常
    /**
     * 默认异常
     * @author TheEnd
     */
    public class DefaultException extends RuntimeException {
    
        public DefaultException() {
        }
    
        public DefaultException(String message) {
            super(message);
        }
    
        public DefaultException(String message, Throwable cause) {
            super(message, cause);
        }
    
        public DefaultException(Throwable cause) {
            super(cause);
        }
    
        public DefaultException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
            super(message, cause, enableSuppression, writableStackTrace);
        }
    }
    业务异常
    /**
     * 业务异常
     * @author TheEnd
     */
    public class BusinessException extends RuntimeException {
    
        public BusinessException() {
        }
    
        public BusinessException(String message) {
            super(message);
        }
    
        public BusinessException(String message, Throwable cause) {
            super(message, cause);
        }
    
        public BusinessException(Throwable cause) {
            super(cause);
        }
    
        public BusinessException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
            super(message, cause, enableSuppression, writableStackTrace);
        }
    }
    远程服务异常
    /**
     * 远程服务异常
     * @author TheEnd
     */
    public class RemoteServiceException extends RuntimeException {
    
        public RemoteServiceException() {
        }
    
        public RemoteServiceException(String message) {
            super(message);
        }
    
        public RemoteServiceException(String message, Throwable cause) {
            super(message, cause);
        }
    
        public RemoteServiceException(Throwable cause) {
            super(cause);
        }
    
        public RemoteServiceException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
            super(message, cause, enableSuppression, writableStackTrace);
        }
    }
  2. 配置全局异常处理器

    创建 GlobalExceptionHandler
    import lombok.extern.slf4j.Slf4j;
    import org.example.blog.common.Result;
    import org.example.blog.exception.BusinessException;
    import org.example.blog.exception.DefaultException;
    import org.example.blog.exception.RemoteServiceException;
    import org.springframework.web.bind.annotation.ExceptionHandler;
    import org.springframework.web.bind.annotation.RestControllerAdvice;
    
    /**
     * 全局异常处理器
     * @author Lenovo
     */
    @Slf4j
    @RestControllerAdvice
    public class GlobalExceptionHandler {
    
        @ExceptionHandler(RemoteServiceException.class)
        public Result<String> handleException(RemoteServiceException e) {
            log.error("远程服务发生异常", e);
            return Result.remoteFail().setMsg(e.getMessage());
        }
    
        @ExceptionHandler(BusinessException.class)
        public Result<String> handleException(BusinessException e) {
            log.error("发生业务异常", e);
            return Result.businessFail().setMsg(e.getMessage());
        }
    
        @ExceptionHandler(DefaultException.class)
        public Result<String> handleException(DefaultException e) {
            log.error("发生异常", e);
            return Result.fail(e.getMessage());
        }
    
        @ExceptionHandler(Exception.class)
        public Result<String> handleException(Exception e) {
            log.error("发生异常", e);
            return Result.fail(e.getMessage());
        }
    
    }

三. Spring-Data-Jdbc

SpringBoot 项目配置动态数据源

通过AbstractRoutingDataSource实现动态数据源
  1. 创建数据源类型枚举

    根据项目数据源实际情况修改
    /**
     * 数据源类型枚举
     * @author TheEnd
     */
    public enum DatasourceType {
       /**
        * 主库
        */
       PRIMARY,
       /**
        * 三方库
        */
       SECONDARY
    }
    
  2. 创建DynamicDataSourceContextHolder对象

    用于切换数据源
    /**
     * 动态数据源切换器
     * @author TheEnd
     */
    public class DynamicDataSourceContextHolder {
    
       private static final ThreadLocal<DatasourceType> CONTEXT_HOLDER = new ThreadLocal<>();
    
       /**
        * 设置当前线程使用的数据源类型
        * @param dataSourceType 数据源类型
        */
       public static void setDataSourceType(DatasourceType dataSourceType) {
          CONTEXT_HOLDER.set(dataSourceType);
       }
    
       /**
        * 获取当前线程使用的数据源类型
        * @return 数据源类型
        */
       public static DatasourceType getDataSourceType() {
          return CONTEXT_HOLDER.get();
       }
    
       /**
        * 清除当前线程使用的数据源类型
        */
       public static void clearDataSourceType() {
          CONTEXT_HOLDER.remove();
       }
    }
  3. 创建DynamicDataSource对象

    动态数据源对象, 继承至AbstractRoutingDataSource
    import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
    
    /**
     * 动态数据源对象
     * @author TheEnd
     */
    public class DynamicDataSource extends AbstractRoutingDataSource {
    
       @Override
       protected Object determineCurrentLookupKey() {
          // 返回当前线程的数据源类型
          return DynamicDataSourceContextHolder.getDataSourceType();
       }
    }
  4. 创建DataSourceConfig对象

    在SpringBoot中配置动态数据源
    import org.example.datasource.DatasourceType;
    import org.example.datasource.DynamicDataSource;
    import org.springframework.boot.context.properties.ConfigurationProperties;
    import org.springframework.boot.jdbc.DataSourceBuilder;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.Primary;
    import org.springframework.jdbc.core.JdbcTemplate;
    
    import javax.sql.DataSource;
    import java.util.HashMap;
    import java.util.Map;
    
    /**
     * 动态数据源配置
     * @author TheEnd
     */
    @Configuration
    public class DataSourceConfig {
    
       /**
        * 主数据源
        * @return 主数据源对象
        */
       @Bean
       @ConfigurationProperties(prefix = "datasource.primary")
       public DataSource primaryDataSource() {
          return DataSourceBuilder.create().build();
       }
    
       /**
        * 三方数据源
        * @return 三方数据源对象
        */
       @Bean
       @ConfigurationProperties(prefix = "datasource.secondary")
       public DataSource secondaryDataSource() {
          return DataSourceBuilder.create().build();
       }
    
       /**
        * 动态数据源
        * @param primaryDataSource 主数据源
        * @param secondaryDataSource 三方数据源
        * @return 动态数据源对象
        */
       @Bean
       @Primary
       public DynamicDataSource dataSource(DataSource primaryDataSource, DataSource secondaryDataSource) {
          // 将数据源封装为 Map
          Map<Object, Object> targetDataSources = new HashMap<>(4);
          targetDataSources.put(DatasourceType.PRIMARY, primaryDataSource);
          targetDataSources.put(DatasourceType.SECONDARY, secondaryDataSource);
    
          // 配置动态数据源对象
          DynamicDataSource dataSource = new DynamicDataSource();
          dataSource.setTargetDataSources(targetDataSources);
          // 设置默认数据源为主数据源
          dataSource.setDefaultTargetDataSource(primaryDataSource);
    
          return dataSource;
       }
    
       @Bean
       public JdbcTemplate jdbcTemplate(DynamicDataSource dataSource) {
          return new JdbcTemplate(dataSource);
       }
    
    }
  5. 数据源切换示例

    使用DynamicDataSourceContextHolder切换数据源
    import org.example.datasource.DatasourceType;
    import org.example.datasource.DynamicDataSourceContextHolder;
    
    /**
     * @author TheEnd
     */
    public class DatasourceService {
    
       public void datasourceChange() {
          // 设置使用主数据源
          DynamicDataSourceContextHolder.setDataSourceType(DatasourceType.PRIMARY);
          // 设置使用三方数据源
          DynamicDataSourceContextHolder.setDataSourceType(DatasourceType.SECONDARY);
          // 清除数据源配置
          DynamicDataSourceContextHolder.clearDataSourceType();
       }
    
    }
如果发生循环依赖异常, 取消SpringBoot中数据源自动配置即可
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class);

基于AOP实现通过注解自动切换数据源

  1. 创建自定义注解DataSourceSwitch

    注解可以标注在类上或方法上。

    在方法上标注注解后, 在方法执行前自动切换数据源,并在方法执行后还原数据源。

    同时,也可以在类级别上使用该注解,使类中的所有方法都具有相同的切换数据源行为。

    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    /**
     * @author TheEnd
     */
    @Target({ElementType.METHOD, ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface DataSourceSwitch {
    
       DatasourceType value();
    
    }
  2. 创建切面类DataSourceSwitchAspect

    import org.aspectj.lang.JoinPoint;
    import org.aspectj.lang.annotation.After;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Before;
    import org.aspectj.lang.reflect.MethodSignature;
    import org.example.datasource.DatasourceType;
    import org.example.datasource.DynamicDataSourceContextHolder;
    import org.springframework.stereotype.Component;
    
    import java.lang.reflect.Method;
    
    /**
     * @author Lenovo
     */
    @Aspect
    @Component
    public class DataSourceSwitchAspect {
    
        private final ThreadLocal<DatasourceType> previousDataSourceType = new ThreadLocal<>();
    
        @Before("@annotation(dataSourceSwitch) || @within(dataSourceSwitch)")
        public void switchDataSource(JoinPoint joinPoint, DataSourceSwitch dataSourceSwitch) {
            MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
            Method method = methodSignature.getMethod();
            DataSourceSwitch methodAnnotation = method.getAnnotation(DataSourceSwitch.class);
    
            if (methodAnnotation != null) {
                DatasourceType dataSourceType = methodAnnotation.value();
                previousDataSourceType.set(DynamicDataSourceContextHolder.getDataSourceType());
                DynamicDataSourceContextHolder.setDataSourceType(dataSourceType);
            } else {
                DatasourceType classDataSourceType = dataSourceSwitch.value();
                previousDataSourceType.set(DynamicDataSourceContextHolder.getDataSourceType());
                DynamicDataSourceContextHolder.setDataSourceType(classDataSourceType);
            }
    
        }
    
        @After("@annotation(dataSourceSwitch) || @within(dataSourceSwitch)")
        public void restoreDataSource(JoinPoint joinPoint, DataSourceSwitch dataSourceSwitch) {
            DynamicDataSourceContextHolder.setDataSourceType(previousDataSourceType.get());
            previousDataSourceType.remove();
        }
    
    }
  3. 使用注解DataSourceSwitch实现自动切换数据源

    import org.example.datasource.DatasourceType;
    import org.example.datasource.DynamicDataSourceContextHolder;
    import org.example.ds.DataSourceSwitch;
    import org.springframework.stereotype.Service;
    
    /**
     * @author Lenovo
     */
    @Service
    @DataSourceSwitch(DatasourceType.SECONDARY)
    public class DsService {
    
        @DataSourceSwitch(DatasourceType.PRIMARY)
        public DatasourceType test() {
            return DynamicDataSourceContextHolder.getDataSourceType();
        }
    
        @DataSourceSwitch(DatasourceType.SECONDARY)
        public DatasourceType test2() {
            return DynamicDataSourceContextHolder.getDataSourceType();
        }
    
        public DatasourceType test3() {
            return DynamicDataSourceContextHolder.getDataSourceType();
        }
    
    }

三. 整合三方框架

评论已关闭