当前位置: 首页 > news >正文

SpringAOP从入门到源码分析大全,学好AOP这一篇就够了(一)

文章目录

  • 系列文档索引
  • 一、认识AOP
    • 1、AOP的引入原因
    • 2、AOP常见使用场景
      • 日志场景
      • 统计场景
      • 安防场景
      • 性能场景
    • 3、AOP概念
      • AOP 的概念
      • Aspect 概念(切面)
      • Join point 概念(连接点)
      • Pointcut 概念(切入点)
      • Advice 概念(通知、动作)
      • Introduction 概念(声明)
  • 二、认识Spring AOP
    • 1、Spring AOP的设计目标(来自官方文档)
    • 2、Spring AOP的工作模式
      • (1)代理模式
        • ① 静态代理
        • ② 动态代理
      • (2)判断模式
      • (3)拦截模式
    • 3、Spring AOP核心特性
    • 4、Spring AOP 编程模型
      • 注解驱动
      • XML 配置驱动
      • 底层 API
  • 三、Spring AOP用到的代理方式
    • 1、JDK 动态代理实现
      • (1)getProxyClass0方法
      • (3)总结
    • 2、CGLIB 动态代理实现(Spring中)
      • (1)代码实例
      • (2)总结
    • 3、AspectJ 适配实现
      • (1)AspectJ 语法
      • (2)AspectJ 注解
      • (3)总结
      • (4)Spring AOP 和 AspectJ AOP 存在哪些区别?
  • 未完待续
  • 参考资料

系列文档索引

SpringAOP从入门到源码分析大全,学好AOP这一篇就够了(一)
SpringAOP从入门到源码分析大全,学好AOP这一篇就够了(二)
SpringAOP从入门到源码分析大全,学好AOP这一篇就够了(三)
SpringAOP从入门到源码分析大全,学好AOP这一篇就够了(四)

一、认识AOP

1、AOP的引入原因

java是静态语言,一旦定义好结构不容易被修改,而且传统的扩展方式都是通过继承和组合的方式组织新的类结构,侵入性太强。

所以aop的出现就是为了解决这个问题,就是让它方便的被修改从而对类的结构进行增强。

Java的Class类一旦被ClassLoader加载之后,就会存储在永久代(JDK8存储在元数据区),要想对其进行修改可太难了,使用ASM或者其他方式对开发者太不友好了,而AOP正是完美解决这个问题,AOP是不需要改变过去的类的结构进行扩展。

2、AOP常见使用场景

日志场景

诊断上下文,如:log4j或logback中的MDC。

记录方法入参出参等关键日志。

辅助信息,如:记录方法执行时间。

统计场景

记录方法调用次数、记录执行异常次数

数据抽样、数值累加

安防场景

熔断,如:Netflix Hystrix

限流和降级:如:Alibaba Sentinel

认证和授权,如:Spring Security

监控,如:JMX

性能场景

缓存,如 Spring Cache

超时控制

3、AOP概念

AOP 的概念

(1)AspectJ中定义的AOP

Aspect-oriented programming is a way of modularizing crosscutting concerns much like object-oriented programming is a way of modularizing common concerns.

面向切面编程是模块化横切关注点的一种方式,就像面向对象编程是模块化公共关注点的一种方式一样。

(2)Spring中定义的AOP

Aspect-oriented Programming (AOP) complements Object-oriented Programming (OOP) by providing another way of thinking about program structure. The key unit of modularity in OOP is the class, whereas in AOP the unit of modularity is the aspect. Aspects enable the modularization of concerns (such as transaction management) that cut across multiple types and objects.

面向切面编程(AOP)通过提供另一种思考程序结构的方式来补充面向对象编程(OOP)。在OOP中模块化的关键单位是类,而在AOP中模块化的关键单位是切面。切面支持跨越多个类型和对象的关注点(例如事务管理)的模块化。

AOP是不需要改变过去的类的结构进行扩展,如果使用OOP需要对过去的类的结构进行改变。

Aspect 概念(切面)

(1)AspectJ中定义的Aspect
aspect are the unit of modularity for crosscutting concerns. They behave somewhat like Java classes, but may also include pointcuts, advice and inter-type declarations.
切面是横切关注点的模块化单位。它们的行为有点像Java类,但也可能包括切入点、通知和类型间声明。

(2)Spring中定义的Aspect
A modularization of a concern that cuts across multiple classes.
跨越多个类的关注点的模块化。

Join point 概念(连接点)

(1)AspectJ中定义的Join point
A join point is a well-defined point in the program flow. A pointcut picks out certain join points and values at those points.
连接点是程序流中定义良好的点。切入点挑选出某些连接点和这些点上的值。

(2)Spring中定义的Join point
A point during the execution of a program, such as the execution of a method or the handling of an exception. In Spring AOP, a join point always represents a method execution.
程序执行期间的一个点,如方法的执行或异常的处理。在Spring AOP中,一个连接点总是代表一个方法执行。

Pointcut 概念(切入点)

(1)AspectJ中定义的Pointcut
pointcuts pick out certain join points in the program flow.
切入点挑选出程序流中的某些连接点。

(2)Spring中定义的Pointcut

A predicate that matches join points.
匹配连接点的条件。

Advice 概念(通知、动作)

(1)AspectJ中定义的Advice
So pointcuts pick out join points. But they don’t do anything apart from picking out join points. To actually implement crosscutting behavior, we use advice. Advice brings together a pointcut (to pick out join points) and a body of code (to run at each of those join points).

因此,切入点挑选出连接点。但是除了选择连接点之外,它们什么也不做。为了实际实现横切行为,我们使用通知。通知将切入点(用于挑选连接点)和代码体(用于在每个连接点上运行)组合在一起。

(2)Spring中定义的Advice
Action taken by an aspect at a particular join point. Different types of advice include “around”, “before” and “after” advice. Many AOP frameworks, including Spring, model an advice as an interceptor and maintain a chain of interceptors around the join point.
方面在特定连接点上采取的操作。不同类型的建议包括“前后”、“之前”和“之后”的动作。许多AOP框架,包括Spring,都将通知建模为一个拦截器,并围绕连接点维护一个拦截器链。

Introduction 概念(声明)

(1)AspectJ中定义的Introduction
Inter-type declarations in AspectJ are declarations that cut across classes and their hierarchies. They may declare members that cut across multiple classes, or change the inheritance relationship between classes.

AspectJ中的类型间声明是跨越类及其层次结构的声明。它们可以声明跨越多个类的成员,或者改变类之间的继承关系。

(2)Spring中定义的Introduction
Declaring additional methods or fields on behalf of a type. Spring AOP lets you introduce new interfaces (and a corresponding implementation) to any advised object.

代表类型声明额外的方法或字段。Spring AOP允许您向任何被建议的对象引入新的接口(以及相应的实现)。

二、认识Spring AOP

1、Spring AOP的设计目标(来自官方文档)

Spring AOP’s approach to AOP differs from that of most other AOP frameworks. The aim is not to provide the most complete AOP implementation (although Spring AOP is quite capable). Rather, the aim is to provide a close integration between AOP implementation and Spring IoC, to help solve common problems in enterprise applications.

Spring AOP实现AOP的方法不同于大多数其他AOP框架。其目的不是提供最完整的AOP实现(尽管Spring AOP相当能干)。相反,其目的是提供AOP实现和Spring IOC之间的紧密集成,以帮助解决企业应用程序中的常见问题。

Spring AOP never strives to compete with AspectJ to provide a comprehensive AOP solution. We believe that both proxy-based frameworks such as Spring AOP and full-blown frameworks such as AspectJ are valuable and that they are complementary, rather than in competition.Spring seamlessly integrates Spring AOP and IoC with AspectJ, to enable all uses of AOP within a consistent Spring-based application architecture. This integration does not affect the Spring AOP API or the AOP Alliance API. Spring AOP remains backwardcompatible.

Spring AOP不与AspectJ竞争以提供全面的AOP解决方案。我们相信基于代理的框架(如Spring AOP)和成熟的框架(如AspectJ)都是有价值的,它们是互补的,而不是竞争的。Spring无缝地将Spring AOP和IOC与AspectJ集成在一起,以支持在一致的基于Spring的应用程序架构中使用AOP。这个集成不会影响Spring AOP API或AOP Alliance API。Spring AOP仍然向后兼容。

实际上,Spring AOP并未对编译期进行字节码的处理,而是都在运行期间进行处理的,通常都是用反射来实现的。

2、Spring AOP的工作模式

(1)代理模式

① 静态代理

Java静态代理,一般常用就是面向对象(OOP)继承和组合相结合的方式进行代理。

代码实例:

public interface EchoService {String echo(String message) throws NullPointerException;
}public class DefaultEchoService implements EchoService {@Overridepublic String echo(String message) {return "[ECHO] " + message;}
}public class ProxyEchoService implements EchoService {private final EchoService echoService;public ProxyEchoService(EchoService echoService) {this.echoService = echoService;}@Overridepublic String echo(String message) {long startTime = System.currentTimeMillis();String result = echoService.echo(message);long costTime = System.currentTimeMillis() - startTime;System.out.println("echo 方法执行的时间:" + costTime + " ms.");return result;}
}public class StaticProxyDemo {public static void main(String[] args) {EchoService echoService = new ProxyEchoService(new DefaultEchoService());echoService.echo("Hello,World");}
}

上面静态代理的实例,就是基于接口,需要实现同一个接口,侵入性很强,一般也称为装饰器模式。

② 动态代理

Java动态代理,常用的是JDK动态搭理或者字节码提升(如CGLIB)

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;/*** JDK动态代理实例*/
public class JDKDynamicProxyDemo {public static void main(String[] args) {ClassLoader classLoader = Thread.currentThread().getContextClassLoader();// 真实的对象DefaultEchoService realObj = new DefaultEchoService();// 代理的对象Object proxy = Proxy.newProxyInstance(classLoader, new Class[]{EchoService.class}, new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("动态前置");Object obj = null;if (EchoService.class.isAssignableFrom(method.getDeclaringClass())) {// 执行真实方法obj = method.invoke(realObj, args);}System.out.println("动态后置");return obj;}});EchoService echoService = (EchoService) proxy;System.out.println(echoService.echo("Hello,World"));}
}

注意!这里需要DefaultEchoService 实现EchoService接口,代理的其实是EchoService接口而不是DefaultEchoService 类。

动态代理对原代码没有侵入性,通常可以动态加载。

(2)判断模式

类型(Class)、方法(Method)、注解(Annotation)、参数(Parameter)、异常(Exception)

SpringAOP通常都是对方法的拦截,此处的操作都是基于方法上的注解、参数、异常等。

public interface EchoService {String echo(String message) throws NullPointerException;
}
import org.springframework.util.ReflectionUtils;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;/*** AOP 目标过滤示例*/
public class TargetFilterDemo {public static void main(String[] args) throws ClassNotFoundException {String targetClassName = "com.demo.EchoService";// 获取当前线程 ClassLoaderClassLoader classLoader = Thread.currentThread().getContextClassLoader();// 获取目标类Class<?> targetClass = classLoader.loadClass(targetClassName);// 方法定义:String echo(String message);// Spring 反射工具类,获取目标类指定方法Method targetMethod = ReflectionUtils.findMethod(targetClass, "echo", String.class);System.out.println(targetMethod);// 查找方法  throws 类型为 NullPointerExceptionReflectionUtils.doWithMethods(targetClass, new ReflectionUtils.MethodCallback() {@Overridepublic void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {System.out.println("仅抛出 NullPointerException 方法为:" + method);}}, new ReflectionUtils.MethodFilter() {// 对方法进行过滤@Overridepublic boolean matches(Method method) {Class[] parameterTypes = method.getParameterTypes(); // 判断参数Class[] exceptionTypes = method.getExceptionTypes(); // 判断抛出的异常Annotation[] annotations = method.getAnnotations(); // 判断注解return parameterTypes.length == 1&& String.class.equals(parameterTypes[0])&& exceptionTypes.length == 1&& NullPointerException.class.equals(exceptionTypes[0]);}});}
}

(3)拦截模式

前置拦截(Before)、后置拦截(After)、异常拦截(Exception)、finally等等。

还有一种比较特殊的Around围绕模式。

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;/*** AOP拦截模式代码实例*/
public class InterceptorDemo {public static void main(String[] args) {ClassLoader classLoader = Thread.currentThread().getContextClassLoader();// 真实的对象DefaultEchoService realObj = new DefaultEchoService();// 前置拦截器Interceptor beforeInterceptor = new Interceptor() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args, Object returnResult, Throwable throwable) {return System.currentTimeMillis();}};// 后置拦截器Interceptor afterInterceptor = new Interceptor() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args, Object returnResult, Throwable throwable) {return System.currentTimeMillis();}};// 异常拦截器Interceptor exceptionInterceptor = new Interceptor() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args, Object returnResult, Throwable throwable) {throwable.printStackTrace();return null;}};// finally拦截器Interceptor finallyInterceptor = new Interceptor() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args, Object returnResult, Throwable throwable) {return null;}};// 代理的对象Object proxy = Proxy.newProxyInstance(classLoader, new Class[]{EchoService.class}, new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {Long startTime = 0L;Long endTime = 0L;Object result = null;Object obj = null;if (EchoService.class.isAssignableFrom(method.getDeclaringClass())) {try {// 执行beforestartTime = (Long)beforeInterceptor.invoke(proxy, method, args, null, null);// 执行真实方法obj = method.invoke(realObj, args);// 执行afterendTime = (Long) afterInterceptor.invoke(proxy, method, args, obj, null);} catch (Exception e) {// 执行异常拦截器exceptionInterceptor.invoke(proxy, method, args, null, e);} finally {// 执行finallyfinallyInterceptor.invoke(proxy, method, args, obj, null);System.out.println("共消耗时间:" + (endTime - startTime));}}return obj;}});EchoService echoService = (EchoService) proxy;System.out.println(echoService.echo("Hello,World"));}static interface Interceptor {Object invoke(Object proxy, Method method, Object[] args, Object returnResult, Throwable throwable);}
}

Spring AOP也是基于类似的接口来进行实现的。

3、Spring AOP核心特性

• 纯 Java 实现、无编译时特殊处理、不修改和控制 ClassLoader
• 仅支持方法级别的 Join Points
• 非完整 AOP 实现框架
• 需要Spring IoC 容器整合
• AspectJ 注解驱动整合(非竞争关系)

Spring AOP(JDK动态代理,CGLIB) 是运行期修改class文件,AspectJ是编译期修改class文件。
Spring 并没有使用 AspectJ 的编译器,而是利用反射来实现的(后续慢慢讲解)。

4、Spring AOP 编程模型

注解驱动

实现:
Enable 模块驱动,@EnableAspectJAutoProxy

注解:
• 激活 AspectJ 自动代理:@EnableAspectJAutoProxy
• Aspect : @Aspect
• Pointcut :@Pointcut
• Advice :@Before、@AfterReturning、@AfterThrowing、@After、@Around
• Introduction :@DeclareParents

XML 配置驱动

实现:
Spring Extensble XML Authoring

XML 元素
• 激活 AspectJ 自动代理:<aop:aspectj-autoproxy/>
• 配置:<aop:config/>
• Aspect : <aop:aspect/>
• Pointcut :<aop:pointcut/>
• Advice :<aop:around/>、<aop:before/>、<aop:after-returning/>、<aop:after-throwing/> 和<aop:after/>
• Introduction :<aop:declare-parents/>
• 代理 Scope : <aop:scoped-proxy/>

底层 API

实现:
JDK 动态代理、CGLIB 以及 AspectJ

API:
• 代理:AopProxy
• 配置:ProxyConfig
• Join Point:JoinPoint
• Pointcut :Pointcut
• Advice :Advice、BeforeAdvice、AfterAdvice、AfterReturningAdvice、ThrowsAdvice

三、Spring AOP用到的代理方式

1、JDK 动态代理实现

JDK 动态代理实现,一般是基于接口代理。在Spring中的核心类是JdkDynamicAopProxy,它实现了AopProxy接口:

public interface AopProxy {Object getProxy();Object getProxy(@Nullable ClassLoader classLoader);
}

JDK动态代理的实例我们上面已经展示过了,为什么 Proxy.newProxyInstance 会生成新的字节码?

ClassLoader classLoader = Thread.currentThread().getContextClassLoader();Object proxy = Proxy.newProxyInstance(classLoader, new Class[]{EchoService.class}, new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {return null;}
});System.out.println(proxy.getClass());// com.sun.proxy.$Proxy0Object proxy2 = Proxy.newProxyInstance(classLoader, new Class[]{Comparable.class}, (proxy1, method, args1) -> {return null;});System.out.println(proxy2.getClass());// com.sun.proxy.$Proxy1

上面代码我们会发现,Java动态代理每生成一个代理,它的class总是com.sun.proxy包下的$Proxy*,从0开始累加,它是如何实现的呢?

我们来分析一下Proxy的newProxyInstance方法:

// java.lang.reflect.Proxy#newProxyInstance
@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)throws IllegalArgumentException
{Objects.requireNonNull(h);// 对象克隆final Class<?>[] intfs = interfaces.clone();final SecurityManager sm = System.getSecurityManager();if (sm != null) {checkProxyAccess(Reflection.getCallerClass(), loader, intfs);}/** Look up or generate the designated proxy class.*/// 先从缓存获取(见 (1))Class<?> cl = getProxyClass0(loader, intfs);/** Invoke its constructor with the designated invocation handler.*/try {if (sm != null) {checkNewProxyPermission(Reflection.getCallerClass(), cl);}// 获取代理对象的构造方法,带着InvocationHandler参数的构造方法final Constructor<?> cons = cl.getConstructor(constructorParams);final InvocationHandler ih = h;if (!Modifier.isPublic(cl.getModifiers())) {AccessController.doPrivileged(new PrivilegedAction<Void>() {public Void run() {cons.setAccessible(true);return null;}});}// 返回Proxy对象return cons.newInstance(new Object[]{h});} catch (IllegalAccessException|InstantiationException e) {throw new InternalError(e.toString(), e);} catch (InvocationTargetException e) {Throwable t = e.getCause();if (t instanceof RuntimeException) {throw (RuntimeException) t;} else {throw new InternalError(t.toString(), t);}} catch (NoSuchMethodException e) {throw new InternalError(e.toString(), e);}
}

(1)getProxyClass0方法

在getProxyClass0方法中,从proxyClassCache缓存中获取了这个代理类:

// java.lang.reflect.Proxy#getProxyClass0
private static Class<?> getProxyClass0(ClassLoader loader,Class<?>... interfaces) {if (interfaces.length > 65535) {throw new IllegalArgumentException("interface limit exceeded");}// If the proxy class defined by the given loader implementing// the given interfaces exists, this will simply return the cached copy;// otherwise, it will create the proxy class via the ProxyClassFactoryreturn proxyClassCache.get(loader, interfaces);
}

而proxyClassCache在初始化时,自动创建了KeyFactory和ProxyClassFactory

private static final WeakCache<ClassLoader, Class<?>[], Class<?>>proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());

ProxyClassFactory的核心方法apply,隐藏着代理接口的创建逻辑:

// java.lang.reflect.Proxy.ProxyClassFactory
private static final class ProxyClassFactoryimplements BiFunction<ClassLoader, Class<?>[], Class<?>>
{// prefix for all proxy class namesprivate static final String proxyClassNamePrefix = "$Proxy";// next number to use for generation of unique proxy class namesprivate static final AtomicLong nextUniqueNumber = new AtomicLong();@Overridepublic Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);for (Class<?> intf : interfaces) { // 遍历我们传入的接口数组/** Verify that the class loader resolves the name of this* interface to the same Class object.*/Class<?> interfaceClass = null;try {// 通过classLoader加载我们的接口interfaceClass = Class.forName(intf.getName(), false, loader);} catch (ClassNotFoundException e) {}if (interfaceClass != intf) {throw new IllegalArgumentException(intf + " is not visible from class loader");}/** Verify that the Class object actually represents an* interface.*/if (!interfaceClass.isInterface()) {// 只能代理接口,非接口直接抛异常throw new IllegalArgumentException(interfaceClass.getName() + " is not an interface");}/** Verify that this interface is not a duplicate.*/if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {throw new IllegalArgumentException("repeated interface: " + interfaceClass.getName());}}String proxyPkg = null;     // package to define proxy class inint accessFlags = Modifier.PUBLIC | Modifier.FINAL;/** Record the package of a non-public proxy interface so that the* proxy class will be defined in the same package.  Verify that* all non-public proxy interfaces are in the same package.*/for (Class<?> intf : interfaces) {int flags = intf.getModifiers();if (!Modifier.isPublic(flags)) {accessFlags = Modifier.FINAL;String name = intf.getName();int n = name.lastIndexOf('.');String pkg = ((n == -1) ? "" : name.substring(0, n + 1));if (proxyPkg == null) {proxyPkg = pkg;} else if (!pkg.equals(proxyPkg)) {throw new IllegalArgumentException("non-public interfaces from different packages");}}}if (proxyPkg == null) { // 包名就是com.sun.proxy// if no non-public proxy interfaces, use com.sun.proxy packageproxyPkg = ReflectUtil.PROXY_PACKAGE + ".";}/** Choose a name for the proxy class to generate.*/long num = nextUniqueNumber.getAndIncrement();String proxyName = proxyPkg + proxyClassNamePrefix + num; // 依次递增/** Generate the specified proxy class.*/// 代理类生成器,返回字节数组,就是字节码byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags);try {// classLoader加载类,是一个native方法,返回一个Class对象return defineClass0(loader, proxyName,proxyClassFile, 0, proxyClassFile.length);} catch (ClassFormatError e) {/** A ClassFormatError here means that (barring bugs in the* proxy class generation code) there was some other* invalid aspect of the arguments supplied to the proxy* class creation (such as virtual machine limitations* exceeded).*/throw new IllegalArgumentException(e.toString());}}
}

(3)总结

vm options参数设置-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true,就可以把生成的代理类的源码保存在com.sun.proxy目录下面,或者用arthas来查看运行中的类信息。

JDK动态代理生成的代理类,我们通过反编译,发现其实是这个样子的:

package com.sun.proxy;import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
import com.demo.EchoService;public final class $Proxy0
extends Proxy
implements EchoService {private static Method m1;private static Method m3;private static Method m2;private static Method m0;public $Proxy0(InvocationHandler invocationHandler) {super(invocationHandler);}static {try {m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));m3 = Class.forName("com.demo.EchoService").getMethod("echo", Class.forName("java.lang.String"));m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);return;}catch (NoSuchMethodException noSuchMethodException) {throw new NoSuchMethodError(noSuchMethodException.getMessage());}catch (ClassNotFoundException classNotFoundException) {throw new NoClassDefFoundError(classNotFoundException.getMessage());}}public final boolean equals(Object object) {try {return (Boolean)this.h.invoke(this, m1, new Object[]{object});}catch (Error | RuntimeException throwable) {throw throwable;}catch (Throwable throwable) {throw new UndeclaredThrowableException(throwable);}}public final String toString() {try {return (String)this.h.invoke(this, m2, null);}catch (Error | RuntimeException throwable) {throw throwable;}catch (Throwable throwable) {throw new UndeclaredThrowableException(throwable);}}public final int hashCode() {try {return (Integer)this.h.invoke(this, m0, null);}catch (Error | RuntimeException throwable) {throw throwable;}catch (Throwable throwable) {throw new UndeclaredThrowableException(throwable);}}public final String echo(String string) throws NullPointerException {try {return (String)this.h.invoke(this, m3, new Object[]{string});}catch (Error | RuntimeException throwable) {throw throwable;}catch (Throwable throwable) {throw new UndeclaredThrowableException(throwable);}}
}

InvocationHandler就是Proxy.newProxyInstance传入的最后一个参数。

当调用代理对象的方法时,会执行InvocationHandler的invoke方法。

注意,JDK生成的代理类的包名不总是com.sun.proxy,只有当接口为Public时是这样的,当接口为非public时,生成的代理类与接口所在包名相同。

2、CGLIB 动态代理实现(Spring中)

CGLIB 动态代理实现,一般是基于类代理(字节码提升)。在Spring中核心类是CglibAopProxy,同样实现了AopProxy接口。

(1)代码实例

import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;import java.lang.reflect.Method;/*** CGLIB 动态代理示例*/
public class CglibDynamicProxyDemo {public static void main(String[] args) {Enhancer enhancer = new Enhancer();// 指定 super class = DefaultEchoService.classClass<?> superClass = DefaultEchoService.class;enhancer.setSuperclass(superClass);// 指定拦截接口,可以不写//enhancer.setInterfaces(new Class[]{EchoService.class});enhancer.setCallback(new MethodInterceptor() {@Overridepublic Object intercept(Object source, Method method, Object[] args,MethodProxy methodProxy) throws Throwable {long startTime = System.currentTimeMillis();// 错误使用
//                Object result = method.invoke(source, args);// 正确的方法调用Object result = methodProxy.invokeSuper(source, args);long costTime = System.currentTimeMillis() - startTime;System.out.println("[CGLIB 字节码提升] echo 方法执行的实现:" + costTime + " ms.");return result;}});// 创建代理对象EchoService echoService = (EchoService) enhancer.create();// 输出执行结果System.out.println(echoService.echo("Hello,World")); // 8ms,对性能影响较大}
}

(2)总结

Java 动态代理无法满足 AOP 的需要,JDK动态代理:只能针对interface进行动态代理,无法对普通类进行动态代理,Java动态代理使用Java原生的反射API进行操作,在生成类上比较高效。

CGLIB能够代理普通类(可以不需要接口) ,CGLIB使用ASM框架直接对字节码进行操作,在类的执行过程中比较高效,但是类的创建过程比较耗时。

CGLIB默认是在类同包下生成一个类,通过反编译我们发现生成的代理类如下:

import java.lang.reflect.Method;
import com.demo.DefaultEchoService;
import org.springframework.cglib.core.ReflectUtils;
import org.springframework.cglib.core.Signature;
import org.springframework.cglib.proxy.Callback;
import org.springframework.cglib.proxy.Factory;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;public class DefaultEchoService$$EnhancerByCGLIB$$c868af31
extends DefaultEchoService
implements Factory {private boolean CGLIB$BOUND;public static Object CGLIB$FACTORY_DATA;private static final ThreadLocal CGLIB$THREAD_CALLBACKS;private static final Callback[] CGLIB$STATIC_CALLBACKS;private MethodInterceptor CGLIB$CALLBACK_0;private static Object CGLIB$CALLBACK_FILTER;private static final Method CGLIB$echo$0$Method;private static final MethodProxy CGLIB$echo$0$Proxy;private static final Object[] CGLIB$emptyArgs;private static final Method CGLIB$equals$1$Method;private static final MethodProxy CGLIB$equals$1$Proxy;private static final Method CGLIB$toString$2$Method;private static final MethodProxy CGLIB$toString$2$Proxy;private static final Method CGLIB$hashCode$3$Method;private static final MethodProxy CGLIB$hashCode$3$Proxy;private static final Method CGLIB$clone$4$Method;private static final MethodProxy CGLIB$clone$4$Proxy;static void CGLIB$STATICHOOK1() {CGLIB$THREAD_CALLBACKS = new ThreadLocal();CGLIB$emptyArgs = new Object[0];Class<?> clazz = Class.forName("com.demo.DefaultEchoService$$EnhancerByCGLIB$$c868af31");Class<?> clazz2 = Class.forName("com.demo.DefaultEchoService");CGLIB$echo$0$Method = ReflectUtils.findMethods(new String[]{"echo", "(Ljava/lang/String;)Ljava/lang/String;"}, clazz2.getDeclaredMethods())[0];CGLIB$echo$0$Proxy = MethodProxy.create(clazz2, clazz, "(Ljava/lang/String;)Ljava/lang/String;", "echo", "CGLIB$echo$0");clazz2 = Class.forName("java.lang.Object");Method[] methodArray = ReflectUtils.findMethods(new String[]{"equals", "(Ljava/lang/Object;)Z", "toString", "()Ljava/lang/String;", "hashCode", "()I", "clone", "()Ljava/lang/Object;"}, clazz2.getDeclaredMethods());CGLIB$equals$1$Method = methodArray[0];CGLIB$equals$1$Proxy = MethodProxy.create(clazz2, clazz, "(Ljava/lang/Object;)Z", "equals", "CGLIB$equals$1");CGLIB$toString$2$Method = methodArray[1];CGLIB$toString$2$Proxy = MethodProxy.create(clazz2, clazz, "()Ljava/lang/String;", "toString", "CGLIB$toString$2");CGLIB$hashCode$3$Method = methodArray[2];CGLIB$hashCode$3$Proxy = MethodProxy.create(clazz2, clazz, "()I", "hashCode", "CGLIB$hashCode$3");CGLIB$clone$4$Method = methodArray[3];CGLIB$clone$4$Proxy = MethodProxy.create(clazz2, clazz, "()Ljava/lang/Object;", "clone", "CGLIB$clone$4");}final String CGLIB$echo$0(String string) {return super.echo(string);}public final String echo(String string) {MethodInterceptor methodInterceptor = this.CGLIB$CALLBACK_0;if (methodInterceptor == null) {DefaultEchoService$$EnhancerByCGLIB$$c868af31.CGLIB$BIND_CALLBACKS(this);methodInterceptor = this.CGLIB$CALLBACK_0;}if (methodInterceptor != null) {return (String)methodInterceptor.intercept(this, CGLIB$echo$0$Method, new Object[]{string}, CGLIB$echo$0$Proxy);}return super.echo(string);}final boolean CGLIB$equals$1(Object object) {return super.equals(object);}public final boolean equals(Object object) {MethodInterceptor methodInterceptor = this.CGLIB$CALLBACK_0;if (methodInterceptor == null) {DefaultEchoService$$EnhancerByCGLIB$$c868af31.CGLIB$BIND_CALLBACKS(this);methodInterceptor = this.CGLIB$CALLBACK_0;}if (methodInterceptor != null) {Object object2 = methodInterceptor.intercept(this, CGLIB$equals$1$Method, new Object[]{object}, CGLIB$equals$1$Proxy);return object2 == null ? false : (Boolean)object2;}return super.equals(object);}final String CGLIB$toString$2() {return super.toString();}public final String toString() {MethodInterceptor methodInterceptor = this.CGLIB$CALLBACK_0;if (methodInterceptor == null) {DefaultEchoService$$EnhancerByCGLIB$$c868af31.CGLIB$BIND_CALLBACKS(this);methodInterceptor = this.CGLIB$CALLBACK_0;}if (methodInterceptor != null) {return (String)methodInterceptor.intercept(this, CGLIB$toString$2$Method, CGLIB$emptyArgs, CGLIB$toString$2$Proxy);}return super.toString();}final int CGLIB$hashCode$3() {return super.hashCode();}public final int hashCode() {MethodInterceptor methodInterceptor = this.CGLIB$CALLBACK_0;if (methodInterceptor == null) {DefaultEchoService$$EnhancerByCGLIB$$c868af31.CGLIB$BIND_CALLBACKS(this);methodInterceptor = this.CGLIB$CALLBACK_0;}if (methodInterceptor != null) {Object object = methodInterceptor.intercept(this, CGLIB$hashCode$3$Method, CGLIB$emptyArgs, CGLIB$hashCode$3$Proxy);return object == null ? 0 : ((Number)object).intValue();}return super.hashCode();}final Object CGLIB$clone$4() throws CloneNotSupportedException {return super.clone();}protected final Object clone() throws CloneNotSupportedException {MethodInterceptor methodInterceptor = this.CGLIB$CALLBACK_0;if (methodInterceptor == null) {DefaultEchoService$$EnhancerByCGLIB$$c868af31.CGLIB$BIND_CALLBACKS(this);methodInterceptor = this.CGLIB$CALLBACK_0;}if (methodInterceptor != null) {return methodInterceptor.intercept(this, CGLIB$clone$4$Method, CGLIB$emptyArgs, CGLIB$clone$4$Proxy);}return super.clone();}public static MethodProxy CGLIB$findMethodProxy(Signature signature) {String string = ((Object)signature).toString();switch (string.hashCode()) {case -1042135322: {if (!string.equals("echo(Ljava/lang/String;)Ljava/lang/String;")) break;return CGLIB$echo$0$Proxy;}case -508378822: {if (!string.equals("clone()Ljava/lang/Object;")) break;return CGLIB$clone$4$Proxy;}case 1826985398: {if (!string.equals("equals(Ljava/lang/Object;)Z")) break;return CGLIB$equals$1$Proxy;}case 1913648695: {if (!string.equals("toString()Ljava/lang/String;")) break;return CGLIB$toString$2$Proxy;}case 1984935277: {if (!string.equals("hashCode()I")) break;return CGLIB$hashCode$3$Proxy;}}return null;}public DefaultEchoService$$EnhancerByCGLIB$$c868af31() {DefaultEchoService$$EnhancerByCGLIB$$c868af31 defaultEchoService$$EnhancerByCGLIB$$c868af31 = this;DefaultEchoService$$EnhancerByCGLIB$$c868af31.CGLIB$BIND_CALLBACKS(defaultEchoService$$EnhancerByCGLIB$$c868af31);}public static void CGLIB$SET_THREAD_CALLBACKS(Callback[] callbackArray) {CGLIB$THREAD_CALLBACKS.set(callbackArray);}public static void CGLIB$SET_STATIC_CALLBACKS(Callback[] callbackArray) {CGLIB$STATIC_CALLBACKS = callbackArray;}private static final void CGLIB$BIND_CALLBACKS(Object object) {block2: {Object object2;block3: {DefaultEchoService$$EnhancerByCGLIB$$c868af31 defaultEchoService$$EnhancerByCGLIB$$c868af31 = (DefaultEchoService$$EnhancerByCGLIB$$c868af31)object;if (defaultEchoService$$EnhancerByCGLIB$$c868af31.CGLIB$BOUND) break block2;defaultEchoService$$EnhancerByCGLIB$$c868af31.CGLIB$BOUND = true;object2 = CGLIB$THREAD_CALLBACKS.get();if (object2 != null) break block3;object2 = CGLIB$STATIC_CALLBACKS;if (CGLIB$STATIC_CALLBACKS == null) break block2;}defaultEchoService$$EnhancerByCGLIB$$c868af31.CGLIB$CALLBACK_0 = (MethodInterceptor)((Callback[])object2)[0];}}@Overridepublic Object newInstance(Callback[] callbackArray) {DefaultEchoService$$EnhancerByCGLIB$$c868af31.CGLIB$SET_THREAD_CALLBACKS(callbackArray);DefaultEchoService$$EnhancerByCGLIB$$c868af31 defaultEchoService$$EnhancerByCGLIB$$c868af31 = new DefaultEchoService$$EnhancerByCGLIB$$c868af31();DefaultEchoService$$EnhancerByCGLIB$$c868af31.CGLIB$SET_THREAD_CALLBACKS(null);return defaultEchoService$$EnhancerByCGLIB$$c868af31;}@Overridepublic Object newInstance(Callback callback) {DefaultEchoService$$EnhancerByCGLIB$$c868af31.CGLIB$SET_THREAD_CALLBACKS(new Callback[]{callback});DefaultEchoService$$EnhancerByCGLIB$$c868af31 defaultEchoService$$EnhancerByCGLIB$$c868af31 = new DefaultEchoService$$EnhancerByCGLIB$$c868af31();DefaultEchoService$$EnhancerByCGLIB$$c868af31.CGLIB$SET_THREAD_CALLBACKS(null);return defaultEchoService$$EnhancerByCGLIB$$c868af31;}@Overridepublic Object newInstance(Class[] classArray, Object[] objectArray, Callback[] callbackArray) {DefaultEchoService$$EnhancerByCGLIB$$c868af31 defaultEchoService$$EnhancerByCGLIB$$c868af31;DefaultEchoService$$EnhancerByCGLIB$$c868af31.CGLIB$SET_THREAD_CALLBACKS(callbackArray);Class[] classArray2 = classArray;switch (classArray.length) {case 0: {defaultEchoService$$EnhancerByCGLIB$$c868af31 = new DefaultEchoService$$EnhancerByCGLIB$$c868af31();break;}default: {throw new IllegalArgumentException("Constructor not found");}}DefaultEchoService$$EnhancerByCGLIB$$c868af31.CGLIB$SET_THREAD_CALLBACKS(null);return defaultEchoService$$EnhancerByCGLIB$$c868af31;}@Overridepublic Callback getCallback(int n) {MethodInterceptor methodInterceptor;DefaultEchoService$$EnhancerByCGLIB$$c868af31.CGLIB$BIND_CALLBACKS(this);switch (n) {case 0: {methodInterceptor = this.CGLIB$CALLBACK_0;break;}default: {methodInterceptor = null;}}return methodInterceptor;}@Overridepublic void setCallback(int n, Callback callback) {switch (n) {case 0: {this.CGLIB$CALLBACK_0 = (MethodInterceptor)callback;break;}}}@Overridepublic Callback[] getCallbacks() {DefaultEchoService$$EnhancerByCGLIB$$c868af31.CGLIB$BIND_CALLBACKS(this);DefaultEchoService$$EnhancerByCGLIB$$c868af31 defaultEchoService$$EnhancerByCGLIB$$c868af31 = this;return new Callback[]{this.CGLIB$CALLBACK_0};}@Overridepublic void setCallbacks(Callback[] callbackArray) {Callback[] callbackArray2 = callbackArray;DefaultEchoService$$EnhancerByCGLIB$$c868af31 defaultEchoService$$EnhancerByCGLIB$$c868af31 = this;this.CGLIB$CALLBACK_0 = (MethodInterceptor)callbackArray[0];}static {DefaultEchoService$$EnhancerByCGLIB$$c868af31.CGLIB$STATICHOOK1();}
}

我们可以看出,生成这个类比JDK动态代理生成的类复杂多了,但是也不难看出,该代理类继承了我们的父类DefaultEchoService,并不像JDK动态代理那样必须实现同一个接口。

而且代理类的echo方法,是直接调用的super.echo()方法,不像JDK动态代理,使用的是反射调用的,所以CGLIB执行的性能是略优于JDK动态代理的,但是代理类的生成速度是比较差的。

3、AspectJ 适配实现

AspectJ动态代理实现,核心类是AspectJProxyFactory,与AspectJ实现了整合。

Spring aop包含 jdk动态代理、cglib动态代理和aspectj,而DefaultAopProxyFactory 却只包含了前两种创建代理对象的实现,而没包含aspectj,而是单独定义了一个AspectJProxyFactory。实际上 Spring AOP 整合 AspectJ 时,并未使用 AspectJ 的编译器,它是基于 Java 反射来实现,所以只有两种创建代理的方式。这个问题后续会慢慢讲明白。

Spring官网 推荐使用 AspectJ 注解。@AspectJ是一种将方面声明为带有注释的常规Java类的风格。@AspectJ样式是由AspectJ项目作为AspectJ 5发行版的一部分引入的。Spring解释与AspectJ 5相同的注释,使用AspectJ提供的切入点解析和匹配库。不过,AOP运行时仍然是纯Spring AOP,并且不依赖于AspectJ编译器或waever提升器。

(1)AspectJ 语法

• Aspect
• Join Points
• Pointcuts
• Advice
• Introduction

aspectj官方文档

AspectJ 语法用起来对程序员非常不友好,Spring中建议使用AspectJ的注解方式,Spring将其与IOC完美结合了。

(2)AspectJ 注解

• 激活 AspectJ 自动代理:@EnableAspectJAutoProxy
• Aspect : @Aspect
• Pointcut :@Pointcut
• Advice :@Before、@AfterReturning、@AfterThrowing、@After、@Around
• Introduction :@DeclareParents

(3)总结

代理的类型只有两种 JDK 动态代理和 CGLIB 代理,AspectJAopProxy 只是一种利用 AspectJ 表达式的提升,简化了繁杂的 Spring AOP API 的实现。

Spring AOP代理对象还是由JDK动态代理或者是Cglib来实现。AspectJ 主要被 Spring 用于解析其 Pointcut 表达式

(4)Spring AOP 和 AspectJ AOP 存在哪些区别?

• AspectJ 是 _x0008_AOP 完整实现,Spring AOP 则是部分实现
• Spring AOP 比 AspectJ 使用更简单
• Spring AOP 整合 AspectJ 注解与 Spring IoC 容器
• Spring AOP 仅支持基于代理模式的 AOP
• Spring AOP 仅支持方法级别的 Pointcuts

未完待续

参考资料

极客时间《小马哥讲 Spring AOP 编程思想》

相关文章:

SpringAOP从入门到源码分析大全,学好AOP这一篇就够了(一)

文章目录系列文档索引一、认识AOP1、AOP的引入原因2、AOP常见使用场景日志场景统计场景安防场景性能场景3、AOP概念AOP 的概念Aspect 概念&#xff08;切面&#xff09;Join point 概念&#xff08;连接点&#xff09;Pointcut 概念&#xff08;切入点&#xff09;Advice 概念&…...

【单目标优化算法】樽海鞘群算法(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…...

手把手教你,解决C盘分区不足,C盘怎么扩大磁盘分区

由于在磁盘分区中&#xff0c;C盘是很重要的一个磁盘&#xff0c;为了保证C盘有足够的磁盘分区。其中扩大C盘分区很常见的操作之一。那么&#xff0c;C盘怎么扩大磁盘分区&#xff1f;在本文中&#xff0c;易我小编将全面地讲解C盘合并分区的方法。 一、为什么C盘怎么扩大磁盘分…...

Ethernet-APL——过程自动化的新黄金标准

| Ethernet-APL为终客户和设备制造商带来益处 Ethernet-APL&#xff08;Advanced Physical Layer&#xff0c;高级物理层&#xff09;是一种两线制以太网物理层&#xff0c;它使用了由IEEE 802.3cg所定义的10BASE-T1L&#xff0c;并采用了新的工艺制造规定&#xff0c;因此构成…...

LVGL Styles

LVGL StylesGet started按钮添加标签按钮添加风格滑动条值显示StylesSize stylesBackground stylesBorder stylesOutline stylesShadow stylesImage stylesArc stylesText stylesLine stylesGet started 按钮添加标签 /*** brief 按钮事件回调函数* param e */ void btn_eve…...

扬帆优配|联通港股创近两年新高!A股资源类股爆发,食品饮料领跌

今日上午&#xff0c;A股商场和港股商场均现较大起伏震动&#xff0c;临近上午收盘出现一波跳水&#xff0c;不过&#xff0c;到上午收盘&#xff0c;上证指数仍微涨0.10%&#xff0c;煤炭等资源类板块明显上涨。 港股商场上午走弱&#xff0c;科技股领跌。 沪指微涨0.10%资源…...

Win10+VS2019+Qt5.15.2下编译QCAD

一&#xff1a;官方说法&#xff1a;WindowsDownload and install a C compiler, for example:Visual Studio C Express or Visual Studio CommunityDownload and install Qt from qt.io (see supported platforms):Download for example the online installer fileqt-opensour…...

【微信小程序】原生微信小程序ts模板下引入vant weapp

之前一直是在普通项目下使用 vant weapp&#xff0c;这不最近学了ts&#xff0c;使用微信开发工具的tsless初始化项目&#xff0c;再引入 vant 时踩了好久坑&#xff0c;特来记录一下 前言 本文章适合微信开发工具的ts项目&#xff0c;指的是项目目录结构如下图 总结 从上图…...

Puppeteer之Pyppeteer——自动登录Gmail(6)

前言 本文是该专栏的第6篇,结合项目案例让你熟练使用pyppeteer,后面会持续分享Pyppeteer的干货知识,记得关注。 有同学留言,使用selenium自动登录gmail邮箱,被Google识别为机器人怎么办?出现这个问题,主要在于Google可以检测出selenium的控件,无论你怎么加入规避方法,…...

python 绘图 —— 绘制从顶部向底部显示的柱形图[ax.bar()]

python 绘图 —— 绘制从顶部向底部显示的柱形图[ax.bar()] 效果图如下所示&#xff1a; 就是这个样子&#xff0c;一般比较少见将柱形图从上往下绘制的。可能是会为了更好的展示数据对比结果吧。这里绘图的主要思路如下&#xff1a; 利用ax.twinx()这个函数生成一个新的x轴…...

自定义Task工具,调用系统的IdleHandler方法

1、工具类&#xff0c;调用系统的IdleHandler方法package com.abbi.viewdemo;import android.os.Looper;import android.os.MessageQueue;import java.util.LinkedList;import java.util.Queue;public class DelayTaskDispatcher {private Queue<Task> delayTasks new L…...

生态GEP案例分享

浙江省领导参观德清”两山“平台4月30日上午&#xff0c;浙江省政府参事室一行领导&#xff0c;在德清县副县长陪同下考察当地数据智能运营中心&#xff0c;参观了国内首个县域数字“两山”决策支持平台建设成果。国内生产总值(GDP)&#xff0c;是指按国家市场价格计算的一个国…...

企业级信息系统开发学习笔记1.5 初探Spring AOP

文章目录零、本讲学习目标一、Spring AOP&#xff08;一&#xff09;AOP基本含义&#xff08;二&#xff09;AOP基本作用&#xff08;三&#xff09;AOP与OOP对比&#xff08;四&#xff09;AOP使用方式&#xff08;五&#xff09;AOP基本概念二、提出游吟诗人唱赞歌任务&#…...

Mysql数据库主主从设置

注意&#xff1a;在同一台服务器上部署主从或主主之类的时候&#xff0c;数据库data下有一个auto.cnf里的uuid不能重复。 原则&#xff1a;做同步之前要保证两个数据库数据一致. 锁表操作&#xff1a; FLUSH TABLES WITH READ LOCK; 注&#xff1a;没有锁定主服务器&#xf…...

监管持续,医疗卫生机构如何守好“涉疫”数据安全?

肆虐三年的新冠疫情&#xff0c;影响着全球经济发展、社会正常运行&#xff0c;也成为网络攻击、勒索软件攻击快速增长的温床&#xff0c;“滋生”了一系列网络、数据安全问题&#xff0c;受到各界关注。最近&#xff0c;上线运行三年的 “粤康码”发布公告、官宣部分服务下线&…...

STM32开发(13)----获取唯一设备标识符UID

获取唯一设备标识符UID前言一、什么事UID二、实验过程1.CubeMx配置2.代码实现3.实验结果总结前言 这一章节介绍如何获取STM32芯片中的唯一的ID号的两种方法。 一、什么事UID 在许多项目中&#xff0c;识别设备是必要的。从简单的设备描述到更复杂的设备&#xff0c;如 USB 串…...

华为OD机试 - 最优调度策略(Python) | 机试题+算法思路+考点+代码解析 【2023】

最优调度策略 题目 在通信系统中有一个常见的问题是对用户进行不同策略的调度 会得到不同系统消耗的性能 假设由 N 个待串行用户,每个用户可以使用 A/B/C 三种不同的调度策略 不同的策略会消耗不同的系统资源 请你根据如下规则进行用户调度 并返回总的消耗资源数 规则是:相…...

前端零基础入门-002-集成开发环境

本篇目标 了解市面上常用的前端集成开发环境&#xff08;ide&#xff09;掌握 HBuiberX 的使用&#xff1a;下载安装&#xff0c;新建项目、网页、运行网页。 内容摘要 本篇介绍了市面上流行的几款前端集成开发环境&#xff08;ide&#xff09;&#xff0c;并介绍了 Hbuilde…...

su和sudo的区别

linux中 su和sudo的区别 su和sudo是两个常用的切换到root超级用户命令。功能上类似&#xff0c;但使用上还是有不少差异。 su命令 su是切换到root用户的命令&#xff0c;主要用法如下&#xff1a; su&#xff1a;不带参数直接输入su将切换到root用户&#xff0c;需要输入root…...

【ChatGPT】使用ChatGPT进行51单片机程序编程体验

背景 最近ChatGPT大火&#xff0c;笔者尝试使用它进行了51单片机编程尝试。ChatGPT是一种由OpenAI训练的大型语言模型&#xff0c;具有强大的自然语言处理能力和生成代码的能力。 使用ChatGPT进行51单片机编程&#xff0c;笔者发现它可以帮助开发人员快速生成符合要求的代码&a…...

⚡ Hyperlane —— 比 Rocket 更快的 Rust Web 框架!

⚡ Hyperlane —— 比 Rocket 更快的 Rust Web 框架&#xff01; 在现代 Web 服务开发中&#xff0c;开发者需要一个既轻量级又高性能的 HTTP 服务器库来简化开发流程&#xff0c;同时确保服务的高效运行。Hyperlane 正是为此而生——一个专为 Rust 开发者设计的 HTTP 服务器库…...

CentOS_7.9 2U物理服务器上部署系统简易操作步骤

近期单位网站革新&#xff0c;鉴于安全加固&#xff0c;计划将原有Windows环境更新到Linux-CentOS 7.9&#xff0c;这版本也没的说&#xff08;绝&#xff09;了&#xff08;版&#xff09;官方停止更新&#xff0c;但无论如何还是被sisi的牵挂着这一大批人&#xff0c;毕竟从接…...

java对接bacnet ip协议(跨网段方式)

1、环境准备 #maven环境<repositories><repository><id>ias-releases</id><url>https://maven.mangoautomation.net/repository/ias-release/</url></repository></repositories><dependencies><dependency><…...

地震资料裂缝定量识别——学习计划

学习计划 地震资料裂缝定量识别——理解常规采集地震裂缝识别方法纵波各向异性方法蚁群算法相干体及倾角检测方法叠后地震融合属性方法裂缝边缘检测方法 非常规采集地震裂缝识别方法P-S 转换波方法垂直地震剖面方法 学习计划 地震资料裂缝定量识别——理解 地震资料裂缝识别&a…...

如何让 Git 停止跟踪文件?停止后又如何恢复跟踪?

在使用 Git 管理代码时&#xff0c;有时我们希望某些文件不再被 Git 跟踪&#xff08;比如本地配置文件、临时文件等&#xff09;&#xff0c;但保留这些文件在本地&#xff1b;过了一段时间&#xff0c;可能又需要恢复跟踪这些文件。本文将用通俗易懂的语言&#xff0c;教你如…...

NULL与空字符串的区别:数据库专家详解

NULL与空字符串的区别&#xff1a;数据库专家详解 1. NULL的概念解析 1.1 NULL的定义 在数据库系统中&#xff0c;NULL是一个特殊标记&#xff0c;表示"未知"或"不存在"的值。它不是任何数据类型的实例&#xff0c;而是表示缺失值的标记。 go专栏&#…...

设计模式(行为型)-中介者模式

目录 定义 类图结构展示 角色职责详解 模式的优缺点分析 优点 缺点 适用场景 应用实例 与其他模式的结合与拓展 总结 定义 中介者模式的核心思想可以概括为&#xff1a;用一个中介对象来封装一系列的对象交互。这个中介者就像一个通信枢纽&#xff0c;使各对象不需要…...

Cocos 打包 APK 兼容环境表(Android API Level 10~15)

使用 Cocos 打包 APK&#xff1a;Android 10 ~ Android 15 兼容版本对照表 ✅ 本表基于 Cocos Creator 3.x 实际测试及官方建议整理 &#x1f4c6; 最后更新时间&#xff1a;2025年6月 &#x1f4a1; 推荐使用 Android Studio 2022 或命令行构建工具 Android 版本API Level推荐…...

vscode 配置 QtCreat Cmake项目

1.vscode安装CmakeTool插件并配置QT中cmake的路径&#xff0c;不止这一处 2.cmake生成器使用Ninja&#xff08;Ninja在安装QT时需要勾选&#xff09;&#xff0c;可以解决[build] cc1plus.exe: error: too many filenames given; type ‘cc1plus.exe --help’ for usage 编译时…...

如何以 9 种方式将照片从 iPhone 传输到笔记本电脑

您的 iPhone 可能充满了以照片和视频形式捕捉的珍贵回忆。无论您是想备份它们、在更大的屏幕上编辑它们&#xff0c;还是只是释放设备上的空间&#xff0c;您都需要将照片从 iPhone 传输到笔记本电脑。幸运的是&#xff0c;有 9 种方便的方法可供使用&#xff0c;同时满足 Wind…...