最近再研究springboot的原理:yum:颇有收获,现在让我分享一下springboot如何使用吧~
想要解锁更多新姿势?请访问我的博客
和书上理解的不同,我认为Springboot是一个优秀的快速搭建框架,他通过maven继承方式添加依赖来整合很多第三方工具,可以避免各种麻烦的配置,有各种内嵌容器简化Web项目,还能避免依赖的干扰,它内置tomcat,jetty容器,使用的是java app运行程序,而不是传统的用把war放在tomcat等容器中运行
JFinal是国人出品的一个web + orm 框架 ,JFinal,优点是开发迅速、代码量少、学习简单、功能强大、轻量级、易扩展。核心就是极致简洁。他没有商业机构的支持,所以宣传不到位,少有人知。
Springboot相比与JFinal最大的优点就是支持的功能非常多,可以非常方便的将spring的各种框架如springframework , spring-mvc, spring-security, spring-data-jpa, spring-cache等等集成起来进行自动化配置 ,而且生态 比较好,很多产品都对Springboot做出一定支持。
可以这么理解,Springboot里面包含了Springcloud,Springcloud只是Springboot里面的一个组件而已。
Springcloud提供了相当完整的微服务架构。而微服务架构,本质来说就是分布式架构,意味着你要将原来是一个整体的项目拆分成一个个的小型项目,然后利用某种机制将其联合起来,例如服务治理、通信框架等基础设施。
SpringBoot的Web组件,默认集成的是SpringMVC框架。
要往下看的话,注意了:point_down:
我已经好久没用Eclipse了,要知道Eclipse是创建一个maven项目在引入Springboot依赖创建的。
下面我分享一下用IDEA创建Springboot的方法。
很简单,在这个界面里面就可以创建Springboot了。接下来在添加一些组件。
大功告成!
这里用我写的一个秒杀项目作为参考栗子。 秒杀商城
创建一个conntroller包,编写一个样列。
package cn.tengshe789.controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/demo") public class SampleController { @RequestMapping("/hello") public String index() { return "Hello World"; } } 复制代码
接下来在他 同级包 或者 上一级的包 内,创建一个主方法 MainApplication
。方法内容;
@SpringBootApplication @EnableAsync //@ComponentScan("cn.tengshe789.controller") //@EnableAutoConfiguration public class MainApplication { public static void main(String[] args) { SpringApplication.run(MainApplication.class, args); } } 复制代码
在浏览器输入http://127.0.0.1:8080/demo/hello/,就可以启动了!
Springboot将他标识为启动类,用它启动Springboot项目
在上加上RestController 表示修饰该Controller所有的方法返回JSON格式,直接可以编写Restful接口。就相当于 @Controller
+ @ResponseBody
这种实现
用在启动Springboot中,相当于 @ComponentScan
+ @EnableAutoConfiguration
+ @Configuration
控制器扫包范围。
他让 Spring Boot 根据咱应用所声明的依赖来对 Spring 框架进行自动配置。意思是,创建项目时添加的spring-boot-starter-web添加了Tomcat和Spring MVC,所以auto-configuration将假定你正在开发一个web应用并相应地对Spring进行设置。
规则:
1、名用大写比较规范
2、=两边别打空格
3、名值对写完后别打分号
name=tengshe789 复制代码
spring.profiles.active=pre application-dev.properties:开发环境 application-test.properties:测试环境 application-prod.properties:生产环境 复制代码
server.port=8888 server.context-path=/tengshe789 复制代码
规则:
server: port: 8080 context-path: /springboot 复制代码
Springboot官方不推荐xml,略
一个项目用Springboot,十有八九就是用于Web开发。首先让我们看看Springboot怎么快速开发Web把
请在resources目录下创建static文件夹,在该位置放置一个静态资源。
目录:src/main/resources/static
启动程序后,尝试访问http://localhost:8080/img.xxx/。就可以访问了。
在之前的快速使用的示例中,我们都是通过添加 @RestController
来处理请求,所以返回的内容为 json
对象。那么如果需要渲染html页面的时候,要如何实现呢?
Springboot依然可以实现动态HTML,并且提供了多种模板引擎的默认配置支持,Springboot官方文档有如下推荐的模板引擎:
· Thymeleaf
· FreeMarker
· Velocity
· Groovy
· Mustache
Springboot官方建议避免使用JSP,若一定要使用JSP将无法实现Spring Boot的多种特性。
在Springboot中,默认的模板配置路径都时:src/main/resources/templates。当然也可以修改这个路径,具体如何修改,可在各模板引擎的配置属性中查询并修改。
这里还是用我写的一个秒杀项目作为参考栗子。 秒杀商城
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> 复制代码
在 application.properties
中添加:
#thymeleaf spring.thymeleaf.prefix=classpath:/templates/ spring.thymeleaf.suffix=.html spring.thymeleaf.cache=false spring.thymeleaf.servlet.content-type=text/html spring.thymeleaf.enabled=true spring.thymeleaf.encoding=UTF-8 # 一代填 spring.thymeleaf.mode=HTML5 spring.thymeleaf.mode=HTML 复制代码
在src/main/resources/创建一个templates文件夹,新网页后缀为*.html
@RequestMapping("/to_list") public String list(Model model,MiaoshaUser user) { model.addAttribute("user", user); //查询商品列表 List<GoodsVo> goodsList = goodsService.listGoodsVo(); model.addAttribute("goodsList", goodsList); return "goods_list"; } 复制代码
这里注意Thymeleaf语法,Thymeleaf很像HTML,不同之处在标签加了一个th前缀
<!DOCTYPE HTML> <html xmlns:th="http://www.thymeleaf.org"> <head> <title>商品列表</title> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <!-- jquery --> <script type="text/javascript" th:src="@{/js/jquery.min.js}"></script> </head> <body> <div class="panel panel-default" > <div class="panel-heading">秒杀商品列表</div> <table class="table" id="goodslist"> <tr><td>商品名称</td><td>商品图片</td><td>商品原价</td><td>秒杀价</td><td>库存数量</td><td>详情</td></tr> <tr th:each="goods,goodsStat : ${goodsList}"> <td th:text="${goods.goodsName}"></td> <td ><img th:src="@{${goods.goodsImg}}" width="100" height="100" /></td> <td th:text="${goods.goodsPrice}"></td> <td th:text="${goods.miaoshaPrice}"></td> <td th:text="${goods.stockCount}"></td> <td><a th:href="'/goods_detail.htm?goodsId='+${goods.id}">详情</a></td> </tr> </table> </div> </body> </html> 复制代码
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-freemarker</artifactId> </dependency> 复制代码
在 application.properties
中添加:
#Freemarker spring.freemarker.allow-request-override=false spring.freemarker.cache=true spring.freemarker.check-template-location=true spring.freemarker.charset=UTF-8 spring.freemarker.content-type=text/html spring.freemarker.expose-request-attributes=false spring.freemarker.expose-session-attributes=false spring.freemarker.expose-spring-macro-helpers=false #spring.freemarker.prefix= #spring.freemarker.request-context-attribute= #spring.freemarker.settings.*= spring.freemarker.suffix=.ftl spring.freemarker.template-loader-path=classpath:/templates/ #comma-separated list #spring.freemarker.view-names= # whitelist of view names that can be resolved 复制代码
在src/main/resources/创建一个templates文件夹,新网页后缀为*.ftl
@RequestMapping("/freemarkerIndex") public String index(Map<String, Object> result) { result.put("nickname", "tEngSHe789"); result.put("old", "18"); result.put("my Blog", "HTTPS://blog.tengshe789.tech/"); List<String> listResult = new ArrayList<String>(); listResult.add("guanyu"); listResult.add("zhugeliang"); result.put("listResult", listResult); return "index"; } 复制代码
<!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8" /> <title>首页</title> </head> <body> ${nickname} <#if old=="18"> 太假了吧哥们 <#elseif old=="21"> 你是真的21岁 <#else> 其他 </#if> <#list userlist as user> ${user} </#list> </body> </html> 复制代码
不建议用Springboot整合JSP,要的话一定要为war类型,否则会找不到页面.,而且不要把JSP页面存放在resources// jsp 不能被访问到
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.4.RELEASE</version> </parent> <dependencies> <!-- SpringBoot web 核心组件 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> </dependency> <!-- SpringBoot 外部tomcat支持 --> <dependency> <groupId>org.apache.tomcat.embed</groupId> <artifactId>tomcat-embed-jasper</artifactId> </dependency> </dependencies> 复制代码
在 application.properties
中添加:
spring.mvc.view.prefix=/WEB-INF/jsp/ spring.mvc.view.suffix=.jsp 复制代码
在src/main/resources/创建一个templates文件夹,新网页后缀为*.jsp
@Controller public class IndexController { @RequestMapping("/index") public String index() { return "index"; } } 复制代码
略略略:stuck_out_tongue_closed_eyes:
要了解 WebFlux ,首先了解下什么是Reactive响应式(反应式)编程 ,他是一种新的编程风格,其特点是异步或并发、事件驱动、推送PUSH机制以及观察者模式的衍生。reactive应用(响应式应用)允许开发人员构建事件驱动(event-driven),可扩展性,弹性的反应系统:提供高度敏感的实时的用户体验感觉,可伸缩性和弹性的应用程序栈的支持,随时可以部署在多核和云计算架构。
Spring Boot Webflux 就是基于 Reactor 实现的。Spring Boot 2.0 包括一个新的 spring-webflux 模块。该模块包含对响应式 HTTP 和 WebSocket 客户端的支持,以及对 REST,HTML 和 WebSocket 交互等程序的支持。一般来说,Spring MVC 用于同步处理,Spring Webflux 用于异步处理。
Spring Boot Webflux 有两种编程模型实现,一种类似 Spring MVC 注解方式,另一种是使用其功能性端点方式。
WebFlux 支持的容器有 Tomcat、Jetty(Non-Blocking IO API) ,也可以像 Netty 和 Undertow 的本身就支持异步容器。在容器中 Spring WebFlux 会将输入流适配成 Mono 或者 Flux 格式进行统一处理。
复制代码
@RestController public class PersonController { private final PersonRepository repository; public PersonController(PersonRepository repository) { this.repository = repository; } @PostMapping("/person") Mono<Void> create(@RequestBody Publisher<Person> personStream) { return this.repository.save(personStream).then(); } @GetMapping("/person") Flux<Person> list() { return this.repository.findAll(); } @GetMapping("/person/{id}") Mono<Person> findById(@PathVariable String id) { return this.repository.findOne(id); } } 复制代码
Spring Boot 2.0 这里有两条不同的线分别是:
如果使用 Spring Data Reactive ,原来的 Spring 针对 Spring Data (JDBC等)的事务管理会不起作用。因为原来的 Spring 事务管理(Spring Data JPA)都是基于 ThreadLocal 传递事务的,其本质是基于 阻塞 IO 模型,不是异步的。
但 Reactive 是要求异步的,不同线程里面 ThreadLocal 肯定取不到值了。自然,我们得想想如何在使用 Reactive 编程是做到事务,有一种方式是 回调 方式,一直传递 conn :newTransaction(conn ->{})
因为每次操作数据库也是异步的,所以 connection 在 Reactive 编程中无法靠 ThreadLocal 传递了,只能放在参数上面传递。虽然会有一定的代码侵入行。进一步,也可以 kotlin 协程,去做到透明的事务管理,即把 conn 放到 协程的局部变量中去。 那 Spring Data Reactive Repositories 不支持 MySQL,进一步也不支持 MySQL 事务,怎么办?
答案是,这个问题其实和第一个问题也相关。 为啥不支持 MySQL,即 JDBC 不支持。大家可以看到 JDBC 是所属 Spring Data 的。所以可以等待 Spring Data Reactive Repositories 升级 IO 模型,去支持 MySQL。也可以和上面也讲到了,如何使用 Reactive 编程支持事务。
如果应用只能使用不强依赖数据事务,依旧使用 MySQL ,可以使用下面的实现,代码如下:
复制代码
public interface CityService { /** * 获取城市信息列表 * * @return */ List<City> findAllCity(); /** * 根据城市 ID,查询城市信息 * * @param id * @return */ City findCityById(Long id); /** * 新增城市信息 * * @param city * @return */ Long saveCity(City city); /** * 更新城市信息 * * @param city * @return */ Long updateCity(City city); /** * 根据城市 ID,删除城市信息 * * @param id * @return */ Long deleteCity(Long id); } 复制代码
具体案例在我参考博主的 Github
创建一个 Route 类来定义 RESTful HTTP 路由
复制代码
请参考 聊聊 Spring Boot 2.x 那些事儿
需要执行异步方法时,在方法上加上 @Async
之后,底层使用多线程技术 。启动加上需要 @EnableAsync
使用这个需要spring-boot-starter-parent版本要在1.5以上
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> 复制代码
在 application.properties
中添加:
# jdbc模板 spring.datasource.url=jdbc:mysql://localhost:3306/test spring.datasource.username=root spring.datasource.password=123456 spring.datasource.driver-class-name=com.mysql.jdbc.Driver 复制代码
创建一个Service
@Service public class UserServiceImpl implements UserService { @Autowired private JdbcTemplate jdbcTemplate; public void createUser(String name, Integer age) { jdbcTemplate.update("insert into users values(null,?,?);", name, age); } } 复制代码
这里用我写的一个秒杀项目作为参考栗子。 秒杀商城
<dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>1.3.2</version> </dependency> 复制代码
在 application.properties
中添加:
#mybatis mybatis.type-aliases-package=cn.tengshe789.domain mybatis.configuration.map-underscore-to-camel-case=true mybatis.configuration.default-fetch-size=100 mybatis.configuration.default-statement-timeout=3000 mybatis.mapperLocations = classpath:cn/tengshe789/dao/*.xml 复制代码
创建一个Dao(Mapper 代码)
@Mapper @Component public interface GoodsDao { @Select("select g.*,mg.stock_count, mg.start_date, mg.end_date,mg.miaosha_price from miaosha_goods mg left join goods g on mg.goods_id = g.id") public List<GoodsVo> listGoodsVo(); } 复制代码
创建service
@Service public class GoodsService { @Autowired GoodsDao goodsDao; /* * 展示商品列表 */ public List<GoodsVo> listGoodsVo() { return goodsDao.listGoodsVo(); } } 复制代码
PageHelper 是一款好用的开源免费的 Mybatis 第三方物理分页插件
<dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper-spring-boot-starter</artifactId> <version>1.2.5</version> </dependency> 复制代码
在 application.properties
中添加:
# 配置日志 logging.level.cn.tengshe789.dao=DEBUG # Pagehelper pagehelper.helperDialect=mysql pagehelper.reasonable=true pagehelper.supportMethodsArguments=true pagehelper.params=count=countSql pagehelper.page-size-zero=true 复制代码
或者在 application.yml
中添加:
# 与mybatis整合 mybatis: config-location: classpath:mybatis.xml mapper-locations: - classpath:cn/tengshe789/dao/*.xml # 分页配置 pagehelper: helper-dialect: mysql reasonable: true support-methods-arguments: true params: count=countSql 复制代码
@Data public class User { private Integer id; private String name; } 复制代码
public interface UserDao { @Select("SELECT * FROM USERS ") List<User> findUserList(); } 复制代码
@Service public class UserService { @Autowired private UserMapper userDao; /** * page 当前页数<br> * size 当前展示的数据<br> */ public PageInfo<User> findUserList(int page, int size) { // 开启分页插件,放在查询语句上面 PageHelper.startPage(page, size); List<User> listUser = userDao.findUserList(); // 封装分页之后的数据 PageInfo<User> pageInfoUser = new PageInfo<User>(listUser); return pageInfoUser; } } 复制代码
spring-data-jpa三个步骤:
详情:JPA官方网站
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> 复制代码
Springboot 默认使用hibernate作为JPA的实现 。需要在 application.properties
中添加:
# hibernate spring.datasource.url=jdbc:mysql://localhost:3306/test?useSSL=false spring.datasource.username=root spring.datasource.password=root spring.datasource.tomcat.max-active=100 spring.datasource.tomcat.max-idle=200 spring.datasource.tomcat.initialSize=20 spring.jpa.database-platform=org.hibernate.dialect.MySQL5Dialect 复制代码
@Data @Entity(name = "users") public class UserEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; @Column(name = "name") private String name; @Column(name = "age") private Integer age; } 复制代码
注解的意思:
@Entity
会被spring扫描并加载,
@Id
注解在主键上
@Column name="call_phone"
指该字段对应的数据库的字段名,如果相同就不需要定义。数据库下划线间隔和代码中的驼峰法视为相同,如数据库字段create_time等价于Java类中的createTime,因此不需要用@Column注解。
此时需要继承Repository接口~
public interface UserDao extends JpaRepository<User, Integer> { } 复制代码
@RestController public class IndexController { @Autowired private UserDao userDao; @RequestMapping("/jpaFindUser") public Object jpaIndex(User user) { Optional<User> userOptional = userDao.findById(user.getId()); User result = userOptional.get(); return reusltUser == null ? "没有查询到数据" : result; } } 复制代码
很多公司都会使用多数据库,一个数据库存放共同的配置或文件,另一个数据库是放垂直业务的数据。所以说需要一个项目中有多个数据源
这玩意原理很简单,根据不同包名,加载不同数据源。
在 application.properties
中添加:
# datasource1 spring.datasource.test1.driver-class-name = com.mysql.jdbc.Driver spring.datasource.test1.jdbc-url =jdbc:mysql://localhost:3306/test01?useUnicode=true&characterEncoding=utf-8 spring.datasource.test1.username = root spring.datasource.test1.password = 123456 # datasource2 spring.datasource.test2.driver-class-name = com.mysql.jdbc.Driver spring.datasource.test2.jdbc-url =jdbc:mysql://localhost:3306/test02?useUnicode=true&characterEncoding=utf-8 spring.datasource.test2.username = root spring.datasource.test2.password = 123456 复制代码
数据库1的
//DataSource01 @Configuration // 注册到springboot容器中 @MapperScan(basePackages = "tech.tengshe789.test01", sqlSessionFactoryRef = "test1SqlSessionFactory") public class DataSource1Config { /** * @methodDesc: 功能描述:(配置test1数据库) * @author: tEngSHe789 */ @Bean(name = "test1DataSource") @ConfigurationProperties(prefix = "spring.datasource.test1") @Primary public DataSource testDataSource() { return DataSourceBuilder.create().build(); } /** * @methodDesc: 功能描述:(test1 sql会话工厂) */ @Bean(name = "test1SqlSessionFactory") @Primary public SqlSessionFactory testSqlSessionFactory(@Qualifier("test1DataSource") DataSource dataSource) throws Exception { SqlSessionFactoryBean bean = new SqlSessionFactoryBean(); bean.setDataSource(dataSource); //加载mapper(不需要) bean.setMapperLocations( new PathMatchingResourcePatternResolver().getResources("classpath:mybatis/mapper/test1/*.xml")); return bean.getObject(); } /** * * @methodDesc: 功能描述:(test1 事物管理) */ @Bean(name = "test1TransactionManager") @Primary public DataSourceTransactionManager testTransactionManager(@Qualifier("test1DataSource") DataSource dataSource) { return new DataSourceTransactionManager(dataSource); } @Bean(name = "test1SqlSessionTemplate") @Primary public SqlSessionTemplate testSqlSessionTemplate( @Qualifier("test1SqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception { return new SqlSessionTemplate(sqlSessionFactory); } } 复制代码
数据库2的同理。
public interface User1Dao { @Insert("insert into users values(null,#{name},#{age});") public int addUser(@Param("name") String name, @Param("age") Integer age); } 复制代码
在多数据源的情况下,使用 @Transactional
注解时,应该指定事务管理者 @Transactional(transactionManager = "test1TransactionManager")
怎么进行事物管理呢,简单,往下看。
找到service实现类,加上 @Transactional
注解就行,此 @Transactional
注解来自 org.springframework.transaction.annotation
包 ,不是来自 javax.transaction
。而且 @Transactional
不仅可以注解在方法上,也可以注解在类上。当注解在类上的时候意味着此类的所有public方法都是开启事务的。如果类级别和方法级别同时使用了 @Transactional
注解,则使用在类级别的注解会重载方法级别的注解。
注意:Springboot提供了一个 @EnableTransactionManagement
注解在配置类上来开启声明式事务的支持。注解 @EnableTransactionManagement
是默认打开的,想要关闭事务管理,想要在程序入口将这个注解改为false
啥是分布式事务呢,比如我们在执行一个业务逻辑的时候有两步分别操作A数据源和B数据源,当我们在A数据源执行数据更改后,在B数据源执行时出现运行时异常,那么我们必须要让B数据源的操作回滚,并回滚对A数据源的操作。这种情况在支付业务时常常出现,比如买票业务在最后支付失败,那之前的操作必须全部回滚,如果之前的操作分布在多个数据源中,那么这就是典型的分布式事务回滚
了解了什么是分布式事务,那分布式事务在java的解决方案就是JTA(即Java Transaction API)。
springboot官方提供了Atomikos, Bitronix , Narayana 的 类事务管理器
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jta-atomikos</artifactId> </dependency> 复制代码
# Mysql 1 mysql.datasource.test1.url = jdbc:mysql://localhost:3306/test01?useUnicode=true&characterEncoding=utf-8 mysql.datasource.test1.username = root mysql.datasource.test1.password = 123456 mysql.datasource.test1.minPoolSize = 3 mysql.datasource.test1.maxPoolSize = 25 mysql.datasource.test1.maxLifetime = 20000 mysql.datasource.test1.borrowConnectionTimeout = 30 mysql.datasource.test1.loginTimeout = 30 mysql.datasource.test1.maintenanceInterval = 60 mysql.datasource.test1.maxIdleTime = 60 # Mysql 2 mysql.datasource.test2.url =jdbc:mysql://localhost:3306/test02?useUnicode=true&characterEncoding=utf-8 mysql.datasource.test2.username =root mysql.datasource.test2.password =123456 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 复制代码
以下是读取数据库1的配置文件
@Data @ConfigurationProperties(prefix = "mysql.datasource.test1") public class DBConfig1 { private String url; private String username; private String password; private int minPoolSize; private int maxPoolSize; private int maxLifetime; private int borrowConnectionTimeout; private int loginTimeout; private int maintenanceInterval; private int maxIdleTime; private String testQuery; } 复制代码
读取数据库2的配置文件略
数据源1:
@Configuration // basePackages 最好分开配置 如果放在同一个文件夹可能会报错 @MapperScan(basePackages = "tech.tengshe789.test01", sqlSessionTemplateRef = "testSqlSessionTemplate") public class MyBatisConfig1 { // 配置数据源 @Primary @Bean(name = "testDataSource") 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); } } 复制代码
数据库2略
@EnableConfigurationProperties(value = { DBConfig1.class, DBConfig2.class }) @SpringBootApplication public class MainApplication { public static void main(String[] args) { SpringApplication.run(MainApplication.class, args); } } 复制代码
在做项目时有时候会有定时器任务的功能,比如某某时间应该做什么,多少秒应该怎么样之类的。
spring支持多种定时任务的实现。我们来介绍下使用Quartz 和Scheduler
Spring Schedule 实现定时任务有两种方式 1. 使用XML配置定时任务, 2. 使用 @Scheduled 注解。
固定等待时间 @Scheduled(fixedDelay = 时间间隔 )
固定间隔时间 @Scheduled(fixedRate = 时间间隔 )
@Component public class ScheduleJobs { public final static long SECOND = 1 * 1000; FastDateFormat fdf = FastDateFormat.getInstance("yyyy-MM-dd HH:mm:ss"); @Scheduled(fixedDelay = SECOND * 2) public void fixedDelayJob() throws InterruptedException { TimeUnit.SECONDS.sleep(2); System.out.println("[FixedDelayJob Execute]"+fdf.format(new Date())); } } 复制代码
Corn表达式 @Scheduled(cron = Corn表达式)
@Component public class ScheduleJobs { public final static long SECOND = 1 * 1000; FastDateFormat fdf = FastDateFormat.getInstance("yyyy-MM-dd HH:mm:ss"); @Scheduled(cron = "0/4 * * * * ?") public void cronJob() { System.out.println("[CronJob Execute]"+fdf.format(new Date())); } } 复制代码
要在主方法上加上 @EnableScheduling
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-quartz-starter</artifactId> </dependency> 复制代码
# spring boot 2.x 已集成Quartz,无需自己配置 spring.quartz.job-store-type=jdbc spring.quartz.properties.org.quartz.scheduler.instanceName=clusteredScheduler spring.quartz.properties.org.quartz.scheduler.instanceId=AUTO spring.quartz.properties.org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX spring.quartz.properties.org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate spring.quartz.properties.org.quartz.jobStore.tablePrefix=QRTZ_ spring.quartz.properties.org.quartz.jobStore.isClustered=true spring.quartz.properties.org.quartz.jobStore.clusterCheckinInterval=10000 spring.quartz.properties.org.quartz.jobStore.useProperties=false spring.quartz.properties.org.quartz.threadPool.class=org.quartz.simpl.SimpleThreadPool spring.quartz.properties.org.quartz.threadPool.threadCount=10 spring.quartz.properties.org.quartz.threadPool.threadPriority=5 spring.quartz.properties.org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread=true 复制代码
@Configuration public class QuartzConfig { @Bean public JobDetail uploadTaskDetail() { return JobBuilder.newJob(UploadTask.class).withIdentity("uploadTask").storeDurably().build(); } @Bean public Trigger uploadTaskTrigger() { CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule("*/5 * * * * ?"); return TriggerBuilder.newTrigger().forJob(uploadTaskDetail()) .withIdentity("uploadTask") .withSchedule(scheduleBuilder) .build(); } } 复制代码
创建一个配置类,分别制定具体任务类和触发的规则
@Configuration @DisallowConcurrentExecution public class UploadTask extends QuartzJobBean { @Resource private TencentYunService tencentYunService; @Override protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException { System.out.println("任务开始"); try { Thread.sleep(6000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("任务结束"); } } 复制代码
@DisallowConcurrentExecution
禁止并发执行
并发执行方面,系统默认为true,即第一个任务还未执行完整,第二个任务如果到了执行时间,则会立马开启新线程执行任务,这样如果我们是从数据库读取信息,两次重复读取可能出现重复执行任务的情况,所以我们需要将这个值设置为false,这样第二个任务会往后推迟,只有在第一个任务执行完成后才会执行第二个任务
<!-- spring boot start --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> <exclusions> <!-- 排除自带的logback依赖 --> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-logging</artifactId> </exclusion> </exclusions> </dependency> <!-- springboot-log4j --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-log4j</artifactId> <version>1.3.8.RELEASE</version> </dependency> 复制代码
文件名称 log4j.properties
#log4j.rootLogger=CONSOLE,info,error,DEBUG log4j.rootLogger=info,error,CONSOLE,DEBUG log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout log4j.appender.CONSOLE.layout.ConversionPattern=%d{yyyy-MM-dd-HH-mm} [%t] [%c] [%p] - %m%n log4j.logger.info=info log4j.appender.info=org.apache.log4j.DailyRollingFileAppender log4j.appender.info.layout=org.apache.log4j.PatternLayout log4j.appender.info.layout.ConversionPattern=%d{yyyy-MM-dd-HH-mm} [%t] [%c] [%p] - %m%n log4j.appender.info.datePattern='.'yyyy-MM-dd log4j.appender.info.Threshold = info log4j.appender.info.append=true #log4j.appender.info.File=/home/admin/pms-api-services/logs/info/api_services_info log4j.appender.info.File=/Users/dddd/Documents/testspace/pms-api-services/logs/info/api_services_info log4j.logger.error=error log4j.appender.error=org.apache.log4j.DailyRollingFileAppender log4j.appender.error.layout=org.apache.log4j.PatternLayout log4j.appender.error.layout.ConversionPattern=%d{yyyy-MM-dd-HH-mm} [%t] [%c] [%p] - %m%n log4j.appender.error.datePattern='.'yyyy-MM-dd log4j.appender.error.Threshold = error log4j.appender.error.append=true #log4j.appender.error.File=/home/admin/pms-api-services/logs/error/api_services_error log4j.appender.error.File=/Users/dddd/Documents/testspace/pms-api-services/logs/error/api_services_error log4j.logger.DEBUG=DEBUG log4j.appender.DEBUG=org.apache.log4j.DailyRollingFileAppender log4j.appender.DEBUG.layout=org.apache.log4j.PatternLayout log4j.appender.DEBUG.layout.ConversionPattern=%d{yyyy-MM-dd-HH-mm} [%t] [%c] [%p] - %m%n log4j.appender.DEBUG.datePattern='.'yyyy-MM-dd log4j.appender.DEBUG.Threshold = DEBUG log4j.appender.DEBUG.append=true #log4j.appender.DEBUG.File=/home/admin/pms-api-services/logs/debug/api_services_debug log4j.appender.DEBUG.File=/Users/dddd/Documents/testspace/pms-api-services/logs/debug/api_services_debug 复制代码
private static final Logger logger = LoggerFactory.getLogger(IndexController.class); 复制代码
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> 复制代码
@Aspect @Component public class WebLogAspect { private static final Logger logger = LoggerFactory.getLogger(WebLogAspect.class); @Pointcut("execution(public * tech.tengshe789.controller.*.*(..))") public void webLog() { } @Before("webLog()") public void doBefore(JoinPoint joinPoint) throws Throwable { // 接收到请求,记录请求内容 ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = attributes.getRequest(); // 记录下请求内容 logger.info("URL : " + request.getRequestURL().toString()); logger.info("HTTP_METHOD : " + request.getMethod()); logger.info("IP : " + request.getRemoteAddr()); Enumeration<String> enu = request.getParameterNames(); while (enu.hasMoreElements()) { String name = (String) enu.nextElement(); logger.info("name:{},value:{}", name, request.getParameter(name)); } } @AfterReturning(returning = "ret", pointcut = "webLog()") public void doAfterReturning(Object ret) throws Throwable { // 处理完请求,返回内容 logger.info("RESPONSE : " + ret); } } 复制代码
非常简单的办法
<dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.0</version> </dependency> 复制代码
类中添加 @Slf4j
注解即可。使用是直接输入log全局变量
@Data 标签,生成getter/setter toString()等方法 @NonNull : 让你不在担忧并且爱上NullPointerException @CleanUp : 自动资源管理:不用再在finally中添加资源的close方法 @Setter/@Getter : 自动生成set和get方法 @ToString : 自动生成toString方法 @EqualsAndHashcode : 从对象的字段中生成hashCode和equals的实现 @NoArgsConstructor/@RequiredArgsConstructor/@AllArgsConstructor 自动生成构造方法 @Data : 自动生成set/get方法,toString方法,equals方法,hashCode方法,不带参数的构造方法 @Value : 用于注解final类 @Builder : 产生复杂的构建器api类 @SneakyThrows : 异常处理(谨慎使用) @Synchronized : 同步方法安全的转化 @Getter(lazy=true) : @Log : 支持各种logger对象,使用时用对应的注解,如:@Log4 复制代码
拦截器,在AOP(Aspect-Oriented Programming)中用于在某个方法或字段被访问之前,进行拦截,然后在之前或之后加入某些操作。拦截是AOP的一种实现策略。
(1)拦截器是基于java的反射机制的,而过滤器是基于函数回调。
(2)拦截器不依赖于servlet容器,而过滤器依赖于servlet容器。
(3)拦截器只能对Controller请求起作用,而过滤器则可以对几乎所有的请求起作用。
(4)在Controller的生命周期中,拦截器可以多次被调用,而过滤器只能在容器初始化时被调用一次。
过滤器(filter)和拦截器(interceptor)是有区别的,详情 ,他们的执行顺序: 先filter 后 interceptor
->过滤器应用场景:设置编码字符、过滤铭感字符
->拦截器应用场景:拦截未登陆用户、审计日志
注册拦截器
@Configuration public class WebAppConfig { @Autowired private LoginIntercept loginIntercept; @Bean public WebMvcConfigurer WebMvcConfigurer() { return new WebMvcConfigurer() { public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(loginIntercept).addPathPatterns("/*"); }; }; } } 复制代码
创建模拟登录拦截器,验证请求是否有token参数
@Slf4j @Component public class LoginIntercept implements HandlerInterceptor { public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { log.info("开始拦截登录请求...."); String token = request.getParameter("token"); if (StringUtils.isEmpty(token)) { response.getWriter().println("not found token"); return false; } return true; } } 复制代码
在 Spring Boot中,通过 @EnableCaching
注解自动化配置合适的缓存管理器(CacheManager),Spring Boot根据下面的顺序去侦测缓存提供者: Generic , JCache (JSR-107), EhCache 2.x ,Hazelcast , Infinispan ,Redis ,Guava , Simple
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-cache</artifactId> </dependency> 复制代码
<?xml version="1.0" encoding="UTF-8"?> <ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd" updateCheck="false"> <diskStore path="java.io.tmpdir/Tmp_EhCache" /> <!-- 默认配置 --> <defaultCache maxElementsInMemory="5000" eternal="false" timeToIdleSeconds="120" timeToLiveSeconds="120" memoryStoreEvictionPolicy="LRU" overflowToDisk="false" /> <cache name="baseCache" maxElementsInMemory="10000" maxElementsOnDisk="100000" /> </ehcache> 复制代码
配置信息介绍
name
:缓存名称。
maxElementsInMemory
:缓存最大个数。
eternal
:对象是否永久有效,一但设置了,timeout将不起作用。
timeToIdleSeconds
:设置对象在失效前的允许闲置时间(单位:秒)。仅当eternal= false 对象不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大。
timeToLiveSeconds
:设置对象在失效前允许存活时间(单位:秒)。最大时间介于创建时间和失效时间之间。仅当eternal= false 对象不是永久有效时使用,默认是0.,也就是对象存活时间无穷大。
overflowToDisk
:当内存中对象数量达到maxElementsInMemory时,Ehcache将会对象写到磁盘中。
diskSpoolBufferSizeMB
:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区。
maxElementsOnDisk
:硬盘最大缓存个数。
diskPersistent
:是否缓存虚拟机重启期数据 Whether the disk store persists between restarts of the Virtual Machine. The default value is false .
diskExpiryThreadIntervalSeconds
:磁盘失效线程运行时间间隔,默认是120秒。
memoryStoreEvictionPolicy
:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)。
clearOnFlush
:内存数量最大时是否清除。
@CacheConfig(cacheNames = "baseCache") public interface UserDao { @Select("select * from users where name=#{name}") @Cacheable UserEntity findName(@Param("name") String name); } 复制代码
@Autowired private CacheManager cacheManager; @RequestMapping("/remoKey") public void remoKey() { cacheManager.getCache("baseCache").clear(); } 复制代码
主方法启动时加上 @EnableCaching
即可
使用RedisTemplate 连接
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> 复制代码
单机
#redis # Redis数据库索引(默认为0) spring.redis.database=0 # Redis服务器地址 spring.redis.host=127.0.0.1 # Redis服务器连接端口 spring.redis.port=6379 # Redis服务器连接密码(默认为空) spring.redis.password= # 连接池最大连接数(使用负值表示没有限制) spring.redis.pool.max-active=8 # 连接池最大阻塞等待时间(使用负值表示没有限制) spring.redis.pool.max-wait=-1 # 连接池中的最大空闲连接 spring.redis.pool.max-idle=8 # 连接池中的最小空闲连接 spring.redis.pool.min-idle=0 # 连接超时时间(毫秒) spring.redis.timeout=0 复制代码
集群或哨兵模式
#Matser的ip地址 redis.hostName=192.168.177.128 #端口号 redis.port=6382 #如果有密码 redis.password= #客户端超时时间单位是毫秒 默认是2000 redis.timeout=10000 #最大空闲数 redis.maxIdle=300 #连接池的最大数据库连接数。设为0表示无限制,如果是jedis 2.4以后用redis.maxTotal #redis.maxActive=600 #控制一个pool可分配多少个jedis实例,用来替换上面的redis.maxActive,如果是jedis 2.4以后用该属性 redis.maxTotal=1000 #最大建立连接等待时间。如果超过此时间将接到异常。设为-1表示无限制。 redis.maxWaitMillis=1000 #连接的最小空闲时间 默认1800000毫秒(30分钟) redis.minEvictableIdleTimeMillis=300000 #每次释放连接的最大数目,默认3 redis.numTestsPerEvictionRun=1024 #逐出扫描的时间间隔(毫秒) 如果为负数,则不运行逐出线程, 默认-1 redis.timeBetweenEvictionRunsMillis=30000 #是否在从池中取出连接前进行检验,如果检验失败,则从池中去除连接并尝试取出另一个 redis.testOnBorrow=true #在空闲时检查有效性, 默认false redis.testWhileIdle=true #redis集群配置 spring.redis.cluster.nodes=192.168.177.128:7001,192.168.177.128:7002,192.168.177.128:7003,192.168.177.128:7004,192.168.177.128:7005,192.168.177.128:7006 spring.redis.cluster.max-redirects=3 #哨兵模式 #redis.sentinel.host1=192.168.177.128 #redis.sentinel.port1=26379 #redis.sentinel.host2=172.20.1.231 #redis.sentinel.port2=26379 复制代码
@Configuration @EnableCaching public class RedisConfig extends CachingConfigurerSupport{ @Value("${spring.redis.host}") private String host; @Value("${spring.redis.port}") private int port; @Value("${spring.redis.timeout}") private int timeout; //自定义缓存key生成策略 // @Bean // public KeyGenerator keyGenerator() { // return new KeyGenerator(){ // @Override // public Object generate(Object target, java.lang.reflect.Method method, Object... params) { // StringBuffer sb = new StringBuffer(); // sb.append(target.getClass().getName()); // sb.append(method.getName()); // for(Object obj:params){ // sb.append(obj.toString()); // } // return sb.toString(); // } // }; // } //缓存管理器 @Bean public CacheManager cacheManager(@SuppressWarnings("rawtypes") RedisTemplate redisTemplate) { RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate); //设置缓存过期时间 cacheManager.setDefaultExpiration(10000); return cacheManager; } @Bean public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory factory){ StringRedisTemplate template = new StringRedisTemplate(factory); setSerializer(template);//设置序列化工具 template.afterPropertiesSet(); return template; } private void setSerializer(StringRedisTemplate template){ @SuppressWarnings({ "rawtypes", "unchecked" }) Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class); ObjectMapper om = new ObjectMapper(); om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); jackson2JsonRedisSerializer.setObjectMapper(om); template.setValueSerializer(jackson2JsonRedisSerializer); } } 复制代码
@Mapper @CacheConfig(cacheNames = "users") public interface UserMapper { @Insert("insert into user(name,age) values(#{name},#{age})") int addUser(@Param("name")String name,@Param("age")String age); @Select("select * from user where id =#{id}") @Cacheable(key ="#p0") User findById(@Param("id") String id); @CachePut(key = "#p0") @Update("update user set name=#{name} where id=#{id}") void updataById(@Param("id")String id,@Param("name")String name); //如果指定为 true,则方法调用后将立即清空所有缓存 @CacheEvict(key ="#p0",allEntries=true) @Delete("delete from user where id=#{id}") void deleteById(@Param("id")String id); } 复制代码
@Cacheable
将查询结果缓存到redis中,(key="#p0")指定传入的第一个参数作为redis的key。
@CachePut
,指定key,将更新的结果同步到redis中
@CacheEvict
,指定key,删除缓存数据,allEntries=true,方法调用后将立即清除缓存
要注意,redis在5.0版本以后不支持Jedis
<dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> </dependency> 复制代码
@Data @Component @ConfigurationProperties(prefix="redis") public class RedisConfig { private String host; private int port; private int timeout;//秒 private String password; private int poolMaxTotal; private int poolMaxIdle; private int poolMaxWait;//秒 } 复制代码
@Service public class RedisPoolFactory { @Autowired RedisConfig redisConfig; @Bean public JedisPool edisPoolFactory() { JedisPoolConfig poolConfig = new JedisPoolConfig(); poolConfig.setMaxIdle(redisConfig.getPoolMaxIdle()); poolConfig.setMaxTotal(redisConfig.getPoolMaxTotal()); poolConfig.setMaxWaitMillis(redisConfig.getPoolMaxWait() * 1000); JedisPool jp = new JedisPool(poolConfig, redisConfig.getHost(), redisConfig.getPort(), redisConfig.getTimeout()*1000, redisConfig.getPassword(), 0); return jp; } } 复制代码
Springboot监控中心是干什么的呢?他是针对微服务的 服务状态、Http请求资源进行监控,可以看到服务器内存变化(堆内存、线程、日志管理),可以检测服务配置连接地址是否可用(模拟访问,懒加载),可以统计有多少Bean有什么单例多例,可以统计SpringMVC有多少@RequestMapping
Actuator是spring boot的一个附加功能,可帮助你在应用程序生产环境时监视和管理应用程序。
可以使用HTTP的各种请求来监管,审计,收集应用的运行情况.返回的是json
缺点:没有可视化界面。
在springboot2.0中,Actuator的端点(endpoint)现在默认映射到/application,比如,/info 端点现在就是在/application/info。但你可以使用management.context-path来覆盖此默认值。
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> 复制代码
# Actuator 通过下面的配置启用所有的监控端点,默认情况下,这些端点是禁用的; management: endpoints: web: exposure: include: "*" spring: profiles: active: prod datasource: driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://127.0.0.1:3306/test username: root password: 123456 复制代码
通过actuator/+端点名就可以获取相应的信息。
路径 | 作用 |
---|---|
/actuator/beans | 显示应用程序中所有Spring bean的完整列表。 |
/actuator/configprops | 显示所有配置信息。 |
/actuator/env | 陈列所有的环境变量。 |
/actuator/mappings | 显示所有@RequestMapping的url整理列表。 |
/actuator/health | 显示应用程序运行状况信息 up表示成功 down失败 |
/actuator/info | 查看自定义应用信息 |
Admin-UI底层使用actuator,实现监控信息 的界面
<!--服务端--> <dependency> <groupId>de.codecentric</groupId> <artifactId>spring-boot-admin-starter-server</artifactId> <version>2.0.0</version> </dependency> <!--客户端--> <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> 复制代码
//服务端 spring: application: name: spring-boot-admin-server //客户端 spring: boot: admin: client: url: http://localhost:8080 server: port: 8081 management: endpoints: web: exposure: include: "*" endpoint: health: show-details: ALWAYS 复制代码
默认情况下,我们会使用 @SpringBootApplication
注解来自动获取应用的配置信息,但这样也会给应用带来一些副作用。使用这个注解后,会触发自动配置( auto-configuration )和 组件扫描 ( component scanning ),这跟使用 @Configuration
、 @EnableAutoConfiguration
和 @ComponentScan
三个注解的作用是一样的。这样做给开发带来方便的同时,也会有三方面的影响:
1、会导致项目启动时间变长。当启动一个大的应用程序,或将做大量的集成测试启动应用程序时,影响会特别明显。
2、会加载一些不需要的多余的实例(beans)。
3、会增加 CPU 消耗。
针对以上三个情况,我们可以移除 @SpringBootApplication 和 @ComponentScan 两个注解来禁用组件自动扫描,然后在我们需要的 bean 上进行显式配置。
参数名称 | 含义 | 默认值 | |
---|---|---|---|
-Xms | 初始堆大小 | 物理内存的1/64(<1GB) | 默认(MinHeapFreeRatio参数可以调整)空余堆内存小于40%时,JVM就会增大堆直到-Xmx的最大限制. |
-Xmx | 最大堆大小 | 物理内存的1/4(<1GB) | 默认(MaxHeapFreeRatio参数可以调整)空余堆内存大于70%时,JVM会减少堆直到 -Xms的最小限制 |
-Xmn | 年轻代大小(1.4or lator) | 注意:此处的大小是(eden+ 2 survivor space).与jmap -heap中显示的New gen是不同的。 整个堆大小=年轻代大小 + 年老代大小 + 持久代大小. 增大年轻代后,将会减小年老代大小.此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8 | |
-XX:NewSize | 设置年轻代大小(for 1.3/1.4) | ||
-XX:MaxNewSize | 年轻代最大值(for 1.3/1.4) | ||
-XX:PermSize | 设置持久代(perm gen)初始值 | 物理内存的1/64 | |
-XX:MaxPermSize | 设置持久代最大值 | 物理内存的1/4 | |
-Xss | 每个线程的堆栈大小 | JDK5.0以后每个线程堆栈大小为1M,以前每个线程堆栈大小为256K.更具应用的线程所需内存大小进行 调整.在相同物理内存下,减小这个值能生成更多的线程.但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右 一般小的应用, 如果栈不是很深, 应该是128k够用的 大的应用建议使用256k。这个选项对性能影响比较大,需要严格的测试。(校长) 和threadstacksize选项解释很类似,官方文档似乎没有解释,在论坛中有这样一句话:"” -Xss is translated in a VM flag named ThreadStackSize” 一般设置这个值就可以了。 | |
-XX:ThreadStackSize | Thread Stack Size | (0 means use default stack size) [Sparc: 512; Solaris x86: 320 (was 256 prior in 5.0 and earlier); Sparc 64 bit: 1024; Linux amd64: 1024 (was 0 in 5.0 and earlier); all others 0.] | |
-XX:NewRatio | 年轻代(包括Eden和两个Survivor区)与年老代的比值(除去持久代) | -XX:NewRatio=4表示年轻代与年老代所占比值为1:4,年轻代占整个堆栈的1/5 Xms=Xmx并且设置了Xmn的情况下,该参数不需要进行设置。 | |
-XX:SurvivorRatio | Eden区与Survivor区的大小比值 | 设置为8,则两个Survivor区与一个Eden区的比值为2:8,一个Survivor区占整个年轻代的1/10 | |
-XX:LargePageSizeInBytes | 内存页的大小不可设置过大, 会影响Perm的大小 | =128m | |
-XX:+UseFastAccessorMethods | 原始类型的快速优化 | ||
-XX:+DisableExplicitGC | 关闭System.gc() | 这个参数需要严格的测试 | |
-XX:MaxTenuringThreshold | 垃圾最大年龄 | 如果设置为0的话,则年轻代对象不经过Survivor区,直接进入年老代. 对于年老代比较多的应用,可以提高效率.如果将此值设置为一个较大值,则年轻代对象会在Survivor区进行多次复制,这样可以增加对象再年轻代的存活 时间,增加在年轻代即被回收的概率 该参数只有在串行GC时才有效. | |
-XX:+AggressiveOpts | 加快编译 | ||
-XX:+UseBiasedLocking | 锁机制的性能改善 | ||
-Xnoclassgc | 禁用垃圾回收 | ||
-XX:SoftRefLRUPolicyMSPerMB | 每兆堆空闲空间中SoftReference的存活时间 | 1s | softly reachable objects will remain alive for some amount of time after the last time they were referenced. The default value is one second of lifetime per free megabyte in the heap |
-XX:PretenureSizeThreshold | 对象超过多大是直接在旧生代分配 | 0 | 单位字节 新生代采用Parallel Scavenge GC时无效 另一种直接在旧生代分配的情况是大的数组对象,且数组中无外部引用对象. |
-XX:TLABWasteTargetPercent | TLAB占eden区的百分比 | 1% | |
-XX:+CollectGen0First | FullGC时是否先YGC | false |
输入 -XX:+PrintGCDetails 是为了在控制台显示回收的信息
进入对应jar的目录,在CMD输入 java -server -Xms32m -Xmx32m -jar springboot.jar
Undertow 是一个采用 Java 开发的灵活的高性能 Web 服务器,提供包括阻塞和基于 NIO 的非堵塞机制。Undertow 是红帽公司的开源产品,是 JBoss默认的 Web 服务器。:point_down:
Undertow
首先,从依赖信息里移除 Tomcat 配置
<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> 复制代码
然后添加 Undertow:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-undertow</artifactId> </dependency> 复制代码
见:point_right: Spring Boot Memory Performance
热部署,就是在应用程序在不停止的情况下,自动实现新的部署
使用类加载器classroad来检测字节码文件,然后重新加载到jvm内存中
第一步:检测本地 .class
文件变动(版本号,修改时间不一样)
第二步:自动监听,实现部署
本地开发时,可以提高运行环境
spring-boot-devtools 是一个为开发者服务的一个模块,其中最重要的功能就是自动应用代码更改到最新的App上面去
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <optional>true</optional> <scope>true</scope> </dependency> 复制代码
1.使用mvn clean package 打包
2.使用java –jar 包名
1.使用mvn celan package 打包
2.使用java –jar 包名
1.使用mvn celan package 打包
2.将war包 放入到tomcat webapps下运行即可。
注意:springboot2.0内置tomcat8.5.25,建议使用外部Tomcat9.0版本运行即可,否则报错版本不兼容。
<build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>1.8</source> <target>1.8</target> </configuration> </plugin> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <maimClass>com.itmayiedu.app.App</maimClass> </configuration> <executions> <execution> <goals> <goal>repackage</goal> </goals> </execution> </executions> </plugin> </plugins> </build> 复制代码