代理模式是什么?
代理模式是常见的设计模式之一,顾名思义,代理模式就是代理对象具备真实对象的功能,并代替真实对象完成相应操作,并能够在操作执行的前后,对操作进行增强处理。(为真实对象提供代理,然后供其他对象通过代理访问真实对象)。
之前还了解过spring的观察者模式
spring的观察者模式
三丰,公众号:soft张三丰【了解】Spring的观察者模式
静态代理
以租房为例,租客找房东租房,然后中间经过房屋中介,以此为背景,它的UML图如下:
从静态代理的代码中可以发现,静态代理的缺点显而易见,那就是当真实类的方法越来越多的时候,这样构建的代理类的代码量是非常大的,所以就引进动态代理.
动态代理允许使用一种方法的单个类(代理类)为具有任意数量方法的任意类(真实类)的多个方法调用提供服务,看到这句话,可以容易的联想到动态代理的实现与反射密不可分。
JAVA 反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为 java 语言的反射机制。
JDK动态代理有两大核心类,它们都在Java的反射包下(java.lang.reflect),分别为InvocationHandler接口和Proxy类。
InvocationHandler接口
代理实例的调用处理器需要实现InvocationHandler接口,并且每个代理实例都有一个关联的调用处理器。当一个方法在代理实例上被调用时,这个方法调用将被编码并分派到其调用处理器的invoke方法上。
也就是说,我们创建的每一个代理实例都要有一个关联的InvocationHandler,并且在调用代理实例的方法时,会被转到InvocationHandler的invoke方法上。
publicObject invoke(Object proxy, Method method, Object[] args) throws Throwable;
该invoke方法的作用是:处理代理实例上的方法调用并返回结果。
其有三个参数,分别为:
proxy:是调用该方法的代理实例。method:是在代理实例上调用的接口方法对应的Method实例。args:一个Object数组,是在代理实例上的方法调用中传递的参数值。如果接口方法为无参,则该值为null。其返回值为:调用代理实例上的方法的返回值。
Proxy类
Proxy类提供了创建动态代理类及其实例的静态方法,该类也是动态代理类的超类。
代理类具有以下属性:
代理类的名称以 “$Proxy” 开头,后面跟着一个数字序号。代理类继承了Proxy类。代理类实现了创建时指定的接口(JDK动态代理是面向接口的)。每个代理类都有一个公共构造函数,它接受一个参数,即接口InvocationHandler的实现,用于设置代理实例的调用处理器。Proxy提供了两个静态方法,用于获取代理对象。
getProxyClass
用于获取代理类的Class对象,再通过调用构造函数创建代理实例。
该方法有两个参数:
loader:为类加载器。intefaces:为接口的Class对象数组。返回值为动态代理类的Class对象。
newProxyInstance
用于创建一个代理实例。
该方法有三个参数:
loader:为类加载器。interfaces:为接口的Class对象数组。h:指定的调用处理器。返回值为指定接口的代理类的实例。
JDK的动态代理机制只能代理实现了接口的类。而不能实现接口的类就不能使用JDK的动态代理,CGLIB是针对类来实现代理的,它的原理是对指定目标类生成一个子类,并覆盖其中的方法实现增强,但因为采用的是继承,所以不能对final修饰的类进行代理。
CGLIB像是一个拦截器,在调用我们的代理类方法时,代理类(子类)会去找到目标类(父类),此时它会被一个方法拦截器所拦截,在拦截器中才会去实现方法的调用。并且还会对方法进行行为增强。
生成代理类技术不同
java动态代理:jdk自带类ProxyGenerator生成class字节码
cglib:通过ASM框架生成class字节码文件
生成代理类的方式不同
java动态代理:代理类继承java.lang.reflect.Proxy,实现被代理类的接口
cglib:代理类继承被代理类,实现net.sf.cglib.proxy.Factory
生成类数量不同
java动态代理:生成一个proxy类
cglib:生成一个proxy,两个fastclass类
调用方式不同
java动态代理:代理类->InvocationHandler->反射调用被代理类方法
cglib:代理类->MethodInterceptor->调用索引类invoke->直接调用被代理类方法
在 jdk6之前比使用 Java反射效率要高,在 jdk6、jdk7、jdk8 逐步对 JDK 动态代理优化之后,在调用次数较少的情况下,JDK 代理效率 高于 CGLIB 代理效率。只有当进行大量调用的时候,jdk6 和 jdk7 比 CGLIB 代理效率低一点,但是到 jdk8 的时候,jdk 代理效率高于 CGLIB 代理,总之,每一次 jdk 版本升级,JDK 代理效率 都得到提升,而 CGLIB 代理效率 确有点跟不上步伐。
Spring框架运行时,会通过动态字节码技术,在JVM中创建的动态代理对象,运行在JVM内部,等程序结束后,会和jvm一起消失。
这也是为什么叫做动态代理的原因,就是因为这个对象是动态生成出来的。不像静态代理我们必须自己创建。
切入点的表达式是配置的所有方法都执行额外功能,如果想要指定部分方法执行,可以通过修改切入点表达式的方式实现。
在额外功能不变的前提下,创建其他目标类的代理对象时,只需要执行目标对象即可。
spring中也为我们提供了动态代理的实现。可以帮助我们为目标类添加额外功能。
从上图中可以看到,每个对于每个请求,开始与结束一目了然,并且打印了以下参数:
URL: 请求接口地址;
Description: 接口的中文说明信息;
HTTP Method: 请求的方法,是 POST
, GET
, 还是 DELETE
等;
Class Method: 被请求的方法路径 : 包名 + 方法名;
IP: 请求方的 IP 地址;
Request Args: 请求入参,以 JSON 格式输出;
Response Args: 响应出参,以 JSON 格式输出;
Time-Consuming: 请求耗时,以此估算每个接口的性能指数;
项目 pom.xml 文件中添加依赖
<!-- aop 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- 用于日志切面中,以 json 格式打印出入参 -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.5</version>
</dependency>
①:什么时候使用该注解,我们定义为运行时;
②:注解用于什么地方,我们定义为作用于方法上;
③:注解是否将包含在 JavaDoc 中;
④:注解名为 WebLog
;
⑤:定义一个属性,默认为空字符串;
源代码如下:
import java.lang.annotation.*;
/**
* @author 三丰 (微信号:soft张三丰)
* @site https://mp.weixin.qq.com/s?__biz=MzI3MTQyNDc5MA==&mid=2247488556&idx=1&sn=f633fea17f4405d1c9064f6a7596cd74&chksm=eac35855ddb4d143d2cced71dbb5c5449ad63f1d4495eb5677190bf269c50efa80f3a0e4599a&token=1994772128&lang=zh_CN#rd
* @date 2012/12
* @time 下午9:19
* @discription
**/
public WebLog {
/**
* 日志描述信息
*
* @return
*/
String description() default "";
}
在配置 AOP 切面之前,我们需要了解下 aspectj
相关注解的作用:
@Aspect:声明该类为一个注解类;
@Pointcut:定义一个切点,后面跟随一个表达式,表达式可以定义为切某个注解,也可以切某个 package 下的方法;
切点定义好后,就是围绕这个切点做文章了:
@Before: 在切点之前,织入相关代码;
@After: 在切点之后,织入相关代码;
@AfterReturning: 在切点返回内容后,织入相关代码,一般用于对返回值做些加工处理的场景;
@AfterThrowing: 用来处理当织入的代码抛出异常后的逻辑处理;
@Around: 环绕,可以在切入点前后织入代码,并且可以自由的控制何时执行切点;
接下来,定义一个 WebLogAspect.java
切面类,声明一个切点:
然后,定义 @Around
环绕,用于何时执行切点:
①:记录一下调用接口的开始时间;
②:执行切点,执行切点后,会去依次调用 @Before -> 接口逻辑代码 -> @After -> @AfterReturning;
③:打印出参;
④:打印接口处理耗时;
⑤:返回接口返参结果;
再来看看 @Before
方法:
代码
import com.google.gson.Gson;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
/**
* @author 三丰 (微信号:soft张三丰)
* @site https://mp.weixin.qq.com/s?__biz=MzI3MTQyNDc5MA==&mid=2247488556&idx=1&sn=f633fea17f4405d1c9064f6a7596cd74&chksm=eac35855ddb4d143d2cced71dbb5c5449ad63f1d4495eb5677190bf269c50efa80f3a0e4599a&token=1994772128&lang=zh_CN#rd
* @date 2012/12
* @date 2019/2/12
* @time 下午9:19
* @discription
**/
"dev", "test"}) ({
public class WebLogAspect {
private final static Logger logger = LoggerFactory.getLogger(WebLogAspect.class);
/** 换行符 */
private static final String LINE_SEPARATOR = System.lineSeparator();
/** 以自定义 @WebLog 注解为切点 */
"@annotation(site.exception.springbootaopwebrequest.aspect.WebLog)") (
public void webLog() {}
/**
* 在切点之前织入
* @param joinPoint
* @throws Throwable
*/
"webLog()") (
public void doBefore(JoinPoint joinPoint) throws Throwable {
// 开始打印请求日志
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
// 获取 @WebLog 注解的描述信息
String methodDescription = getAspectLogDescription(joinPoint);
// 打印请求相关参数
logger.info("========================================== Start ==========================================");
// 打印请求 url
logger.info("URL : {}", request.getRequestURL().toString());
// 打印描述信息
logger.info("Description : {}", methodDescription);
// 打印 Http method
logger.info("HTTP Method : {}", request.getMethod());
// 打印调用 controller 的全路径以及执行方法
logger.info("Class Method : {}.{}", joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName());
// 打印请求的 IP
logger.info("IP : {}", request.getRemoteAddr());
// 打印请求入参
logger.info("Request Args : {}", new Gson().toJson(joinPoint.getArgs()));
}
/**
* 在切点之后织入
* @throws Throwable
*/
"webLog()") (
public void doAfter() throws Throwable {
// 接口结束后换行,方便分割查看
logger.info("=========================================== End ===========================================" + LINE_SEPARATOR);
}
/**
* 环绕
* @param proceedingJoinPoint
* @return
* @throws Throwable
*/
"webLog()") (
public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
long startTime = System.currentTimeMillis();
Object result = proceedingJoinPoint.proceed();
// 打印出参
logger.info("Response Args : {}", new Gson().toJson(result));
// 执行耗时
logger.info("Time-Consuming : {} ms", System.currentTimeMillis() - startTime);
return result;
}
/**
* 获取切面注解的描述
*
* @param joinPoint 切点
* @return 描述信息
* @throws Exception
*/
public String getAspectLogDescription(JoinPoint joinPoint)
throws Exception {
String targetName = joinPoint.getTarget().getClass().getName();
String methodName = joinPoint.getSignature().getName();
Object[] arguments = joinPoint.getArgs();
Class targetClass = Class.forName(targetName);
Method[] methods = targetClass.getMethods();
StringBuilder description = new StringBuilder("");
for (Method method : methods) {
if (method.getName().equals(methodName)) {
Class[] clazzs = method.getParameterTypes();
if (clazzs.length == arguments.length) {
description.append(method.getAnnotation(WebLog.class).description());
break;
}
}
}
return description.toString();
}
}
源码地址
https://github.com/weiwosuoai/spring-boot-tutorial/tree/master/spring-boot-aop-web-request
猜您喜欢:
微服务微信交流群添加微信,备注微服务进群交流,备注低开进低开群交流
关注公众号 soft张三丰