赞
踩
目标
这里给自己定一个该框架搭建完成的目标,如下 :
框架要求功能: - 处理http/json 请求 - 日志记录 - 持久化 - 数据源,事务控制 - 定时任务 - 视图模版
搭建环境: - 编译器:idea 2016.2.4 - Maven : maven3.0 - JDK: java7 - 系统: mac OS 10.10.4 - 数据库: mysql5.6
搭建记录
============2018-6-28 =============
一.新建maven应用(不需要web应用)
1.在pom中添加以下:
org.springframework.boot spring-boot-starter-parent 1.4.0.RELEASE org.springframework.boot spring-boot-starter-web
2.新建controller
@Controller@EnableAutoConfigurationpublic class TestBootController { @RequestMapping("/") @ResponseBody String home() { return "hello world"; } public static void main(String[] args) throws Exception { SpringApplication.run(TestBootController.class, args); }}
此时的项目目录结构是这样的
3.启动main函数,可以看到控制台输出,非常简单明了
端口默认是8080,启动log已经写了,访问后如图所示
4.在搭建了基础应用的基础上,想增加service层抽离控制层和业务层代码
//业务层接口public interface TestInterFace { public int testInterFace(); public User testUser();}//业务层接口实现@Servicepublic class TestInterFaceImpl implements TestInterFace { @Override public int testInterFace() { return 0; } @Override public User testUser() { return new User(); }}
修改controller层代码
@Controller@EnableAutoConfigurationpublic class TestBootController { @Autowired private TestInterFace testInterFace; @RequestMapping("/num") @ResponseBody int home() { int i = testInterFace.testInterFace(); return i; } @RequestMapping("/get") @ResponseBody User getUser() { return testInterFace.testUser(); } public static void main(String[] args) throws Exception { SpringApplication.run(TestBootController.class, args); }}
启动后抛异常
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name ‘testBootController’: Unsatisfied dependency expressed through field ‘testInterFace’: No qualifying bean of type [com.kx.springboot.service.TestInterFace] found for dependency [com.kx.springboot.service.TestInterFace]: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [com.kx.springboot.service.TestInterFace] found for dependency [com.kx.springboot.service.TestInterFace]: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
相信大家对这种异常非常常见,注入失败,这时候问题来了,按照传统的方式,对注入进行检查。service层已经加了@service的注解,怎么还是注入失败了呢,想查看配置文件,发现springboot没有配置文件,那么他是怎么扫描注解的呢?
百度了一下才知道,springboot默认按照包顺序注入,所以在创建controller时service还没有注入,springboot不需要传统的xml配置扫描包,只需要添加注解@ComponentScan(basePackages={“com.kx.springboot.service”}),代码如下
@Controller@EnableAutoConfiguration@ComponentScan(basePackages={"com.kx.springboot.service"})//添加的注解public class TestBootController { @Autowired private TestInterFace testInterFace; @RequestMapping("/num") @ResponseBody int home() { int i = testInterFace.testInterFace(); return i; } @RequestMapping("/get") @ResponseBody User getUser() { return testInterFace.testUser(); } public static void main(String[] args) throws Exception { SpringApplication.run(TestBootController.class, args); }}
再次启动,成功,访问http://localhost:8088/get
{“username”:”usernamekx123”,”password”:”password123”}
这时候,又有一个问题,web应用都是多controller,这种启动方式一次只能启动一个controller,那么,怎么才能启动应用后访问多个controller,查阅springboot官方教程后,修改代码如下
增加 UserController
@Controller@RequestMapping("user")public class UserController { @Autowired private TestInterFace testInterFace; @RequestMapping("/get") @ResponseBody User getUser() { return testInterFace.testUser(); }}
修改原来 TestBootController
@Controller@RequestMapping("test")public class TestBootController { @Autowired private TestInterFace testInterFace; @RequestMapping("/num") @ResponseBody int home() { int i = testInterFace.testInterFace(); return i; } @RequestMapping("/get") @ResponseBody User getUser() { return testInterFace.testUser(); }}
在包的最外层增加新的应用启动入口 —> Application
@EnableAutoConfiguration@ComponentScan(basePackages={"com.kx.springboot"})public class Application { public static void main(String[] args) throws Exception { SpringApplication.run(Application.class, args); }}
可以看到,应用启动入口配置了扫描所有包。此时整个项目的结构如图所示
启动应用后访问
http://localhost:8080/test/get
{“username”:”usernamekx123”,”password”:”password123”}
http://localhost:8080/user/get
{“username”:”username寇鑫123”,”password”:”password寇鑫123”}
到此,符合处理http/json 的web框架已经搭建ok了
============2018-6-30 =============
第一步处理http/json已经完成了,现在给我们的框架里加上日志记录的功能
要求: - 日志按天记录,自动生成当天的记录文件 - 日志分级存储(info,error)
Springboot自带日志,所以我们现在直接在SpringBoot中添加日志
修改 TestController
@Controller@RequestMapping("test")public class TestBootController {//增加日志 private final Logger log = LoggerFactory.getLogger(TestBootController.class); @Autowired private TestInterFace testInterFace; @RequestMapping("/num") @ResponseBody int home() { int i = testInterFace.testInterFace(); return i; } @RequestMapping("/get") @ResponseBody User getUser() { //打印日志 log.info("TestBootController getUser info"); return testInterFace.testUser(); }}
只增加了两行代码,现在启动尝试打印日志
访问 http://localhost:8080/test/get
可以看到我们的日志已经打出来了,那么现在又有疑问了,这个日志只是打在控制台了,并没有生成文件,查询后发现默认日志如果要打印生成文件需要添加application.properties,而添加这个只可以打固定名称格式的日志,并不能灵活的配置,所以添加Springboot默认支持的logback作为标准日志输出
添加新的pom依赖
org.springframework.boot spring-boot-starter-logging
在resource下添加logback配置文件logback.xml
首先配置按天生成日志,尝试日志文件打印输出
<?xml version="1.0" encoding="UTF-8"?> %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n ${LOG_HOME}/TestSpringBoot.log.%d{yyyy-MM-dd}.log 30 %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n 10MB
启动应用,尝试输出日志
访问 http://localhost:8080/test/get
好吧,首先发现我们的日志不是彩色的了,哭~,不过还好日志打出来了,那么看看我们的文件有没有生成
//生成文件-rw-r--r-- 1 kx staff 5117 3 30 22:19 TestSpringBoot.log.2017-03-30.log//info 日志打印效果2017-03-30 22:21:03.341 [http-nio-8080-exec-3] INFO com.kx.springboot.controller.TestBootController - TestBootController getUser info
OK,文件也完美的按照我们指定的路径生成了,并且文件命名方式按照配置的日期命名,日志打印的内容也按照配置的内容正确的打印了,现在配置info日志和error日志,区分info和error打出来的日志文件
修改 logback.log 增加新的error appender 修改原来的appender 为 info
<?xml version="1.0" encoding="UTF-8"?> %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n ${LOG_HOME}/TestSpringBoot_info.log.%d{yyyy-MM-dd}.log 30 error ACCEPT DENY %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n 10MB ${LOG_HOME}/TestSpringBoot_error.log.%d{yyyy-MM-dd}.log 30 error ACCEPT DENY %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n 10MB
启动应用后发现已经新增了两个日志文件,命名完全按照配置
-rw-r--r-- 1 kx staff 0 3 30 22:19 TestSpringBoot_error.log.2017-03-30.log-rw-r--r-- 1 kx staff 5117 3 30 22:19 TestSpringBoot_info.log.2017-03-30.log
访问 http://localhost:8080/test/get
//info 日志打印效果2017-03-30 22:21:03.341 [http-nio-8080-exec-3] INFO com.kx.springboot.controller.TestBootController - TestBootController getUser info//error 日志打印效果2017-03-30 22:21:03.342 [http-nio-8080-exec-3] ERROR com.kx.springboot.controller.TestBootController - TestBootController getUser error
我们发现error日志和info日志已经按照我们的要求已经打印到对应的文件中了,那么现在我们想解决一下控制台彩色输出的模块,刚好百度到了这里的代码(珍藏)
${CONSOLE_LOG_PATTERN} utf8
看一下我们现在的logback日志总文件
<?xml version="1.0" encoding="UTF-8"?> ${CONSOLE_LOG_PATTERN} utf8 ${LOG_HOME}/TestSpringBoot_info.log.%d{yyyy-MM-dd}.log 30 info ACCEPT DENY %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n 10MB ${LOG_HOME}/TestSpringBoot_error.log.%d{yyyy-MM-dd}.log 30 error ACCEPT DENY %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n 10MB
再次运行项目,访问 http://localhost:8080/test/get
哈哈哈,我们的彩色日志又回来了,而且结构清晰,调试代码的时候简直神器啊
============2018-7-2 =============
============整合Mybatis============
在众多orm框架中,我对mybatis最熟悉,所以我采用mybatis进行整合,对我们的orm框架,这里我们也提出几点要求:
支持分页
curd接口抽象处理
事务控制
多数据源
在查阅了一些资料后,找到目前为止最简单的一种整合mybatis方式,这里贴出原始博客地址
http://blog.didispace.com/springbootmybatis/
参照大神的整合,我们的整合异常的顺利,贴出我们增加的代码
在pom中添加以下依赖
org.mybatis.spring.boot mybatis-spring-boot-starter 1.1.1 mysql mysql-connector-java 5.1.21
在application.properties 中增加以下配置
spring.datasource.url=jdbc:mysql://localhost:3306/testspring.datasource.username=rootspring.datasource.password=123456spring.datasource.driver-class-name=com.mysql.jdbc.Driver
表结构
Create Table: CREATE TABLE `userinfo` ( `id` int(20) NOT NULL AUTO_INCREMENT, `username` varchar(20) DEFAULT NULL, `password` varchar(20) DEFAULT NULL, PRIMARY KEY (`id`)) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8
编写dao层代码,新建UserDao 接口
@Mapperpublic interface UserDao { @Select("SELECT * FROM USERINFO WHERE username = #{username}") UserInfo findByName(@Param("username") String username); @Insert("INSERT INTO USERINFO(username, password) VALUES(#{username}, #{password})") int insert(@Param("username") String name, @Param("password") String password);}
在业务层增加对dao层接口的引用,修改TestInterFace,TestInterFaceImpl
public interface TestInterFace { public int testInterFace(); public UserInfo testUser(); public int insertUser(String username,String password);//新增的接口}@Servicepublic class TestInterFaceImpl implements TestInterFace { //引入dao层接口 @Autowired UserDao userDao; @Override public int testInterFace() { return 0; } @Override public UserInfo testUser() { return new UserInfo(); } //新增的接口实现 @Override public int insertUser(String username,String password) { return userDao.insert(username,password); }}
接下来修改我们的controller,提供对外的接口,修改UserController
@Controller@RequestMapping("user")public class UserController { @Autowired private TestInterFace testInterFace; @RequestMapping("/get") @ResponseBody UserInfo getUser() { return testInterFace.testUser(); } //增加新的对外访问接口 @RequestMapping("/add") @ResponseBody String add() { testInterFace.insertUser("username123寇鑫","password123寇鑫"); return "插入成功"; }}
来测试一下我们的新接口,访问 http://localhost:8080/user/add
看到浏览器已经正常返回了,接下来去数据库看看数据有没有实际插入
+----+-------------------+-------------------+| id | username | password |+----+-------------------+-------------------+| 1 | username123 | password123 |+----+-------------------+-------------------+
可以看到,在我们的表中,已经插入了一条数据,并且中文显示正常。但现在每次新加一个接口,都要对应的写一条sql,这样很麻烦,而且不利于开发,业务方不能专注于业务的开发,所以我们要抽象出来通用的curd接口,并且支付分页。
============2018-7-3=============
mybatis有很多成熟的分页插件以及通用接口插件,这里我们也采用目前较为成熟的方案,不必重复造轮子。
添加pom
com.github.pagehelper pagehelper 4.2.1 tk.mybatis mapper 3.3.9
因为springboot 不需要设置xml,所以这里都采用注解和代码的形式注入插件
新建配置类
package com.kx.springboot.dao.mybatis;import com.github.pagehelper.PageHelper;import org.apache.ibatis.plugin.Interceptor;import org.apache.ibatis.session.SqlSessionFactory;import org.mybatis.spring.SqlSessionFactoryBean;import org.springframework.context.annotation.Bean;import org.springframework.core.io.support.PathMatchingResourcePatternResolver;import org.springframework.core.io.support.ResourcePatternResolver;import java.util.Properties;/*** Created by kx on 17/4/2.*/public class MyBatisConfig { @Bean(name = "sqlSessionFactory") public SqlSessionFactory sqlSessionFactoryBean() { SqlSessionFactoryBean bean = new SqlSessionFactoryBean();// bean.setDataSource(dataSource()); bean.setTypeAliasesPackage("com.kx.springboot.bean"); //分页插件设置 PageHelper pageHelper = new PageHelper(); Properties properties = new Properties(); properties.setProperty("reasonable", "true"); properties.setProperty("supportMethodsArguments", "true"); properties.setProperty("returnPageInfo", "check"); properties.setProperty("params", "count=countSql"); pageHelper.setProperties(properties); //添加分页插件 bean.setPlugins(new Interceptor[]{pageHelper}); ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); try { //基于注解扫描Mapper,不需配置xml路径 //bean.setMapperLocations(resolver.getResources("classpath:mapper/*.xml")); return bean.getObject(); } catch (Exception e) { e.printStackTrace(); throw new RuntimeException(e); } }}
新建配置扫描类
package com.kx.springboot.dao.mybatis;import org.springframework.boot.autoconfigure.AutoConfigureAfter;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import tk.mybatis.spring.mapper.MapperScannerConfigurer;import java.util.Properties;/*** Created by kx on 17/4/2.*/@Configuration//必须在MyBatisConfig注册后再加载MapperScannerConfigurer,否则会报错@AutoConfigureAfter(MyBatisConfig.class)public class MyBatisMapperScannerConfig { @Bean public MapperScannerConfigurer mapperScannerConfigurer() { MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer(); mapperScannerConfigurer.setSqlSessionFactoryBeanName("sqlSessionFactory"); mapperScannerConfigurer.setBasePackage("com.kx.springboot.dao.mybatis"); //初始化扫描器的相关配置,这里我们要创建一个Mapper的父类 Properties properties = new Properties(); properties.setProperty("mappers", "com.kx.springboot.dao.baseDao.MyMapper"); properties.setProperty("notEmpty", "false"); properties.setProperty("IDENTITY", "MYSQL"); mapperScannerConfigurer.setProperties(properties); return mapperScannerConfigurer; }}
新建对外暴露接口
package com.kx.springboot.dao.baseDao;import tk.mybatis.mapper.common.Mapper;import tk.mybatis.mapper.common.MySqlMapper;/*** Created by kx on 17/4/2.*/public interface MyMapper extends Mapper, MySqlMapper { //TODO //FIXME 特别注意,该接口不能被扫描到,否则会出错}
现在,修改我们的dao层实现类,继承我们的通用接口
@Mapperpublic interface UserDao extends MyMapper {}
在业务层中增加想要调用的方法
public interface TestInterFace { public int testInterFace(); public UserInfo testUser(); public int insertUser(UserInfo userInfo);//新增加的方法 List selectALL();}@Servicepublic class TestInterFaceImpl implements TestInterFace { @Autowired UserDao userDao; @Override public int testInterFace() { return 0; } @Override public UserInfo testUser() { return new UserInfo(); } @Override public int insertUser(UserInfo userInfo) { return userDao.insert(userInfo); } //新增加的实现 @Override public List selectALL(){ return userDao.selectAll(); }}
看到我们在调用userDao时已经有了通用的单表查询方法。将新接口暴露给http,修改controller
@Controller@RequestMapping("user")public class UserController { @Autowired private TestInterFace testInterFace; @RequestMapping("/get") @ResponseBody UserInfo getUser() { return testInterFace.testUser(); } @RequestMapping("/add") @ResponseBody String add() { UserInfo user = new UserInfo(); user.setUsername("username123寇鑫"); user.setPassword("password123寇鑫"); testInterFace.insertUser(user); return "插入成功"; }//新增的接口方法 @RequestMapping("/getall") @ResponseBody List getall() { return testInterFace.selectALL(); }}
访问http://localhost:8080/user/getall
程序抛出异常
### Error querying database. Cause: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Table 'ssmtest.user_info' doesn't exist### The error may exist in com/kx/springboot/dao/UserDao.java (best guess)### The error may involve defaultParameterMap### The error occurred while setting parameters### SQL: SELECT username,password FROM user_info### Cause: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Table 'ssmtest.user_info' doesn't exist; bad SQL grammar []; nested exception is com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Table 'ssmtest.user_info' doesn't exist] with root causecom.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Table 'ssmtest.user_info' doesn't exist at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:57) at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) at java.lang.reflect.Constructor.newInstance(Constructor.java:526)
从异常可以看出来,我们的表和我们查询数据库的字段没有映射起来,查询资料后发现原因是通用插件的问题,默认的字段中有下划线,我们需要手动指定映射
修改userinfo类
package com.kx.springboot.bean;import javax.persistence.Column;import javax.persistence.Table;/*** Created by kx on 17/3/29.*///增加注解声明表名@Table(name = "userinfo")public class UserInfo { //增加注解声明字段名 @Column(name = "username") private String username = "username寇鑫123"; @Column(name = "password") private String password = "password寇鑫123"; public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } @Override public String toString() { return "User{" + "username='" + username + ''' + ", password='" + password + ''' + '}'; }}
再次启动访问 http://loclhost:8080/user/getall
[ { "username":"user123寇鑫", "password":"password123寇鑫" }, { "username":"username123寇鑫", "password":"password123寇鑫" }]
得到如下结果,到此已经成功整合了通用mapper插件,现在加入定时任务测试 springboot 定时任务的启动和配置要简单很多,只需要增加一个注解即可
修改Application.java 启动类
@EnableAutoConfiguration@ComponentScan(basePackages={"com.kx.springboot"})@EnableScheduling//增加支持定时任务的注解public class Application { public static void main(String[] args) throws Exception { SpringApplication.run(Application.class, args); }}
新建定时任务类
@Componentpublic class SchedulingTest { private final Logger logger = LoggerFactory.getLogger(getClass()); @Scheduled(cron = "0/5 * * * * ?") // 每5秒执行一次 public void scheduler() { logger.info(">>>>>>>>>>>>> scheduled test... "); }}
启动测试
2017-04-05 00:19:00.002 INFO 6512 --- [pool-1-thread-1] c.kx.springboot.schedul.SchedulingTest : >>>>>>>>>>>>> scheduled test... 2017-04-05 00:19:05.000 INFO 6512 --- [pool-1-thread-1] c.kx.springboot.schedul.SchedulingTest : >>>>>>>>>>>>> scheduled test... 2017-04-05 00:19:10.000 INFO 6512 --- [pool-1-thread-1] c.kx.springboot.schedul.SchedulingTest : >>>>>>>>>>>>> scheduled test... 2017-04-05 00:19:15.000 INFO 6512 --- [pool-1-thread-1] c.kx.springboot.schedul.SchedulingTest : >>>>>>>>>>>>> scheduled test... 2017-04-05 00:19:20.001 INFO 6512 --- [pool-1-thread-1] c.kx.springboot.schedul.SchedulingTest : >>>>>>>>>>>>> scheduled test... 2017-04-05 00:19:25.002 INFO 6512 --- [pool-1-thread-1] c.kx.springboot.schedul.SchedulingTest : >>>>>>>>>>>>> scheduled test... 2017-04-05 00:19:30.003 INFO 6512 --- [pool-1-thread-1] c.kx.springboot.schedul.SchedulingTest : >>>>>>>>>>>>> scheduled test... 2017-04-05 00:19:35.003 INFO 6512 --- [pool-1-thread-1] c.kx.springboot.schedul.SchedulingTest : >>>>>>>>>>>>> scheduled test...
可以看到我们的定时任务执行结果没有问题,接下来要在框架中增加多数据源以及事务控制。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。