多配置文件:
不同环境需要用到不同的配置文件: 比如dev .pro .local
格式:application-{profile}.properties/yml:
• application-dev.properties、application-prod.properties
2、多profile文档块模式:
3、激活方式:
– 命令行 --spring.profiles.active=dev
– 配置文件 spring.profiles.active=dev
– jvm参数 –Dspring.profiles.active=dev
一般开发直接配置在tomcat的环境变量里就可以,然后通过占位符获取
SpringBoot读取配置文件的顺序:
1、项目jar包同级下的config文件夹是优先级最高的,是在执行命令的目录下建config文件夹。执行命令需要在jar包目录下才行
2、项目jar包同级下直接放properties文件是次优先级,是直接把配置文件放到jar包的同级目录。
3、项目内部的classpath同级config文件夹是第三优先级,在classpath下建一个config文件夹,然后把配置文件放进去。
4、项目内部的classpath同级放properties文件是最低优先级,是在classpath下直接放配置文件。
日志:
SpringBoot默认帮我们配置好了日志;
直接创建即可
@SpringBootTest
class DemoApplicationTests {
Logger logger = LoggerFactory.getLogger(Controllerhello.class);
@Test
void contextLoads() {
logger.info("kkkkkkkkkkkk");
}
}
可以修改日志模式;
logging.level.com.springboottest.demo.DemoApplicationTests=info
日志输出格式:
%d表示日期时间,
%thread表示线程名,
%‐5level:级别从左显示5个字符宽度
%logger{50} 表示logger名字最长50个字符,否则按照句点分割。
%msg:日志消息,
%n是换行符 ‐‐>
logging.pattern.console=%d{yyyy-MM-dd} [%thread] %-5level %logger{50} - %msg%n
日志的配置文件springboot其实默认配好了,如果想要自己的配置文件,直接在resources下面添加即可:logback-spring.xml,最好带spring。这样可以支持
既可以配置在不同环境下用不通过格式
<?xml version="1.0" encoding="UTF-8"?>
<!--
scan:当此属性设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true。
scanPeriod:设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒当scan为true时,此属性生效。默认的时间间隔为1分钟。
debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。
-->
<configuration scan="false" scanPeriod="60 seconds" debug="false">
<!-- 定义日志的根目录 -->
<property name="LOG_HOME" value="/app/log" />
<!-- 定义日志文件名称 -->
<property name="appName" value="demo-springboot"></property>
<!-- ch.qos.logback.core.ConsoleAppender 表示控制台输出 -->
<appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
<!--
日志输出格式:
%d表示日期时间,
%thread表示线程名,
%-5level:级别从左显示5个字符宽度
%logger{50} 表示logger名字最长50个字符,否则按照句点分割。
%msg:日志消息,
%n是换行符
-->
<layout class="ch.qos.logback.classic.PatternLayout">
<springProfile name="dev">
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} ----> [%thread] ---> %-5level %logger{50} - %msg%n</pattern>
</springProfile>
<springProfile name="!dev">
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} =+++=== [%thread] ==== %-5level %logger{50} - %msg%n</pattern>
</springProfile>
</layout>
</appender>
<!-- 滚动记录文件,先将日志记录到指定文件,当符合某个条件时,将日志记录到其他文件 -->
<appender name="appLogAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 指定日志文件的名称 -->
<file>${LOG_HOME}/${appName}.log</file>
<!--
当发生滚动时,决定 RollingFileAppender 的行为,涉及文件移动和重命名
TimeBasedRollingPolicy: 最常用的滚动策略,它根据时间来制定滚动策略,既负责滚动也负责出发滚动。
-->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!--
滚动时产生的文件的存放位置及文件名称 %d{yyyy-MM-dd}:按天进行日志滚动
%i:当文件大小超过maxFileSize时,按照i进行文件滚动
-->
<fileNamePattern>${LOG_HOME}/${appName}-%d{yyyy-MM-dd}-%i.log</fileNamePattern>
<!--
可选节点,控制保留的归档文件的最大数量,超出数量就删除旧文件。假设设置每天滚动,
且maxHistory是365,则只保存最近365天的文件,删除之前的旧文件。注意,删除旧文件是,
那些为了归档而创建的目录也会被删除。
-->
<MaxHistory>365</MaxHistory>
<!--
当日志文件超过maxFileSize指定的大小是,根据上面提到的%i进行日志文件滚动 注意此处配置SizeBasedTriggeringPolicy是无法实现按文件大小进行滚动的,必须配置timeBasedFileNamingAndTriggeringPolicy
-->
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>100MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
<!-- 日志输出格式: -->
<layout class="ch.qos.logback.classic.PatternLayout">
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [ %thread ] - [ %-5level ] [ %logger{50} : %line ] - %msg%n</pattern>
</layout>
</appender>
<!--
logger主要用于存放日志对象,也可以定义日志类型、级别
name:表示匹配的logger类型前缀,也就是包的前半部分
level:要记录的日志级别,包括 TRACE < DEBUG < INFO < WARN < ERROR
additivity:作用在于children-logger是否使用 rootLogger配置的appender进行输出,
false:表示只用当前logger的appender-ref,true:
表示当前logger的appender-ref和rootLogger的appender-ref都有效
-->
<!-- hibernate logger -->
<logger name="com.springboottest" level="debug" />
<!-- Spring framework logger -->
<logger name="org.springframework" level="debug" additivity="false"></logger>
<!--
root与logger是父子关系,没有特别定义则默认为root,任何一个类只会和一个logger对应,
要么是定义的logger,要么是root,判断的关键在于找到这个logger,然后判断这个logger的appender和level。
-->
<root level="info">
<appender-ref ref="stdout" />
<appender-ref ref="appLogAppender" />
</root>
</configuration>
也可以切换日志框架,改下pom文件
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<artifactId>spring-boot-starter-logging</artifactId>
<groupId>org.springframework.boot</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
同样log4j2也被默认配置了,同样可以添加自己的logj2-spring.xml配置文件
整合Mybatis
引入依赖,配置好数据源,配置文件可以直接配置mybatis的一些属性,比如驼峰啊,别名扫描,mapper扫描
mybatis.mapper-locations=classpath:mapper/*.xml
mybatis.type-aliases-package=com.kk.domain
mybatis.configuration.map-underscore-to-camel-case=true
然后直接就可以用了,写好相应的mapper, 对应的mapper.xml即可
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.0</version>
</dependency>
邮件功能
引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
看下自动配置包的有关mail的
mail的配置前缀 spring.mail 例如spring.mail.host;spring.mail.properties.mail.smtp.ssl.enable
@Test
void sendMail() throws MessagingException {
//简单邮件
SimpleMailMessage mailMessage = new SimpleMailMessage();
mailMessage.setSubject("K");
mailMessage.setText("kkkkkk");
mailMessage.setTo("2315644@qq.com");
mailMessage.setFrom("afasfa4@qq.com");
javaMailSender.send(mailMessage);
//复杂邮件
MimeMessage mimeMessage = javaMailSender.createMimeMessage();
MimeMessageHelper messageHelper = new MimeMessageHelper(mimeMessage,true);
messageHelper.setText("<b>KKKKK</b>",true);
messageHelper.setSubject("K");
messageHelper.addAttachment("kk.jpg",new File("D://hh/kk.jpg"));
messageHelper.setTo("2315644@qq.com");
messageHelper.setFrom("afasfa4@qq.com");
javaMailSender.send(mimeMessage);
}
使用redis作为缓存
引入依赖,添加配置即可使用,domain记得序列化
application.properties配置redis
spring.redis.host=
@EnableCaching
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
Springboot多数据源
使用的是spring自带的抽象类AbstractRoutingDataSource
先写个DataSourceObserver
/**
* 动态数据源上下文,保证每个线程都可以独立改变
*/
public class DataSourceObserver{
private static final ThreadLocal<String> local = new ThreadLocal<>();
public static void setDataSourceName(String name){
local.set(name);
}
public static String getDataSourceName(){
return local.get();
}
public static void clearDataSourceName(){
local.remove();
}
}
然后写个DynamicDataSource 继续AbstractRoutingDataSource
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DataSourceObserver.getDataSourceName();
}
}
为什么重写这个方法,从AbstractRoutingDataSource源码里可以看到determineCurrentLookupKey这个方法是获取你传入的数据源的key,即你传入redshift,你就可以得到redshift的连接池;
@Override
public Connection getConnection() throws SQLException {
return determineTargetDataSource().getConnection();
}
protected DataSource determineTargetDataSource() {
Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
Object lookupKey = determineCurrentLookupKey();
DataSource dataSource = this.resolvedDataSources.get(lookupKey);
if (dataSource == null && (this.lenientFallback || lookupKey == null)) {
dataSource = this.resolvedDefaultDataSource;
}
if (dataSource == null) {
throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
}
return dataSource;
}
然后配置下数据源和配置类
@Configuration
public class DataSourceConfig{
@Bean(name="maria")
@ConfigurationProperties(prefix = "spring.datasource.maria")
public DataSource mariaDBDataSource() {
return new HikariDataSource();
}
@Bean(name = "redshift")
@ConfigurationProperties(prefix = "spring.datasource.redshift")
public DataSource redshiftDataSource() {
return new HikariDataSource();
}
@Bean(name = "vertica")
@ConfigurationProperties(prefix = "spring.datasource.vertica")
public DataSource verticaDataSource(){
return new HikariDataSource();
}
@Bean(name = "dynamic")
public DataSource dynamicDataSource(@Qualifier("redshift") DataSource redshift,
@Qualifier("maria") DataSource maria,
@Qualifier("vertica") DataSource vertica
){
DynamicDataSource dynamicDataSource = new DynamicDataSource();
Map<Object, Object> resolvedDataSources = new HashMap<>();
resolvedDataSources.put(ProgramConstant.REDSHIFT,redshift);
resolvedDataSources.put(ProgramConstant.MARIA,maria);
resolvedDataSources.put(ProgramConstant.VERTICA,vertica);
//设置所有的数据源
dynamicDataSource.setTargetDataSources(resolvedDataSources);
//设置默认的数据源
dynamicDataSource.setDefaultTargetDataSource(maria);
return dynamicDataSource;
}
@Bean(name = "template")
public JdbcTemplate template(@Qualifier("dynamic") DataSource dynamicDatasource) {
return new JdbcTemplate(dynamicDatasource);
}
@Bean(name = "namedTemplate")
public NamedParameterJdbcTemplate namedTemplate(@Qualifier("dynamic") DataSource dynamicDatasource) {
return new NamedParameterJdbcTemplate(dynamicDatasource);
}
}
这个时候就可以通过 DataSourceObserver.setDataSourceName(“redshift”)实现动态切换了;
可以通过aop对每个方法进行自动切换,可以搭配个注解
@Target(ElementType.METHOD)
@Retention(RUNTIME)
@Documented
public @interface SelectDatasoure {
String value() default "maria";
}
写好注解就可以在service层使用了
@SelectDatasoure(value = "redshift")
public List<User> findUser(){
return null;
}
@SelectDatasoure(value = "maria")
public List<User> findUse2r(){
return null;
}
运用AOP拦截注解为SelectDatasoure的方法
@Aspect
@Component
@Order(-1)//保证在事务之前执行
public class DynamicDataSourceAspect {
//sd 表示的是我们要拦截的注解,其他的不会拦截
@Before("@annotation(sd)")
public void changeDataSource(JoinPoint point, SelectDatasoure sd) throws Throwable {
String dsId = ds.value();
DataSourceObserver.setDataSourceName(dsId);
}
@After("@annotation(sd)")
public void restoreDataSource(JoinPoint point, SelectDatasoure sd) {
DataSourceObserver.clearDataSourceName();
}
}
到这里动态切换就完成了;
异常处理
创建一个全局异常处理类即可,注意注解@ControllerAdvice
常用于:
全局异常处理
全局数据绑定
全局数据预处理
@ControllerAdvice
public class MyExceptionHandler {
//指定出现什么异常执行这个方法
@ExceptionHandler(Exception.class)
@ResponseBody //为了返回数据
public String error(Exception e) {
e.printStackTrace();
return"执行了全局异常处理..";
}
}
你可以指定异常处理,例在@ExceptionHandler(ArithmeticException.class)里写入即可
同样你可以自定义异常,写个异常类继承RuntimeException,在try{}catch(Excepetion e){ throw new 自定义异常类 }中 throw即可