Spring的AOP思想 & SpringMVC基本操作
Spring的AOP: ( Aspect Oriented Programming ) 面向切面编程
就是将一些重复的逻辑抽取为一个通知方法,然后通过切点来匹配哪些目标方法需要应用这个通知方法,其中利用了代理技术,在代理中检查切点是否匹配,是否要调用哪些通知方法,然后再进行调用目标方法
代理技术:Spring中的代理技术:JDK动态代理 和 CGlib 代理
静态代理(Proxy):代理类由程序员自己编写的,不是由程序自动生成的
****注意:代理类 必须与 目标类 实现 同一接口 , 静态代理实现中,一个委托类对应一个代理类,代理类在编译期间就已经确定
静态代理实现的例子: 用 **伪代码** 实现对 事务管理的重复逻辑 的重用
1:提供一个代理类(UserServiceProxy)
1) 获取目标方法的对象和实际参数
2)调用通知类的 invoke() 方法
import java.lang.reflect.Method;
/**
* UserService 的静态代理类,必须与目标类实现同一 UserService 接口,(目的是让使用者察觉不出是代理替换了原来的目标)
* 1) 调用通知类的invoke方法
* 2) 利用反射获取方法对象和方法实际参数
*/
public class UserServiceProxy implements UserService {
private TranscationAdvice advice = new TranscationAdvice();
static Method business1;
static Method business2;
static {
// 通过反射获得 UserService 类对象
Class<UserService> c = UserService.class;
try {
// 通过反射调用获得方法对象,
business1 = c.getMethod("business1");
business2 = c.getMethod("business2");
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
@Override
public void business1() {
//调用通知类的 invoke() 方法
advice.invoke(this,business1,new Object[]{});
}
@Override
public void business2() {
//调用通知类的 invoke() 方法
advice.invoke(this,business2,new Object[]{});
}
}
2:提供一个通知类(TranscationAdvice):将 目标方法 和 通知方法 结合在一起
1) 实现了事务管理的重复代码
2)利用反射调用目标对象的方法
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
/**
* 通知类: 放置重复的逻辑代码,例如 事务管理, 实现 InvocationHandler 通知接口(包含invoke()方法),使得代码更加通用
*
* 1) 反射调用了目标对象的方法
* 2) 把重复代码和目标方法联系在了一起
*/
public class TranscationAdvice implements InvocationHandler {
// 目标类
private UserServiceTarget target = new UserServiceTarget();
/**
* 通知类中重复代码的方法,通过方法的反射调用
* @param proxy
* @param method
* @param args
* @return
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args){
System.out.println("开启事务");
Object obj = null;
try {
// 调用目标方法
obj = method.invoke(target,args);
System.out.println("提交事务");
}catch (Exception e) {
System.out.println("回滚事务");
e.printStackTrace();
}
return obj;
}
}
测试代码:
import com.chen.service.UserService;
import com.chen.service.UserServiceProxy;
import com.chen.service.UserServiceTarget;
/**
* 静态代理: 自己实现代理类 *.java -> javac -> *.class -> java -> 加载该class到虚拟机
*/
public class AopTest {
public static void main(String[] args) {
/**
* 获得的是代理类对象:class com.chen.service.UserServiceProxy
**/
// 通过 代理类 将 通知类 和 目标类 结合在一起调用
UserService service = new UserServiceProxy();
service.business1();
System.out.println("================================");
// 直接调用目标类,获得的是 目标类对象:class com.chen.service.UserServiceTarget
UserService service1 = new UserServiceTarget();
service1.business1();
}
}
动态代理:
与静态代理不同,动态代理不需要我们手动编写代理类,在程序运行期间, jdk会根据反射机制自动生成一个实现代理接口的匿名类(代理类), 在调用具体方法前调用InvokeHandler(通知接口)来处理
动态代理的代码实现: 主要实现 Proxy.newProxyInstance(类加载器, 要实现的接口数组, InvocationHandler);
测试代码:
import java.lang.reflect.Proxy;
/**
* JDK 的动态代理:自动生成代理类,不用程序员自己实现代理类,直接生成了*.class字节码, 加载该class到虚拟机
*/
public class JdkDynamicProxy {
public static void main(String[] args) {
/**
* 创建一个新的代理类对象
* 参数1, 类加载器
* 参数2, 代理类, 要实现哪些接口 Class[]{}
* 参数3, InvocationHandler (通知对象), 其中包含了代理要执行的代码
*/
// 得到类加载器
ClassLoader classLoader = JdkDynamicProxy.class.getClassLoader();
// 返回的是一个 代理类对象:JDK自动生成的代理类
UserService service = (UserService) Proxy.newProxyInstance(classLoader,new Class[]{UserService.class},new TranscationAdvice());
service.business1();
}
}
动态代理的优缺点:
优点:
动态代理中,代理类并不是在Java代码中实现,而是在运行时期生成,相比静态代理,动态代理可以很方 便的对委托类的方法进行统一处理,如添加方法调用次数、添加日志功能等等
缺点:
通过反射类Proxy和InvocationHandler回调接口实现的jdk动态代理,要求委托类必须实现一个接口, 但事实上并不是所有类都有接口,对于没有实现接口的类,便无法使用该方式实现动态代理
CGLib动态代理:CGLib采用底层的字节码技术,原理是通过字节码技术为一个类创建子类, 并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑
***cglib生成的代理类,继承了代理对象,代理与被代理对象是继承关系***
Spring两种代理方式: 默认使用 JDK动态代理,若 没实现接口,则使用CGLib动态代理
JDK动态代理
若目标对象实现了若干接口,spring使用JDK的java.lang.reflect.Proxy类代理。
优点:因为有接口,所以使系统更加松耦合
缺点:为每一个目标类创建接口CGLib动态代理
若目标对象没有实现任何接口,spring使用CGLIB库生成目标对象的子类。
优点:因为代理类与目标类是继承关系,所以不需要有接口的存在。
缺点:因为没有使用接口,所以系统的耦合性没有使用JDK的动态代理好
Spring的AOP实现:
1)在 pom.xml 文件中添加 AOP 相关依赖
<!-- 新加的依赖 -->
<!-- spring aop -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>4.3.17.RELEASE</version>
</dependency>
<!-- 第三方 aop依赖 aspectJ -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.9</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.8.9</version>
</dependency>
2)在 spring.xml的配置文件中引入 aop 的命名空间(xmlns:aop)
<!-- 引入 aop 的命名空间 -->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 启动 aop相关的注解 能够帮我们生成底层的代理对象 Proxy -->
<aop:aspectj-autoproxy/>
<!-- 目标类 -->
<bean id="orderServiceTarget" class="com.chen.spring.service.OrderServiceTarget"></bean>
<!-- 通知类 -->
<bean id="transcationAdvice" class="com.chen.spring.advice.TranscationAdvice"></bean>
<bean id="beforeAdvice" class="com.chen.spring.advice.BeforeAdvice"></bean>
<bean id="afterAdvice" class="com.chen.spring.advice.AfterAdvice"></bean>
<bean id="afterReturningAdvice" class="com.chen.spring.advice.AfterReturningAdvice"></bean>
<bean id="afterThrowingAdvice" class="com.chen.spring.advice.AfterThrowingAdvice"></bean>
<bean id="countTimeAdvice" class="com.chen.spring.advice.CountTimeAdvice"></bean>
</bean>
3) 编写相应的 通知类(Advice)
package com.chen.spring.advice;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
/**
* 通知类: 事务管理的方法
*
*/
//切面= 通知+切点
@Aspect
public class TranscationAdvice {
/**
* @Around:环绕通知: 决定了哪些目标方法会与 该通知方法一起使用, 通过切点表达式 within(包名.类名) 匹配**哪些目标方法**要和下面的**通知方法**结合到一起使用
*
* 作用: 当 OrderServiceTarget中的任意一个方法调用时,都会和该 transcation() 方法结合在一起
*/
@Around("within(com.chen.spring.service.OrderServiceTarget)")
/**
* execution(访问修饰符 返回值类型 包名.类名.方法名( 参数类型... )): 指定目标方法配合该公职方法一起使用
* 注意 ' * ' :可以匹配任意类型, 可以出现在方法返回值,方法名,类名当中
* 注意' .. ' :可以匹配方法参数,表示参数的个数和类型都是任意的
*/
/* @Around("execution(public * com.chen.spring.service.OrderServiceTarget.service3(..))")*/
/**
* @annotation(包名.注解名)
* 它是根据方法上的注解进行匹配, 如果有注解则匹配, 只匹配使用了 @Transcational 注解的目标方法
*
* */
//@Around("@annotation(org.springframework.transaction.annotation.Transactional)")
// 这个方法统称为 通知方法,负责处理重复的逻辑代码,并在内部调用相对应的目标方法
public Object transcation(ProceedingJoinPoint point){
System.out.println("开启事务");
Object obj = null;
try{
// 调用目标方法
obj = point.proceed();
System.out.println("提交事务");
} catch (Throwable throwable) {
System.out.println("回滚事务");
throwable.printStackTrace();
}
return obj;
}
}
测试代码:
package test;
import com.chen.spring.service.OrderService;
import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* Spring 的AOP 动态代理调用流程:使用上,尽量实现了无侵入的效果,原来的代码不受影响
* 1) 获得 Spring 容器,检查 **是否** 要为哪些 <bean> 创建代理类
* 2) 检查所有的切点表达式( @Around("within()") ),看哪些目标**匹配到了表达式**,为匹配到的目标动态**创建代理类**
* 3) 获得对象时( getBean() ),检查该对象是否有已经创建好的代理类,如果已经有代理类,则返回该对象的代理类对象
* 4) 代理类调用目标方法时,先经过 (多个)通知类,继续匹配切点表达式,如果匹配,则进行下面的通知调用(根据不同类型的通知进行先后调用)
* 5) 匹配的通知调用后,再在通知内调用目标方法
*/
public class SpringAopTest {
@Test
public void test1(){
// 获得 spring 容器,
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
// 返回的是 Spring 自动生成的动态代理类
OrderService service = context.getBean(OrderService.class);
service.service5();
}
}
SpingAOP动态代理的调用流程:
1) 获得 Spring 容器,检查 **是否** 要为哪些 <bean> 创建代理类
2) 检查所有的切点表达式( @Around("within()") ),看哪些目标**匹配到了表达式**,为匹配到的目标动态**创建代理类**
3) 获得对象时( getBean() ),检查该对象是否有已经创建好的代理类,如果已经有代理类,则返回该对象的代理类对象
4) 代理类调用目标方法时,先经过 (多个)通知类,继续匹配切点表达式,如果匹配,则进行下面的通知调用(根据不同类型的通知进行先后调用)
5) 匹配的通知调用后,再在通知内调用目标方法
AOP:
代理 Proxy 代理类
目标 Target 被代理的对象
通知 Advice 包含重复逻辑的代码
切点 Pointcut 把匹配到的目标方法和通知方法结合在一起,定义的是一种匹配规则
切面 Aspect = 切点 + 通知
aspectj中定义的一些切点表达式
1) within(包名.类名) 这个类中所有方法执行时,都会应用通知方法
eg: @Around("within(com.chen.spring.service.OrderServiceTarget)")2)execution(访问修饰符 返回值类型 包名.类名.方法名( 参数类型... ))
注意 * : 可以匹配任意类型, 可以出现在方法返回值,方法名,类名当中
注意 .. :可以匹配方法参数,表示参数的个数和类型都是任意的
eg: @Around("execution(public * com.chen.spring.service.OrderServiceTarget.service3(..))")3)@annotation(包名.注解名)
它是根据方法上的注解进行匹配, 如果有注解则匹配eg: @Around("@annotation(org.springframework.transaction.annotation.Transactional)")
通知类型:
环绕通知: @Around(切点表达式) 加在通知方法上(功能最全的), 通知方法环绕着目标方法
前置通知: @Before 通知代码只会出现在目标方法之前
正常结束通知: @AfterReturning 在目标方法正常结束后,调用通知方法
异常通知: @AfterThrowing 在目标方法出现异常后,调用通知方法
结束通知: @After 在目标方法结束后(正常或异常),总会调用的通知
Spring MVC:是一个基于 spring构建 web应用程序的全功能MVC模块,它把程序分为 Model、View、Controller三个部分,将每个部分赋予不同的职能, 减少了错误的出现,增加了组内成员的配合,提高了程序的可维护性
Model 模型 - 数据和操作数据的逻辑(狭义的就是数据) 包括了实体类和业务类(例如 User,UserService)
View 视图 - 数据展现, 包括(jsp, jstl, el)
Controller 控制器 把模型和视图关联在一起, 包括servlet
Spring-MVC的重要特点:
- Spring-MVC 拥有强大的灵活性,非入侵性和可配置性
- Spring-MVC 提供了一个前控制器(DispatcherServlet),开发者无需额外开发控制器对象
- Spring-MVC 分工明确,包括控制器,验证器,命令对象,模型对象,处理程序映射。视图解析器.. 每个功能实现都 由一个专门的对象负责完成
- Spring-MVC 可以自动绑定用户输入,并正确地转换数据类型,能自动将字符串解析为模型数据相对应的数据类型
- Spring-MVC 使用一个 名称=值 的Map对象实现更加灵活的模型数据传输
- Spring-MVC 内置了常见的校验器,可以校验用户输入,如果校验不通过,则重定向回输入表单
- Spring-MVC 支持国际化,根据用户的区域显示多国语言,并且国际化的配置非常简单
- Spring-MVC 支持多种视图技术,常见的有 jsp,FreeMarker,Velocity
- Spring-MVC 提供了一个简单而强大的JSP标签库,支持数据绑定功能,使得编写 JSP 页面更加容易
Spring-MVC的工作原理:
Spring-MVC的基本使用:
1)在 pom.xml 的文件中添加进 mvc 相关的依赖
***注意:pom.xml文件中需要添加`<packaging>war</packaging>` (决定了是maven的web项目)
然后手动补充一个src/main/webapp目录, src/main/webapp/WEB-INF/web.xml
在配置tomcat时选择 `项目名:war exploded`
<dependencies>
<!-- spring 容器 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.3.17.RELEASE</version>
</dependency>
<!-- spring 对mvc的支持 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>4.3.17.RELEASE</version>
</dependency>
<!-- 添加servlet相关依赖 -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope> <!-- tomcat已经提供了该jar包,此jar包只工作在编译环境 -->
</dependency>
<!-- jsp 的依赖-->
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>javax.servlet.jsp-api</artifactId>
<version>2.2.1</version>
<scope>provided</scope> <!-- tomcat已经提供了该jar包,此jar包只工作在编译环境 -->
</dependency>
<!-- jstl 的依赖-->
<dependency>
<groupId>jstl</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<!--
如果转换json时出现了错误: No converter found for return value of type:
是因为json转换的jar包没有加, 可以在pom文件中加入相应的依赖:
-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.1</version>
</dependency>
</dependencies>
2) 在 resources 目录下 配置 spring-mvc.xml 文件
引入 mvc 的命名空间(xmlns:mvc)
<?xml version="1.0" encoding="UTF-8" ?>
<!-- 引入 mvc 命名空间(xmlns:mvc)-->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!-- 解决无法直接访问静态资源(图片,html,css,js等)
原因: DispatcherServlet的路径是/ 跟tomcat中处理静态资源(图片)的Servlet路径冲突了
所以所有静态资源(图片)的请求被DispatcherServlet所处理,把图片的路径当做了控制器路径
解决办法:
在spring配置文件中加入:`<mvc:default-servlet-handler/>`
-->
<mvc:default-servlet-handler/>
<!-- 启动 mvc 相关注解-->
<mvc:annotation-driven/>
<!-- 将 控制类 交给 spring容器 管理-->
<bean id="controllerDemo" class="com.chen.controller.ControllerDemo"></bean>
<!-- 视图解析器:处理控制类返回的视图名,判断该视图是哪种视图
解析为 jsp视图技术:
根据 控制中的方法返回的 视图名 进行 前缀(/:指的是从当前目录(webapp)下访问) 和 后缀(后缀名".jsp")的拼接
"/" + "视图名" + ".jsp" ==> /视图名.jsp :相当于生成了访问jsp页面的路径,再进行请求转发 request.getRequestDispatcher("/hello.jsp").forward(request,response);
例:"/" + "hello" + ".jsp" ==> /hello.jsp
-->
<mvc:view-resolvers>
<mvc:jsp prefix="/" suffix=".jsp"/>
</mvc:view-resolvers>
</beans>
3)配置 web.xml 文件 ,配置 前控制器(DispatcherServlet)
<!-- 前控制器的职责:
1.作为统一入口
2.创建spring容器
3.在tomcat启动时,就把spring容器创建好
-->
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 告诉 DispatcherServlet springmvc.xml 配置文件在哪里,并将spring容器的创建好-->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value>
</init-param>
<!-- 在tomcat启动时,就创建这个 DispatcherServlet,并调用初始化方法-->
<load-on-startup>1</load-on-startup>
</servlet>
<!-- 给 DispatcherServlet 指定路径
假设浏览器 /hello 没有,会找 / 这个路径
/s1 没有,会找 / 这个路径
只要没有其他servlet能够精确匹配这个请求路径,这个请求都会被 / 的这个servlet来处理
-->
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
4) 编写控制器,并将该控制器类要交给spring容器管理
在 spring-mvc.xml 中加入:`<mvc:annotation-driven/>` 来启用mvc的相关功能
package com.chen.controller;
import com.chen.entity.User;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.Date;
/**
* 请求被处理的调用流程:
* 1) 请求从浏览器发送到tomcat: http://localhost:8080/test1
* 2) tomcat先去精确匹配该访问路径的servlet,如果匹配到了,则进入该servlet进行业务处理
* 3) 若没有匹配到,则匹配到了**前控制器**(DispatcherServlet)的路径 “ / ”,由它来处理请求
* 4) DispatcherServlet 则去spring容器中查找控制器中每个带有@RequestMapper("/路径")的方法
* 5) 如果匹配成功,则执行该方法,并返回一个结果(视图名)到 视图解析器中,解析器解析出该请求应该跳转到哪个jsp页面,并返回响应到浏览器上显示
* 6) 否则报 404 错误!!!
*/
//指示该类是个 Controller 控制器类
@Controller
public class ControllerDemo {
@RequestMapping("/test1")
public String test1(){
System.out.println("进入了ControllerDemo控制器");
//返回的是 视图名(jsp的名字)
return "hello";
}
/**
* 获取请求参数的三种方法: 相当于servlet中的 request.getParameter("参数名")
* 1) 通过 方法参数 与 请求参数 一一对应 (表单get方式的提交),spring容器会自动将数据类型转化
* 注意: 日期格式 @DateTimeFormat(pattern="yyyy-MM-dd")将日期按规定格式输出,否则报 400 bad request
* eg: http://localhost:8080/test2?name=chen&age=18&birthday=1997-11-24
*
* 2) 将 请求参数封装到一个**实体类**中,方法参数就是这个实体类
* eg: http://localhost:8080/test3?name=chen&age=18&birthday=1997-11-24
*
* 3) 路径参数: 将请求参数写到访问路径中,通过 **RequestMapper(" /路径/{参数名} ")** 获取到该请求参数的值
* 再在方法参数中利用注解**@PathVariable("参数名")**将获取到的**请求参数值**赋值给方法中的**参数**
* eg: @RequestMapping("/test4/{id}/{name}")
* @PathVariable("id")int x,@PathVariable("name")String n
* http://localhost:8080/test4/1/chen
*
*/
@RequestMapping("/test2")
public String test2(String name, int age, @DateTimeFormat(pattern = "yyyy-MM-dd") Date birthday){
System.out.println("name: "+name+" age: "+age+" birthday:"+birthday);
// 返回视图名,进入视图解析器
return "hello";
}
@RequestMapping("/test3")
public String test3(User user){
System.out.println("name: "+user.getName()+" age: "+user.getAge()+" birthday:"+user.getBirthday());
return "hello";
}
@RequestMapping("/test4/{id}/{name}")
public String test4(@PathVariable("id")int x,@PathVariable("name")String n){
System.out.println("name: "+n+" id: "+x);
return "hello";
}
/**
* Model: 代表了模型数据, 它也是加在控制器方法之上 可以直接使用它的实现类ModelMap或Map
* 向模型添加数据: model.addAttribute("变量名", 值);
* model中的数据在转发之前,都会被存入request作用域
* 相当于 request.setAttribute("username","zhangsan");,添加进 request 的作用域,在 jsp 页面上用 EL(${}) 表达式取出来
*/
@RequestMapping("/test5")
public String test5(User user, Model model){
model.addAttribute("name",user.getName());
model.addAttribute("age",user.getAge());
model.addAttribute("birthday",user.getBirthday());
return "hello";
}
@RequestMapping("/test6")
@ResponseBody//把方法的返回值转换成json字符串
public User test6(){
User user = new User("chen",18,new Date());
return user;
}
}
5) 处理视图名: 控制器方法返回的就是视图名,通过视图解析器将视图名解析出来相应的 jsp 路径 根据该路径请求转发到浏览器上显示 ( request.getRequestDispatcher("jsp路径").forward(request,response))
在 spring-mvc.xml 文件中配置视图解析器
<!-- 视图解析器:处理控制类返回的视图名,判断该视图是哪种视图
解析为 jsp视图技术:
根据 控制中的方法返回的 视图名 进行 前缀(/:指的是从当前目录(webapp)下访问) 和 后缀(后缀名".jsp")的拼接
"/" + "视图名" + ".jsp" ==> /视图名.jsp :相当于生成了访问jsp页面的路径,再进行请求转发 request.getRequestDispatcher("/hello.jsp").forward(request,response);
例:"/" + "hello" + ".jsp" ==> /hello.jsp
-->
<mvc:view-resolvers>
<mvc:jsp prefix="/" suffix=".jsp"/>
</mvc:view-resolvers>
6) 启动 tomcat 服务器,进行访问
Spring-MVC请求被处理流程:
- 请求从浏览器发送到tomcat, 地址为:http://localhost:8080/htest1
- tomcat先去精确匹配该访问路径的servlet,如果匹配到了,则进入该servlet进行业务处理
- 若没有匹配到,则匹配到了**前控制器**(DispatcherServlet)的路径 “ / ”,由它来处理请求
- DispatcherServlet 则去spring容器中查找控制器中每个带有@RequestMapper("/路径")的方法
- 如果匹配成功,则执行该方法,并返回一个结果(视图名)到 视图解析器中,解析器解析出该请求应该跳转到哪个jsp页面,并返回响应到浏览器上显示
- 否则报 404 错误!!!
Spring-MVC获取请求参数的三种方式:
1) 通过 方法参数 与 请求参数 一一对应 (表单get方式的提交),spring容器会自动将数据类型转化
注意: 日期格式 @DateTimeFormat(pattern="yyyy-MM-dd")将日期按规定格式输出,否则报 400 bad request
eg: http://localhost:8080/test2?name=chen&age=18&birthday=1997-11-24
2) 将 请求参数封装到一个**实体类**中,方法参数就是这个实体类
eg: http://localhost:8080/test3?name=chen&age=18&birthday=1997-11-24
3) 路径参数: 将请求参数写到访问路径中,通过 **RequestMapper(" /路径/{参数名} ")** 获取到该请求参数的值
再在方法参数中利用注解**@PathVariable("参数名")**将获取到的**请求参数值**赋值给方法中的**参数**
eg: @RequestMapping("/test4/{id}/{name}")
@PathVariable("id")int x,@PathVariable("name")String n
http://localhost:8080/test4/1/chen
Spring-MVC使用模型接口(Model)进行数据处理
/**
* Model: 代表了模型数据, 它也是加在控制器方法之上 可以直接使用它的实现类ModelMap或Map
* 向模型添加数据: model.addAttribute("变量名", 值);
* model中的数据在转发之前,都会被存入request作用域
* 相当于 request.setAttribute("username","zhangsan");,添加进 request 的作用域,在 jsp 页面上用 EL(${}) 表达式取出来
*/
@RequestMapping("/test5")
public String test5(User user, Model model){
model.addAttribute("name",user.getName());
model.addAttribute("age",user.getAge());
model.addAttribute("birthday",user.getBirthday());
return "hello";
}
JSP代码:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
hello,world!!!!
${name}
${age}
${birthday}
</body>
</html>
页面显示:
Spring-MVC的常见错误:
1)中文乱码问题:表单提交数据时,中文显示乱码
解决办法: 配置一个编码过滤器(CharacterEncodingFilter)进行解码,防止出现乱码问题
<!-- 防止中文乱码: spring提供了一个已经实现的编码过滤器(CharacterEncodingFilter),只需配置就行 -->
<filter>
<filter-name>encodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<!-- 配置编码 utf-8 -->
<param-name>encoding</param-name>
<param-value>utf-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
2)静态资源(图片,html,css,js等)无法访问问题
原因: DispatcherServlet的路径是/ 跟tomcat中处理静态资源(图片)的Servlet路径冲突了
所以所有静态资源(图片)的请求被DispatcherServlet所处理,把图片的路径当做了控制器路径
解决办法:
在 spring-mvc.xml 配置文件中加入:`<mvc:default-servlet-handler/>`
配置之后,springMVC容器会定义一个 DefaultServletHttpRequestHandler,它就像是个检查员,对进入
DispatcherServlet 的 URL 进行筛选,如果发现它是静态资源的请求,则将该请求转由给 web 服务器默认的
Servlet进行处理,如果不是静态资源的请求,才由 DispatcherServlet 继续进行处理
<!-- 解决无法直接访问静态资源(图片,html,css,js等)
原因: DispatcherServlet的路径是/ 跟tomcat中处理静态资源(图片)的Servlet路径冲突了
所以所有静态资源(图片)的请求被DispatcherServlet所处理,把图片的路径当做了控制器路径
解决办法:
在spring配置文件中加入:`<mvc:default-servlet-handler/>`
-->
<mvc:default-servlet-handler/>