都说 springboot 是新形势的主流框架工具, 然而我的工作中并没有真正用到 springboot;
都说 springboot 里面并没有什么新技术, 不过是组合了现有的组件而已, 但是自己却说不出来;
都说 springboot 让开发更简单, 然而对于刚转换过来使用的时候总会发现各种不适应;
网上查过许多的教程, 下过 demo 来玩, 却无法用于实战, 着实可惜.
最近有个项目终于用 springboot 来开发了, 一切从 0 开始, 刚好可以练练手. 来谈谈几点经验吧!(注: 本文非教程, 请当闲聊谈资)
1. 入门?
springboot 的入门 demo 在 spring 官网可以直接下载, 可以使用 maven 开发, https://start.spring.io/ 下载下来, 运行 main() 方法就可以启动服务了.
简单的 helloworld 就 ok 了, 再也不用复杂的搭建过程了.
不过, 有空的话还是有必要看一下完整点的入门 demo 教程: https://spring.io/guides/gs/rest-service/
2. 如何接入各常用组件及配置?
这个需求是很强烈的, 一个空白的框架是没有啥用的, 因为我们必定要基于: 数据库, 缓存, zk,mq, 日志, mongo, 页面模板等等...
所以, 如何配置?
三个步骤:
1. 引入组件依赖 dependency;
2. 在 Bootstrap-xx.properties 文件中加入配置属性;
3. 在配置 java 文件中, new 出相应实例或框架自己初始化实例以备用;
就单是这点来说, 其实 springboot 和 spring 的 xml 配置方式步骤是一样一样的, 三步式导入. 不过显然 java 代码写得更复杂和难找, xml 更直观!(这里先忽略 dependecy 依赖的个数对比)
3. 如何做到加载动态配置?
在使用 xml 配置的方式时, 我们可以使用 spring 的 org.springframework.beans.factory.config.PropertyPlaceholderConfigurer
组件, 去加载一个配置中心的值, 从而实现替换各种连接的作用, 使其脱离代码的硬编码;
- <!-- spring 的属性加载器, 加载 properties 文件中的属性 -->
- <bean class="com.xx.zk.property.PropertyPlaceholderConfigurer">
- <property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_OVERRIDE" />
- <property name="ignoreResourceNotFound" value="true" />
- <property name="locations">
- <list>
- <value>classpath*:/spring/conf.properties</value>
- </list>
- </property>
- </bean>
那么, 在 springboot 中是怎么做的呢? springboot 提供了多种配置文件共存的方式, 比如: Bootstrap-prod.properties, Bootstrap-dev.properties, 用于区分测试环境和生产环境的配置而不互相影响;
其大致原理为, 环境准备好时, 会触发监听器, 然后加载相应配置文件:
- // - org.springframework.boot.context.event.EventPublishingRunListener
- @Override
- public void contextLoaded(ConfigurableApplicationContext context) {
- for (ApplicationListener<?> listener : this.application.getListeners()) {
- if (listener instanceof ApplicationContextAware) {
- ((ApplicationContextAware) listener).setApplicationContext(context);
- }
- context.addApplicationListener(listener);
- }
- // 加载 Bootstrap.properties, Bootstrap-dev.properties...
- // 在 ConfigFileApplicationListener 的 ApplicationPreparedEvent 事件中触发
- this.initialMulticaster.multicastEvent(
- new ApplicationPreparedEvent(this.application, this.args, context));
- }
如果要使用配置中心, 可以直接使用 spring-cloud-config 组件, 配置即可;
4. 如何注册 beans ?
1. 和 spring 一样, 直接使用 @Service, @Controller, @Component... 注解直接注册简单的 bean;
2. 对于一些复合 bean 组件, 需要单独配置, 如数据库连接:
如 spring 中 druid 连接池的 xml 配置是这样的:
- <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
- destroy-method="close">
- <property name="url" value="${jdbc.url}" />
- <property name="driverClassName" value="${jdbc.driver}" />
- <property name="maxActive" value="${pool.maxPoolSize}" />
- <property name="username" value="${jdbc.username}" />
- <property name="password" value="${jdbc.password}" />
- <property name="removeAbandoned" value="true" />
- <property name="removeAbandonedTimeout" value="${pool.removeAbandonedTimeout}" />
- <property name="maxWait" value="${pool.maxWait}" />
- <property name="timeBetweenEvictionRunsMillis" value="${pool.timeBetweenEvictionRunsMillis}" />
- <property name="minEvictableIdleTimeMillis" value="${pool.minEvictableIdleTimeMillis}" />
- <property name="validationQuery" value="${pool.validationQuery}" />
- <property name="testWhileIdle" value="true" />
- <property name="testOnBorrow" value="false" />
- <property name="testOnReturn" value="false" />
- </bean>
而在 springboot 中, 则是使用 java 代码直接创建:
- @Bean(name = "druidDataSource")
- public DruidDataSource druidDataSource(){
- DruidDataSource ds = new DruidDataSource();
- ds.setUrl(config.getJdbcUrl());
- ds.setDriverClassName(config.getDriverName());
- ds.setMaxActive(config.getMaxPoolSize());
- ds.setUsername(config.getJdbcUserName());
- ds.setPassword(config.getJdbcPwd());
- ds.setRemoveAbandoned(true);
- ds.setMaxWait(config.getJdbcMaxWait());
- ds.setTimeBetweenEvictionRunsMillis(config.getTimeBetweenEvictionRunsMillis());
- ds.setMinEvictableIdleTimeMillis(config.getMinEvictableIdleTimeMillis());
- ds.setValidationQuery(config.getValidationQuery());
- ds.setTestWhileIdle(true);
- ds.setTestOnBorrow(false);
- ds.setTestOnReturn(false);
- return ds;
- }
3. 还有一种特殊的加载方式, 值得注意, 就是使用了 @Bean 注解, 但是其直接 new 了一对象返回:
- @Bean(name = "directHelloService")
- public HelloService directHelloService(){
- HelloService service = new HelloService();
- return service;
- }
这个有什么问题呢? 因为我们的 service 一般都会依赖于其他的服务, 所以, 往往都会有依赖注入的过程, 但是你使用了一个 new 创建, 则没有了依赖注入问题了. 因此, 当你想直接使用这个服务的时候, 很可能就会拿到一些空对象;
那怎么办? 三个办法:
1. 没事就不要直接 new 有依赖的对象了;
2. 如果实在要 new, 需要在 new 的对象上添加注解 @DependsOn 注解标明需要依赖的组件, 这样, 在使用的时候就会再次去检测依赖, 从而完成依赖注入了;
3. 自己手动完成依赖注入;
4. 将加载动作委托给 springContext, 比如使用 getBean("xxx") 的方式获取;(没有试验过)
单从这一点来点, 想完全摆脱 xml 束缚的 springboot, 还是显得有些力不从心! 另外, 使用 java 配置文件的另一个不好的地方是, 配置文件散落在各处, 很不直观!
5. 日志如何记录?
日志是必备工具. 所以 springboot 默认集成了 logback 的日志组件, 所以, 我们要做的只是, 配置好打印属性就好了; 如在 Bootstrap.properties 文件中添加如下:
logging.config=classpath:logback.xml
意思就是说, 你将配置写入到 resources/logback.xml 中, 其中的配置规则同理自不必细说;
6. 如何自定义 RequestMappingHandlerMapping ?
做一个 web 应用时, 对 webmvc 的定制化配置是一定的. 因为我们的包路径查找, 可能会有自己一些特定的规则, 所以需要自定义 RequestMappingHandlerMapping. 这在 spring 中, 则只需要注册一个 requestMappingBean 就可以了 (要先排除系统自动扫描 @Controller 注解), 如:
- <bean name='requestMappingHandlerMapping'
- class='com.xxx.cust.URLRequestMappingHandlerMapping'>
- <property name="interceptors">
- <list>
- <!-- 添加会话拦截器 -->
- <bean class="com.xxx.interceptor.SessionInterceptor">
- </bean>
- </list>
- </property>
- </bean>
而在 springboot 中, 好像就不是那么回事了, 它变成是这样的, 先继承一个 WebMvcConfigurationSupport 的基础组件, 然后自定义各种配置如:
- @Configuration
- public class SpringMVCConfig extends WebMvcConfigurationSupport {
- @Resource
- private SessionInterceptor sessionInterceptor;
- @Override
- public void addInterceptors(InterceptorRegistry registry) {
- registry.addInterceptor(sessionInterceptor)
- .excludePathPatterns(
- "/error");
- }
- /**
- * 添加自定义的 Converters 和 Formatters.
- */
- @Override
- public void addFormatters(FormatterRegistry registry) {
- registry.addConverter(new StringToDateConverter("yyyy-MM-dd HH:mm:ss"));
- }
- /**
- * Protected method for plugging in a custom subclass of
- * {@link RequestMappingHandlerMapping}.
- * @since 4.0
- */
- protected RequestMappingHandlerMapping createRequestMappingHandlerMapping() {
- return new RequestMappingHandlerMapping();
- }
- /**
- * Protected method for plugging in a custom subclass of
- * {@link RequestMappingHandlerAdapter}.
- * @since 4.3
- */
- protected RequestMappingHandlerAdapter createRequestMappingHandlerAdapter() {
- return new RequestMappingHandlerAdapter();
- }
- }
如上配置的依据是, 在其父类 WebMvcConfigurationSupport 中, 会创建一个 requestMappingHandlerMapping 的 bean, 而创建的过程, 将方法暴露给了一个可供继承覆写的 createRequestMappingHandlerMapping 的方法, 从而达到自定义 RequestMappingHandlerMapping 的目的. 同理于 RequestMappingHandlerAdapter .
而对于其他的各种自定义组件的接入, 则按照文档说明来即可. 对于一些通用的组件, 一般都会有 xxx-starter 提供, 从而可以避免 n 多的依赖配置, 这也是 springboot 的一重要开发优势吧. 毕竟, spring 里面, 你需要知道的太多了!
综上, 咱们就可以规规矩矩地写业务代码了. 总体的步骤就是: 写配置变量到 properties 文件, 使用 @Configuration 读取配置; 实例化 bean 以供使用;
至于 xml 和 properties 的习惯问题, 咱们就先不说了.
7. 最后, 还有一个关键问题, 打包部署?
springboot 往往是直接启动一个 main() 方法来运行的, 和 基于 Web 容器的应用是不一样的.
在 spring 中, 我们一般是通过 maven 打一个 war 包, 然后部署到 tomcat 中. 而在 springboot 中, 则不一定要这么干了 (甚至是不建议这么干), 所以需要打一个 jar 包.
打 jar 包部署有两个问题:
1. jar 包中的其他第三方依赖怎么办?
2. 部署维护交给谁?
一, 针对第三方的 jar 包依赖问题, 我们可以有两个解决方法: 1. 将第三方的 jar 包打包进项目的 jar 包中; 2. 将依赖的 jar 包放抽离出来放到一个独立的 lib 库文件夹中, 启动应用时再指定加载位置; 各有优劣, 一个是会导致 jar 包体积变大, 一个是会导致开发维护困难 (这是个大问题). 当然我们应该会选将其打包到一个 jar 中, 一点体积是不会难倒我们的. 3. 其实我觉得还有一种打包方式, 就是将所有可能用到的 class 文件, 全部解压出来打包到最终的 jar 包中, 这样既做到体积小, 又做到代码维护容易, 但是可能会有些难度, 因为你很难确定哪些 class 文件是不用的, 所以一般也不敢排除 (白干了);
打 jar 包的依赖, 可以参照如下插件配置:
- <build>
- <resources>
- <resource>
- <directory>src/main/java</directory>
- </resource>
- <resource>
- <directory>src/main/resources</directory>
- </resource>
- </resources>
- <plugins>
- <plugin>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-maven-plugin</artifactId>
- </plugin>
- <plugin>
- <groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-jar-plugin</artifactId>
- <configuration>
- <archive>
- <manifest>
- <mainClass>com.xxx.service.StartApplication</mainClass>
- <addClasspath>true</addClasspath>
- <classpathPrefix>lib/</classpathPrefix>
- </manifest>
- </archive>
- <classesDirectory>
- </classesDirectory>
- </configuration>
- </plugin>
- </plugins>
- </build>
注意: 错误的配置可能导致依赖包嵌入有问题, 或者切换环境不成功!
二, 针对部署维护的问题, 则依赖于你想运行的环境, 如果你想使用原来的 tomcat 这种 Web 容器运行服务, 则无需另外担心维护问题, 因为 tomcat 已经有了这些设备. 而如果你使用 jar 包运行, 则需要自行编写维护脚本了, 其实功能也不外乎几个:
1. 启动;
2. 停止;
3. 查看状态;
4. springboot 需要的功能, 就是支持动态修改配置属性, 从而使测试环境与生产环境隔离;
- #!/bin/sh
- ## project info
- SERVICE_DIR=/www/xxx
- SERVICE_NAME=myproject-1.0.0-SNAPSHOT
- SPRING_PROFILES_ACTIVE=prod
- ## java env
- JAVA_HOME=/usr/java/jdk1.8.0_101
- pidfile="/opt/springboot/xxx.pid"
- JAVA_OPTS="$JAVA_OPTS -server -Xms512m -Xmx2048m -Dfile.encoding=UTF-8 -Xloggc:/opt/springboot/logs/xxx_gc.log -XX:+PrintGCDetails -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/opt/springboot/logs/"
- case "$1" in
- start)
- pid=`ps -ef | grep -w "${SERVICE_NAME}" | grep -w "java"| grep -v "grep" | awk '{print $2}'`
- if [ "${pid}" = "" ]; then
- if [ "$2" != "" ]; then
- SPRING_PROFILES_ACTIVE=$2
- fi
- echo "- Starting ${SERVICE_NAME} ..."
- echo "- Using JAVA_HOME: $JAVA_HOME ..."
- echo "- Using Environment: spring.profiles.active=${SPRING_PROFILES_ACTIVE}"
- exec nohup ${JAVA_HOME}/bin/java ${JAVA_OPTS} -jar ${SERVICE_DIR}/${SERVICE_NAME}\.jar --spring.profiles.active=${SPRING_PROFILES_ACTIVE}>/dev/null 2>&1 &
- echo "$!"> ${pidfile};
- echo "- Congraduations!!! Started project [${SERVICE_DIR}/${SERVICE_NAME}.jar] success, pid=$! ."
- else
- echo "- Oops!!! ${SERVICE_NAME} is alreaddy started @pid=${pid}, kill it ?"
- fi
- ;;
- stop)
- pid=`ps -ef | grep -w "${SERVICE_NAME}" | grep -w "java" | grep -v "grep" | awk '{print $2}'`
- rm -rf ${pidfile};
- if [ "${pid}" = "" ]; then
- echo "- ${SERVICE_NAME} is Already stopped."
- else
- echo "- Stopping ${SERVICE_NAME} by kill -15 ${pid} ...";
- kill -15 ${pid}
- sleep 1
- pid2=`ps -ef | grep -w "${SERVICE_NAME}" | grep -w "java" | grep -v "grep" | awk '{print $2}'`
- if [ "${pid2}" = "" ]; then
- echo "- ${SERVICE_NAME} stopped success !!!"
- else
- kill -9 ${pid2}
- echo "- Stop Failed! ${SERVICE_NAME} stop error, force kill ${pid2} !!!"
- fi
- fi
- ;;
- restart)
- $0 stop
- sleep 1
- $0 start $2
- ;;
- status)
- pid=`ps -ef | grep -w "${SERVICE_NAME}" | grep -w "java" | grep -v "grep" | awk '{print $2}'`;
- if [ "${pid}" = "" ]; then
- echo "- Oops!!! ${SERVICE_NAME} is Already stopped."
- else
- # echo -e "- ${SERVICE_NAME} is ruuning, \033[36m pid=${pid} \033[0m .";
- echo -e "- ${SERVICE_NAME} is ruuning, pid=${pid} .";
- echo -e "- ${SERVICE_NAME}'s server port is: `netstat -tunlp | grep "${pid}/" | awk '{print $1" "$4;}'`.";
- echo -e "- ${SERVICE_NAME} up info:`ps -eo pid,lstart,etime,cmd | grep ${SERVICE_NAME} | grep -v"grep"| awk'{print "startTime:"$2""$3" "$4" "$5" "$6", uptime:"$7}'`.";
- echo -e "- Current MEMORY Usage: `free -h | grep"Mem:"| awk'{print "total:"$2", used:"$3".";}'`.";
- echo -e "- Current CPU Usage: `top -bn 1 -i -c | sed -n'3p'`";
- fi
- ;;
- *)
- echo "- Wrong command!!! Usage: $0 [start|stop|restart|status] [dev|test|prod]"
- ;;
- esac
运行方式如下:
springboot_xxx [start|stop|restart|status] [dev|test|prod]
以上, 就是一些关于 springboot 使用的一些实践历程, 对比 spring 和 springboot 的差异, 总体来说, 思路并没有变化, 基本上只是习惯上的变化.
来源: https://www.cnblogs.com/yougewe/p/10263029.html