代理模式
代理模式介绍
意图:为其他对象提供一种代理以控制对这个对象的访问。
主要解决:在直接访问对象时带来的问题,比如说:要访问的对象在远程的机器上。在面向对象系统中,有些对象由于某些原因(比如对象创建开销很大,或者某些操作需要安全控制,或者需要进程外的访问),直接访问会给使用者或者系统结构带来很多麻烦,我们可以在访问此对象时加上一个对此对象的访问层。
何时使用:想在访问一个类时做一些控制。
代理模式主要用于实现AOP(面向切面编程)。AOP允许在程序的核心业务逻辑之外,横切关注点(如日志记录、事务管理等)可以被模块化地插入到应用程序中。Spring中的代理模式通常涉及两种代理方式:静态代理和动态代理。
静态代理
静态代理是在编译时就已经确定的代理类,代理类和目标类的关系在编译期间就已经确定。Spring中使用静态代理一般通过实现接口来实现代理。
优点:好理解
缺点:
1.代码冗余:当多个被代理类,多个需增强方法的增强内容一样时,代码重复冗余
2.不易维护:每个被代理类需要专门维护一个代理类,类成倍增加,需增强方法增加时,需要同时维护代理类和被代理类
演示静态代理:
定义一个Shopping接口,其中有shopping方法
package org.example.proxy.staticproxy;
public interface Shopping {
void shopping();
}
分别定义两个类继承Shopping接口并重写shopping方法
EasyA类只继承并重写
package org.example.proxy.staticproxy;
public class EasyA implements Shopping{
@Override
public void shopping(){
System.out.println("购物");
}
}
Proxy类继承重写后,还在shopping方法之前和之后插入两句代码(块)
体现面向切面的思想,额外的事务可以被模块化的插入代码中
package org.example.proxy.staticproxy;
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("什么时间回去");
}
}
编写工厂类
package org.example.proxy.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();
}
}
动态代理
动态代理是在运行时生成的代理类,相比静态代理更加灵活。Spring主要使用JDK动态代理和CGLIB动态代理来实现AOP。
JDK动态代理:基于接口的代理,在运行时通过反射机制动态生成代理类。
CGLIB动态代理:基于类的代理,通过继承目标类并重写方法的方式实现代理。
演示JDK动态代理
编写接口和方法
package org.example.proxy.dynamic.jdk;
public interface EasyInterface {
void easy();
}
编写类继承接口并重写方法
package org.example.proxy.dynamic.jdk;
public class EasyObj implements EasyInterface{
@Override
public void easy() {
System.out.println("----正常业务逻辑");
}
}
编写新的事物并将其插入到原代码块中
package org.example.proxy.dynamic.jdk;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class EasyInvocationHandler implements InvocationHandler {
private Object proxyedObj;//被代理对象
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;
}
}
package org.example.proxy.dynamic.jdk;
import org.example.proxy.staticproxy.EasyA;
import org.example.proxy.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);//动态生成的代理类对象
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动态代理
被代理的类不能使用final
public class UserService {
public void saveUser() {
// 实际的业务逻辑
System.out.println("保存用户信息");
}
}
public class MyMethodInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("代理操作:开始事务");
Object result = proxy.invokeSuper(obj, args); // 调用实际的业务方法
System.out.println("代理操作:提交事务");
return result;
}
}
// 创建代理对象
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(UserService.class);
enhancer.setCallback(new MyMethodInterceptor());
UserService proxy = (UserService) enhancer.create();
proxy.saveUser(); // 调用代理对象的方法
AOP(Aspect-Oriented Programming,面向切面编程)
AOP介绍
AOP (Aspect Orient Programming),直译过来就是面向切面编程,AOP 是一种编程思想,是面向对象编程(OOP)的一种补充。实现在不修改源代码的情况下给程序动态统一添加额外功能的一种技术
AOP可以拦截指定的方法并且对方法增强,而且无需侵入到业务代码中,使业务与非业务处理逻辑分离,比如Spring的事务,通过事务的注解配置,Spring会自动在业务方法中开启、提交事务,并且在事务处理失败时,执行相应的回滚策略。
AOP的作用
AOP 采取横向抽取机制(动态代理),取代了传统纵向继承机制的重复性代码,其应用主要体现在事务处理、日志管理、权限控制、异常处理等方面。
主要作用是分离功能性需求和非功能性需求,使开发人员可以集中处理某一个关注点或者横切逻辑,减少对业务代码的侵入,增强代码的可读性和可维护性。
简单的说,AOP 的作用就是保证开发者在不修改源代码的前提下,为系统中的业务组件添加某种通用功能。
核心概念
连接点(Join Point)
连接点是程序执行过程中的一个具体点,比如方法的调用、异常的处理、字段的修改等。在 AOP 中,连接点是指可以被切面插入逻辑的特定点。
切入点(Pointcut):
切入点是连接点的集合。它定义了切面在哪些连接点上生效。切入点通常使用表达式来描述,以便选择特定的方法或者类。
通知(Advice)
通知是切面在特定连接点上执行的动作。主要的通知类型包括:
前置通知(Before advice):在方法调用之前执行。
后置通知(After returning advice):在方法正常返回之后执行。
异常通知(After throwing advice):在方法抛出异常后执行。
最终通知(After finally advice):无论方法如何结束(正常返回或异常退出),都会执行。
环绕通知(Around advice):围绕目标方法执行,可以在方法调用前后灵活控制。
目标对象(Target Object)
目标对象是被一个或多个切面所通知的对象。通常情况下,目标对象是在应用中正常运行的对象,而不是代理对象。
植入(Weaving)
植入是指将切面逻辑应用到目标对象的过程。AOP 框架通过编译时、类加载时或者运行时的方式来植入切面,以确保切面的逻辑能够在正确的连接点上执行。
代理(Proxy)
代理是在 AOP 中用来实现横切逻辑的对象。当客户端通过代理访问目标对象时,AOP 框架可以在方法调用前后执行通知,从而实现切面的功能。
切面(Aspect)
切面是横切关注点的模块化实现。它包含了切入点和通知的组合,定义了在哪些连接点上执行什么样的操作。
AOP的优点
模块化:将横切关注点从业务逻辑中分离出来,提高了代码的模块化程度。
可维护性:通过将通用功能抽取到切面中,可以避免代码重复,提高了代码的可维护性和可重用性。
聚焦业务逻辑:使开发人员能够更专注于核心业务逻辑,而不是分散注意力于横切关注点。
减少重复代码:通过集中处理横切关注点,避免了在多个地方重复实现相同功能的代码。
AOP 可以通过以下方式来实现
编译时增强:在编译源代码时,AOP 框架根据切面规则生成增强后的字节码文件。
类加载时增强:在类加载到 JVM 时,AOP 框架通过字节码增强技术修改字节码。
运行时增强:在应用运行时,AOP 框架通过动态代理等技术为目标对象生成代理对象,动态地将切面逻辑织入到目标对象的方法调用中。
注解
在 Spring MVC 中,注解起着非常重要的作用,用于标记和配置各种组件、处理器以及请求映射等。以下是一些常用的注解及其作用:
控制器相关注解
@Controller:标记一个类作为 Spring MVC 控制器。
@RestController:结合 @Controller 和 @ResponseBody,使得返回的对象直接写入 HTTP 响应正文中,一般用于 RESTful API。
@RequestMapping:用于映射 HTTP 请求 URL 到特定的方法上,可以用于类和方法上,支持更详细的配置如请求方法、参数等。
请求处理相关注解
@GetMapping、@PostMapping、@PutMapping、@DeleteMapping:分别映射 GET、POST、PUT、DELETE 请求到特定的处理方法上。
@PatchMapping:映射 PATCH 请求到处理方法上,用于局部更新资源。
参数绑定注解
@RequestParam:用于将请求参数绑定到方法的参数上。
@PathVariable:用于将 URL 中的模板变量映射到方法的参数上。
@RequestBody:用于将 HTTP 请求体绑定到方法的参数上,主要用于接收 JSON 或 XML 格式的请求数据。
响应相关注解
@ResponseBody:标记方法返回的对象直接作为 HTTP 响应体内容返回给客户端。
@ResponseStatus:用于设置 HTTP 响应状态码。
数据校验与格式化注解
@Valid:配合 javax.validation 校验框架使用,用于请求参数的验证。
@InitBinder:用于自定义数据绑定/格式化规则。
视图相关注解
@ModelAttribute:将方法返回值放入模型中,使得在视图中可以直接访问。
@SessionAttributes:将模型中的某些属性暂存于会话中,多用于表单提交后的重定向处理。
异常处理相关注解
@ControllerAdvice:标记一个类作为全局异常处理器。
@ExceptionHandler:用于处理特定异常类的异常处理方法。
其他
@Autowired、@Qualifier:用于依赖注入。
@Configuration、@ComponentScan:用于配置 Spring 应用上下文。
HTTP 方法
(GET、POST、PUT、DELETE)是定义在 HTTP 协议中用于指定请求类型的标准方法。它们在 RESTful 架构中扮演重要角色,每种方法有其特定的语义和用途:
GET
作用:用于请求访问已被 URI(统一资源标识符)识别的资源。通过 GET 方法,客户端可以检索资源的信息(一般用于读取操作)。
安全性:GET 请求是幂等的,多次请求同一资源返回的结果应该是相同的。它不应该改变服务器的状态。
POST
作用:用于向指定资源提交数据,请求服务器进行处理(一般用于创建资源或提交数据)。
安全性:POST 请求不是幂等的,多次提交同样的数据可能会导致不同结果。它可以用于修改服务器的状态,如创建新资源、在服务器上提交表单数据等。
PUT
作用:用于向指定的 URI 更新资源,即更新已存在的资源或者在指定的 URI 下创建新资源。
安全性:PUT 请求应该是幂等的,多次提交同一资源的修改请求结果应该是相同的。它通常用于更新服务器上已存在的资源的完整内容。
DELETE
作用:用于请求服务器删除指定的 URI 所标识的资源。
安全性:DELETE 请求是幂等的,多次请求同一资源的删除请求结果应该是相同的。它用于删除服务器上的资源。
Spring MVC运行原理
当客户端发送请求至服务器时,Spring MVC 的运行原理可以分为以下流程
客户端发送请求
客户端(浏览器)发送 HTTP 请求至服务器,请求包括 URL、HTTP 方法(GET、POST 等)、请求参数等信息。
DispatcherServlet (前端控制器) 接收请求
Servlet 容器(如 Tomcat)接收到请求后,根据配置的 Servlet 映射,将请求交给 DispatcherServlet。
Handler Mapping(处理器映射器) 确定处理器
DispatcherServlet 通过 Handler Mapping(处理器映射器)来确定请求的处理器(Controller)。
Handler Mapping 根据请求的 URL 确定对应的 Controller 类和方法,以及其他的处理器拦截器。
HandlerAdapter处理器适配器 调用处理器
一旦确定了处理器,DispatcherServlet 调用适当的 HandlerAdapter 来执行处理器方法。
HandlerAdapter 负责将请求的参数绑定到方法的参数中,并调用实际的处理器方法。
Handler处理器(Controller控制器) 处理请求
控制器(Controller)类中的方法被调用,处理请求并进行适当的业务处理。
控制器方法可以访问请求参数、调用业务逻辑服务、准备模型数据等。
Handler 返回 ModelAndView 或视图名:
处理器方法处理完请求后,返回一个 ModelAndView 对象或者视图名。
ModelAndView 包含处理结果的模型数据以及用于渲染结果的视图名。
ViewResolver视图解析器 解析视图名:
DispatcherServlet 使用 ViewResolver(视图解析器)来将逻辑视图名解析为具体的视图对象。
视图解析器根据配置将逻辑视图名解析为 JSP、Thymeleaf 模板等具体的视图类型。
视图渲染
最后,视图负责将模型数据渲染为最终的响应内容(如 HTML 页面、JSON 数据等)。
渲染后的内容由 Servlet 容器发送回客户端,完成整个请求-响应周期。
总共分为11步:如图