三次框架
在B/S架构中,系统标准的三层架构包括:表现层、业务层、持久层。
表现层
也就是常说的web层。
它负责接收客户端请求,向客户端响应结果,通常客户端使用http协议请求web层,web需要接收http请求,完成http响应。
表现层包括展示层和控制层:控制层负责接收请求,展示层负责结果的展示。
表现层依赖业务层,接收到客户端请求一般会调用业务层进行业务处理,并将处理结果响应给客户端。
表现层的设计一般都使用MVC模型。(MVC是表现层的设计模型,和其他层没有关系)
业务层
也就是常说的service层。
它负责业务逻辑处理,和我们开发项目的需求息息相关。
web层依赖业务层,但是业务层不依赖web层。
业务层在业务处理时可能会依赖持久层,如果要对数据持久化需要保证事务一致性。(也就是我们说的,事务应该放到业务层来控制)
持久层
也就是常说的dao层。
负责数据持久化,包括数据层即数据库和数据访问层,数据库是对数据进行持久化的载体,数据访问层是业务层和持久层交互的接口,业务层需要通过数据访问层将数据持久化到数据库中。
通俗的讲,持久层就是和数据库交互,对数据库表进行曾删改查的。
MVC模型
MVC全名是Model View Controller,是模型(model)-视图(view)-控制器(controller)的缩写。
Model(模型): 通常指的就是我们的数据模型。作用一般情况下用于封装数据。
View(视图): 通常指的就是我们的jsp或者html。作用一般就是展示数据的。 通常视图是依据模型数据创建的。
Controller(控制器): 是应用程序中处理用户交互的部分。作用一般就是处理程序逻辑的。
SpringMVC是什么
SpringMVC是一种基于Java的实现MVC设计模型的请求驱动类型的轻量级Web框架,属于 Spring FrameWork 的后续产品,已经融合在Spring Web Flow里面。
SpringMVC和Struts2的优势分析
共同点:
它们都是表现层框架,都是基于MVC模型编写的。
它们的底层都离不开原始ServletAPI。
它们处理请求的机制都是一个核心控制器。
区别:
Spring MVC 的入口是 Servlet, 而 Struts2 是 Filter
Spring MVC 是基于方法设计的,而Struts2是基于类,Struts2每次执行都会创建一个动作类。所以Spring MVC 会稍微比 Struts2 快些。 Spring MVC 使用更加简洁,同时还支持 JSR303, 处理 ajax 的请求更方便。
Struts2 的OGNL 表达式使页面的开发效率相比Spring MVC 更高些,但执行效率并没有比JSTL提升,尤其是struts2的表单标签,远没有html执行效率高。
SpringMVC中的部分组件
DispatcherServlet:前端控制器
用户请求到达前端控制器,它就相当于mvc模式中的c,dispatcherServlet是整个流程控制的中心,由它调用其它组件处理用户的请求,dispatcherServlet的存在降低了组件之间的耦合性。
HandlerMapping:处理器映射器
HandlerMapping负责根据用户请求找到Handler即处理器,SpringMVC提供了不同的映射器实现不同的映射方式,例如:配置文件方式,实现接口方式,注解方式等。
Handler:处理器
它就是我们开发中要编写的具体业务控制器。由DispatcherServlet把用户请求转发到Handler。由Handler对具体的用户请求进行处理。
HandlAdapter:处理器适配器
通过HandlerAdapter对处理器进行执行,这是适配器模式的应用,通过扩展适配器可以对更多类型的处理器进行执行。
View Resolver:视图解析器
View Resolver负责将处理结果生成View视图,View Resolver首先根据逻辑视图名解析成物理视图名即具体的页面地址,再生成View视图对象,最后对View进行渲染将处理结果通过页面展示给用户。
View:视图
SpringMVC框架提供了很多的View视图类型的支持,包括:jstlView、freemarkerView、pdfView等。我们最常用的视图就是jsp。
入门案例
配置核心控制器web.xml
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<display-name>Archetype Created Web Application</display-name>
<!-- 配置spring mvc的核心控制器 -->
<servlet>
<!--固定的写控制器组件DispatcherServlet,起控制作用,相当于servlet的指挥中心-->
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 配置初始化参数,用于读取SpringMVC的配置文件 -->
<init-param>
<param-name>contextConfigLocation</param-name>
<!--配置文件的位置-->
<param-value>classpath:springmvc.xml</param-value>
</init-param>
<!-- 配置servlet的对象的创建时间点:应用加载时创建。 取值只能是非0正整数,表示启动顺序 -->
<!--配置为1代表当Tomcat启动的时候DispatcherServlet就会被创建-->
<load-on-startup>1</load-on-startup>
</servlet>
<!--用servlet拦截-->
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<!--填写"/"代表拦截全部页面-->
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
创建SpringMVC的配置文件springmvc.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!--开启注解扫描-->
<context:component-scan base-package="com.lwl"></context:component-scan>
<!--配置视图解析器,视图解析器对象,id和class都是固定不变的,它可以帮助我们跳转到指定页面-->
<bean id="internalResourceViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!--prefix指定跳转到文件的位置-->
<property name="prefix" value="/WEB-INF/pages/"/>
<!--suffix指定跳转到的文件的后缀名-->
<property name="suffix" value=".jsp"></property>
</bean>
<!--开启SpringMVC框架注解的支持-->
<mvc:annotation-driven></mvc:annotation-driven>
</beans>
编写控制器并使用注解配置
@Controller
public class HelloController {
//@RequestMapping请求映射标签,当该类被调用的时候会执行这个方法
//path属性指定方法的请求路径
@RequestMapping(path="/hello")
public String sayHello(){
System.out.println("Hello StringMVC");
//默认表示为跳转到名称为success.jsp的页面
return "success";
}
}
SpringMVC的请求响应流程
@RequestMapping注解的作用
源码:
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping
public @interface RequestMapping {
作用:用于建立请求URL和处理请求方法之间的对应关系。
出现的位置:该注解可以写在类上也可以写在方法上。
出现在类上:
请求URL的第一级访问目录。此处不写的话,就相当于应用的根目录。写的话需要以/开头。 它出现的目的是为了使我们的URL可以按照模块化管理:
例如:
账户模块:
/account/add
/account/update
/account/delete ...
订单模块:
/order/add
/order/update
/order/delete
上面例子中 /account 和 /order 可以使用 @RequestMapping 填写在类上, 而 /add 等这些作为二级目录写在方法上
方法上: 请求URL的第二级访问目录。
注解的属性:
- value:用于指定请求的URL。它和path属性的作用是一样的。
- method:用于指定请求的方式(选择GET,POST,PUT,PATCH等方式)。
- params:用于指定限制请求参数的条件。它支持简单的表达式。要求请求参数的key和value必须和配置的一模一样。
例如: params = {“accountName”},表示请求参数必须有accountName params = {“moeny!100”},表示请求参数中money不能是100。 - headers:用于指定限制请求消息头的条件。
请求参数的绑定
- 请求参数的绑定说明
- 绑定机制
- 表单提交的数据都是key=value格式的 username=haha&password=123
- SpringMVC的参数绑定过程是把表单提交的请求参数,作为控制器中方法的参数进行绑定的
- 要求:提交表单的name和参数的名称是相同的
- 绑定机制
- 支持的数据类型
- 基本数据类型和字符串类型
- 实体类型(JavaBean)
- 集合数据类型(List、map集合等)
零散接收参数-案例
编写一个简单的jsp页面,超链接中传递两个参数:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>请求参数绑定</title>
</head>
<body>
<a href="param/testParam?username=hehe&password=12345">请求参数绑定</a>
</body>
</html>
参数将会进入@RequestMapping指定的控制类的方法中:
@Controller
@RequestMapping("/param")
public class ParamController {
//请求参数绑定入门
@RequestMapping("/testParam")
public String testParam(String username, String password) {
System.out.println("testParam执行了。。");
System.out.println("账户:" + username + ",密码:" + password);
return "success";
}
}
当访问该jsp页面并点击该链接后就会调用testParam方法,在控制台输出:
账户:hehe,密码:1234
用类封装参数-案例
当时如果传入的参数比较多,就应该把参数封装到一个类中:
新建一个实体类Account并添加get,set和toString方法:
public class Account implements Serializable {
private String username;
private String password;
private Double money;
//省去get,set和toString方法
}
创建一个表单,并且地址指向一个方法:
<!--创建一个表单-->
<form action="param/saveAccount" method="post">
姓名:<input type="text" name="username"/><br>
密码:<input type="text" name="password"/><br>
金额:<input type="text" name="money"/><br>
<input type="submit" value="提交"><br>
</form>
在测试类中创建这个方法,直接在方法的形参中输入这个类,就可以直接封装进去:
@Controller
@RequestMapping("/param")
public class ParamController {
//请求参数封装到类中
@RequestMapping("/saveAccount")
public String saveAccount(Account account) {
System.out.println("testParam执行了。。");
System.out.println(account);
return "success";
}
}
输入:
控制台输出:
如果Account 类中包含另一个类的实例。应该怎么封装呢?
User类:
public class User implements Serializable {
private String uname;
private Integer age;
}
Account :
public class Account implements Serializable {
private String username;
private String password;
private Double money;
//新增
private User user;
那么在创建表单的时候可以这样实现:
<!--创建一个表单-->
<form action="param/saveAccount" method="post">
姓名:<input type="text" name="username"/><br>
密码:<input type="text" name="password"/><br>
金额:<input type="text" name="money"/><br>
用户姓名:<input type="text" name="user.uname"/><br>
用户年龄:<input type="text" name="user.age"/><br>
<input type="submit" value="提交"><br>
</form>
解决中文乱码问题:在过滤器中实现:
web.xml中添加:
<!--配置解决中文乱码的过滤器-->
<filter>
<filter-name>characterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>characterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
如果这个Account类中还包含了List与Map集合应该怎么封装?
public class Account implements Serializable {
private String username;
private String password;
private Double money;
//新增
private User user;
//新增
List<User> users;
}
jsp中的表单:
<!--创建一个表单,类中存在List、Map集合-->
<form action="param/saveAccount" method="post">
姓名:<input type="text" name="username"/><br>
密码:<input type="text" name="password"/><br>
金额:<input type="text" name="money"/><br>
用户姓名:<input type="text" name="user.uname"/><br>
用户年龄:<input type="text" name="user.age"/><br>
<%--封装到list中的一个User对象里--%>
路人姓名0:<input type="text" name="users[0].uname"/><br>
路人年龄0:<input type="text" name="users[0].age"/><br>
<%--封装到list中的一个User对象里--%>
路人姓名1:<input type="text" name="users[1].uname"/><br>
路人年龄1:<input type="text" name="users[1].age"/><br>
<%--封装到map中的一个User对象里,键设置为‘key2’--%>
路人姓名2:<input type="text" name="map['key2'].uname"/><br>
路人年龄2:<input type="text" name="map['key2'].age"/><br>
<input type="submit" value="提交"><br>
</form>
如此就可以实现集合的封装。
输入:
控制台输出;
testParam执行了。。
Account{username=‘张张’, password=‘123456’, money=200.0, user=User{uname=‘打哈’, age=22}, users=[User{uname=‘甲’, age=21}, User{uname=‘乙’, age=25}], map={key2=User{uname=‘丙’, age=30}}}
自定义类型转换器
在User中添加一个生日:
public class User implements Serializable {
private String uname;
private Integer age;
private Date date;
}
创建表单:
<!--创建一个表单-->
<form action="param/saveUser" method="post">
姓名:<input type="text" name="uname"/><br>
年龄:<input type="text" name="age"/><br>
生日:<input type="text" name="date"/><br>
<input type="submit" value="提交"><br>
</form>
处理提交的数据:
//自定义类型转换器
@RequestMapping("/saveUser")
public String saveUser(User user) {
System.out.println("saveUser执行了。。");
System.out.println(user);
return "success";
}
输入:
此时控制台会打印:
saveUser执行了。。
User{uname=‘张张’, age=123456, date=Sun Feb 02 00:00:00 CST 2020}
但是如果想把Date类型的输入进行修改:
访问就会报错:
此时我们需要自定义类型转换器:
/**
* 字符串转换成日期
* @author liwenlong
* @data 2020/5/21
*/
public class StringToDate implements Converter<String, Date> {
@Override
public Date convert(String s) {
if (s == null) {
throw new RuntimeException("请传入数据");
}
DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
try {
//把字符串转日期
return df.parse(s);
} catch (ParseException e) {
throw new RuntimeException("错误");
}
}
}
在springmvc.xml文件中注册类型转换器:
<!--配置自定义类型转换器-->
<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
<!--注册类型转化器-->
<property name="converters">
<set>
<bean class="com.lwl.util.StringToDate"></bean>
</set>
</property>
</bean>
<!--开启SpringMVC框架注解的支持-->
<mvc:annotation-driven conversion-service="conversionService"></mvc:annotation-driven>
此时输入2020-02-02就不会再报错,但是原来的2020/02/02就不能用了。