当单体架构到达一定规模时,修正bug和正确的添加新功能变的非常困难,并且很耗时。单体应用模块之间的强依赖很可能因为某一模块而导致整个应用宕机,很影响开发效率。所以说复杂而笨重的单体式应用就非常不适合持续性开发了。这时候Spring
Boot完美解决了复杂臃肿的单体式应用出现的问题。它对开发者异常友好,很容易常见一个单模块的Spring应用、内置了常见的web服务器,模块最终可以打包成jar包启动、提供了很多可以选择的’starter’、简化了Spring大量配置等…当前开发系统(百万行代码)就存在单体式应用的问题,花了2天时间研究了下Spring Boot,准备后期拆分成Spring Boot微服务架构
Spring Boot框架搭建
一、创建Spring Boot
IDEA编辑器的话,只需要File–>New–>Project–>Spring Initializr 需要的依赖选择后就可以创建一个Spring Boot应用了。这个时候就可以启动应用程序了,代码块:
@Controller
@EnableAutoConfiguration
public class SampleController {
@RequestMapping("/")
@ResponseBody
String home() {
return "Hello World!";
}
public static void main(String[] args) throws Exception {
SpringApplication.run(SampleController.class, args);
}
}
运行SampleController.java后,访问http://localhost:8080,页面会返回Hello World!结果。这样一个基本的微服务就搭建成功了。比普通的web框架简易太多了,再也不用管理Tomcat和Spring的大量配置了!
二、Spring Boot集成log4j2
添加POM依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
添加log4j2.xml配置文件:
<?xml version="1.0" encoding="UTF-8"?>
<configuration status="warn">
<appenders>
<console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss:SSS}] [%p] - [Thread:%t] -%l - %m%n"/>
</console>
<RollingFile name="RollingFileInfo" fileName="/Users/tian_dd/logs/spring-boot/info.log"
filePattern="/Users/tian_dd/logs/spring-boot/$${date:yyyy-MM}/info-%d{yyyy-MM-dd}-%i.log">
<Filters>
<ThresholdFilter level="INFO"/>
<ThresholdFilter level="WARN" onMatch="DENY" onMismatch="NEUTRAL"/>
</Filters>
<PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss:SSS}] [%p] - [Thread:%t] -%l - %m%n"/>
<Policies>
<TimeBasedTriggeringPolicy/>
<SizeBasedTriggeringPolicy size="100 MB"/>
</Policies>
</RollingFile>
<RollingFile name="RollingFileWarn" fileName="/Users/tian_dd/logs/spring-boot/warn.log"
filePattern="/Users/tian_dd/logs/spring-boot/$${date:yyyy-MM}/warn-%d{yyyy-MM-dd}-%i.log">
<Filters>
<ThresholdFilter level="WARN"/>
<ThresholdFilter level="ERROR" onMatch="DENY" onMismatch="NEUTRAL"/>
</Filters>
<PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss:SSS}] [%p] - [Thread:%t] -%l - %m%n"/>
<Policies>
<TimeBasedTriggeringPolicy/>
<SizeBasedTriggeringPolicy size="100 MB"/>
</Policies>
</RollingFile>
<RollingFile name="RollingFileError" fileName="/Users/tian_dd/logs/spring-boot/error.log"
filePattern="/Users/tian_dd/logs/spring-boot/$${date:yyyy-MM}/error-%d{yyyy-MM-dd}-%i.log">
<ThresholdFilter level="ERROR"/>
<PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss:SSS}] [%p] - [Thread:%t] -%l - %m%n"/>
<Policies>
<TimeBasedTriggeringPolicy/>
<SizeBasedTriggeringPolicy size="100 MB"/>
</Policies>
</RollingFile>
</appenders>
<loggers>
<logger name="org.springframework" level="INFO">
</logger>
<logger name="org.hibernate" level="INFO">
</logger>
<root level="all">
<appender-ref ref="Console"/>
<appender-ref ref="RollingFileInfo"/>
<appender-ref ref="RollingFileWarn"/>
<appender-ref ref="RollingFileError"/>
</root>
</loggers>
</configuration>
这样系统就可以记录操作日志了。
三、Spring Boot集成Mybatis(数据库动态切换)&YML&单元测试
添加POM依赖:
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.1.1</version>
<exclusions>
<exclusion>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-jdbc</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
yml文件配置:
spring:
profiles:
active: dev
#配置监控项
server:
port: 8082
management:
port: 9999
health:
mail:
enabled: false
security:
enabled: false
info:
app:
name: "@project.name@"
description: "@project.description@"
version: "@project.version@"
mybatis:
mapperLocations: classpath:mapper/*.xml
---
# 开发环境配置
spring:
profiles: dev
aop:
multiple:
datasources:
default:
type: com.mysql.jdbc.jdbc2.optional.MysqlXADataSource
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8
username: root
password: root
slave:
type: com.mysql.jdbc.jdbc2.optional.MysqlXADataSource
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/test_slave?useUnicode=true&characterEncoding=UTF-8
username: root
password: root
测试环境配置
spring:
profiles: test
aop:
multiple:
datasources:
default:
type: com.mysql.jdbc.jdbc2.optional.MysqlXADataSource
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8
username: root
password: root
slave:
type: com.mysql.jdbc.jdbc2.optional.MysqlXADataSource
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/test_slave?useUnicode=true&characterEncoding=UTF-8
username: root
password: root
---
生产环境配置
spring:
profiles: prod
aop:
multiple:
datasources:
default:
type: com.mysql.jdbc.jdbc2.optional.MysqlXADataSource
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8
username: root
password: root
slave:
type: com.mysql.jdbc.jdbc2.optional.MysqlXADataSource
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/test_slave?useUnicode=true&characterEncoding=UTF-8
username: root
password: root
PersonDao接口:
@Repository
@Mapper
public interface PersonDao {
@Select("SELECT id, first_name AS firstName, last_name AS lastName, birth_date AS birthDate, sex, phone_no AS phoneNo"
+ " FROM t_person WHERE id=#{0};")
Person getPersonById(int id);
int insertPerson(Person person);
}
PersonDaoMapper.xml文件:
<insert id="insertPerson">
INSERT INTO t_person(first_name,last_name,birth_date,sex,phone_no,update_dt)
VALUES(#{firstName},#{lastName},#{birthDate},#{sex},#{phoneNo},NOW())
</insert>
Spring框架提供了AbstractRoutingDataSource类来实现数据库动态路由,具体编码如下。
MultipleDataSource.java
public class MultipleDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return MultipleDataSourceContextHolder.getDataSourceName();
}
}
MultipleDataSourceContextHolder.java
private static final ThreadLocal contextHolder = new ThreadLocal(); //每个请求单独一个数据库持有线程
public static List dataSourceNames = new ArrayList<>(10);
public static void setDataSourceName(String dataSourceName) {
contextHolder.set(dataSourceName);
}
public static String getDataSourceName() {
return contextHolder.get();
}
public static void resetDataSourceName() {
contextHolder.remove();
}
public static boolean containsDataSource(String dataSourceName) {
return dataSourceNames.contains(dataSourceName);
}
MultipleDataSourceConfiguration.java
@Configuration
@ConfigurationProperties(prefix = "aop.multiple") //Spring Boot会自动从yml文件里解析配置
public class MultipleDataSourceConfiguration {
private Logger logger = LoggerFactory.getLogger(MultipleDataSourceConfiguration.class);
//数据源配置,从yml文件中加载
private Map> datasources;
@Bean
public DataSource dataSource() {
Object defaultDataSource = null;
Map targetDataSources = new HashMap();
for (String name : datasources.keySet()) {
try {
Map config = datasources.get(name);
AtomikosDataSourceBean datasource = new AtomikosDataSourceBean();
datasource.setUniqueResourceName(name);
datasource.setXaDataSourceClassName(config.get("type"));
Properties properties = new Properties();
properties.put("URL", config.get("url"));
properties.put("user", config.get("username"));
properties.put("password", config.get("password"));
datasource.setXaProperties(properties);
if (name.equals("default")) {
defaultDataSource = datasource; //默认为主数据库
} else {
targetDataSources.put(name, datasource);
MultipleDataSourceContextHolder.dataSourceNames.add(name);
}
} catch (Exception e) {
logger.error("please check datasources config, ex={}", ExceptionUtils.getStackTrace(e));
System.exit(1);
}
}
MultipleDataSource multipleDataSource = new MultipleDataSource();
multipleDataSource.setTargetDataSources(targetDataSources);
multipleDataSource.setDefaultTargetDataSource(defaultDataSource);
return multipleDataSource;
}
}
TargetDataSource
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TargetDataSource {
String name();
}
MultipleDataSourceAspect.java
@Aspect
@Component
public class MultipleDataSourceAspect {
private Logger logger = LoggerFactory.getLogger(MultipleDataSourceAspect.class);
@Around("@annotation(com.netease.mail.springboot.annotation.TargetDataSource) && @annotation(targetDataSource)")
public Object processed(ProceedingJoinPoint point, TargetDataSource targetDataSource) throws Throwable {
try {
logger.info("method={}, choose={} database", getMethodName(point), targetDataSource.name());
String dataSourceName = targetDataSource.name();
if (MultipleDataSourceContextHolder.containsDataSource(dataSourceName)) {
MultipleDataSourceContextHolder.setDataSourceName(dataSourceName);
}
return point.proceed();
} finally {
MultipleDataSourceContextHolder.resetDataSourceName();
}
}
private String getMethodName(ProceedingJoinPoint point) throws NoSuchMethodException{
Signature sig = point.getSignature();
MethodSignature msig = (MethodSignature) sig;
Object target = point.getTarget();
Method currentMethod = target.getClass().getMethod(msig.getName(), msig.getParameterTypes());
return currentMethod.getName();
}
通过对数据库操作方法进行注解&AOP拦截实现数据库动态切换。
PersonService.java
@Service
public class PersonService {
protected final Logger logger = LoggerFactory.getLogger(this.getClass());
@Autowired
private PersonDao personDao;
@Cacheable(value="getPersonById", sync=true)
@TargetDataSource(name = "slave") //查询请求走Slave
public Person getPersonById(int id){
logger.info("getting data from database, personId={}", id);
return personDao.getPersonById(id);
}
@Transactional //如果不添加注解,则不会被AOP拦截,这时候会默认走主库
public int insertPerson(Person person){
return personDao.insertPerson(person);
}
}
可以通过单元测试验证数据库结果。
四、Spring Boot集成JTA事物
如上,配置了多个数据源后,如果一个service方法存在多个数据源,使用jdbc事物不能实现整个方法的回滚操作。这时候需要使用另外一个一种事物管理框架JTA事物。它可以跨数据库来保证数据有效性。当然Spring Boot集成它也比较方便。
DataSourceTransactionManager.java
@Configuration
@ComponentScan
@EnableTransactionManagement //自动开启事物管理
public class DataSourceTransactionManager {
/**
* 自定义事务
* MyBatis自动参与到Spring事务管理中,无需额外配置,只要org.mybatis.spring.SqlSessionFactoryBean引用的数据源与DataSourceTransactionManager引用的数据源一致即可,否则事务管理会不起作用。
* @return
*/
@Bean(name = "userTransaction")
public UserTransaction userTransaction() throws Throwable {
UserTransactionImp userTransactionImp = new UserTransactionImp();
userTransactionImp.setTransactionTimeout(10000);
return userTransactionImp;
}
@Bean(name="userTransactionManager", destroyMethod = "close", initMethod = "init")
public UserTransactionManager userTransactionManager() {
UserTransactionManager userTransactionManager = new UserTransactionManager();
userTransactionManager.setForceShutdown(false);
return userTransactionManager;
}
@Bean(name = "transactionManager")
@DependsOn({ "userTransaction", "userTransactionManager" })
public JtaTransactionManager transactionManager() throws Throwable {
UserTransaction userTransaction = userTransaction();
JtaTransactionManager manager = new JtaTransactionManager(userTransaction,userTransactionManager());
return manager;
}
}
开发单元测试验证下:
MultiDataBaseService.java
protected final Logger logger = LoggerFactory.getLogger(this.getClass());
@Autowired
private PersonService personService;
@Transactional
public void handleInMultiDataBase(){
Person person = new Person();
person.setBirthDate(new Date());
person.setFirstName("tian");
person.setLastName("dd");
person.setPhoneNo("222");
person.setSex('F');
personService.insertPerson(person);
Person p = personService.getPersonById(1);
logger.info("handleInMultiDataBase p={}", JSON.toJSONString(p));
throw new RuntimeException("测试多数据源数据处理异常事物回滚");
}
PersonServiceTest.java
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class PersonServiceTest{
@Autowired
MultiDataBaseService multiDataBaseService;
@Test
public void testInsertPerson(){
Person person = new Person();
person.setBirthDate(new Date());
person.setFirstName("tian");
person.setLastName("dd");
person.setPhoneNo("111");
person.setSex('F');
personService.insertPerson(person);
}
}
通过测试方法发现本条记录并没有插入到主数据库中,说明JTA事物配置成功。
五、Spring Boot监控搭建&编译&部署
添加POM
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
Spring Boot会自动监控系统属性。当然我们也可以自定义一些监控项目。
HealthCheck.java
@Component
public class HealthCheck implements HealthIndicator{
@Override
public Health health() {
//自定义监控项目,实际情况扩充...
return Health.up().withDetail("health_check", true).build();
}
}
编译&部署使用如下命令即可:
mvn clean package -Dskiptests
java -jar target/spring-boot.jar --spring.profiles.active=test
#根据系统环境有dev/test/prod
服务启动成功后,就可以直接访问接口了。如Controller层代码如下:
PersonController.java
@RestController
@EnableAutoConfiguration
public class PersonController {
@Autowired
private PersonService personService;
@RequestMapping("/person/getPersonById")
@ResponseBody
public JsonResult getPersonById(@RequestParam("id") Integer id){
return new JsonResult(personService.getPersonById(id));
}
}
服务配置如下:
#配置监控项
server:
port: 8082
management:
port: 9999
health:
mail:
enabled: false
security:
enabled: false
info:
app:
name: "@project.name@"
description: "@project.description@"
version: "@project.version@"
curl ‘http://localhost:8082/person/getPersonById?id=11’
{
code: "200",
message: "成功",
data: {
id: 11,
firstName: "tian",
lastName: "dd_slave",
birthDate: 1510243200000,
sex: "F",
phoneNo: "111"
}
}
curl ‘http://localhost:9999/health’
{
status: "UP",
healthCheck: {
status: "UP",
health_check: true
},
diskSpace: {
status: "UP",
total: 120108089344,
free: 56391913472,
threshold: 10485760
},
db: {
status: "UNKNOWN"
}
}
可以看到系统自定义的监控检查项也返回了。当然Spring Boot健康检查项还有很多API接口,如:info,beans,error等
Spring Boot集成Spring Boot Admin
一、创建服务端
新建立Spring Boot工程,添加如下POM:
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-server</artifactId>
<version>1.5.2</version>
</dependency>
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-server-ui</artifactId>
<version>1.5.2</version>
</dependency>
配置文件:
spring:
application:
name: spring-boot-admin
boot:
admin:
context-path: /sba # 配置访问路径为:http://localhost:8888/spring-boot-admin/sba
notify:
mail:
to: example@example.com
mail:
host: smtp.example.com
server:
port: 8888
context-path: /spring-boot-admin/ #统一为访问的url加上一个前缀
启动类:
@SpringBootApplication
@EnableScheduling
@EnableAdminServer //一定要加上
public class SpringBootAdminApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootAdminApplication.class, args);
}
}
二、配置客户端
添加POM:
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-starter-client</artifactId>
<version>1.5.2</version>
</dependency>
配置:
spring:
profiles:
active: dev
boot:
admin:
client:
prefer-ip: true
url: http://localhost:8888/spring-boot-admin # 向服务端注册的地址
先后启动服务端和客户端后,访问http://localhost:8888/spring-boot-admin/sba链接可查看到被监控的Spring Boot微服务。
可以通过这个平台监控系统基本属性&方法调用次数,执行时长&日志&请求Trace等等。当然也可以配置发送邮件模块、集群配置等。
参考资料
http://projects.spring.io/spring-boot
http://codecentric.github.io/spring-boot-admin/1.5.2/#register-clients-via-spring-boot-admincr.田躲躲
看到这里的小伙伴,如果你喜欢这篇文章的话,别忘了转发、收藏、留言互动!
如果你想知道更多大厂同学亲授的干货,可以私信我!
还有新整理的海量资料,包含面经分享、模拟试题、视频干货……