写在前面:这篇文章是写给刚刚接触或者准备学习web/APP应用开发的同学的,分享我的后台开发经验。
你也许曾经Google/百度过“如何编写在服务器上运行的程序”、“怎样搭建一个服务器”这些关键词句,那么一定会有所了解servlet、tomcat。你肯定也百度过“后台怎么操纵数据库”这类问题,那么JDBC你一定不会陌生。一个不使用框架的后台,靠上面这些关键词就能搭建出来。
Java Servlet,可以看作Java的一个类,或者是运行在服务器上完成某个功能的Java程序。
有了Servlet之后,用户通过单击某个链接或者直接在浏览器的地址栏中输入URL来访问Servlet,调用Servlet完成响应。
Web服务器接收到该请求后,并不是将请求直接交给Servlet,而是交给Servlet容器。Servlet容器实例化Servlet,调用Servlet的一个特定方法对请求进行处理, 并产生一个响应。这个响应由Servlet容器返回给Web服务器,Web服务器包装这个响应,以HTTP响应的形式发送给Web浏览器。
这个过程可以用下图表示:
原文链接:servlet --百度百科
一个开源的轻量级服务器,适合小项目或者学习用,只要Tomcat处于启动状态,搭载着它的主机就是一台能被外界通过IP访问的Web服务器。
我们可以在自己的个人电脑上安装Tomcat,用来学习Web服务器的相关知识,不需要租借一些商用服务器。
JDBC 全称为 Java Database Connect。提供一系列Java类帮助程序沟通数据库,完成增删查改等操作。
开发部署简单,书写 servlet 程序并将部署到 Tomcat,通过URL访问即可。
简单代表很多事需要亲力亲为,下面提到其中的两点。
假如有一个User类,是用户的一个实体类,当我们想要插入一个user时,也许会像下面这样写,通过字符串的拼接书写SQL语句,简单粗暴。
// 相当于insert into user(name, password, age) values ('xx','xx',x)
String sql = "insert into user(name, password, age) values ('" + user.getName()+ "','" + user.getPassword + "'," +user.getAge() + ")" ;
注意到拼接字符串要考虑传入的参数可能为null,如name为null怎么办。很遗憾,JDBC的空值不能直接传入null,而是调用类似于 setNull() 这种方法。所以我们不得不提前检验传入的参数是否为null,并针对不同字段为null的情况重新拼接SQL语句。很明显,这种检验 + 拼接十分困难,大大增加了程序员的工作量。一般的表十几个字段很正常,多的几十个、上百个字段。
servlet在web容器中是单实例的,在第一次请求或者容器初始化的时候就会创建servlet。每次请求都会先交给 servlet 的 service() 方法,再由 service() 根据请求类型具体分配到 doPost()、doGet() 等方法来处理请求。servlet 的书写格式如下:
public class Servlet_1 extends HttpServlet{ // 继承 HttpServlet 类
public void init() throws ServletException{
// 初始化
}
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 处理Get类请求
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 处理Post类请求
}
public void destroy() {
// 销毁servlet
}
}
为了用户能够通过 URL 访问 servlet,我们需要为每一个 servlet 配置映射路径,从下面代码看出,我们配置的每一条路径将会对应一个 servlet 的 .class文件,确保URL的唯一性。
<servlet>
<servlet-name>ServletNameservlet-name>
<servlet-class>com.xx.Servlet_1servlet-class>
servlet>
<servlet-mapping>
<servlet-name>ServletNameservlet-name>
<url-pattern>/Login.dourl-pattern>
servlet-mapping>
显然,这种开发部署方式不够灵活,重复代码多,可移植性差,不适合功能复杂的系统。
如果你学习Java已经有一段时间了,那么肯定对Spring有所耳闻,Java EE 的开发离不开Spring,当然还有更加简便的Spring boot框架,一个基于Spring的强大框架,也是微服务的基础,建议学会使用SSM后马上投入Spring boot 的学习。
限于篇幅,我不可能面面俱到地讲解SSM框架的各种应用,我会对它的特色与基本应用进行介绍。
下面动手写一个SSM小Demo吧,这里 是GitHub的地址。
使用Maven工程,具体的依赖看demo的pom.xml
文件。
这是Spring的一个特色,使用配置文件的方式取代代码控制,比如数据库的连接。
这是Spring的核心配置文件,下面介绍几个标签。
数据源(dataSource)就是数据库,观察属性,driverClassName 是 mysql 的驱动(需要添加mysql-connector的依赖),url 是数据库地址。
事务管理器就是管理事务的,可以给程序的某些方法/类添加事务管理。
至于标签
,代表这个类交给容器来管理,用到它的时候会从容器获取,而不是自己new 一个对象出来,也就是常说的依赖注入(DI)。
...
<bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://{your_ip}:{your_port}/{your_database_name}?characterEncoding=UTF-8"/>
<property name="username" value="..."/>
<property name="password" value="..."/>
bean>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
bean>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
bean>
<tx:annotation-driven transaction-manager="transactionManager"/>
<context:component-scan base-package="com.example"/>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.example.mapper"/>
bean>
...
SpringMVC的核心配置文件,只有两个一级标签。
第二个标签中的
,有时候我们向后台请求的不是一个页面,而是一些数据,我们使用 @ResposneBody 注解对应的方法,通过配置转换器,指明返回给调用者的数据格式是 Json。
<context:component-scan base-package="com.example.controller"/>
<mvc:annotation-driven>
<mvc:message-converters>
<bean id="fastJsonHttpMessageConverter" class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter">
<property name="supportedMediaTypes">
<list>
<value>application/json;charset=UTF-8value>
list>
property>
bean>
mvc:message-converters>
mvc:annotation-driven>
如果不需要使用@ResposneBody返回Json数据,可以删掉
,只保留
,这样文件就相当简洁了,只有两行,如下。
<context:component-scan base-package="com.example.controller"/>
<mvc:annotation-driven>
Json 大概长下面这样子,有点 key-value 的感觉,语义清晰,适合前后端数据通信。
{
"code": 0,
"data":{
"name":"Jack",
"age":16,
"sex":"male"
}
}
使用注解来简化xml的编写是Spring的重要部分,上面的xml文件已经很简化了,因为我接下来实现的功能都是基于注解的,这样就免除大家写配置文件的痛苦,也推荐大家使用注解而不是xml文件的方式来开发。
当然,使用注解要开启注解扫描,这样才能对包里的注解进行编译,细心的朋友应该留意到我在配置文件中说的 开启包扫描。
在org.xujiale.entity包下写个实体类User。
package com.example.entity;
public class User {
private long id; //数据库设计的时候就设为自增类型的,便于管理
private String name;
private String account;
private int age;
// 省略了一堆getter和setter
}
mapper 提供对数据库的插入和查询,一般会以接口的形式提供给上一层调用。mapper可以解释为映射,表示将 代码/注解/xml文件 映射为SQL查询语句。需要在配置文件中开启 mapper 扫描。
@Repository注释和@bean类似,一般是传统数据访问层(Dao)的实例化;
@Insert、@Select等是mybatis的注解,可以直接将sql语句放入注解中,顾名思义,对应的语句是插入与查询,类似注解还有@Delete、@Update。
书写注意:#{name}
可以直接替换为 user 实例对应成员变量 name,数据类型也会自动检查。
package com.example.mapper;
@Repository
public interface UserMapper {
// 插入信息
@Insert("insert into user(name,password,age) values(#{name},#{password},#{age})")
int insert(User user);
//查询信息
@Select("select * from user where name=#{name}")
User select(@Param("name") String name);
}
有了底层数据访问,还要提供服务层(Service),UserService 接口提供给上一层调用,其中提供两个方法。
package com.example.service
public interface UserService {
// 添加用户
int addUser(User user);
// 获取用户信息
User getUser(long id);
}
服务层就不能像数据访问层一样只提供接口了,服务层有时涉及很多业务逻辑,我们写一个UserService接口的实现类UserServiceImpl来处理一些逻辑,我们的demo比较简单,直接返回访问的数据。
@Service和@Repository类似,不过特指服务层的实例;
@Transactional 表示添加事务;
@Autowired 代表自动装配,容器会自动向添加该注解的变量注入实例,在这段代码中这个实例就是@Repository 标注的 mapper。
package con.example.service
@Service("userService")
@Transactional
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper mapper;
@Override
public int addUser(User user) {
return mapper.insert(user);
}
@Override
public User getUser(long id) {
return mapper.select(id);
}
}
@Controller和@Service等类似,特指controller层的实例;
@RequestMapping 指定URL的映射路径。可以添加method参数表示处理请求的类型,如@RequestMapping(value = "/AddUser", method="POST")
,不添加则可以处理所有类型请求;
@RequestParam 用于提取 http 报文中的参数;
留意@ResponseBody,在添加这个注解之后,配合我们上面在springmvc-config.xml中的配置,getUser()将会返回Json数据。同时,使用 @ResponseBody 也代表这个方法的返回值不需要经过视图解析器,直接返回给前端(Controller的返回值一般会经过视图解析器,用于将对应页面返回给前端,实现页面跳转,如 return index.jsp
)
package com.example.controller
@Controller
public class UserController {
@Autowired
UserService userService; //调用service接口
@RequestMapping(value = "/AddUser")
@ResponseBody
public String addUser(@RequestParam("name") String name,
@RequestParam("password") String password,
@RequestParam("age")int age) {
User user = new User();
user.setName(name);
user.setPassword(password);
user.setAge(age);
int result = userService.addUser(user); //获取结果
if (result == 1) { //受影响行数为1,说明数据库已经插入,所以返回成功("Succeed")
return "Succeed";
}
return "Failed";
}
@RequestMapping(value = "/GetUser")
@ResponseBody
public User getUser(@RequestParam("id") long id) {
return userService.getUser(id); //直接向前端返回这个对象
}
}
配置web.xml文件,在Tomcat原有的web.xml添加下列代码,完整文件看demo,注释已经写的比较清楚了。我们上面写了两个配置文件,但是我们并没有提供 tomcat 访问他们的入口,tomcat 可没有这么智能,所以我们会手动在 web.xml 这个文件下添加我们的配置文件入口。
...
<context-param>
<param-name>contextConfigLocationparam-name>
<param-value>classpath:applicationContext.xmlparam-value>
context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListenerlistener-class>
listener>
...
<servlet>
<servlet-name>crmservlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class>
<init-param>
<param-name>contextConfigLocationparam-name>
<param-value>classpath:springmvc-config.xmlparam-value>
init-param>
<load-on-startup>1load-on-startup>
servlet>
<servlet-mapping>
<servlet-name>crmservlet-name>
<url-pattern>/url-pattern>
servlet-mapping>
...
将项目打包成war包,放到服务器的webapp目录下 ,重启Tomcat。(注意数据库的表和字段提前建好)
发起一个“添加用户”的GET请求,格式为ip:端口(默认8080)/工程名/AddUser?参数
。
根据REST风格,插入请求应该使用POST,但是为了省事,直接GET了。
返回了Succeed,也就是说明插入成功了。
发起一个查看用户的请求。可以看到id为1(也就是我们刚刚插入的用户)的数据已经返回来了,而且就是json格式。
至此关于SSM框架的基本应用已经讲得差不多了,当然讲解的只是冰山一角,希望这个小demo会对你的学习有所帮助。
GitHub地址:ssm-demo