Spring是一个分层的轻量级的开源Java框架。核心是IOC(Inverse of Control 控制反转)和AOP(Aspect Oriented Programming 面向切面编程)
AOP 面向切面
AOP (Aspect Orient Programming),直译过来就是 面向切面编程,AOP 是一种编程思想,是面向对象编程(OOP)的一种补充。AOP是通过动态代理实现的面向切面的编程,实现在不修改源代码的情况下给程序动态统一添加额外功能的一种技术。
AOP的应用场景:
日志、事务、权限、监测
Spring AOP的术语:
名称 | 说明 |
---|---|
Joinpoint(连接点) | 指那些被拦截到的点,在Spring中,指可以被动态代理拦截目标类的方法。 |
Pointcut(切入点) | 指要对哪些Joinpoint进行拦截,即被拦截的连接点。 |
Advice(通知) | 指拦截到Joinpoint之后要做的事情,即对切入点增强的内容。 |
Target(目标) | 指代理的目标对象 |
Weaving(植入/织入) | 指把增强代码应用到目标上,生成代理对象的过程 |
Proxy(代理) | 指生成的代理对象 |
Aspect(切面) | 切入点和通知的结合 |
通知分类有五种:
即通知切入的五种时机
通知 | 说明 |
---|---|
before(前置通知) | 通知方法在目标方法调用之前执行 |
after(后置通知) | 通知方法在目标方法返回或异常之后执行 |
after-returning(返回后通知) | 通知方法会在目标方法返回后执行 |
after-throwing(抛出异常通知) | 通知方法会在目标方法抛出异常后执行 |
around(环绕通知) | 通知方法会将目标方法封装起来 |
Spring AOP 织入时期
时期 | 说明 |
---|---|
编译期 | 切面在目标类编译时被织入,这种方式需要特殊的编译器,AspectJ的织入编译器就是以这种方式织入切面的 |
类加载期 | 切面的目标类加载到JVM时被织入,这种方式需要特殊的类加载器(ClassLoader)它可以再目标类引入应用之前增强目标类的字节码。 |
运行期 | 切面在应用运行到某个时期被织入,一般情况下,在织入切面时,AOP容器会为目标动态的创建一个代理对象,Spring AOP 采用的就是这种织入方式。 |
Spring AOP 基于AspectJ
AspectJ是一个基于Java语言的AOP框架,Spring2.0以后新增了对AspectJ切点表达式支持
定义切点:通过execution函数来定义切点
语法:execution(访问修饰符 返回类型 方法名 参数 异常)
1.匹配所有类public方法:execution(public * *(..))第一个*表示返回值 ..表示任意个任意类型的参数
2.匹配指定包下所有类所有方法: execution(* cn.xxx.dao.*.*(..)) 第一个想*表示忽略权限和返回值类型
3.匹配指定包下所有类所有方法:execution(* cn.xxx.dao..*(..))包含子包
4.匹配指定类所有方法: execution(* cn.xxx.service.UserService.*(..))
5.匹配实现特定接口所有类方法 : execution(* cn.xxx.dao.GenericDAO+.*(..))
6.匹配所有save开头的方法: execution(* save*(..))
7.匹配某个指定的方法: execution(* com.yh.dao.StudentService.save(..))
通过使用 @Aspect 注解,我们可以将一个类标记为切面类。
使用 @Pointcut 注解用于定义切点表达式
@Aspect @Component public class AOPObj { //定义切点 @Pointcut("execution(* com.easy.controller.EasyAController.testA(..))") public void pointCuttesta(){} @Before(value = "pointCuttesta()") public void before(){ System.out.println("-----前置通知"); } @AfterThrowing(value = "pointCuttesta()") public void afterThrowing(){ System.out.println("-----异常通知"); } @AfterReturning(value = "pointCuttesta()") public void afterReturn(){ System.out.println("-----返回通知"); } @After(value = "pointCuttesta()") public void after(){ System.out.println("-----前置通知"); } }
Spring 的代理模式
Spring的代理模式指的是Spring框架中使用代理对象来管理和增强目标对象的行为。
意图:为其他对象提供一种代理以控制对这个对象的访问。
主要解决:在直接访问对象时带来的问题,比如说:要访问的对象在远程的机器上。在面向对象系统中,有些对象由于某些原因(比如对象创建开销很大,或者某些操作需要安全控制,或者需要进程外的访问),直接访问会给使用者或者系统结构带来很多麻烦,我们可以在访问此对象时加上一个对此对象的访问层。
何时使用:想在访问一个类时做一些控制。
静态代理
静态代理的实现方式是创建一个实现了与目标对象相同接口的代理类,该代理类在方法调用前后添加额外的逻辑。代理类将目标对象注入进来,并在代理方法中调用目标对象的对应方法。这样,我们可以在不修改目标对象代码的前提下,通过代理类来对目标对象进行增强和控制。
例如,定义一个Shopping接口:
public interface Shopping { void shopping(); }
有一个接口实现类EasyA
public class EasyA implements Shopping{ @Override public void shopping(){ System.out.println("去购物"); } }
创建一个实现了相同接口的代理类:传入代理类的目标对象,在方法调用前后增加额外的业务逻辑,并在代理方法中调用目标对象的对应方法
public class Proxy implements Shopping{ //静态代理 Shopping s; //需要传入对象 public Proxy(Shopping s){ this.s=s; } //前后增加业务 @Override public void shopping(){ System.out.println("------一支穿云箭"); s.shopping(); System.out.println("------千军万马来相见"); } }
这里通过测试工厂类创建对象并演示:
静态代理的优点是实现简单,易于理解和调试。缺点是:
1.代码冗余:当多个被代理类,多个需增强方法的增强内容一样时,代码重复冗余
2.不易维护:每个被代理类需要专门维护一个代理类,类成倍增加,需增强方法增加时,需要同时维护代理类和被代理类
动态代理
动态代理在运行时动态地生成类字节码,加载进JVM,分为两种:
JDK动态代理
JDK动态代理是通过接口实现的代理方式。当目标对象实现了接口时,Spring会利用JDK的java.lang.reflect.Proxy类来创建代理对象,代理对象会实现与目标对象相同的接口,并且在代理方法中添加自定义的增强逻辑。
演示:定义一个接口:
public interface EasyInterface { void easy(); }
接口实现类中是基本的正常业务逻辑
public class EasyObj implements EasyInterface{ @Override public void easy(){ System.out.println("-----正常业务逻辑"); } }
定义代理处理器,实现 InvocationHandler 接口,在代理方法中添加自定义的增强逻辑。
//定义代理处理器 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("-------执行方法之前执行的业务"); try{ result=method.invoke(proxyedObj,args);//正常执行业务逻辑 }catch (Exception e){ System.out.println("抛出异常"); }finally { System.out.println("-------执行方法之后要做的处理"); } return result; } }
声明代理工厂,通过Proxy.newProxyInstance
方法创建了一个代理对象。该方法需要三个参数:
ClassLoader
:用于加载代理类的类加载器。interfaces
:目标对象所实现的接口数组,代理类会实现这些接口。InvocationHandler
:代理对象调用方法时的处理器。
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);//动态生成的一个代理类的对象 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(easy.getClass()); }
动态代理的优点是更加灵活,可以应用于多个目标对象,但实现和理解相对复杂一些。
CGLIB代理
CGLIB代理是通过继承实现的代理方式。当目标对象没有实现任何接口时,Spring会利用CGLIB库来创建代理对象,代理对象将继承目标对象的类,并覆盖其中的方法来添加自定义的增强逻辑。(目标对象的类不可以用final修饰)
被定义切点的类之后使用@Autoward注入时会注入此类的代理类(此类的子类,包含代理处理器,处理程序等等),而不是本类,通过动态代理实现。
前面在EasyAController的testA上定义了切点:
@Pointcut("execution(* com.easy.controller.EasyAController.testA(..))"
这里我们通过测试打印类对象发现:注入的是此类通过CGLIB动态代理自动生成的代理类:
Spring MVC
项目有三层架构:控制层、业务层、数据访问层
与项目的三层架构不同,Spring MVC的三层架构基于MVC设计模式。它将应用程序的不同部分分为三个独立的层:模型层(Model)、视图层(View)和控制层(Controller)。
@Controller 注解
在Spring MVC中,@Controller是一个用于标记控制器类的注解。使用@Controller注解的类被Spring容器识别为控制器,可以处理来自用户的请求。
@RequestMapping 注解
@RequestMapping是一个用于映射请求路径的注解。它可以用在类级别和方法级别上,用于标识控制器类或方法应该处理的请求路径。
修饰方法:
@RequestMapping 注解被标注在方法上时,value 属性值就表示访问该方法的 URL 地址。当用户发送过来的请求想要访问该 Controller 下的控制器方法时,请求路径就必须与这个 value 值相同
修饰类:
@RequestMapping 注解标注在控制器类上时,value 属性的取值就是这个控制器类中的所有控制器方法 URL 地址的父路径。也就是说,访问这个 Controller 下的任意控制器方法都需要带上这个父路径。
@RestController @RequestMapping("user") public class EasyDController { // ... }
@RequestMapping注解可以传入多种属性:
1. value 属性
- 在 @RequestMapping 注解中,value 属性用来设置控制器方法的请求映射地址。所有能够匹配到该请求映射地址的请求,都可以被该控制器方法处理
- value 属性是 @RequestMapping 注解的默认属性,如果我们在 @RequestMapping 注解中只设置了一个 value 属性,则该属性名可以被省略
- value 属性的取值是一个字符串类型的数组,表示该控制器方法可以匹配多个请求地址。
2. name 属性
- name 属性相当于方法的注释,用于解释这个方法是用来干什么的,使方法更易理解。
如:@RequestMapping(value = "toUser",name = "获取用户信息")
3. method 属性
- method 属性用来设置控制器方法支持的请求方式。如果一个控制器方法没有设置 @RequestMapping 注解的 method 属性,则说明该控制器方法支持全部请求类型,可以处理所有类型的请求。
- method 属性的取值是一个 RequestMethod 类型的数组,表示一个控制器方法支持多种方式的请求,常用的请求方式有 GET、POST、DELETE、PUT 等。
请求方式 | 作用 |
---|---|
GET(获取资源) | 用于从服务器获取数据,可以在URL中传递参数,参数会暴露在URL上,通常用于获取数据。 |
POST(提交数据) | 用于向服务器提交数据,数据不会暴露在URL上,通常用于提交表单数据、上传文件等。 |
DELETE(删除资源) | 用于删除服务器上的资源,通常用于删除指定的数据。 |
PUT(更新资源) | 用于更新服务器上的资源,通常用于更新指定的数据。 |
如:@RequestMapping(value = "/toUser",method = RequestMethod.GET)
- 我们也可以为同一个控制器方法指定支持多种类型的请求。例如,一个方法既支持 GET 方式的请求,也支持 POST 方式的请求
如:@RequestMapping(value = "/toUser",method = {RequestMethod.GET,RequestMethod.POST})
4. params 属性
- params 属性用于指定请求中的参数,只有当请求中携带了符合条件的参数时,控制器方法才会对该请求进行处理。
- params 属性的取值是一个字符串类型的数组,表示只有请求中同时携带了 params 属性指定的全部参数时,控制器方法才会对该请求进行处理。
如:@RequestMapping(value = "/testParam", params = {"name=javaeasy", "url=www.easy.com"})
5. headers 属性
- headers 属性用于设置请求中请求头信息,只有当请求中携带指定的请求头信息时,控制器方法才会处理该请求。
- header 属性是一个字符换类型的数组,表示只有当请求同时携带数组中规定的所有头信息时,控制器方法才会对该请求进行处理。
如:@RequestMapping(value = "toUser",headers = "Referer=http://c.biancheng.net")
映射地址支持 Ant 风格的路径
Spring MVC 还提供了对 Ant 风格路径的支持,我们可以在 @RequestMapping 注解的 value 属性中使用 Ant 风格的统配符,来设置请求映射地址。
Spring MVC 获取请求参数
1.通过形参获取请求参数
在 Controller 的控制器方法中设置与请求参数同名的形参,以获取请求中携带的参数。当浏览器发送的请求匹配到这个控制器方法时,Spring MVC 会自动将请求参数赋值给相应的方法形参。
注意:
(1)必须保证参数名一致
- 我们必须保证控制器方法的形参名称与请求中携带参数的名称完全一致(区分大小写),否则控制器方法接收到的请求参数值会是 null。
- 如果由于一些特殊原因,实在无法保证参数名严格一致,可以通过 @RequestParam 注解来解决。
(2)无视数据类型
- 在控制器方法中使用 String 字符串类型的形参接收所有的请求参数,也可以根据实际情况在控制器方法中使用对应数据类型的参数来接收请求参数,而无须自行进行数据类型转换。
(3)不适用于请求参数过多的请求
同名请求参数的处理方式:
//接收前端的参数 方法的参数名称和前台传递的参数名一样 @RequestMapping("parama") public String paramA(String name){ return "spingmvc接收到的参数:"+name; }
//获取地址上的参数 @RequestMapping("paramd/{id}") public String paramD(@PathVariable Integer id, HttpServletRequest request){ String username= request.getParameter("username"); return "接收到的参数是:"+id+"----nsername:"+username; }
@PathVariable是Spring MVC中用于将URL路径中的变量提取到方法参数中的注解。它可以用于在控制器方法中获取URI路径中的值,并将其绑定到方法的参数上。
使用@RequestParam
可以在控制器方法中通过 @RequestParam 注解,在请求参数与控制器方法的形参之间建立起映射关系,将它们绑定起来。这样即使请求参数与控制器方法中的形参名称不一致,也能获取到对应的请求参数值。
//同时接收多个参数 Map 接收,非常灵活,但是有安全问题 @RequestMapping("paramb") public Map paramB(@RequestParam Map params){//默认是model的容器,加了注解就作为存储参数的容器 // 将请求的参数注入到对象中 return params; }
使用Map可以非常灵活的接收多个参数,但会将传入的参数全盘接收,受到恶意攻击有安全问题
2.通过实体类对象获取(推荐)
在 Controller 控制器方法的形参中设置一个实体类形参,如果请求参数的参数名与实体类中的属性名一致,那么 Spring MVC 会自动将请求参数封装到该实体类对象中。此时我们就可以通过该实体类对象获取所需的请求参数了。
//使用封装对象接收 程序中只接收我们需要的数据 @RequestMapping("paramc") public Staff paramc(Staff staff){ return staff; }
这种方式当传入多个参数时,只会保留匹配到所需的参数,能够有效地解决“控制器方法形参不适用于请求参数过多的请求”问题。
请求转发和重定向
请求转发(Forward)是指将当前的HTTP请求转发给另一个资源进行处理,然后将处理结果再返回给客户端。请求转发是在服务器内部完成的,客户端对此过程是无感知的。在请求转发时,URL地址栏不会改变。
转发:同一个服务器中不同的服务进行转发,可以转发到项目中受保护的资源 如 WEB-INF,是request对象执行forward方法。
当控制器方法中所设置的逻辑视图名称以“forward:”为前缀时,该逻辑视图名称不会被 Spring MVC 配置的视图解析器解析,而是会将前缀“forward:”去掉,以剩余部分作为最终路径通过转发的方式实现跳转。
请求重定向(Redirect)是指服务器收到客户端的请求后,返回一个特殊的响应码和一个新的URL地址给客户端,让客户端重新发送一个新的请求到这个URL地址。客户端会根据新的URL地址再次发起请求,并在URL地址栏显示新的地址。重定向是在客户端完成的。
重定向:浏览器发送了两次请求,可以访问到本项目之外的外网资源,重定向在Java Servlet中是通过response对象通知浏览器重新访问,使用redirect来重定向。
@RequestMapping("methoda") public String methodA(){ System.out.println("-----methodA"); return "redirect:/methodb"; } @RequestMapping("methodb") @ResponseBody //将返回值直接写入回应对象 public String methodB(){ System.out.println("-----methodB"); return "this is methodB"; }
请求转发和重定向都可以用于页面跳转和流程控制,但它们之间有一些区别:
- 请求转发是服务器内部的操作,速度较快,而重定向是需要客户端重新发起请求,速度较慢。
- 请求转发是在服务器内部进行的,因此共享了同一个HTTP请求对象;而重定向是两次独立的请求,每次请求都是独立的,因此无法共享数据。
- 请求转发可以在同一个请求中访问被转发的资源,而重定向则会创建一个新的请求,无法直接访问之前的资源。
Spring MVC 拦截器
拦截器(Interceptor)是 Spring MVC 提供的一种强大的功能组件。它可以对用户请求进行拦截,并在请求进入控制器(Controller)之前handler、控制器处理完请求后、甚至是渲染视图后,执行一些指定的操作。
在 Spring MVC 中,拦截器的作用与 Servlet 中的过滤器类似,它主要用于拦截用户请求并做相应的处理,例如通过拦截器,我们可以执行权限验证、记录请求信息日志、判断用户是否已登录等操作。
定义拦截器
Spring MVC 在 org.springframework.web.servlet 包中提供了一个 HandlerInterceptor 接口
@Component public class EasyInterceptor implements HandlerInterceptor { //三个方法,前置,后置,处理完毕 @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("-----preHandle"); return true; //return HandlerInterceptor.super.preHandle(request, response, handler); } @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); } }
在三种方法中实现业务逻辑,上述代码仅作为演示过程使用。
配置拦截器
@Configuration public class InterceptorConfig implements WebMvcConfigurer{ //拦截器的注册中心 @Autowired EasyInterceptor easyInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(easyInterceptor) //要拦截的地址 .addPathPatterns("/methodc") //排除在外的地址 .excludePathPatterns("/","/index"); } }
Spring MVC 的工作流程
- 用户通过浏览器发起一个 HTTP 请求,该请求会被 DispatcherServlet(前端控制器)拦截;
- DispatcherServlet 调用 HandlerMapping(处理器映射器)找到具体的处理器(Handler)及拦截器,
- HandlerMapping将Handler以 HandlerExecutionChain 执行链的形式返回给 DispatcherServlet。
- DispatcherServlet 将执行链返回的 Handler 信息发送给 HandlerAdapter(处理器适配器);
- HandlerAdapter 根据 Handler 信息找到并执行相应的 Handler(即 Controller 控制器)对请求进行处理;
- Handler 执行完毕后会返回给 HandlerAdapter 一个 ModelAndView 对象(Spring MVC 的底层对象,包括 Model 数据模型和 View 视图信息);
- HandlerAdapter 接收到 ModelAndView 对象后,将其返回给 DispatcherServlet ;
- DispatcherServlet 接收到 ModelAndView 对象后,会请求 ViewResolver(视图解析器)对视图进行解析;
- ViewResolver 解析完成后,会将 View 视图并返回给 DispatcherServlet;
- DispatcherServlet 接收到具体的 View 视图后,进行视图渲染,将 Model 中的模型数据填充到 View 视图中的 request 域,生成最终的 View(视图);
- 视图负责将结果显示到浏览器(客户端)。
Spring MVC 常用(核心)组件
Spring MVC 的常用组件共有 5 个,它们分别是: DispatcherServlet(前端控制器)、HandlerMapping(处理器映射器)、HandlerAdapter(处理器适配器)、Handler(处理器)、ViewResolver(视图解析器)
Spring MVC 异常处理
在实际的应用开发中,经常会不可避免地遇到各种可预知的、不可预知的异常,此时我们就需要对这些异常处理,以保证程序正常运行。
Spring MVC 提供了一个名为 HandlerExceptionResolver 的异常处理器接口,它可以对控制器方法执行过程中出现的各种异常进行处理。
Srping MVC 为 HandlerExceptionResolver 接口提供了多个不同的实现类:
DefaultHandlerExceptionResolver
ResponseStatusExceptionResolver
ExceptionHandlerExceptionResolver
SimpleMappingExceptionResolver
- ExceptionHandlerExceptionResolver、ResponseStatusExceptionResolver 和 DefaultHandlerExceptionResolver 是 Spring MVC 的默认异常处理器。
- 如果程序发生异常,Spring MVC 会按照 ExceptionHandlerExceptionResolver → ResponseStatusExceptionResolver → DefaultHandlerExceptionResolver 的顺序,依次使用这三个异常处理器对异常进行解析,直到完成对异常的解析工作为止。
这里编写一个请求结果类,用于在浏览器发出请求后反馈自定义的对应结果:
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(){ 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(){ return new CommonResult(400,"操作失败",null); } //其他不同参数的方法重载... //getter和setter方法... }
定义异常处理器:
局部处理
- Spring MVC 允许我们在控制器类(Controller 类)中通过 @ExceptionHandler 注解来定义一个处理异常的方法,以实现对控制器类内发生异常的处理。
- @ExceptionHandler 注解中包含了一个 value 属性,我们可以通过该属性来声明一个指定的异常。如果在程序运行过程中,这个 Controller 类中的方法发生了这个指定的异常,那么 ExceptionHandlerExceptionResolver 就会调用这个 @ExceptionHandler 方法对异常进行处理。
- 定义在某个控制器类中的 @ExceptionHandler 方法只在当前的控制器中有效,它只能处理其所在控制器类中发生的异常。
- @ExceptionHandler 方法的优先级--指定异常类型最精确的
@ExceptionHandler(Exception.class) @ResponseBody public CommonResult exh(){ return CommonResult.success(200,"稍有问题"); }
使用 @ResponseBody 注解时,可以在控制器方法上添加该注解,将方法的返回值直接转换为指定格式(如JSON、XML等)的响应,并发送给客户端。
全局处理
- @ExceptionHandler 方法定义在一个使用了 @ControllerAdvice 注解的类中。使用 @ControllerAdvice 注解的类可以包含多个不同的带有 @ExceptionHandler 注解的方法
@RestController @ControllerAdvice //这样下面的异常处理器就不止处理本类的异常,可以处理整个项目的异常 public class StaffController { //... }
API接口测试
Apipost是一个用于测试和调试API接口的工具。它可以帮助开发人员在开发和调试API接口时,方便地发送HTTP请求,并查看请求和响应的内容和结果。
编写如下控制类用于测试,启动项目:
@RestController 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(); } }
打开Apipost,新建接口:
输入端口号,地址并选择请求方式,发送请求: