第一次接触spring,之前从来没有学过spring,所以算是赶鸭子上架,花了差不多一个星期来搭建,中间遇到各种各样的问题,一度觉得这个框架搭建非常麻烦,没有一点技术含量,纯粹就是配置,很低级!但随着搭建的完成,有一点点体会:框架可以让我们的代码更加像一个项目,而不是一个普普通通的作业,这在之前我们学生时代往往不会注意到这一点。我觉得这就是专业和业余的区别。当然,目前,我连spring入门可能都算不上,只是为了完成任务来搭建这套框架,但还是很有收获的,所以记录下这篇博客,给过来人参考。
另外还有一个重要原因是,网上的框架搭建如spring+mybatis的博客,老实说,有很多明显有错误!最终竟然还能正确运行出结果,简直匪夷所思,还有的,缺少文件或者说明,会让刚学习spring的人摸不着头脑,尤其是你兴致勃勃地参考一篇博客,辛苦地搭建了一大半,结果到最后发现,有一个文件没有提供,有一个函数没有提供,有一个类没有提供。。。总之你无法继续下去,内心一定会非常奔溃!所以,我决定写这篇博客!
关于spring的介绍,我就不多说了,我也是门外汉~我就说一下,我为什么要使用这几个框架,spring自然不必多说,mybatis是对数据库的封装,用来操作数据库,mina是对网络通信(如socket)的封装,logback是关于日志的框架。我要实现的是一个短信平台的验证系统,需要操作数据库,需要通过向第三方服务器发送短信,这几个功能可以由这些框架一一实现。
参考博客:
http://chenjc-it.iteye.com/blog/1402939
http://www.cnblogs.com/nick-huang/p/3838326.html
mybatis
mybatis和spring整合需要的包有mybatis、mybatis-spring、数据库驱动包(oracle是ojdbcXXX.jar或者mysql,根据用到的数据库配置)和数据库连接池dbcp包。如果对数据库连接池不太清楚的,可以上网搜索。关于包的版本,我用的跟网上的一些也都不一样,所以大家也没必要纠结一定要用哪个版本的包,不过确实会存在版本过高或过低导致的问题,到时候注意一下就行了。包的下载,我强烈推荐一个网址:https://mvnrepository.com/,只要输入相关关键字就可以找到相应不同版本的包,非常方便!好了,现在开始搭建。假设,我已经用oracle创建了一个表-wkzf,表的字段有(telephone,starttime...),为了方便,我只列出这两个字段,这两个字段的类型都是NUMBER。telephone表示手机号,是11位,starttime表示发送短信的时间,它是毫秒,用来表示现在与 1970 年1月1日00:00:00.000的偏移量,这个可以很方便地计算一个验证码是否超过60秒,如果是日期如yy-mm-dd-hh这样的格式就不太好计算了。
接下来,我们创建一个包,包名可以叫作com.xysj.mybatis.entity,包名的规则可以参考:http://www.runoob.com/java/java-package.html。这些小细节可以让你看起来更加专业。在这个包下面,我们可以创建一个实体类,用来与上面我们设计的表中的字段对应起来,代码如下:
1 package com.xysj.mybatis.entity 2 3 public class User { 4 private long telephone; 5 private long starttime; 6 7 public long getTelephone() 8 public void setTelephone(long telephone) 9 ... 10 }
可以看到,我们实体类User当中,有两个私有变量,很显然分别对应表中的两个字段,然后还有get和set函数,分别用来对这些变量进行操作。
接下来,我们定义一个接口类UserMapper,用来对数据库进行操作。同样,我们创建一个包:com.xysj.mybatis.mapper,在该包下面,创建一个java文件,代码如下:
1 package com.xysj.mybatis.mapper 2 3 import com.xysj.mybatis.User 4 5 public interface UserMapper { 6 public User selectUser(long telephone) 7 public void insertUser(User user) 8 ... 9 }
UserMapper只是一个接口类,那它的具体实现,即操作数据库的SQL语句在哪里呢?我们可以通过xml文件来进行配置,即具体的实现都写在xml文件当中。所以我们可以在整个工程的src目录下,创建一个UserMapper.xml的文件,文件的具体内容如下:
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.xysj.mybatis.mapper.UserMapper"> <select id="selectUser" resultType="user"> select * from wkzf where telephone = #{telephone} </select> <insert id="insertUser" parameterType="user"> insert into wkzf values(#{telephone},#{starttime}) </insert> </mapper>
需要注意的有几点:1)namespace后面填写的是包名+接口类名,一定不能错,错了,不像java有异常机制,可以捕捉异常,xml配置错了,很难发现,所以一定要小心!2)id后面的名字对应UserMapper.java中函数名,不能错!3)可能有人会疑问,为什么这里是“user”而不是“User”,我们定义的类是User啊,稍安勿躁,后面还有一个mybatis-configure.xml文件配置时,会对user进行说明,简单来说就是把User用了一个别名user。4)其他就是注意一些格式要求,#{}代表参数,对应着user中定义的变量。
ok,现在对mybatis-configure.xml文件进行说明,代码如下:
1 <?xml version="1.0" encoding="UTF-8" ?> 2 <!DOCTYPE configuration 3 PUBLIC "-//mybatis.org//DTD Config 3.0//EN" 4 "http://mybatis.org/dtd/mybatis-3-config.dtd"> 5 6 <configuration> 7 8 <!-- Register Alias --> 9 <typeAliases> 10 <typeAlias alias="user" type="com.xysj.mybatis.entity.User" /> 11 </typeAliases> 12 13 <!-- Register Mapper --> 14 <mappers> 15 <!-- SQL Mapper --> 16 <mapper resource="UserMapper.xml" /> 17 </mappers> 18 19 </configuration>
可以看到,我们对User取了一个别名user,这样就不需要把User的包名全部都写上了。同时,我们还关联了上面刚刚定义的UserMapper.xml文件。如果是单独的使用mybatis,用不到spring框架,这个xml文件中还应该声明DataSource的相关信息,好让mybatis知道与那个数据库连接。不过有了spring之后,这部分可以在spring-configure.xml文件中声明。
接下来我们先创建一个包com.xysj.service,然后在包下创建一个UserService的类,代码如下:
1 package com.xysj.service; 2 3 import com.xysj.mybatis.entity.User; 4 import org.mybatis.spring.SqlSessionTemplate; 5 6 public class UserService { 7 private SqlSessionTemplate sqlSession; 8 9 public SqlSessionTemplate getSqlSession() { 10 return sqlSession; 11 } 12 13 public void setSqlSession(SqlSessionTemplate sqlSession) { 14 this.sqlSession = sqlSession; 15 } 16 17 public UserInfo selectUser(long telephone) { 18 User user = null; 19 try { 20 user = (User) sqlSession.selectOne("com.xysj.mybatis.mapper.UserMapper.selectUser", telephone); 21 } catch (Exception e) { 22 e.printStackTrace(); 23 } 24 25 return user; 26 } 27 }
注意selectOne中的函数第一个参数,一定要写完整!包名+接口名+函数名。之前,我这边没怎么搞懂,代码老是出错,我调试了很久才搞定!当然,这里只写了一个函数,你可以自己写insertUser、updateUser等函数,都是类似的。
spring
好了,现在我们开始配置spring-configure.xml文件,这个非常关键,一点也不能错,代码如下:
1 <?xml version="1.0" encoding="UTF-8"?> 2 <beans xmlns="http://www.springframework.org/schema/beans" 3 default-autowire="byName" 4 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 5 xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"> 6 7 <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"> 8 <property name="driverClassName" value="oracle.jdbc.OracleDriver" /> 9 <property name="url" value="jdbc:oracle:thin:@localhost:1521:SID" /> 10 <property name="username" value="xxxx" /> 11 <property name="password" value="xxxx" /> 12 <property name="maxActive" value="100"></property> 13 <property name="maxIdle" value="30"></property> 14 <property name="maxWait" value="500"></property> 15 <property name="defaultAutoCommit" value="true"></property> 16 </bean> 17 18 <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> 19 <property name="configLocation" value="mybatis-configure.xml"></property> 20 <property name="dataSource" ref="dataSource" /> 21 </bean> 22 23 <bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate"> 24 <constructor-arg index="0" ref="sqlSessionFactory" /> 25 </bean> 26 27 <bean id="userService" class="com.xysj.service.UserService"> 28 <property name="sqlSession" ref="sqlSessionTemplate" /> 29 </bean> 30 31 </beans>
需要注意的有几点:1)关于数据源的配置,因为用到了dbcp数据库连接池,所以需要相应的库(commons-dbcp-1.2.jar和commons-pool-1.4.jar ),localhost代表本机,如果你是远程连接到某一个数据库服务器上,则写服务器的IP地址,1521是端口号,SID是数据库名,记住是数据库名,不是表名!2)configLocation后面的value值填写正确,是mybatis的xml配置文件。3)最后一个bean,即“userService”后面的class处写正确,包名+类名。差不多要注意就是这几点,可能还有遗漏的,等到发现了再说。
mina
因为我们只是调用第三方的短信发送平台,所以我们只需要写一个客户端就行,不需要写服务端。总共有4个文件,我们可以先创建一个包:com.xysj.mina。第一个文件ClientHandle.java:
1 package com.xysj.mina; 2 3 import ... 4 5 public class ClientHandle extends IoHandlerAdapter { 6 private final static Log log = LogFactory.getLog(ClientHandle.class); 7 8 @Override 9 public void messageReceived(IoSession session, Object message) throws Exception { 10 log.debug("客户端收到消息" + message.toString()); 11 super.messageReceived(session, message); 12 } 13 14 @Override 15 public void messageSent(IoSession session, Object message) throws Exception { 16 log.debug("客户端发送消息" + message.toString()); 17 super.messageSent(session, message); 18 } 19 20 @Override 21 exceptionCaught 22 sessiogCreated 23 sessionOpened 24 sessionClosed 25 sessionIdle 26 }
因为我们公司是在云桌面开发的,所有的代码和开发环境都在思杰的云桌面上,没有办法拷贝下来,所以只能手打,错误在所难免啊~我也没有办法,import后面的内容省略了,一般你敲完代码,eclipse会提示需要import哪些类,一些重载的函数我也省略了,函数里的内容基本上都差不多,除了一些参数不一样。还有网上搜一下,也能找到这些函数的实现,这里我就不一一敲了。都是手工敲的,难免会有一些代码会敲错,如果出错,请大家指出来哈!第二个文件CodeFactory.java:
1 package com.xysj.mina; 2 3 import ... 4 5 public class CodeFactory implements ProtocolCodecFactory { 6 private XmlDecoder xmldecoder = null; 7 private XmlEncoder xmlencoder = null; 8 9 public CodeFactory() { 10 this(Charset.defaultCharset()); 11 } 12 13 public codeFactory(Charset charset) { 14 this.xmldecoder = new XmlDecoder(charset); 15 this.xmlencoder = new XmlEncoder(charset); 16 } 17 18 @Override 19 public ProtocolDecoder getDecoder(IoSession session) throws Exception { 20 return xmldecoder 21 } 22 23 @Override 24 public ProtocolEncoder getEncoder(IoSession session) throws Exception { 25 return xmlencoder 26 } 27 }
从字面意思不难发现,一个是编码,一个是解码,当你把消息发送出去前(即调用messageSent函数前),需要先编码。当你收到消息及messageReceived响应时,会先进行解码。所以接下来就是对这两部分分别进行实现,网上也有响应的代码,也可以参考,老实说mian这部分我也不是很清楚,正如我前面所说的,我只是先搭建了这么一个框架,然后把代码调通,以后有时间,再慢慢深入了解这些框架。好了,首先是XmlDecoder.java:
1 package com.xysj.mina; 2 3 import ... 4 5 public class XmlDecoder extends CumulativeProtocolDecoder { 6 private final static Log log = LogFactory.getLog(XmlDecoder.class); 7 private final Charset charset; 8 9 public XmlDecoder(Charset charset) { 10 this.charset = charset; 11 } 12 13 @Override 14 protected boolean doDecode(IoSession session, IoBuffer in, 15 ProtocolDecoderOutput out) throws Exception { 16 CharsetDecoder decoder = charset.newDecoder(); 17 IoBuffer ioBuffer = IoBuffer.allocate(3072).setAutoExpand(true); 18 log.info(“开始解码”); 19 20 byte[] b = new byte[in.limit()]; 21 in.get(b); 22 23 Charset charset = Charset.forName("GBK"); 24 ByteBuffer buf = ByteBuffer.wrap(b); 25 CharBuffer cbuf = charset.decoder(buf); 26 log.debug("客户端收到消息" + cbuf.toString()); 27 28 while(in.hasRemaining()) { 29 byte bte = in.get(); 30 ioBuffer.put(bte); 31 if(bte == ' ') { 32 ioBuffer.flip(); 33 byte[] bt = new byte[ioBuffer.limit()]; 34 ioBuffer.get(bt); 35 String message = new String(bt, decoder.charset()); 36 ioBuffer = IoBuffer.allocate(100).setAutoExpand(true)limit 37 out.write(message); 38 } 39 } 40 return true; 41 } 42 }
有了解码的,还有编码的XmlEncode.java:
1 package com.xysj.mina; 2 3 import ... 4 5 public class XmlEncoder extends ProtocolEncoderAdapter { 6 7 private final Charset charset; 8 9 public XmlEncoder(Charset charset) { 10 this.charset = charset; 11 } 12 13 @Override 14 protected void encode(IoSession session, Object obj, 15 ProtocolEncoderOutput out) throws Exception { 16 CharsetEncoder encoder = charset.newEncoder(); 17 IoBuffer io = IoBuffer.allocate(100).setAutoExpand(true); 18 19 io.putString(obj.toString(), encoder); 20 io.put((byte)' '); 21 io.put((byte)' '); 22 io.flip(); 23 out.write(io); 24 } 25 }
好了,关于mina的四个文件都已经写好了,关键是编码和解码的实现,感觉这是要按照相同的格式才行。如果要配置mina的服务器的话,还需要创建响应的xml配置文件,大家自己上网搜找。我们这里只涉及客户端的代码。
logback
日志框架,这个一句话说明就行了,相信大家看上面的代码看到这一行代码:private final static Log log = LogFactory.getLog(类名.class)。然后,在函数中,只要加上log.info()或者log.debug()就可以生成相应的日志。当然,生成的日志放在哪里,日志是什么样的格式,日志的名字等等信息都需要在logback的xml配置文件中声明,logback.xml文件如下:
1 <?xml version="1.0" encoding="UTF-8"?> 2 <configuration debug="false"> 3 <!--定义日志文件的存储地址 勿在 LogBack 的配置中使用相对路径--> 4 <property name="LOG_HOME" value="/log" /> 5 <!-- 控制台输出 --> 6 <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> 7 <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> 8 <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符--> 9 <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern> 10 </encoder> 11 </appender> 12 <!-- 按照每天生成日志文件 --> 13 <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> 14 <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> 15 <!--日志文件输出的文件名--> 16 <FileNamePattern>${LOG_HOME}/TestWeb.log.%d{yyyy-MM-dd}.log</FileNamePattern> 17 <!--日志文件保留天数--> 18 <MaxHistory>30</MaxHistory> 19 </rollingPolicy> 20 <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> 21 <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符--> 22 <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern> 23 </encoder> 24 <!--日志文件最大的大小--> 25 <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy"> 26 <MaxFileSize>10MB</MaxFileSize> 27 </triggeringPolicy> 28 </appender> 29 30 <!-- 日志输出级别 --> 31 <root level="INFO"> 32 <appender-ref ref="STDOUT" /> 33 </root> 34 </configuration>
logback的配置文件,我直接从网上拷贝下来的,参考的网址是:http://www.cnblogs.com/warking/p/5710303.html。我的云桌面上的配置文件跟这个差不多,一行一行敲下来肯定不现实,所以直接从网上找了一个类似的。有些地方可以自己改一改,比如日志生成的路径啊,日志名字啊等等,还是很容易理解的。
OK,终于到最后了!是不是感觉这些框架配置起来挺麻烦的,一点错误都不能有,一旦xml中出错,查找起来还比较麻烦,因为没有报错机制啊,也不能设置断点一行一行查看,所有细心非常重要!接下来我们需要写一个测试类,用来测试上面所有的框架。简单点,就test.java:
test的目的是调用上面框架,代码也不少,一个一个敲实在是太麻烦,所以就贴图片了,格式会比较乱,但也没有办法。OK,到此,关于这个框架的搭建就差不多了。最后想说的是,后面的路还很长,我还是一个java初学者,需要努力加油!
日志
1.2017.9.11正式发布第一版本