SpringMVC
1.什么是SpringMVC
SpringMvc是spring的一个模块,基于MVC的一个框架 无需中间整合层来整合 。它提供了一种基于MVC(Model-View-Controller)的架构模式,使得开发者能够更加轻松地构建灵活、可扩展的Web应用。
- 控制器(Controller):控制器负责处理用户请求,并调用相应的业务逻辑处理。在Spring MVC中,控制器通常使用注解(如@Controller)来标识,并通过@RequestMapping注解来映射URL请求。
- 视图(View):视图负责展示用户界面。在Spring MVC中,视图通常是一个JSP页面或者Thymeleaf模板。通过ViewResolver来将逻辑视图名称解析为实际的视图。
- 模型(Model):模型用于封装业务逻辑处理的数据。在Spring MVC中,模型通常是一个POJO(Plain Old Java Object),它可以通过@ModelAttribute注解来绑定到请求参数或者通过ModelAndView对象传递给视图。
- 数据绑定和验证:Spring MVC提供了强大的数据绑定和验证功能,可以方便地处理表单提交和数据校验。通过@Valid和@ModelAttribute注解以及JSR-303(Bean Validation)规范,可以实现数据验证。
- RESTful支持:Spring MVC框架支持RESTful风格的URL,可以方便地构建RESTful API。通过@RequestMapping注解的method属性来指定HTTP请求方法,以及通过@PathVariable注解来获取URL路径参数。
- 拦截器(Interceptor):Spring MVC提供了拦截器机制,可以对请求进行预处理和后处理。通过实现HandlerInterceptor接口,可以定义拦截器来实现日志记录、权限验证等功能。
2.常用配置简要说明
spring坐标
<dependencies>
<!-- spring相关坐标-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.8</version>
</dependency>
</dependencies>
组件扫描器
使用context:component-scan自动扫描标记@Controller的控制器类,
在springmvc.xml配置文件中配置如下:
<!-- 配置controller扫描包,多个包之间用,分隔 -->
<context:component-scan base-package="cn.itcast.springmvc.controller" />
注解驱动
可以在springmvc.xml配置文件中使用mvc:annotation-driven替代注解处理器和适配器的配置。
<!-- 注解驱动 -->
<mvc:annotation-driven />
视图解析器
在springmvc.xml配置文件中配置如下:
<!-- 配置视图解析器 -->
<bean
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!-- 配置逻辑视图的前缀 -->
<property name="prefix" value="/WEB-INF/jsp/" />
<!-- 配置逻辑视图的后缀 -->
<property name="suffix" value=".jsp" />
</bean>
使用注解代替xml
- 非自定义的Bean的配置:
<bean>
- 加载properties文件的配置:
<context:property-placeholder>
- 组件扫描的配置:
<context:component-scan>
- 引入其他配置文件:
<import>
注解 | 说明 |
---|---|
@Configuration | 用于指定当前类是一个Spring配置类,当创建容器时会从该类上加载注解 |
@ComponentScan | 用于指定Spring在初始化容器时要扫描的包。 作用和在Spring的xml配置文件中的<context:component-scan base-package=“cn.edu.cqie”/>一样 |
@Bean | 使用在方法上,标注将该方法的返回值存储到Spring容器中 |
@PropertySource | 用于加载.properties文件中的配置 |
@Import | 用于导入其他配置类 |
使用纯java代码替换web.xml和spring-mvc.xml
在config包下创建配置类SpringMvcConfig替换spring-mvc.xml
package com.cqgcxy.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan("com.cqgcxy.controller")
public class SpringMvcConfig {
}
在config包下创建配置类ServletContainersInitConfig替换web.xml
package com.cqgcxy.config;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.support.AbstractDispatcherServletInitializer;
public class ServletContainersInitConfig extends AbstractDispatcherServletInitializer {
@Override
protected WebApplicationContext createServletApplicationContext() {
AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
ctx.register(SpringMvcConfig.class);
return ctx;
}
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
protected WebApplicationContext createRootApplicationContext() {
return null;
}
}
3. SpringMVC多种数据类型响应
RequestMapping
- 作用:用于建立请求URL和处理请求方法之间的对应关系
- 位置:
- 类上,请求URL的第一级访问目录。此处不写的话,就相当于应用的根目录
- 方法上,请求URL的第二级访问目录,与类上的使用@ReqquestMapping标注的一级目录一起组成访问虚拟路径
- 属性:
- value:用于指定请求的URL。它和path属性的作用是一样的
- method:用于指定请求的方式
- params:用于指定限制请求参数的条件。它支持简单的表达式。要求请求参数的key和value必须和配置的一模一样
直接返回字符串
-
直接返回字符串:此种方式会将返回的字符串与视图解析器的前后缀拼接后跳转
资源地址:例:/WEB-INF/views/hello.jsp
-
返回带有前缀的字符串:
转发:例:forward:/WEB-INF/views/hello.jsp
重定向:例:redirect:/index.jsp
回写数据
-
通过SpringMVC框架注入的response对象,使用response.getWriter().print(“hello world”)回写数据,此时不需要视图跳转,业务方法返回值为void。
@RequestMapping("/data1") public void data1(HttpServletResponse response) throws IOException { response.setContentType("text/html;charset=utf-8"); response.getWriter().print("重庆工程学院"); }
-
将需要回写的字符串直接返回,但此时需要通过**@ResponseBody**注解告知SpringMVC框架,方法返回的字符串不是跳
转,而是直接在http响应体中返回。
@RequestMapping(value = "/data2",produces = "text/html;charset=utf-8") @ResponseBody public String data2(){ return "软件工程研究所"; }
4.获得Restful风格的参数
Restful是一种软件架构风格、设计风格,而不是标准,只是提供了一组设计原则和约束条件。主要用于客户端和服务器交互类的软件,基于这个风格设计的软件可以更简洁,更有层次,更易于实现缓存机制等。
Restful风格的请求是使用“url+请求方式”表示一次请求目的,HTTP协议里面四个表示操作方式的动词如下:
-
GET:用于获取资源
-
POST:用于新建资源
-
PUT:用于更新资源
-
DELETE:用于删除资源
例如:
-
/user/1 GET :得到id =1的user
-
/user/1 DELETE:删除id = 1的user
-
/user/1 PUT:更新id = 1的user
-
/user POST:新增user
上述url地址/user/1中的1就是要获得的请求参数,在SpringMVC中可以使用占位符进行参数绑定。地址/user/1可以写成/user/{id},占位符{id}对应的就是1的值。在业务方法中我们可以使用@PathVariable注解进行占位符的匹配获取工作。
5.使用拦截器
-
拦截器的作用
Spring MVC的拦截器类似于Servlet开发中的过滤器Filter,用于对处理器进行预处理和后处理。
将拦截器按一定的顺序联结成一条链,这条链称为拦截器链(Interceptor Chain)。在访问被拦截的方法或字段时,拦截器链中的拦截器就会按其之前定义的顺序被调用。拦截器也是AOP思想的具体实现。
-
拦截器和过滤器的区别
区别 过滤器 拦截器 使用范围 是servlet规范中的一部分,任何Java Web工程都可以使用 是SpringMVC框架自己的,只有使用了SpringMVC框架的工程才能用 拦截范围 在url-pattern中配置了/*之后,可以对所有要访问的资源拦截 只会拦截访问的控制器方法,如果访问的是jsp,html,css,image或者js 是不会进行拦截的 -
拦截器方法说明
方法名 说明 preHandle 方法将在请求处理之前进行调用,该方法的返回值是布尔值Boolean类型的,当它返回为false时,表示请求结束,后续的Interceptor和Controller都不会再执行;当返回值为true时就会继续调用下一个Interceptor的preHandle方法 postHandle 该方法是在当前请求进行处理之后被调用,前提是preHandle方法的返回值为true时才能被调用,且它会在DispatcherServlet进行视图返回渲染之前被调用,所以我们可以在这个方法中对Controller处理之后的ModelAndView对象进行操作 afterCompletion 该方法将在整个请求结束之后,也就是在DispatcherServlet渲染了对应的视图之后执行,前提是preHandle方法的返回值为true时才能被调用
日志拦截器实例
package com.gcxy.interceptor;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class LogInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 记录访问时间
long startTime = System.currentTimeMillis();
// 存入request对象
request.setAttribute("startTime", startTime);
// 记录Controller的路径和方法
String controllerPath = request.getRequestURI().split("\\?")[0];
// 获取handler对象的方法和名称
String methodName = ((HandlerMethod) handler).getMethod().getName();
System.out.println("访问时间"+ new java.util.Date() + ", Controller的路径:"+controllerPath + ", 对应的方法名称:"+ methodName);
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
//记录请求参数和请求相对路径
String requestParams = request.getQueryString(); // 获取查询字符串,请求的参数信息
// 获取URI,并且去除上下文路径,保留相对路径
String rPath = request.getRequestURI().substring(request.getContextPath().length());
System.out.println("请求参数为:" + requestParams + ", 请求相对路径: "+ rPath);
// 请求处理时长
long endTime = System.currentTimeMillis();
long startTime = (Long)request.getAttribute("startTime");
long cTime = endTime- startTime;
System.out.println("请求处理时长为:" + cTime + "ms");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// 记录响应结果和失败原因
int statusCode = response.getStatus(); // 获取状态码
String result = statusCode >= 200 && statusCode < 300 ? "成功" : "失败";
System.out.println("响应状态码为:" + statusCode + "响应结果:" + result);
if(ex != null){
System.out.println("失败原因:" + ex.getMessage());
}else {
System.out.println("响应成功: oK");
}
}
}
SpringMvcConfig设置
package com.gcxy.config;
import com.gcxy.interceptor.LogInterceptor;
import com.gcxy.interceptor.LoginInterceptor;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
@EnableWebMvc
@ComponentScan("com.gcxy.controller")
public class SpringMvcConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor()).addPathPatterns(new String[]{"/**"}).excludePathPatterns(new String[]{"/login"});
registry.addInterceptor(new LogInterceptor()).addPathPatterns("/**");
}
}
6. 什么是AOP和IOC
AOP为Aspect Oriented Programming的缩写,意思为面向切面编程,是通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP实际上是OOP(面向对象编程)的补充,利用AOP的思想可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
IOC(Inversion of control )控制反转/反转控制。它是一种思想不是一个技术实现。描述的是:Java 开发领域对象的创建以及管理的问题。
例如:现有类 A 依赖于类 B
传统的开发方式 :往往是在类 A 中手动通过 new 关键字来 new 一个 B 的对象出来使用 IoC 思想的开发方式 :不通过 new 关键字来创建对象,而是通过 IoC 容器(Spring 框架) 来帮助我们实例化对象。我们需要哪个对象,直接从 IoC 容器里面过去即可。
从以上两种开发方式的对比来看:我们 “丧失了一个权力” (创建、管理对象的权力),从而也得到了一个好处(不用再考虑对象的创建、管理等一系列的事情)
如何理解AOP
假如这里有两个流程,这两个流程都需要进行安全校验、性能统计等辅助逻辑,传统OOP很难将这些逻辑进行封装,OOP是至上而下的编程方式,犹如一个树状图,A调用B,B调用C,或者A继承B,B继承C,这种方法对于业务逻辑是合适的,可以通过调用或继承的方式进行复用,但对于辅助逻辑就显得不太灵活。
AOP就是为了解决这一问题存在的。AOP可以做到在不修改原有代码同时,能让辅助逻辑在所有业务逻辑中生效,就像一把铡刀横穿所有方法。OOP是纵向的,而AOP做到了横向,两者结合可以构建出良好的程序结构。
有了AOP,我们就可以把几个类共有的代码,抽取到一个切片中,等到需要时再切入对象中去,从而改变其原有的行为。
以下是关于AOP的一些名词的解释。
- Target(目标对象):织入 advice 的目标对象. 目标对象也被称为 advised object.
- Proxy(代理):一个类被AOP织入增强后,就产生一个结果代理类
- Joinpoint(连接点):在应用执行过程中能够插入切面的一个点。(注:就是抽象的「切点」声明所指代的那些具体的点。
- Pointcut(切入点)::一组连接点的总称,用于指定某个增强应该在何时被调用
- Advice(通知/增强): Advice 是一个动作, 即一段 Java 代码, 这段 Java 代码是作用于Point Cut 所限定的那些Join Point 上的
- Aspect(切面):Aspect 由 Pointcut 和 Advice 组成, 它既包含了横切逻辑的定义, 也包括了连接点的定义. Spring AOP就是负责实施切面的框架, 它将切面所定义的横切逻辑织入到切面所指定的连接点中.
- Weaving (织入):是指把增强应用到目标对象来创建新的代理对象的过程。spring采用动态代理织入,而AspectJ采用编译期织入和类装载期织入
我们为什么要使用AOP?
我们可以利用AOP的思想来对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
7 事务
什么是事务
事务(Transaction)是访问并可能更新数据库中各项数据项的一个程序执行单元(unit)。 事务具有四个特征:原子性、一致性、隔离性和持久性。这四个特征通常称为ACID。
事务的四个特征
原子性(Atomicity)
原子性是指事务是一个不可分割的工作单位,整个事务中的所有操作要么全部提交成功,要么全部失败回滚,对于一个事务来说,不可能只执行其中的一部分操作。
一致性(Consistency)
一致性是指事务必须使数据库从一个一致性状态变换到另一个一致性状态。 也就是说事务前后数据的完整性必须保持一致。
隔离性(Isolation)
隔离性是指一个事务的执行不能有其他事务的干扰,事务的内部操作和使用数据对其他的并发事务是隔离的,互不干扰。
持久性(Durability)
持久性是指一个事务一旦提交,对数据库中数据的改变就是永久性的。此时即使数据库发生故障,修改的数据也不会丢失。接下来其他的操作不会对已经提交了的事务产生影响。
脏读、不可重复读、幻读
脏读
例:A事务读取B事务尚未提交的数据,此时如果B事务发生错误并执行回滚操作,那么A事务读取到的数据就是脏数据。就好像原本的数据比较干净、纯粹,此时由于B事务更改了它,这个数据变得不再纯粹。这个时候A事务立即读取了这个脏数据,但事务B良心发现,又用回滚把数据恢复成原来干净、纯粹的样子,而事务A却什么都不知道,最终结果就是事务A读取了此次的脏数据,称为脏读。
这种情况常发生于转账与取款操作中
不可重复读
意思是前后多次读取,数据内容不一致
例:事务A在执行读取操作,由整个事务A比较大,前后读取同一条数据需要经历很长的时间 。而在事务A第一次读取数据,比如此时读取了小明的年龄为20岁,事务B执行更改操作,将小明的年龄更改为30岁,此时事务A第二次读取到小明的年龄时,发现其年龄是30岁,和之前的数据不一样了,也就是数据不重复了,系统不可以读取到重复的数据,成为不可重复读。
幻读
前后多次读取,数据总量不一致
例:事务A在执行读取操作,需要两次统计数据的总量,前一次查询数据总量后,此时事务B执行了新增数据的操作并提交后,这个时候事务A读取的数据总量和之前统计的不一样,就像产生了幻觉一样,平白无故的多了几条数据,成为幻读。
事务的隔离级别
未提交读(Read uncommitted):最低的隔离级别,允许“脏读”(dirty reads),事务可以看到其他事务“尚未提交”的修改。如果另一个事务回滚,那么当前事务读到的数据就是脏数据。
提交读(read committed):一个事务可能会遇到不可重复读(Non Repeatable Read)的问题。不可重复读是指,在一个事务内,多次读同一数据,在这个事务还没有结束时,如果另一个事务恰好修改了这个数据,那么,在第一个事务中,两次读取的数据就可能不一致。
可重复读(repeatable read):一个事务可能会遇到幻读(Phantom Read)的问题。
串行化(Serializable):最严格的隔离级别,所有事务按照次序依次执行,不会出现脏读、不可重复读、幻读的情况。虽然 Serializable 隔离级别下的事务具有最高的安全性,但是,由于事务是串行执行,所以效率会大大下降,应用程序的性能会急剧降低。如果没有特别重要的情景,一般都不会使用 Serializable 隔离级别。
上面提到的需要的依赖
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.8</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.0</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
</dependencies>