--- title: 微服务架构之 SpringBoot date: 2019-04-04 18:17:48 categories: - 应用框架 tags: - 分布式 --- # 微服务架构之 SpringBoot ## 目录 - [简介](#简介) - [快速入门](#快速入门) - [正篇](#正篇) - [参考链接](#参考链接) - [结束语](#结束语) ## 简介 ### `什么是SpringBoot` ``` SpringBoot是一个快速开发的框架,能够快速整合第三方框架,简化XML配置完全采用注解形式,内置Web服务器(Jetty和Tomcat),最终以java应用程序进行执行。 SpringBoot不是微服务框架。 ``` ### `为什么要用SpringBoot` ``` 解决传统SSM项目的开发效率低、Jar冲突、配置多的问题。 ``` ### `与SpringMVC、SpringCloud之间的关系` ``` SpringCloud: 是一套完整的微服务解决框架,采用Http+Json格式的通讯协议,底层依赖于SpringBoot框架(web组件集成springmvc),使用SpringMVC书写Http协议接口。 采用SpringBoot+Dubbo也能实现微服务。 SpringMVC: SpringBoot默认集成了SpringMVC作为web层的框架。 总结: 如果需要做微服务的话,就需要整合SpringBoot+SpringCloud;如果项目中不需要实现微服务,可以只单纯使用SpringBoot而不使用SpringCloud。 ``` ## 快速入门 1. 创建一个普通 maven 工程,packaging类型为jar(可省略) 2. pom文件配置 ``` org.springframework.boot spring-boot-starter-parent 2.0.0.RELEASE UTF-8 UTF-8 1.8 org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-configuration-processor true ``` ``` spring-boot-starter-parent作用 统一整合第三方框架依赖信息,以后再申明其它dependency的时候就不需要写版本号了。 spring-boot-starter-web作用 把传统方式的SpringMVC依赖的所有jar全部给下载下来, spring-boot-maven-plugin作用   如果直接通过Main启动spring,那么以下plugin必须要添加,否则是无法启动的。如果使用 maven 的spring-boot:run的话是不需要此配置的。 ``` 3. 配置application.yml ``` server: tomcat: accept-count: 1000 max-connections: 2000 max-threads: 300 min-spare-threads: 50 uri-encoding: UTF-8 # 指定tomcat的编码格式 max-http-post-size: 100MB accesslog: enabled: true port: 8080 # 设定web访问端口号,也是http监听端口 connection-timeout: 60000 servlet: context-path: /monitor # 2.0之后的配置,应用的上下文路径,也可以称为项目路径,是构成url地址的一部分 compression: enabled: true # 是否开启压缩,默认为false http2: enabled: true # 应用支持http2 ``` 4. 编写helloworld服务 ``` package top.lvzhiqiang.controller; @RestController @EnableAutoConfiguration public class IndexController { @RequestMapping("/index") public String index(){ return "hello springboot!"; } public static void main(String[] args) { SpringApplication.run(IndexController.class,args); } } ``` ``` @RestController的作用 表示该Controller下所有的方法返回JSON格式,可以直接编写Restful接口;省略了在方法上加上@ResponseBody。 如果项目中的类加上了此注解,说明这个项目可能是一个微服务项目,需要统一接口,提供服务接口。 @EnableAutoConfiguration的作用 表示让Spring Boot根据添加的jar依赖来对Spring框架进行自动配置。 由于spring-boot-starter-web添加了Tomcat和Spring MVC,所以auto-configuration将假定你正在开发一个web应用并相应地对Spring进行设置。 @Configuration的作用 底层含有@Component,标注在类上,相当于把该类作为spring的xml配置文件中的 用来来配置spring容器(应用上下文) @Bean的作用 标注在方法上(返回某个实例的方法),相当于spring的xml配置文件中的 用来注册bean对象(实例化bean,并交给spring管理) ``` 5. 启动应用 ``` 方法1: 直接启动main方法,main方法中指定IndexController为启动入口,默认端口号为8080。 方法2: @ComponentScan(basePackages = "top.lvzhiqiang.controller") @EnableAutoConfiguration public class App { public static void main(String[] args) { SpringApplication.run(App.class, args); } } 单独写个类并使用@ComponentScan注解添加自动扫包范围。 方法3: @SpringBootApplication public class App { public static void main(String[] args) { SpringApplication.run(App.class, args); } } 单独写个类并使用@SpringBootApplication注解,等同于@ComponentScan + @EnableAutoConfiguration。 扫包范围:当前包下或者子包下所有的类都可以扫到。 ``` ## 正篇 ### Web开发 - `静态资源访问` ``` 在我们开发Web应用的时候,需要引用大量的js、css、图片等静态资源。 Spring Boot默认提供静态资源目录位置需置于classpath(src/main/java或者src/main/resources)下,目录命名如下: static、public、resources、META-INF/resources ``` - `渲染Web页面` ``` 如果需要返回html等页面时,Spring Boot提供了多种模板引擎,来帮助我们很快的上手开发动态网站。 Thymeleaf、FreeMarker、Velocity、Groovy、Mustache Spring Boot建议使用模板引擎(例如FreeMarker),避免使用JSP,若一定要使用JSP将无法实现Spring Boot的多种特性。 如果使用模板引擎,它们默认的模板配置路径为:src/main/resources/templates。当然也可以修改这个路径。 ``` - `使用Freemarker模板引擎渲染Web视图` ``` 1.pom文件引入FreeMarker的依赖包 org.springframework.boot spring-boot-starter-freemarker 2.在src/main/resources/创建一个templates文件夹,在templates下新建ftlIndex.ftl @Controller @EnableAutoConfiguration public class IndexController { @RequestMapping("/ftlIndex") public String ftlIndex(Model model) { model.addAttribute("name", "张三"); model.addAttribute("sex", 1); List userList = new ArrayList<>(); userList.add("a"); userList.add("b"); userList.add("c"); model.addAttribute("userList", userList); return "ftlIndex"; } ... } 3.在ftl文件中填充数据 ${name} <#if sex == 1>男 <#elseif sex == 2>女 <#else>其他 <#list userList as user>${user} 4.新建src/main/resources/下新建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 ``` - `使用JSP渲染Web视图` ``` 1.pom文件引入Jsp相关的依赖包 org.springframework.boot spring-boot-starter-tomcat org.apache.tomcat.embed tomcat-embed-jasper 2.新建src/main/resources/下新建application.properties文件进行Jsp配置 spring.mvc.view.prefix=/WEB-INF/jsp/ spring.mvc.view.suffix=.jsp 3.在src/main/webapp下创建/WEB-INF/jsp/目录,并在jsp目录下新建jspIndex.jsp @Controller @EnableAutoConfiguration public class IndexController { @RequestMapping("/jspIndex") public String jspIndex() { return "jspIndex"; } ... } 4.如果找不到页面,可在pom文件中改为war类型项目 ``` - `全局捕获异常` ``` @ControllerAdvice(basePackages = "top.lvzhiqiang.controller") public class GlobalExceptionHandler { @ExceptionHandler(RuntimeException.class) @ResponseBody public Map exceptionHandler() { Map map = new HashMap<>(); map.put("errorCode", "101"); map.put("errorMsg", "系統错误!"); return map; } } ``` ``` @ExceptionHandler表示拦截异常 @ControllerAdvice是controller的一个辅助类,最常用的就是作为全局异常处理的切面类 @ControllerAdvice可以指定扫描范围 @ControllerAdvice约定了几种可行的返回值,如果是直接 返回json,需要使用@ResponseBody进行json转换 返回页面,返回值类型为ModelAndView或者String 实际开发中,可在全局捕获异常中将异常日志信息写入nosql数据库中,如果是集群环境,可采用分布式日志收集系统(kafka,logstash等),方便排查 ``` ### 日志管理 1. `使用log4j记录日志` ``` 1.pom文件引入log4j相关的依赖包 org.springframework.boot spring-boot-starter org.springframework.boot spring-boot-starter-logging org.springframework.boot spring-boot-starter-log4j 1.3.8.RELEASE 2.新建src/main/resources/下新建log4j.properties文件进行log4j配置 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=D:\\log4j\\springbootdemo\\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=D:\\log4j\\springbootdemo\\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=D:\\log4j\\springbootdemo\\debug ``` 2. `使用AOP统一处理Web请求日志` ``` 1.pom文件新增aop依赖 org.springframework.boot spring-boot-starter-aop 2.aop代码 @Aspect @Component public class WebLogAspect { private static final Logger logger = LoggerFactory.getLogger(WebLogAspect.class); @Pointcut("execution(public * top.lvzhiqiang.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 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); } } ``` 3. `Spring Boot集成lombok让代码更简洁` ``` 0.IDE开发工具需要先安装lombok插件(lombok底层使用asm字节码技术,在编译时修改字节码文件,来生成相应的set,get等相应方法) 1.pom文件引入lombok相关的依赖包 org.projectlombok lombok 2.实体类测试 @Slf4j @Setter @Getter public class UserEntity { // @Getter // @Setter private String userName; // @Getter // @Setter private Integer age; @Override public String toString() { return "UserEntity [userName=" + userName + ", age=" + age + "]"; } public static void main(String[] args) { UserEntity userEntity = new UserEntity(); userEntity.setUserName("zhangsan"); userEntity.setAge(20); System.out.println(userEntity.toString()); log.info("####我是日志##########"); } } ``` ``` 其他特性: @Data: 自动生成set/get方法,toString方法,equals方法,hashCode方法,不带参数的构造方法 @Setter/@Getter: 自动生成set和get方法 @ToString: 自动生成toString方法 @NonNull: 让你不在担忧并且爱上NullPointerException @CleanUp: 自动资源管理,不用再在finally中添加资源的close方法 @EqualsAndHashcode: 从对象的字段中生成hashCode和equals的实现 @NoArgsConstructor/@RequiredArgsConstructor/@AllArgsConstructor: 自动生成构造方法 @Value: 用于注解final类 @Builder: 产生复杂的构建器api类 @SneakyThrows: 异常处理(谨慎使用) @Synchronized : 同步方法安全的转化 @Log: 支持各种logger对象,使用时用对应的注解,如:@Log4j ``` ### 数据访问 - `整合mybatis和HikariCP` ``` 1.pom文件中引入数据源驱动与mybatis依赖 org.mybatis.spring.boot mybatis-spring-boot-starter 1.1.1 mysql mysql-connector-java 5.1.21 2.在application.yml中配置数据源和mybatis ########################################################### # # 配置数据源信息 # ########################################################### spring: datasource: # 数据源的相关配置 type: com.zaxxer.hikari.HikariDataSource # 数据源类型:HikariCP driver-class-name: com.mysql.jdbc.Driver # mysql驱动 url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8&autoReconnect username: root password: root hikari: # Hikari连接池配置 connection-timeout: 30000 # 等待连接池分配连接的最大时长(毫秒),超过这个时长还没可用的连接则发生SQ minimum-idle: 5 # 最小空闲连接数 maximum-pool-size: 20 # 最大连接数,默认是10 auto-commit: true # 自动提交 idle-timeout: 600000 # 连接超时的最大时长(毫秒),超时则被释放(retired),默认:10分钟 pool-name: DateSourceHikariCP # 连接池名字 max-lifetime: 1800000 # 此属性控制池中连接的最长生命周期(毫秒),值0表示无限生命周期,超时而且没被使用则被释放(retired),默认:30分钟 connection-test-query: SELECT 1 ########################################################### # # mybatis配置 # ########################################################### mybatis: type-aliases-package: com.riskraider.monitor.entity # 所有POJO类所在包路径,只能指定具体的包,多个配置可以使用英文逗号隔开 mapper-locations: classpath:mapper/**/*.xml # mapper映射文件的路径,支持Ant风格的通配符,多个配置可以使用英文逗号隔开 configuration: # 开启驼峰命名转换,如:Table(create_time) -> Entity(createTime)。 # 不需要我们关心怎么进行字段匹配,mybatis会自动识别`大写字母与下划线` map-underscore-to-camel-case: true 3.Mapper代码 public interface UserMapper { @Select("SELECT * FROM USERS WHERE NAME = #{name}") User findByName(@Param("name") String name); @Insert("INSERT INTO USERS(NAME, AGE) VALUES(#{name}, #{age})") int insert(@Param("name") String name, @Param("age") Integer age); } 4.在启动类上加上@MapperScan(basePackages = "top.lvzhiqiang.mapper") 也可以在Mapper接口上加上@Mapper,不过这样比较繁琐 ``` ``` Mybatis整合分页插件(pageHelper) PageHelper是一款好用的开源免费的Mybatis第三方物理分页插件 支持常见的12种数据库。Oracle,MySql,MariaDB,SQLite,DB2,PostgreSQL,SqlServer等 支持多种分页方式 支持常见的 RowBounds(PageRowBounds),PageHelper.startPage方法调用,Mapper接口参数调用 ------------------ 1.添加maven依赖 com.github.pagehelper pagehelper-spring-boot-starter 1.2.5 org.apache.commons commons-lang3 3.7 2.添加配置 pagehelper.helperDialect=mysql pagehelper.reasonable=true pagehelper.supportMethodsArguments=true pagehelper.params=count=countSql pagehelper.page-size-zero=true 3.service层 public PageInfo findUserList(int pageNo, int pageSize) { // 开启分页插件,放在查询语句上面 PageHelper.startPage(pageNo, pageSize); List listUser = userMapper.findUserList(); // 封装分页之后的数据 PageInfo pageInfoUser = new PageInfo(listUser); return pageInfoUser; } //以上方法上的公共代码可用模板设计模式或者aop思想抽出来 ``` - `整合jdbcTemplate` ``` 1.pom文件引入jdbcTemplate相关的依赖包 org.springframework.boot spring-boot-starter-jdbc mysql mysql-connector-java 5.1.21 2.修改application.properties spring.datasource.jdbc-url=jdbc:mysql://localhost:3306/test spring.datasource.username=root spring.datasource.password=root spring.datasource.driver-class-name=com.mysql.jdbc.Driver 3.代码 @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); } } ``` - `整合多数据源` ``` 1.修改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 = root ###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 = root 2.在top/lvzhiqiang/下新建datasource目录(任意),新建DataSourceConfig类,采用分包(业务)机制来读取不同的数据源 //datasource1 @Configuration @MapperScan(basePackages = "top.lvzhiqiang.user1", sqlSessionFactoryRef = "test1SqlSessionFactory") public class DataSource1Config { @Bean(name = "test1DataSource") @Primary @ConfigurationProperties(prefix = "spring.datasource.test1") public DataSource testDataSource() { return DataSourceBuilder.create().build(); } @Bean(name = "test1SqlSessionFactory") @Primary public SqlSessionFactory testSqlSessionFactory(@Qualifier("test1DataSource") DataSource dataSource) throws Exception { SqlSessionFactoryBean bean = new SqlSessionFactoryBean(); bean.setDataSource(dataSource); //bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mybatis/mapper/test1/*.xml")); return bean.getObject(); } @Bean(name = "test1TransactionManager") @Primary public DataSourceTransactionManager testTransactionManager(@Qualifier("test1DataSource") DataSource dataSource) { return new DataSourceTransactionManager(dataSource); } @Bean(name = "test1SqlSessionTemplate") public SqlSessionTemplate testSqlSessionTemplate(@Qualifier("test1SqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception { return new SqlSessionTemplate(sqlSessionFactory); } } ------------------------------ //datasource2 @Configuration @MapperScan(basePackages = "top.lvzhiqiang.user2", sqlSessionFactoryRef = "test2SqlSessionFactory") public class DataSource2Config { @Bean(name = "test2DataSource") @ConfigurationProperties(prefix = "spring.datasource.test2") public DataSource testDataSource() { return DataSourceBuilder.create().build(); } ... } 3.创建分包Mapper public interface User1Mapper { @Insert("insert into users values(null,#{name},#{age});") public int addUser(@Param("name") String name, @Param("age") Integer age); } 4.多数据源事务注意事项 在多数据源的情况下,使用@Transactional注解时,应该指定事务管理器 @Transactional(transactionManager = "test2TransactionManager") ``` ### 事务管理 - `SpringBoot整合事物管理` ``` springboot默认集成并开启事物,只要在方法上加上@Transactional即可 ``` - `SpringBoot分布式事物管理` ``` 针对多数据源的分布式事务解决方案:jta+atomikos 针对微服务的分布式事务解决方案:mq或者tcc等 ``` ``` 1.pom文件引入atomikos相关的依赖包 org.springframework.boot spring-boot-starter-jta-atomikos 2.新增多数据源配置信息,修改application.properties # 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 = root 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 =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 3.读取多数据源配置信息,可在top/lvzhiqiang/下新建config目录(任意),新建2个config类 @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; } ---------------------- @Data @ConfigurationProperties(prefix = "mysql.datasource.test2") public class DBConfig2 { 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; } 4.将多数据源注册到atomikos中去,在top/lvzhiqiang/下新建datasource目录(任意),新建DataSourceConfig类 @Configuration @MapperScan(basePackages = "top.lvzhiqiang.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); } } ---------------------- @Configuration @MapperScan(basePackages = "top.lvzhiqiang.test02", sqlSessionTemplateRef = "test2SqlSessionTemplate") public class MyBatisConfig2 { @Bean(name = "test2DataSource") public DataSource testDataSource(DBConfig2 testConfig) throws SQLException { MysqlXADataSource mysqlXaDataSource = new MysqlXADataSource(); mysqlXaDataSource.setUrl(testConfig.getUrl()); mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true); ... } ... } 5.在启动类加上此注解以开启读取配置文件 @EnableConfigurationProperties(value = { DBConfig1.class, DBConfig2.class }) ``` ### 监控管理 - `Actuator监控应用` ``` 什么是SpringBoot监控中心 Actuator是spring boot的一个附加功能,可帮助你在应用程序生产环境时监视和管理应用程序。 特别对于微服务管理十分有意义,缺点是没有可视化界面(返回json格式)。 默认情况下,监控中心只提供3个接口权限。 在SpringBoot2.0后,监控中心接口地址发生变化,需要在访问接口前面中上/actuator。 主要用途 针对微服务服务器监控,包括服务器的内存变化(堆内存、线程、日志管理等) 检测服务配置连接地址(懒加载形式)是否可用(模拟访问) 统计当前Spring容器中有多少个bean 统计SpringMVC有多少个@RuquestMapping(http接口) 应用场景 生产环境 ``` ``` 1.添加maven依赖 org.springframework.boot spring-boot-starter-actuator 2.yml配置 ###通过下面的配置启用所有的监控端点,默认情况下,这些端点是禁用的 management: endpoints: web: exposure: include: "*" 3.开始访问(通过actuator/+端点名就可以获取相应的信息) /actuator/beans 显示应用程序中所有Spring bean的完整列表。 /actuator/mappings 显示所有@RequestMapping的url完整列表。 /actuator/configprops 显示所有配置信息。 /actuator/env 显示所有的环境变量。 /actuator/health 显示应用程序运行状况信息,up表示成功,down失败。 /actuator/info 查看自定义应用信息(显示在配置文件配置info开头的配置信息) ``` - `Admin-UI分布式微服务监控中心` - Admin-UI底层基于actuator实现能够返回界面展示监控信息,原理是将所有服务的监控中心管理存放在admin-ui平台上。 --- ``` Admin-UI-Server -------------------- 1.添加maven依赖 de.codecentric spring-boot-admin-starter-server 2.0.0 org.springframework.boot spring-boot-starter-webflux org.jolokia jolokia-core org.springframework.boot spring-boot-starter-actuator com.googlecode.json-simple json-simple 1.1 2.yml配置 spring: application: name: spring-boot-admin-server 3.启动server端 @Configuration @EnableAutoConfiguration @EnableAdminServer public class AdminServerApplication { public static void main(String[] args) { SpringApplication.run(AdminServerApplication.class, args); } } 4.访问 http://localhost:8080 ``` ``` Admin-UI-Client -------------------- 1.添加maven依赖 de.codecentric spring-boot-admin-starter-client 2.0.0 org.springframework.boot spring-boot-starter-actuator org.jolokia jolokia-core com.googlecode.json-simple json-simple 1.1 org.springframework.boot spring-boot-starter-web 2.yml配置 spring: boot: admin: client: url: http://localhost:8080 ###配置client端注册到admin-ui平台 server: port: 8081 management: endpoints: web: exposure: include: "*" endpoint: health: show-details: ALWAYS 3.启动client端 @SpringBootApplication public class AppClient { public static void main(String[] args) { SpringApplication.run(AppClient.class, args); } } ``` ### 性能优化 - `JVM参数调优(运行优化)` ``` -Xms和-Xmx最好设置成一样,不然会频繁造成GC 外部运行调优使用:java -server -Xms32m -Xmx32m -jar springboot.jar ``` - `扫包优化(启动优化,非运行优化)` ``` 针对大项目时不要用@SpringBootApplication注解,会扫描到无用的包,可以采用@ComponentScan来扫描指定的包 ``` - `将Servlet容器变成Undertow` ``` 默认情况下,Spring Boot 使用 Tomcat 来作为内嵌的 Servlet 容器,可以将 Web 服务器切换到 Undertow 来提高应用性能。 Undertow 是一个采用 Java 开发的灵活的高性能 Web 服务器,提供包括阻塞和基于NIO的非堵塞机制。 Undertow 是红帽公司的开源产品,是 Wildfly 默认的 Web 服务器。 可用jmeter作吞吐量测试,结果是tomcat吞吐量为5000,undertow为8000 ``` ``` 1.从依赖信息里移除Tomcat org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-tomcat 2.添加Undertow依赖 org.springframework.boot spring-boot-starter-undertow ``` ### 其他内容 - `使用@Scheduled创建定时任务` ``` 在启动类中加入@EnableScheduling注解,启用定时任务的配置 ------------------- @Component public class ScheduledTasks { private static final SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss"); @Scheduled(fixedRate = 5000) public void reportCurrentTime() { System.out.println("现在时间:" + dateFormat.format(new Date())); } } ``` - `使用@Async异步执行方法` ``` 在启动类加上@EnableAsync来开启异步调用,并在需要执行异步的方法上加上@Async @Async注解使用AOP技术在运行时创建一个单线程来执行这个方法 ``` - `使用@Value自定义参数` ``` application.properties文件 name=lvzhiqiang.top 代码中 @Value("${name}") private String name; @ResponseBody @RequestMapping("/getValue") public String getValue() { return name; } ``` - `区分不同环境配置文件` ``` 1.在src/main/resources下新建各个环境的配置文件 application-dev.properties:开发环境 application-uat.properties:测试环境 application-pre.properties:生产环境 2.在application.properties中进行切换 spring.profiles.active=uat ``` - `修改端口号` ``` server.port=8888 server.context-path=/springbootdemo ``` - `Spring Boot yml 使用` ``` SpringBoot默认读取application.yml|properties YML比properties配置文件更加简约(结构) --------------- server: port: 8081 context-path: /springbootdemo ``` - `发布打包` ``` jar类型打包方式 1.使用mvn celan package 打包 2.使用java –jar 包名 war类型打包方式 1.使用mvn celan package 打包 2.将war包放入到tomcat webapps下运行即可 注意:springboot2.0内置tomcat8.5.25,建议使用外部Tomcat9.0版本运行即可,否则报错版本不兼容。 ``` ``` 如果执行java -jar命令提示没有主清单属性时,可在pom文件中新增如下代码 org.apache.maven.plugins maven-compiler-plugin 1.8 1.8 org.springframework.boot spring-boot-maven-plugin top.lvzhiqiang.StartApp repackage ``` - `热部署` ``` 1.什么是热部署 在应用程序不停止的情况下,实现新的部署 2.热部署原理 使用类加载器重新读取字节码文件到JVM中去 3.纯手写实现一个热部署功能 监听class文件是否发生改变(版本号或者修改时间),如果class文件发生改变,就使用classloader进行得新读取 4.优缺点 适合本地开发,提高运行效率,不用重启服务器;缺点是项目比较大时,非常卡,比较占内存. ``` ``` 1.引入devtools依赖 org.springframework.boot spring-boot-devtools true true 2.Devtools原理 1.devtools会监听classpath下的文件变动,并且会立即重启应用(发生在保存时机),注意:因为其采用的虚拟机机制,该项重启是很快的。 2.devtools可以实现页面热部署(即页面修改后会立即生效,这个可以直接在application.properties文件中配置spring.thymeleaf.cache=false来实现(这里注意不同的模板配置不一样) 3.不推荐使用 ``` - `整合拦截器` ``` 1.创建拦截器 @Slf4j @Component //创建模拟登录拦截器,验证请求是否有token参数 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; } } 2.注册拦截器 @Configuration public class WebAppConfig { @Autowired private LoginIntercept loginIntercept; @Bean public WebMvcConfigurer WebMvcConfigurer() { return new WebMvcConfigurer() { public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(loginIntercept).addPathPatterns("/*"); }; }; } } ``` ``` 拦截器与过滤器区别 拦截器是AOP( Aspect-Oriented Programming)的一种实现,底层通过动态代理模式完成。 (1)拦截器是基于java的反射机制的,而过滤器是基于函数回调。 (2)拦截器不依赖于servlet容器,而过滤器依赖于servlet容器。 (3)拦截器只能对Controller请求起作用,而过滤器则可以对几乎所有的请求起作用。 (4)在Controller的生命周期中,拦截器可以多次被调用,而过滤器只能在容器初始化时被调用一次。 过滤器应用场景:设置编码字符、过滤铭感字符 拦截器应用场景:拦截未登陆用户、审计日志 ``` ### 2.0版本新特性 - `以Java 8 为基准` ``` Spring Boot 2.0 要求 Java 版本必须 8 以上, Java 6 和 7 不再支持。 ``` - `内嵌容器包结构调整` ``` 为了支持reactive使用场景,内嵌的容器包结构被重构了的幅度有点大。EmbeddedServletContainer被重命名为WebServer, 并且org.springframework.boot.context.embedded 包被重定向到了org.springframework.boot.web.embedded包下。 举个例子,如果你要使用TomcatEmbeddedServletContainerFactory回调接口来自定义内嵌Tomcat容器,你现在应该使用TomcatServletWebServerFactory。 ``` - `Servlet-specific 的server properties调整` ``` 大量的Servlet专属的server.* properties被移到了server.servlet下: Old property New property server.context-parameters.* server.servlet.context-parameters.* server.context-path server.servlet.context-path server.jsp.class-name server.servlet.jsp.class-name server.jsp.init-parameters.* server.servlet.jsp.init-parameters.* server.jsp.registered server.servlet.jsp.registered server.servlet-path server.servlet.path 由此可以看出一些端倪,那就是server不再是只有servlet了,还有其他的要加入。 ``` - `Actuator 默认映射` ``` actuator的端点(endpoint)现在默认映射到/application,比如,/info 端点现在就是在/application/info。但你可以使用management.context-path来覆盖此默认值。 ``` - `Spring Loaded不再支持` ``` 由于Spring Loaded项目已被移到了attic了,所以不再支持Spring Loaded了。现在建议你去使用Devtools。Spring Loaded不再支持了。 ``` - `支持Quartz Scheduler` ``` Spring Boot 2.0针对Quartz调度器提供了支持。你可以加入spring-boot-starter-quartz starter来启用。而且支持基于内存和基于jdbc两种存储。 ``` - `OAuth 2.0 支持` ``` Spring Security OAuth 项目中的功能将会迁移到Spring Security中。将会OAuth 2.0。 ``` - `支持Spring WebFlux` ``` WebFlux 模块的名称是 spring-webflux,名称中的 Flux 来源于 Reactor 中的类 Flux。该模块中包含了对反应式 HTTP、服务器推送事件和 WebSocket 的客户端和服务器端的支持。对于开发人员来说,比较重要的是服务器端的开发,这也是本文的重点。在服务器端,WebFlux 支持两种 不同的编程模型:第一种是 Spring MVC 中使用的基于 Java 注解的方式;第二种是基于 Java 8 的 lambda 表达式的函数式编程模型。这两种 编程模型只是在代码编写方式上存在不同。它们运行在同样的反应式底层架构之上,因此在运行时是相同的。WebFlux 需要底层提供运行时的支持, WebFlux 可以运行在支持 Servlet 3.1 非阻塞 IO API 的 Servlet 容器上,或是其他异步运行时环境,如 Netty 和 Undertow。 ``` - `版本要求` ``` Jetty 要求Jetty最低版本为9.4。 Tomcat 要求Tomcat最低版本为8.5。 Hibernate 要求Hibernate最低版本为5.2。 Gradle 要求Gradle最低版本为3.4。 SendGrid SendGrid最低支持版本是3.2。为了支持这次升级,username和password已经被干掉了。因为API key现在是唯一支持的认证方式。 ``` ### 手写springboot框架 - springboot核心原理 ``` 基于springmvc无配置文件完全注解化(纯java)+内置tomcat-embed-core来实现springboot框架,main函数启动 快速整合第三方框架 原理:Maven继承依赖关系 完全无配置文件采用注解化 原理:使用java代码(spring3内置注解)来对springmvc容器进行初始化 内置web服务器 原理:Java提供内置Tomcat容器框架,使用java语言创建并操作tomcat容器,加载class文件 ``` - 内置web服务器 ``` 1.添加pom依赖 org.apache.tomcat.embed tomcat-embed-core 8.5.16 org.springframework spring-web 5.0.4.RELEASE compile org.springframework spring-webmvc 5.0.4.RELEASE compile org.apache.tomcat tomcat-jasper 8.5.16 2.创建servlet类 public class IndexServet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doPost(req, resp); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.getWriter().print("springboot2.0"); } } 3.创建tomcat运行 private static int PORT = 8080;// 端口号 private static String CONTEXTPATH = "/demo";// 项目名称 public static void main(String[] args) throws LifecycleException { // 创建Tomcat服务器 Tomcat tomcatServer = new Tomcat(); // 设置Tomcat端口号 tomcatServer.setPort(PORT); tomcatServer.getHost().setAutoDeploy(false); // 创建Context上下文 StandardContext standardContext = new StandardContext(); standardContext.setPath(CONTEXTPATH); standardContext.addLifecycleListener(new FixContextListener()); // tomcat容器添加standardContext tomcatServer.getHost().addChild(standardContext); // 创建servlet tomcatServer.addServlet(CONTEXTPATH, "IndexServet", new IndexServet()); // 添加servleturl映射 standardContext.addServletMappingDecoded("/index", "IndexServet"); tomcatServer.start(); tomcatServer.getServer().await(); } ``` - 完全无配置文件采用注解化 ``` 1.加载SpringMVC的DispatcherServlet AbstractAnnotationConfigDispatcherServletInitializer这个类负责配置DispatcherServlet、初始化Spring MVC容器和Spring容器。 getRootConfigClasses()方法用于获取Spring应用容器的配置文件,这里我们给定预先定义的RootConfig.class; getServletConfigClasses负责获取Spring MVC应用容器,这里传入预先定义好的WebConfig.class; getServletMappings()方法负责指定需要由DispatcherServlet映射的路径,这里给定的是"/",意思是由DispatcherServlet处理所有向该应用发起的请求。 public class SpittrWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer { // 加载根容器 protected Class[] getRootConfigClasses() { return new Class[] { RootConfig.class }; } // 加载SpringMVC容器 protected Class[] getServletConfigClasses() { return new Class[] { WebConfig.class }; } // SpringMVCDispatcherServlet 拦截的请求 protected String[] getServletMappings() { return new String[] { "/" }; } } 2.加载SpringMVC容器 @Configuration @EnableWebMvc @ComponentScan("top.lvzhiqiang.controller") public class WebConfig extends WebMvcConfigurerAdapter { // 创建SpringMVC视图解析器 @Bean public ViewResolver viewResolver() { InternalResourceViewResolver viewResolver = new InternalResourceViewResolver(); viewResolver.setPrefix("/WEB-INF/views/"); viewResolver.setSuffix(".jsp"); viewResolver.setExposeContextBeansAsAttributes(true);//可以在JSP页面中通过${}访问beans return viewResolver; } } 3.加载RootConfig容器 @Configuration @ComponentScan(basePackages = "top.lvzhiqiang") public class RootConfig { } 4.运行 public static void main(String[] args) throws ServletException, LifecycleException { start(); } public static void start() throws ServletException, LifecycleException { // 创建Tomcat容器 Tomcat tomcatServer = new Tomcat(); // 端口号设置 tomcatServer.setPort(9090); // 读取项目路径,主要用来加载一些静态资源 StandardContext ctx = (StandardContext) tomcatServer.addWebapp("/", new File("src/main").getAbsolutePath()); // 禁止重新载入 ctx.setReloadable(false); // class文件读取地址 File additionWebInfClasses = new File("target/classes"); // 创建WebRoot WebResourceRoot resources = new StandardRoot(ctx); // tomcat内部读取Class执行 resources.addPreResources(new DirResourceSet(resources, "/WEB-INF/classes", additionWebInfClasses.getAbsolutePath(), "/")); tomcatServer.start(); // 异步等待请求执行 tomcatServer.getServer().await(); } ``` ## 参考链接 ## 结束语 - 未完待续...