Spring MVC IDEA 框架搭建:https://www.cnblogs.com/zuti666/p/13987082.html
个人笔记 做个记录
前置知识
具体可以看:https://www.w3cschool.cn/wkspring/xypt1icg.html
Spring 概述
Spring 的优良特性
- 非侵入式:基于Spring开发的应用中的对象可以不依赖于Spring的API
- 控制反转:IOC——Inversion of Control,指的是将对象的创建权交给 Spring 去创建。使用 Spring 之前,对象的创建都是由我们自己在代码中new创建。而使用 Spring 之后。对象的创建都是给了 Spring 框架。
- 依赖注入:DI——Dependency Injection,是指依赖的对象不需要手动调用 setXX 方法去设置,而是通过配置赋值。
- 面向切面编程:Aspect Oriented Programming——AOP
- 容器:Spring 是一个容器,因为它包含并且管理应用对象的生命周期
- 组件化:Spring 实现了使用简单的组件配置组合成一个复杂的应用。在 Spring 中可以使用XML和Java注解组合这些对象。
- 一站式:在 IOC 和 AOP 的基础上可以整合各种企业应用的开源框架和优秀的第三方类库(实际上 Spring 自身也提供了表现层的 SpringMVC 和持久层的 Spring JDBC)
IOC容器
IOC容器即控制反转。Spring IOC 将类的初始化与配置 和 使用拆分开来,Spring IOC 将组件自动进行初始化,并且都存放在 IOC 容器中,而这些初始化好的就叫做 Bean,当我们需要使用的时候进行注入就可以了。
具体可查看:https://www.liaoxuefeng.com/wiki/1252599548343744/1282381977747489
Bean
被称作 bean 的对象是构成应用程序的支柱也是由 Spring IoC 容器管理的。bean 是一个被实例化,组装,并通过 Spring IoC 容器所管理的对象
可参考树哥:https://www.yuque.com/u92750/bpdenk/tbs0o9
Bean 与容器的关系
AOP
看了看代码,说白了就是动态代理
https://www.liaoxuefeng.com/wiki/1252599548343744/1310052352786466
用到再说吧
IDEA 环境搭建
pom 文件中的 properties 中添加 <spring.version>4.3.6.RELEASE</spring.version>
添加 spring 核心包
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>3.2.13.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>3.2.13.RELEASE</version>
</dependency>
创建 webapp 文件夹
路径对上就行了
开始
SpringMVC 感觉和 Servlet 差不多,都是要配置 servlet ,不同的是 servlet 中配置都是在 web.xml 中的,SpringMVC都是在springmvc-servlet.xml 里面的
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<display-name>springMVC</display-name>
<welcome-file-list>
<welcome-file>/index</welcome-file>
</welcome-file-list>
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
存放在 /WEB-INF/ 下的文件不会被直接请求到,所以要把jsp放在 WEB-INF 下面
通常路径为 webapp/WEB-INF
第一个 SpringMVC
视图解析
视图解析之后controller就可以直接String返回了
<bean id="viewResolver"
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!--前缀 -->
<property name="prefix" value="/WEB-INF/jsp/" />
<!--后缀 -->
<property name="suffix" value=".jsp" />
</bean>
/**
* Controller 层中调用 Services 层中的业务代码来进行处理
* Controller 主要是流程的控制,具体业务实现细节都在 services
*/
@Controller
@RequestMapping("/user")
public class UserController {
@Autowired
private UserServices userServices;
@RequestMapping("/login")
public String login(User user, Model model, HttpSession session){
if (userServices.login(user)) {
// 登录成功,将用户信息保存到session对象中
session.setAttribute("user", user);
// 重定向到主页面的跳转方法
return "redirect:index";
}
System.out.println(2);
model.addAttribute("msg", "用户名或密码错误,请重新登录! ");
return "login";
}
@RequestMapping("/index")
public String toIndex(){
return "index";
}
}
Spring MVC 执行流程
主要有以下这些组件:
DispatcherServlet :Spring MVC 的所有请求都要经过 DispatcherServlet 来统一分发。DispatcherServlet 相当于一个转发器或中央处理器,控制整个流程的执行,对各个组件进行统一调度,以降低组件之间的耦合性,有利于组件之间的拓展。
HandlerMapping:处理器映射器,其作用是根据请求的 URL 路径,通过注解或者 XML 配置,寻找匹配的处理器(Handler)信息。
HandlerAdapter:处理器适配器,其作用是根据映射器找到的处理器(Handler)信息,按照特定规则执行相关的处理器(Handler)
Handler:处理器,和 Java Servlet 扮演的角色一致。其作用是执行相关的请求处理逻辑,并返回相应的数据和视图信息,将其封装至 ModelAndView 对象中
ViewResolver:将收到的请求解析到对应的视图
单看这张图确实看懂了,但是针对源码中的一些细节其实也可以再看一下,接下来去查看每个模块的源码部分
DispatcherServet
initStrategies 中会进行初始化,请求响应这些都在 ApplicationContext 中
DispatcherServet#doDispatch 为主要函数 ,传入参数为 request、response
HandlerMapping
首先会调用 this.getHandler 来获取 HandlerMapping,通过迭代器的方式来遍历 handlerMappings ,然后根据传入的请求返回对应的 HandlerExecutionChain (Handler 执行链)
private List<HandlerMapping> handlerMappings;
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
Iterator i$ = this.handlerMappings.iterator();
HandlerExecutionChain handler;
do {
if (!i$.hasNext()) {
return null;
}
HandlerMapping hm = (HandlerMapping)i$.next();
if (this.logger.isTraceEnabled()) {
this.logger.trace("Testing handler map [" + hm + "] in DispatcherServlet with name '" + this.getServletName() + "'");
}
handler = hm.getHandler(request);
} while(handler == null);
return handler;
}
HandlerAdapter
然后会通过调用 getHandlerAdapter 来获取 HandlerExecutionChain 中的 HandlerAdapter
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
Iterator i$ = this.handlerAdapters.iterator();
HandlerAdapter ha;
do {
if (!i$.hasNext()) {
throw new ServletException("No adapter for handler [" + handler + "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}
ha = (HandlerAdapter)i$.next();
if (this.logger.isTraceEnabled()) {
this.logger.trace("Testing handler adapter [" + ha + "]");
}
} while(!ha.supports(handler));
return ha;
}
获取到 HandlerAdapter 之后会调用 HandlerAdapter#handle ,注意 handle 方法的传参类型为 (HttpServletRequest request, HttpServletResponse response, Object handler),最后一个 handler 从 HandlerExecutionChain 中进行获取
来找到对应的 Controller 进行后续处理
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
return ((Controller)handler).handleRequest(request, response);
}
接下来就是视图那块就不跟了...
所以捋一下思路:Spring 中核心调度在 DispatcherServlet,然后 DispatcherServlet#doDispatch 是核心方法,当收到请求之后首先会从 handlermapping 中找到该请求对应的 HandlerExecutionChain(说人话就是返回一条链,其实理解成 handler Plus 比较好),然后 handlerAdapter调用适配器中的 handle 方法来执行处理器 handler
请求 => DispatcherServlet#doDispatch => HandlerExecutionChain(Handler) => handlerAdapter#handle => Handler(Controller)=> 返回视图 .....
注解
Controller
最常用的就是 Controller 注解,来对请求进行处理 ,和 SpringBoot 类似
@Controller
@RequestMapping("/user")
public class UserController {
@RequestMapping("/info")
public ModelAndView UserInfo(){
return new ModelAndView("/WEB-INF/jsp/info.jsp");
}
}
但是在 MVC 中我们需要在 springmvc-servlet.xml 中添加如下代码,这样就代表 controller 中所有的注解都会被扫描
<context:component-scan base-package="com.example.mvc01.controller" />
RequestMapping
该注解可以定义路由,即将请求映射到对应的处理器上,同时可以用 params 来指定参数
@Controller
@RequestMapping("/user")
public class UserController {
@RequestMapping(value = "/info",params = "id")
public ModelAndView UserInfo(){
return new ModelAndView("/WEB-INF/jsp/info.jsp");
}
}
@RequestMapping(value = "/info",params = "id") // 指定参数
@RequestMapping(value = "toUser",headers = "Referer=http://www.xxx.com") // 制定 referer
当然还有很多注解,用到了再去看就行了
参数传递
参数传递有很多方法,具体可以看:http://c.biancheng.net/spring_mvc/pass-param.html
我就调几种我有可能会常用的来记录一下
PathVariable 注解来获取 path 中的参数值
Url: http://localhost/user/info/admin
@Controller
@RequestMapping("/user")
public class UserController {
@RequestMapping(value = "/info/{name}")
public ModelAndView UserInfo(@PathVariable String name){
if ("admin".equals(name)){
return new ModelAndView("/WEB-INF/jsp/info.jsp");
}else {
return new ModelAndView("/WEB-INF/jsp/login.jsp");
}
}
}
RequestParam 注解来获取请求中的参数值
Url:http://localhost:8080/user/info?name=admin
@Controller
@RequestMapping("/user")
public class UserController {
@RequestMapping(value = "/info")
public ModelAndView UserInfo(@RequestParam String name){
if ("admin".equals(name)){
return new ModelAndView("/WEB-INF/jsp/info.jsp");
}else {
return new ModelAndView("/WEB-INF/jsp/login.jsp");
}
}
}
利用 java bean 来接受,但是需要确保对一点传递过来的参数和 bean 的属性是一致的
@RequestMapping("/login")
public String login(User user, Model model, HttpSession session){
if (userServices.login(user)) { => user.getUsername()/user.getPassword()
// 登录成功,将用户信息保存到session对象中
session.setAttribute("user", user);
// 重定向到主页面的跳转方法
return "redirect:index";
}
System.out.println(2);
model.addAttribute("msg", "用户名或密码错误,请重新登录! ");
return "login";
}
// username==username , password==password
public class User {
private String username;
private String password;
}
<form action="${pageContext.request.contextPath}/user/login" method="POST">
用户名:<input type="text" name="username" /><br>
密码:<input type="password" name="password" /><br>
<input type="submit" value="登录" />
</form>
还有就是一种利用类来接受的,这个的话暂时不知道咋写,后面应该会遇到
重定向和转发
转发:
这里转发不是之前那样的视图转发,其实就是 url 302 跳转,所以这里要输入的是路径,就是能正常访问到的路径 而不是 web-inf 那个
return new ModelAndView("redirect:/login"); // 这里就不是资源了 就是url那边请求的路径了
转发到请求方法
return "forward:/index/isLogin";
在 Spring MVC 框架中,不管是重定向或转发,都需要符合视图解析器的配置,如果直接转发到一个不需要 DispatcherServlet 的资源,例如:
return "forward:/html/my.html";
则需要使用 mvc:resources 配置:
Autowired & Service 注解
关于编写代码中层级的问题:https://blog.csdn.net/zdwzzu2006/article/details/6053006
前面了解过 ioc 所以相对清晰点,ioc 帮我们省去了配置和初始化的步骤,所有初始化好的对象都在 ioc 容器里面,那么我们如果某些地方需要引入对象的话 我们就需要利用一些注解来注入进来
注入进来的都是类似数据库连接的 connection 一样
@Autowired:可以对类成员变量、方法及构造函数进行标注,完成自动装配的工作
@Service:会将被标注的类注册到 ioc 容器中
按照教程写了一下代码大致清楚了开发的一个流程:
entity:User
services:UserService(接口)、UserServicesImpl(接口实现类) => 业务代码
controller:UserController => 主要是流程的控制,具体业务逻辑都是 services
Services 层
public interface UserServices {
/**
* 注册登录的业务逻辑
*/
boolean register(User user);
boolean login(User user);
}
@Service // 记得添加注解,否则spring扫描不到
public class UserServicesImpl implements UserServices{
@Override
public boolean register(User user) {
return "admin".equals(user.getUsername()) && "admin".equals(user.getPassword());
}
@Override
public boolean login(User user) {
return "admin".equals(user.getUsername()) && "admin".equals(user.getPassword());
}
}
Entity 层
public class User {
private String username;
private String password;
public String getUsername() {
return username;
}
public String getPassword() {
return password;
}
public void setUsername(String username) {
this.username = username;
}
public void setPassword(String password) {
this.password = password;
}
}
Controller 层
@Controller
@RequestMapping("/user")
public class UserController {
@Autowired
private UserServices userServices;
@RequestMapping("/login")
public String isLogin(Model model){
User user = new User();
user.setUsername("admin");
user.setPassword("admin");
userServices.login(user);
model.addAttribute("user", user); // 添加进去了 所以前端模版可以找到
return "login";
}
@RequestMapping("/register")
public String isRegister(Model model){
User user = new User();
user.setUsername("admin");
user.setPassword("admin");
userServices.register(user);
model.addAttribute("user", user);
return "register";
}
}
这样前端就可以通过 ${user.username} 来直接获取到了 ,其实和 pagecontext 那些是一个原理
@Model Attribute
@Controller
public class ModelAttributeController {
// 方法有返回值
@ModelAttribute("name")
public String myModel(@RequestParam(required = false) String name) {
return name;
}
@RequestMapping(value = "/model")
public String model() {
return "index";
}
}
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>编程帮</title>
</head>
<body>
${string }
</body>
</html>
类型转换器
配置转换器
<mvc:annotation-driven conversion-service="conversionService" />
<!--注册类型转换器UserConverter -->
<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
<property name="converters">
<list>
<bean class="net.biancheng.converter.UserConverter" />
</list>
</property>
</bean>
就是对输入进行转换一下
@Component
public class UserConverter implements Converter<String, User> {
@Override
public User convert(String source) {
// 创建User实例
User user = new User();
// 以“,”分隔
String stringvalues[] = source.split(",");
if (stringvalues != null && stringvalues.length == 3) {
// 为user实例赋值
user.setName(stringvalues[0]);
user.setAge(Integer.parseInt(stringvalues[1]));
user.setHeight(Double.parseDouble(stringvalues[2]));
return user;
} else {
throw new IllegalArgumentException(String.format("类型转换失败, 需要格式'编程帮, 18,1.85',但格式是[% s ] ", source));
}
}
}
格式化
略
标签化
略
Json交互
http://c.biancheng.net/spring_mvc/json.html (用库)
拦截器 Interceptor
拦截器都要实现 HandlerInterceptor 接口,其中主要方法都写在 preHandle 中
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {
// 主要写在这里
String url = httpServletRequest.getRequestURI();
if (url.contains("/user/login")){ // /user/login/..;/index /..;/ 相当于bypass字符串检测然后跳到前面
return true;
}
HttpSession httpSession = httpServletRequest.getSession();
Object obj = httpSession.getAttribute("user");
if (obj != null){
return true;
}
httpServletRequest.setAttribute("msg", "还没登录,请先登录!");
httpServletRequest.getRequestDispatcher("/WEB-INF/jsp/login.jsp").forward(httpServletRequest,httpServletResponse);
return false;
}
@Override
public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
}
}
这样 if (url.contains("/user/login"))
写有漏洞,会导致权限绕过 /..;/,/..;/..;/ 就是跳两层
控制器需要在 springmvc-servlet.xml 进行注册
<mvc:interceptors>
<mvc:interceptor>
<!-- 配置拦截器作用的路径 -->
<mvc:mapping path="/**" />
<bean class="com.example.mvc01.interceptor.LoginInterceptor" />
</mvc:interceptor>
</mvc:interceptors>
数据校验
其实就是一些注解,然后能在上面加一点正则
public class User {
@NotNull(message = "用户id不能为空")
private Integer id;
@NotNull
@Length(min = 2, max = 8, message = "用户名不能少于2位大于8位")
private String name;
@Email(regexp = "[a-zA-Z0-9]+@[a-zA-Z0-9]+\\.[a-zA-Z0-9]", message = "邮箱格式不正确")
private String email;
/** 省略setter和getter方法*/
}
大致如下:
名称 | 说明 |
---|---|
@Null | 被标注的元素必须为 null |
@NotNull | 被标注的元素必须不为 null |
@AssertTrue | 被标注的元素必须为 true |
@AssertFalse | 被标注的元素必须为 false |
@Min(value) | 被标注的元素必须是一个数字,其值必须大于等于指定的最小值 |
@Max(value) | 被标注的元素必须是一个数字,其值必须小于等于指定的最大值 |
@DecimalMax(value) | 被标注的元素必须是一个数字,其值必须大于等于指定的最大值 |
@DecimalMin(value) | 被标注的元素必须是一个数字,其值必须小于等于指定的最小值 |
@size | 被标注的元素的大小必须在指定的范围内 |
@Digits(integer,fraction) | 被标注的元素必须是一个数字,其值必须在可接受的范围内;integer 指定整数精度,fraction 指定小数精度 |
@Past | 被标注的元素必须是一个过去的日期 |
@Future | 被标注的元素必须是一个将来的日期 |
@Pattern(value) | 被标注的元素必须符合指定的正则表达式 |
异常处理
@ExceptionHandler
局部异常处理仅能处理指定 Controller 中的异常。
示例 1:下面使用 @ExceptionHandler 注解实现。定义一个处理过程中可能会存在异常情况的 testExceptionHandle 方法。
@RequestMapping("/testExceptionHandle")
public String testExceptionHandle(@RequestParam("i") Integer i) {
System.out.println(10 / i);
return "success";
}
显然,当 i=0 时会产生算术运算异常。
下面在同一个类中定义处理异常的方法。
@ExceptionHandler({ ArithmeticException.class })
public String testArithmeticException(Exception e) {
System.out.println("打印错误信息 ===> ArithmeticException:" + e);
// 跳转到指定页面
return "error";
}
REST 风格
之前看过 restful 这块,其实就是遵循了规范的api接口,也没啥好说的
@Controller
public class UserController {
@RequestMapping("/torest")
public String torest() {
return "rest";
}
@RequestMapping(value = "/user/{id}", method = RequestMethod.GET)
public String hello(@PathVariable Integer id) {
System.out.println("test rest get:" + id);
return "success";
}
@RequestMapping(value = "/user/{id}", method = RequestMethod.POST)
public String hello() {
System.out.println("test POST:");
return "success";
}
@RequestMapping(value = "/user/{id}", method = RequestMethod.DELETE)
public String helloDelete(@PathVariable Integer id) {
System.out.println("test rest delete:" + id);
return "success";
}
@RequestMapping(value = "/user/{id}", method = RequestMethod.PUT)
public String helloPUt(@PathVariable Integer id) {
System.out.println("test rest put:" + id);
return "success";
}
}
参考c语言中文网教程学习