springboot原理及使用
什么是springboot?
springboot是一个用于快速开发的框架,去除了繁琐的xml配置文件,全部采用注解的方式完成配置,内置Web服务器,可以快速的整合第三方的各种框架。是便捷开发的首选利器
springcloud与springboot的关系
spring只是一个快速开发的框架。而springcloud是微服务的一站式解决方案(服务熔断、降级、负载均衡、配置中心、注册中心)等等。
springcloud 依赖于springboot。
spring-boot-statat-web 依赖与springmvc的关系
这个依赖只是集成了springmvc
springboot 的静态资源默认目录
# 在以下目录存放的资源可以直接通过
# 域名/资源路径名访问到,不需要添加一个static或者public
"classpath:/META-INF/resources/","classpath:/resources/",
"classpath:/static/", "classpath:/public/"
springboot的组件解释
- spring-boot-starter-parent作用
它可以提供dependency management,也就是说依赖管理,引入以后在申明其它dependency的时候就不需要version了,后面可以看到。 - spring-boot-starter-web作用
springweb 核心组件,包含springmvc之类的
Springboot多环境配置
- 定义三个环境的不同配置文件
- 在application.properties中配置spring.profiles.active属性
spring.profiles.active=prod
#spring.profiles.active=dev
#spring.profiles.active=test
- 也可以在运行的时候添加虚拟机参数
--spring.profiles.active=dev
整合框架部分
整合mybatis框架
- 引入依赖 mybatis-spring-boot-starter、以及数据库连接驱动mysql-connector-java
- 在springboot的配置文件中直接配置数据库连接参数,springboot默认集成了HikariCP数据源
mybatis:
mapper-locations: classpath:mapping/*Mapper.xml # 扫描dao层对应的映射
type-aliases-package: com.it.entity # 为这个包的类定义别名,不用写全限定类名,并且类名首字母可以小写
configuration:
# log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 想要打印sql日志必须加上这一句
# map-underscore-to-camel-case: true
spring:
datasource:
username: root
password: root
url: jdbc:mysql://localhost:192.168.254.128/test?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=UTC
driver-class-name: com.mysql.jdbc.Driver
#打印mybatis查询日志 相当于在日志的配置文件里加了一个 logging 如果使用log4j2,则不管用,就需要配置mybatis 的log-impl: org.apache.ibatis.logging.stdout.StdOutImpl 属性
logging:
level:
com:
it:
mapper : debug
- 扫描Dao层,可以在springboot的启动类上加上@MapperScan注解配置需要扫描的Dao层(用于告诉mybatis这是一个Dao层),会将dao中加上了 @Insert @Select 等接口的类利用动态代理的技术创建动态代理对象来拦截执行方法。
- 或者在Dao的接口上直接加上@Mapper注解,一样能实现一样的功能,但是比较麻烦(不推荐)
常用功能
springboot的actuator监控中心
作用
针对微服务服务器进行监控,监控服务器内存变化(堆内存、线程、日志管理等)、检测服务器链接地址是否可用(模拟访问)、通知容器中有多少个baen(spring容器中的bean)、统计@RequestMapping中有多少个映射。
Actuator是没有图形化界面的(返回Json格式)
AdminUI 底层使用Actuator监控应用,实现可视化界面
应用场景:生产环境
为什么要使用监控中心
可以帮助应用程序在生产环境下的监视和管理、统计应用的运行情况,特别堆微服务的管理有意义。
使用actuator监控中心
- 在pom文件中添加依赖
- 修改yml文件,配置允许访问所有监控端点,默认只开放了3个
management:
endpoints:
web:
exposure:
include: "*"
- 这个时候项目启动后浏览器访问时,项目路径+/actuator/mappings,然后在里面可以搜索到所有的当前项目的requestmapping的映射
- 常用的监控信息
/actuator/beans 显示应用程序中所有spring中的bean
/actuator/configprops 显示所有的配置信息
/actuator/env 显示所有的环境变量
/actuator/mappings 显示所有@RequestMapping的url列表
/actuator/health 显示程序的运行状况,会模拟访问配置文件中配置的mysql、redis等连接是否可用,不可用(控制台抛出异常,页面返回down)。可用页面返回up,控制台无输出
/actuator/info 查看在配置文件中自定义的信息,例如
info:
name: 张三
age: 15
使用AdminUi监控actuator信息
- 定义一个服务端
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-starter-server</artifactId>
<version>2.0.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<!-- Spring Boot Actuator对外暴露应用的监控信息,Jolokia提供使用HTTP接口获取JSON格式 的数据 -->
<dependency>
<groupId>org.jolokia</groupId>
<artifactId>jolokia-core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>com.googlecode.json-simple</groupId>
<artifactId>json-simple</artifactId>
<version>1.1</version>
</dependency>
- 修改配置文件
spring:
application:
name: spring-boot-admin-server
- 创建客户端,将其注册到AdminUi服务端中去
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-starter-client</artifactId>
<version>2.0.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.jolokia</groupId>
<artifactId>jolokia-core</artifactId>
</dependency>
<dependency>
<groupId>com.googlecode.json-simple</groupId>
<artifactId>json-simple</artifactId>
<version>1.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
- 修改配置文件
spring:
boot:
admin:
client:
url: http://localhost:8080
server:
port: 8081
management:
endpoints:
web:
exposure:
include: "*"
endpoint:
health:
show-details: ALWAYS
- 项目启动,服务端使用
@Configuration
@EnableAutoConfiguration
@EnableAdminServer
- 客户端使用
@SpingBootApplication
配置多数据源
- 在application配置文件中配置2个dataSource,注意这里必须要用大驼峰命名
spring:
# datasource:
# username: root
# password: root
# url: jdbc:mysql://192.168.254.128:3306/test?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=UTC
# driver-class-name: com.mysql.jdbc.Driver
datasource1:
username: root
password: root
jdbcUrl: jdbc:mysql://192.168.254.128:3306/test?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=UTC
driverClassName: com.mysql.jdbc.Driver
datasource2:
username: root
password: root
jdbcUrl: jdbc:mysql://192.168.254.128:3306/test2?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=UTC
driverClassName: com.mysql.jdbc.Driver
- 配置2个配置类,用于给不用的包使用不同的DataSource,哪个@Primary是指定这个为主数据源
@Configuration
@MapperScan(basePackages = "com.it.test1.dao", sqlSessionFactoryRef = "test1SqlSessionFactory")
public class DataSourceConfig1 {
@Bean("test1DataSource")
@ConfigurationProperties("spring.datasource1")
@Primary
public DataSource test1DataSource(){
return DataSourceBuilder.create().build();
}
@Bean("test1SqlSessionFactory")
@Primary
public SqlSessionFactory sqlSessionFactory() throws Exception {
SqlSessionFactoryBean sqlSessionFactory = new SqlSessionFactoryBean();
sqlSessionFactory.setDataSource(test1DataSource());
// sqlSessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver().
// getResources("classpath:mapper/test1/*Mapper.xml"));
return sqlSessionFactory.getObject();
}
@Bean("test1DataSourceTransactionManager")
@Primary
public DataSourceTransactionManager transactionManager() {
return new DataSourceTransactionManager(test1DataSource());
}
@Bean(name = "test1SqlSessionTemplate")
@Primary
public SqlSessionTemplate testSqlSessionTemplate() throws Exception {
return new SqlSessionTemplate(sqlSessionFactory());
}
}
- 开启事物、由于springboot本来就自带了事物,所以直接添加注解即可
@Transactional(transactionManager = "test2DataSourceTransactionManager")
传统分布式事物解决方案(比如在控制器中调用了2个不同数据源的Dao层,如果只是在控制器上配置了事物,则会出现事物无法一同回滚的情况)
- 使用springboot + jta-atomikos 的方式来配置多数据源,由jta-atomikos来管理数据源。它会将本地事物转换成全局事物,大家都使用同一个事物
- 引入依赖 spring-boot-starter-jta-atomikos
- 还是跟之前一样在application配置文件中配置多个数据源
mysql.datasource.test2.url =jdbc:mysql://localhost:3306/test02?useUnicode=true&characterEncoding=utf-8
mysql.datasource.test2.username =root
mysql.datasource.test2.password =root
mysql.datasource.test2.minPoolSize = 3
mysql.datasource.test2.maxPoolSize = 25
mysql.datasource.test2.maxLifetime = 20000
mysql.datasource.test2.borrowConnectionTimeout = 30
mysql.datasource.test2.loginTimeout = 30
mysql.datasource.test2.maintenanceInterval = 60
mysql.datasource.test2.maxIdleTime = 60
- 可以配置2个Entity类来注入配置文件的数据源。
@ConfigurationProperties(prefix = "mysql.datasource1")
- 配置多数据源
@MapperScan(basePackages = "com.itmayiedu.test01", sqlSessionTemplateRef = "testSqlSessionTemplate")
public class MyBatisConfig1 {
// 配置数据源
@Primary
@Bean(name = "testDataSource")
// 这里的DBConfig1 就是上一步所说的装载的Bean,这里面有着数据源信息
public DataSource testDataSource(DBConfig1 testConfig) throws SQLException {
MysqlXADataSource mysqlXaDataSource = new MysqlXADataSource();
mysqlXaDataSource.setUrl(testConfig.getUrl());
mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true);
mysqlXaDataSource.setPassword(testConfig.getPassword());
mysqlXaDataSource.setUser(testConfig.getUsername());
mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true);
AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean();
xaDataSource.setXaDataSource(mysqlXaDataSource);
xaDataSource.setUniqueResourceName("testDataSource");
xaDataSource.setMinPoolSize(testConfig.getMinPoolSize());
xaDataSource.setMaxPoolSize(testConfig.getMaxPoolSize());
xaDataSource.setMaxLifetime(testConfig.getMaxLifetime());
xaDataSource.setBorrowConnectionTimeout(testConfig.getBorrowConnectionTimeout());
xaDataSource.setLoginTimeout(testConfig.getLoginTimeout());
xaDataSource.setMaintenanceInterval(testConfig.getMaintenanceInterval());
xaDataSource.setMaxIdleTime(testConfig.getMaxIdleTime());
xaDataSource.setTestQuery(testConfig.getTestQuery());
return xaDataSource;
}
@Primary
@Bean(name = "testSqlSessionFactory")
public SqlSessionFactory testSqlSessionFactory(@Qualifier("testDataSource") DataSource dataSource)
throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
return bean.getObject();
}
@Primary
@Bean(name = "testSqlSessionTemplate")
public SqlSessionTemplate testSqlSessionTemplate(
@Qualifier("testSqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
项目打包运行
- 在pom文件中加入如下配置
<build>
<plugins>
<!-- 指定jdk编译版本-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<!-- 这样就可以将项目打包成可运行的jar包 -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
- 在当前项目的根目录执行 mvn package
- 然后会在target文件夹目录下看到一个jar包
- 命令运行 java -jar jar包的全名.jar 即可运行
热部署实现原理
1、监听class文件是否发生改变。通过保存时间或者版本号
2、通过类加载器classloader 将修改后的class文件重新加载进jvm内存中。
热部署是否可以放在生产环境?
不可以,对性能不好。不安全。
应用场景:本地开发,提高开发效率,不需要重启服务器
缺点:如果项目比较大,就会很大,因为读取哪些字节码文件发生了改变,需要扫包。占用内存大。
springboot的devtools原理
原理就是监听保存,在你每次保存的时候帮你重启服务器
监听文件或文件夹的增删改查
@Component
public class MonitorInitializer implements ApplicationRunner {
@Autowired
private FileAlteration fileAlteration;
@Override
public void run(ApplicationArguments args) throws Exception {
FileAlterationObserver observer =
new FileAlterationObserver("D:\\IdeaProjects\\spring-boot-demo\\target\\classes\\com\\it\\controller");
observer.addListener(fileAlteration);
FileAlterationMonitor monitor = new FileAlterationMonitor(1000); // 监听频率
monitor.addObserver(observer);
monitor.start();
}
}
@Component
public class FileAlteration extends FileAlterationListenerAdaptor {
@Override
public void onFileCreate(File file) {
System.out.println("创建了新的文件");
}
@Override
public void onFileChange(File file) {
System.out.println("文件发生了改变");
}
}
常见错误
- 如果将jar包或war包放到外部tomcat运行,出现版本不兼容问题。需要切换到更高版本的tomcat,springboot2.0以上使用的都是tomcat8.5及以上了
- 使用springboot1.5版本打包后访问不到页面的,需要指定pom文件中的resources标签
常用注解
@ResponseBody
该注解是使用一个convert转换器来将返回的数据转换为Json格式,适用于方法上
@AfterReturning
在AOP切面类中使用,在方法执行后可以获取到拦截方法的返回值结果。
@Aspect
@Component
public class LogAop {
private final Logger log = LoggerFactory.getLogger(this.getClass());
@Pointcut("execution(* com.it.controller.*.*(..))")
public void logPointcut(){}
@Before("logPointcut()")
public void before(JoinPoint joinPoint) {
log.info("日志系统开始记录日志");
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = requestAttributes.getRequest();
log.info("当前访问的路径是:" + request.getRequestURL().toString());
log.info("当前执行的方法是:" + request.getMethod());
log.info("当前执行的方法的IP是:" + request.getRemoteAddr());
}
/**
* ret这个名字随便取的
* @param ret
*/
@AfterReturning(returning = "ret",pointcut = "logPointcut()")
public void doAfterReturning(Object ret) {
log.info("返回的结果为 " + ret);
}
}
@EnableAsync 开启异步,使@Async生效
@EnableAsync实现原理,开启了之后在springboot启动扫包的时候会扫描添加了@Controller,@Service 等等注入到了容器中的bean下的方法上的@Async注解。
@Async(添加到方法上):如果扫描到了这个注解,那么会利用aop技术为该类创建代理对象,其中会创建一条新的线程来执行这个方法。这样的话主线程就不会进入阻塞状态。
@Value
底层在创建bean对象的时候访问配置文件,从配置文件中取出对应的value然后通过反射给添加了@Value注解的属性赋值
@ControllerAdvice 增强控制器
底层也是使用aop技术实现的
@ControllerAdvice
@Slf4j
public class MyGlobalExceptionHandler {
@ExceptionHandler(Exception.class)
public ModelAndView customException(Exception e) {
//通常在这里记录日志,将日志存到nosql中
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = requestAttributes.getRequest();
log.warn(request.getRequestURL().toString());
ModelAndView mv = new ModelAndView();
mv.addObject("message", e.getMessage());
mv.setViewName("error");
return mv;
}
@InitBinder("b")
public void b(WebDataBinder binder) {
//在控制器接收参数加上@ModelAttribute("b") 即可使用,这样传递的参数就需要加上前缀 b.
binder.setFieldDefaultPrefix("b.");
}
// 定义全局属性
@ModelAttribute(name = "md")
public Map<String,Object> myData(ModelAndView mv) {
HashMap<String, Object> map = new HashMap<>();
map.put("age", 20);
map.put("gender", "男");
return map;
}
SpringBoot优化
扫包优化(提高启动速度)
//项目中不使用@SpringBootApplication注解
/*而是使用
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(自行指定需要扫描的包)
*/
JVM参数调优(影响到运行效果,例如吞吐量)
调优策略
- 初始化堆大小与最大堆大小一致(可以减少gc回收次数)
- 设置新生代的ende区与form/to的比例为2:1:1
- 设置新生代与老年代的比例为 1:2 或者 1:3
- 尽量使用并行回收器
- 如果是并发量大的话,可以将tomcat服务器替换成Ontertow服务器
替换的方式就是在spring-boot的web依赖包中去掉tomcat的依赖
然后引入Ontertow的依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-undertow</artifactId>
</dependency>
常见的jvm参数
//
-XX:+PrintGC 每次触发GC的时候打印相关日志
-XX:+UseSerialGC 串行回收
-XX:+PrintGCDetails 更详细的GC日志
-Xms 堆初始值
-Xmx 堆最大可用值
-Xmn 新生代堆最大可用值
-XX:SurvivorRatio 用来设置新生代中eden空间和from/to空间的比例.
-XX:NewRatio 配置新生代与老年代占比 1:2
含以-XX:SurvivorRatio=eden/from=den/to
总结:在实际工作中,我们可以直接将初始的堆大小与最大堆大小相等,
这样的好处是可以减少程序运行时垃圾回收次数,从而提高效率。
-XX:SurvivorRatio 用来设置新生代中eden空间和from/to空间的比例.
示例:
-Xms20m -Xmx20m # 堆初始化大小20m,最大值20M
-XX:SurvivorRatio=2 # Ened区和from/to的比例为 2:1:1
-XX:+PrintGCDetails # 打印更详细的GC日志
-XX:+UseSerialGC # 使用串行GC回收
-XX:+UseParNewGC # 使用并行回收,其实就是串行回收的多线程版,新生代使用并行回收、老年代使用串行回收
XX:+USeParNewGC # 使用parnew收集器,这个收集器更关注系统的吞吐量。新生代使用复制算法、老年代使用标记-压缩
-XX:+UseConcMarkSweepGC # CMS收集器,
-XX:NewRatio=2 # 设置新生代与老年代的比例为2:1
-XX:+HeapDumpOnOutOfMemoryError 当堆空间溢出时生成dump文件(堆的内存快照),也可以指定路径 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/export/home/tomcat/logs/...
- 并发与并行的区别
并发:具备同时处理多件事的能力,但不能同时
并行:同时处理多件事的能力
区别就在于是否能够同时处理。 - 内存溢出与内存泄漏的区别
内存溢出:你申请的内存已经超过了堆的最大内存值,所以会出现内存溢出oom
内存泄漏:指的是你程序申请到了内存空间之后,无法释放已经申请的内存空间,最后这种无法释放的空间越来越多,就造成了内存泄漏(例如你不关流)
springboot 底层实现原理
实现快速化整合第三方框架
- 使用maven的子父依赖,直接引入第三方框架的所需jar包,意思就是将需要整合的环境的jar包封装好成一个依赖,导入一个依赖就导入了全部
完全无配置文件(注解方式)如何实现
- spring 3.0 以上开始提供注解
- spring内置注解加载整个SpringMvc容器 @EnableWebMvc
- 使用java语言创建tomcat 来加载class文件运行
- 手写类似springboot的效果
需要引入的依赖有
tomcat-embed-core java创建tomcat容器
tomcat-jasper tomcat支持JSP
tomcat-embed-core、spring-web、spring-webmvc、tomcat-jasper
@EnableWebMvc
@Configuration
@ComponentScan("com.it.controller")
public class MvcConfig extends WebMvcConfigurerAdapter {
@Bean
public ViewResolver viewResolver(){
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setPrefix("/WEB-INF/pages/");
resolver.setSuffix(".jsp");
//可以在JSP页面中通过${}访问beans
resolver.setExposeContextBeansAsAttributes(true);
return resolver;
}
}
@Configuration
@ComponentScan("com.it")
public class RootConfig {
}
public class SpringDispathServlet extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[]{RootConfig.class};
}
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[]{MvcConfig.class};
}
@Override
protected String[] getServletMappings() {
return new String[]{ "/" };
}
}
@Controller
public class UserController {
@GetMapping("/index")
public String index() {
return "index";
}
}
public class MyTomcat {
public static void main(String[] args) throws Exception {
Tomcat tomcat = new Tomcat();
tomcat.setPort(8082);
File file = new File("src/main");
//这里是直接通过扫描src/main生成的classes文件执行
tomcat.addWebapp("springboot", file.getAbsolutePath());
// 以下的代码是可以将classes创建到内存中,然后在内存中执行
/*// 读取项目的路径
StandardContext standardContext = (StandardContext) tomcat.addWebapp("springboot", file.getAbsolutePath());
//禁止重新载入
standardContext.setReloadable(false);
//CLass文件读取地址
File classesFile = new File("target/classes");
//WebRoot
WebResourceRoot resource = new StandardRoot(standardContext);
resource.addPreResources(
new DirResourceSet(resource,"/WEB-INF/classes",classesFile.getAbsolutePath(),"/")
);*/
tomcat.start();
//异步等待执行的请求
tomcat.getServer().await();
}
}