Spring MVC
-
Spring MVC 使用 MVC 架构模式的思想,将 Web 应用进行职责解构,把一个复杂的 Web 应用划分成模型(Model)、控制器(Contorller)以及视图(View)三层,有效地简化了 Web 应用的开发,降低了出错风险,同时也方便了开发人员之间的分工配合
一.静态代理
静态代理的特点
-
代理类和目标类在编译时期就已经存在:静态代理中的代理类和目标类都是在编译时期就已经存在的类,它们实现了相同的接口或者继承了相同的抽象类。
-
代理类通常包含目标对象的引用:代理类中通常会包含一个对目标对象的引用,这样就可以调用目标对象的方法。
-
代理类可以在目标方法之前或之后添加额外的行为:代理类可以在调用目标方法之前执行一些操作(前置通知),也可以在目标方法执行后执行一些操作(后置通知)。
1>首先建立一个接口Shopping,在里面定义一个shopping方法
这个接口是所有服务类共同遵守的契约,无论是真实的服务还是代理服务都需要实现这个接口。
package com.easy.staticproxy; public interface Shopping { void shopping(); }
2>定义实现接口的目标类 : EasyA实现Shopping接口,在该类里面实现Shopping接口里的shopping方法
package com.easy.staticproxy; public class EasyA implements Shopping{ @Override public void shopping() { System.out.println("去购物"); } }
3>实现接口的代理类 : 实现了 Shoppoing 接口。它包含了一个 Shopping 类型的成员变量 s,该成员变量用于引用目标对象。构造函数Proxy接收一个 Shopping 类型的对象作为参数,并将其赋值给 s 成员变量。shopping() 方法在调用目标对象的同名方法之前和之后打印一些信息,以此来实现额外的行为。
package com.easy.staticproxy; public class Proxy implements Shopping{ Shopping s; public Proxy(Shopping s){ this.s=s; } @Override public void shopping() { System.out.println("-----1111"); s.shopping(); System.out.println("-----2222"); } }
4>使用静态代理 创建工厂类 : 创建了一个Factory类,定义一个工厂方法getShopping(),创建一个EasyA的实例a,使用这个实例创建一个Proxy实例s,并返回s,定义main方法,在main方法中调用getShopping()方法获取Shopping接口的实现,调用shopping方法打印结果
package com.easy.staticproxy; public class Factory { public static Shopping getShopping(){ EasyA a=new EasyA(); Shopping s=new Proxy(a); return s; } public static void main(String[] args) { Shopping shopping=getShopping(); shopping.shopping(); } }
二.动态代理
动态代理是在运行时动态创建代理对象的代理模式。Java 提供了两种动态代理机制:JDK 动态代理和 CGLIB 动态代理
JDK 动态代理
JDK 动态代理通过 java.lang.reflect.Proxy
类和 java.lang.reflect.InvocationHandler
接口来实现。被代理的对象必须实现至少一个接口,代理类也是通过实现相同的接口来创建的。InvocationHandler
的 invoke
方法会在代理对象的方法被调用时执行。
动态代理的特点
- 动态创建代理类:代理类是在运行时动态创建的,而不是在编译时就存在的。
- 灵活:可以为多个接口提供统一的代理逻辑,无需为每个接口单独编写代理类。
- 基于反射:通常使用 Java 的
java.lang.reflect.Proxy
类和java.lang.reflect.InvocationHandler
接口来实现。
动态代理的实现步骤
-
定义接口:定义一个或多个接口,这些接口将是代理类和目标类共同遵循的契约。
-
实现目标类:实现上述接口,定义具体的操作。
-
实现 InvocationHandler:创建一个实现了
InvocationHandler
接口的类,该类将包含代理逻辑。 -
创建代理对象:使用
Proxy.newProxyInstance()
方法创建代理对象。 -
使用代理对象:通过代理对象调用目标方法。
1>定义EasyInterface接口,实现一个easy()方法;
package com.easy.staticproxy.dynamic.jdk; public interface EasyInterface { void easy(); }
2>创建一个目标类 EasyObj,提供easy()方法的具体实现,打印正常业务逻辑
package com.easy.staticproxy.dynamic.jdk; public class EasyObj implements EasyInterface{ @Override public void easy() { System.out.println("---正常业务逻辑"); } }
3>实现Invocationhandler 创建类EasyInvocationhandler类
package com.easy.staticproxy.dynamic.jdk; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; public class EasyInvocationhandler implements InvocationHandler { private Object proxyedObj;//被代理对象 public EasyInvocationhandler(Object proxyedObj){ this.proxyedObj=proxyedObj; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object result=null;//定义方法的返回值对象 System.out.println("-----执行方法之前添加的业务"); //正常执行业务逻辑 result =method.invoke(proxyedObj,args); System.out.println("执行方法之后的处理"); return result; } }
4>
-
创建代理对象:
getProxy
方法接收一个实现了某个接口的对象obj
。- 使用
Proxy.newProxyInstance
方法来创建一个代理对象。这个方法需要三个参数:- 类加载器:
obj.getClass().getClassLoader()
用来获取obj
所属类的类加载器。 - 接口数组:
obj.getClass().getInterfaces()
获取obj
所实现的所有接口。 InvocationHandler
对象:new EasyInvocationHandler(obj)
创建了一个EasyInvocationHandler
实例,该实例将持有被代理对象的引用。
- 类加载器:
- 首先创建了一个
EasyObj
类的实例easy
,然后通过getProxy
方法获取了一个代理对象obj
。 - 检查
obj
是否是EasyInterface
的实例。如果是,则打印一条消息并调用easy
方法。 - 获取代理对象
obj
的类信息。 - 创建了一个
EasyA
类的实例easya
,再次通过getProxy
方法获取了一个新的代理对象obj
。 - 检查
obj
是否是Shopping
的实例,并打印结果。 - 将
obj
转换为Shopping
接口类型,并调用shopping
方法,打印。
package com.easy.staticproxy.dynamic.jdk; import com.easy.staticproxy.EasyA; import com.easy.staticproxy.Shopping; import java.lang.reflect.Proxy; public class Factory { public static Object getProxy(Object obj){ //JDK代理只能实现接口中的方法 return Proxy.newProxyInstance(obj.getClass().getClassLoader(),//类加载器 obj.getClass().getInterfaces(),//实现的接口 new EasyInvocationhandler(obj)); } public static void main(String[] args) { EasyObj easy=new EasyObj(); Object obj=getProxy(easy);//动态生成的一个代理类的对象 // System.out.println(obj); if(obj instanceof EasyInterface){ System.out.println("obj是代理对象是EasyInterface的实例"); EasyInterface e=(EasyInterface) obj; e.easy(); } Class c=obj.getClass(); // System.out.println(c); EasyA easya=new EasyA(); obj=getProxy(easya); System.out.println(obj instanceof Shopping); Shopping s=(Shopping) obj; s.shopping(); // System.out.println(obj.getClass()); } }
CGLIB 动态代理
CGLIB 是一个高性能的代码生成库,它通过字节码技术对类进行子类化,因此被代理的类不能是 final 类,同时其方法也不能是 final 的。CGLIB 不要求类实现接口,而是通过继承的方式实现代理。
总结
JDK 动态代理:
要求被代理对象实现接口。
通过 Proxy.newProxyInstance 方法创建代理对象。
适用于面向接口编程的场景。
CGLIB 动态代理:
不要求实现接口,而是通过继承实现。
适用于面向类编程的场景。
被代理的类不能是 final 类,方法也不能是 final 的。
三.面向切面 AOP
AOP (Aspect Orient Programming),直译过来就是 面向切面编程,AOP 是一种编程思想,是面向对象编程(OOP)的一种补充。
面向切面编程,实现在不修改源代码的情况下给程序动态统一添加额外功能的一种技术
Spring AOP的术语
Spring AOP 通知分类
示例代码:
package com.easy.aop; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.springframework.stereotype.Component; @Aspect @Component public class AOPObj { //定义切点 @Pointcut("execution(* com.easy.controller.EasyAController.testA(..))") public void pointCuttesta(){} @Before("pointCuttesta()") public void before(){ System.out.println("---前置通知"); } }
@Aspect 注解:这个注解标记了当前类是一个切面(Aspect),即用来封装横切关注点的行为。
@Component 注解:这个注解用于指示这个切面应该由 Spring 容器管理,并作为 Bean 进行注册。
定义切点:@Pointcut
注解定义了一个切点(Pointcut),指定了匹配的方法签名。这里的切点表达式 execution(* com.easy.controller.EasyAController.testA(..))
指定的是 com.easy.controller.EasyAController
类中的 testA
方法,无论该方法的返回类型是什么,也不论方法接受什么参数类型。
@Pointcut("execution(* com.easy.controller.EasyAController.testA(..))")
@Before
注解定义了一个前置通知(Advice),它会在匹配的连接点(Join Point)之前执行。这里的 "pointCuttesta()"
是引用前面定义的切点 pointCuttesta()
@Before("pointCuttesta()") public void before(){ System.out.println("---前置通知"); }
需要在网页上搜索testa网页,控制台才会打印 结果
四.SpringMVC
1.注解
@Controller:
这个注解会优先将返回的字符串解析成地址,然后跳转到这个地址,如果不想让它解析成地址可以使用@ResponseBody注解,这样就可以返回要返回的
@RestController
这个注解就是类似于加了@ResponseBody的@Controller
@RequestMapping
前端控制器,来浏览器输入这个括号内的字符串地址就会访问到这个方法
@PathVariable
从路径上获取参数,在浏览器输入本机/端口号/地址之后?参数=--
@requestparam
接收参数的时候使用
@Get/Post/Put/DeleteMapping
请求资源状态转换
接收前端参数
我们在类的前面用@Requestmapping定义出一级路径。
1.方法的参数名称和前台传递的参数名一样
@RequestMapping("user")//一级路径 public class EasyDController { //接受前端的参数 方法的参数名称和前台传递的参数名一样 @RequestMapping("parama") public String paramA(String name){ return "springmvc接收到的参数是:"+name; }
编辑我们在地址上面写name=张三,所以它输出的就是张三
2.接收多个参数
map 接受非常灵活 但是会有安全问题
@RequestParam可以将请求参数注入到对象上
如果不加这个注解,Map就只会是一个Moudle容器
使用这个之后即使不想接受这个参数也必须接受
@RequestMapping("paramb") public Map paramb(@RequestParam Map params){ return params; }@RequestParam Map params: 表示从HTTP请求中读取所有的请求参数,并将它们作为键值对存储在一个Map中。这里的params就是用来接收这些键值对的变量。
编辑我们在地址上填入什么都可以被写入Map,这很不安全
3.使用封装对象接收参数 程序中只接受所需要的数据(比较安全?)
只会接受想要的参数,如果注入参数会是默认值
程序中只接收我们需要的数据我们定义一个Staff类,里面有id,name,salary这三个属性,我们将Staff类的对象当做参数传入,只有在Staff中定义的属性才能被写入。Staff类的代码
package com.easy.bean; public class Staff { private Integer id; private String name; private int salary; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getSalary() { return salary; } public void setSalary(int salary) { this.salary = salary; } }
访问的代码:
//封装对象来接收参数 程序中只接收我们需要的数据 @RequestMapping("paramc") public Staff paramc(Staff staff){ return staff; }
编辑我们没有传入id所以它显示的是null,我们输入age但是Staff中未定义,所以他也无法写入
4.获取地址上的参数
//获取地址上的参数 @RequestMapping("paramd/{id}") public String paramd(@PathVariable Integer id, HttpServletRequest request){ String username=request.getParameter("username"); return "接收到的参数是:"+id+"----"+username; }
接收到的参数是:"+id+"----"+username; }@PathVariable从 URL 中提取路径变量,并将这些变量用作方法参数。
编辑从地址上获取参数id=12。
5.作用域对象
page HttpServletRequest HttpSession (ServletContext)application
五.请求资源状态转换
//REST 请求资源状态转换 //get post put delete //get user/1 user/get //post user.... 新增 user/add //delete user/1 删除 user/del?id=1 //put user/1 编辑 user/edit
六.拦截器
拦截器的执行流程
-
多个拦截器的执行流程。
-
在大型的企业级项目中,通常都不会只有一个拦截器,开发人员可能会定义许多不同的拦截器来实现不同的功能。
-
-
当请求的路径与拦截器拦截的路径相匹配时,程序会先执行拦截器类(MyInterceptor)的 preHandle() 方法。若该方法返回值为 true,则继续向下执行 Controller(控制器)中的方法,否则将不再向下执行;
-
控制器方法对请求进行处理;
-
调用拦截器的 postHandle() 方法,此时我们可以对请求域中的模型(Model)数据和视图做出进一步的修改;
-
通过 DispatcherServlet 的 render() 方法对视图进行渲染;
-
调用拦截器的 afterCompletion () 方法,完成资源清理、日志记录等工作。
多个拦截器的执行流程
代码示例:
@Component public class EasyInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("----preHandle"); // return HandlerInterceptor.super.preHandle(request, response, handler); return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("----postHandle"); // HandlerInterceptor.super.postHandle(request, response, handler, modelAndView); } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("----afterCompletion---整个请求处理完毕"); // HandlerInterceptor.super.afterCompletion(request, response, handler, ex); } }
七.转发和重定向
1.转发:同一个服务器中不同的服务进行转发
浏览器发送了一个请求 可以转发到项目中受保护的资源WEB-INF
转发是request对象执行forward方法
@RequestMapping("methoda")
public String methodA(){
System.out.println("---methodA");
return "forward:/methodb";
}
@RequestMapping("methodb")
@ResponseBody
public String methodB(){
System.out.println("---methodB");
return "this is methodB";
}
当我们输入methodA的地址时,它显示的是methodB中的方法,但地址不会改变,说明浏览器只发送了一次请求
2.重定向 : 可以在不同的服务器之间跳转
浏览器发送了两次请求
重定向是通过response对象通知浏览器重新访问 redirect
@RequestMapping("methoda")
public String methodA(){
System.out.println("---methodA");
// return "forward:/methodb";
// return "redirect:/methodb";//重定向
return "redirect:http://www.baidu.com";
}
@RequestMapping("methodb")
@ResponseBody
public String methodB(){
System.out.println("---methodB");
return "this is methodB";
}
在methodA中输入methodB的地址或者百度的地址,浏览器请求一次,然后methodA将methodB或者百度的地址回复给浏览器,然后浏览器按照地址进行第二次请求。
八.全局控制
首先定义一个Staff类
package com.easy.bean;
public class Staff {
private int id;
private String name;
private String sex;
private int salary;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public int getSalary() {
return salary;
}
public void setSalary(int salary) {
this.salary = salary;
}
}
然后我们通过CommonResult类定义返回结果的格式
package com.easy.common;
public class CommonResult {
private int code;
private String message;
private Object data;
public CommonResult(int code, String message, Object data) {
this.code = code;
this.message = message;
this.data = data;
}
public static CommonResult success(int code, String message, Object data) {
return new CommonResult(code, message, data);
}
public static CommonResult success(int code, String message) {
return new CommonResult(code, message, null);
}
public static CommonResult success(Object data) {
return new CommonResult(200, "操作成功", data);
}
public static CommonResult success() {
return new CommonResult(200, "操作成功", null);
}
public static CommonResult fail(int code, String message, Object data) {
return new CommonResult(code, message, data);
}
public static CommonResult fail(int code, String message) {
return new CommonResult(code, message, null);
}
public static CommonResult fail(){
return new CommonResult(400,"操作失败",null);
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
}
下面是增删改查Staff类对象的代码实现,每个都默认会返回成功的结果。
@ExceptionHandler(Exception.class) 注解用于指定一个方法来处理特定类型的异常。在这个例子中,它处理所有类型的Exception异常。当一个异常被抛出时,Spring框架会检查是否有用此注解标记的方法,并调用它来处理异常。
package com.easy.controller;
import com.easy.bean.Staff;
import com.easy.common.CommonResult;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@ControllerAdvice//全局控制
//@RequestMapping("staff")
public class StaffController {
@GetMapping("staff")
public CommonResult getList(Staff staff){
List<Staff> list=null;
System.out.println("获取数据");
return CommonResult.success(list);
}
@PostMapping("staff")
public CommonResult addStaff(Staff staff){
System.out.println("新增数据");
return CommonResult.success();
}
@DeleteMapping("staff/{id}")
public CommonResult deleteStaff(@PathVariable int id){
System.out.println("删除数据"+id);
return CommonResult.success();
}
@PutMapping("staff")
public CommonResult editStaff(Staff staff){
System.out.println("编辑数据");
return CommonResult.success();
}
@RequestMapping("ex")
public CommonResult ex(){
int a=12/0;
return CommonResult.success();
}
@ExceptionHandler(Exception.class)
@ResponseBody
public CommonResult exh(){
return CommonResult.success(200,"11111");
}
}
输入@ControllerAdvice就是全局控制,不写的话就只能控制本类。
九.Spring MVC运行原理
1.用户通过浏览器发起一个 HTTP 请求,该请求会被 DispatcherServlet(前端控制器)拦截;
2.DispatcherServlet 调用 HandlerMapping(处理器映射器)找到具体的处理器(Handler)及拦截器,
3.HandlerMapping将Handler以 HandlerExecutionChain 执行链的形式返回给 DispatcherServlet。
4.DispatcherServlet 将执行链返回的 Handler 信息发送给 HandlerAdapter(处理器适配器);
5.HandlerAdapter 根据 Handler 信息找到并执行相应的 Handler(即 Controller 控制器)对请求进行处理;
6.Handler 执行完毕后会返回给 HandlerAdapter 一个 ModelAndView 对象(Spring MVC 的底层对象,包括 Model 数据模型和 View 视图信息);
7.HandlerAdapter 接收到 ModelAndView 对象后,将其返回给 DispatcherServlet ;
8.DispatcherServlet 接收到 ModelAndView 对象后,会请求 ViewResolver(视图解析器)对视图进行解析;
9.ViewResolver 解析完成后,会将 View 视图并返回给 DispatcherServlet;
10.DispatcherServlet 接收到具体的 View 视图后,进行视图渲染,将 Model 中的模型数据填充到 View 视图中的 request 域,生成最终的 View(视图);
11.视图负责将结果显示到浏览器(客户端)。