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

【微服务】Spring Aop原理深入解析

目录

一、前言

二、aop概述

2.1 什么是AOP

2.2 AOP中的一些概念

2.2.1 aop通知类型

2.3 AOP实现原理

2.3.1 aop中的代理实现

2.4 静态代理与动态代理

2.4.1 静态代理实现

三、 jdk动态代理与cglib代理

3.1 jdk动态代理

3.1.1 jdk代理示例

3.1.2 jdk动态代理模拟实现

3.2 CGLIB 代理

3.2.1 cglib代理代码示例

3.2.2 cglib代理源码模拟实现

3.2.3 cglib代理补充说明

四、spring aop源码探究

4.1 环境准备

4.1.1 引入aop依赖包

4.1.2 自定义aop配置类

4.1.3 测试方法

4.2 spring aop切点表达式

4.2.1 语法结构

4.2.2 常见的切点表达式示例

4.3 spring aop源码分析过程

五、写在文末


一、前言

spring 是一个流行的 Java 企业应用程序开发框架,其中的 aop(面向切面编程)是 spring 框架中一个非常重要的概念。可以说在spring框架以及生态体系下,随处可见aop编程思想的运用,借助这一编程思想,在很多特殊的业务场景下,aop的使用让编码变得易扩展、更优雅、更灵活,同时也能很好的解决通用的业务问题,提升开发效率。本文将详细介绍aop的核心技术和底层实现原理。

二、aop概述

2.1 什么是AOP

AOP,即面向切面编程,AOP是一种编程范式,用于在不修改原始代码的情况下向现有应用程序添加新功能。这种编程方式将应用程序分成许多独立的部分,称为切面。这些切面可以在应用程序的不同位置进行编写和维护,从而提高了应用程序的可重用性和可维护性

AOP是OOP(面向对象编程)的延续,主要用于实现横切关注点,比如:日志记录、性能统计、安全控制、事务处理等方面。简单来说,针对应用程序中那些可以预见的,以及归总为公共的处理业务的场景均可以考虑使用aop的编程实现。

2.2 AOP中的一些概念

为了后续更全面深入的掌握aop的使用,有必要了解下spring aop的相关概念。这些概念能够更好的指导我们在编码中对aop的原理的理解。

切面(Aspect)

AOP核心就是切面,它将多个类的通用行为封装成可重用的模块,该模块含有一组API提供横切功能。比如,一个日志模块可以被称作日志的AOP切面。根据需求的不同,一个应用程序可以有若干切面。在Spring AOP中,切面通过带有@Aspect注解的类实现。

连接点(Join Point)

AOP核心就是切面,它将多个类的通用行为封装成可重用的模块,该模块含有一组API提供横切功能。比如,一个日志模块可以被称作日志的AOP切面。根据需求的不同,一个应用程序可以有若干切面。在Spring AOP中,切面通过带有@Aspect注解的类实现。

连接点(Join Point)

哪些方法需要被AOP增强,这些方法就叫做连接点

通知(Advice)

AOP在特定的切入点上执行的增强处理

常用的通知类型有:

  • before,前置通知 ;
  • after,后置通知 ;
  • afterReturning,返回通知 ;
  • afterThrowing,异常通知 ;
  • around,环绕通知 ;

切入点(Pointcut)

实际真正被增强的方法,称为切入点

2.2.1 aop通知类型

通知(advice)是你希望在横切关注点具体的实现方式,即方法之前触发?执行后触发...,aop中的Advice主要有以下5种类型:

前置通知(Before Advice)

在连接点之前执行的Advice,即方法执行前,使用@Before注解使用这个Advice

返回之后通知(After Retuning Advice)

在连接点正常结束之后执行的Advice,即你的方法正常执行完成之后执行,通过 @AfterReturning注解使用它,前提是你的方法没有抛出异常

抛出(异常)后执行通知(After Throwing Advice)

如果方法执行抛异常的时候,这个Advice就会被执行,通过 @AfterThrowing注解来使用

后置通知(After Advice)

无论连接点通过什么方式退出(方法正常返回或者抛出异常)都会执行在结束后执行这些Advice,通过 @After注解使用

围绕通知(Around Advice)

围绕连接点执行的Advice,方法执行前可以拦截参数,方法执行后可以拿到执行结果,属于几种通知中比较灵活的一种,通过@Around注解使用

2.3 AOP实现原理

Spring AOP 的实现原理是基于动态代理字节码操作的。具体来说,

  • 在编译阶段, Spring 会使用 AspectJ 编译器将切面代码编译成字节码文件;
  • 在运行阶段, Spring 会使用 Java 动态代理或 CGLIB 代理生成代理类,这些代理类会在目标对象方法执行前后插入切面代码,从而实现AOP增强的功能;

Spring AOP 主要使用两种代理方式:JDK动态代理和 CGLIB 代理。如果目标对象实现了至少一个接口,则框架使用JDK动态代理;否则,使用 CGLIB 代理。

2.3.1 aop中的代理实现

基于接口情况下,使用JDK动态代理

JDK动态代理,创建一个接口实现类的代理对象,该接口实现类的代理对象会调用该接口的真实实现,并且在代理对象中调用真实的实现类的方法进行增强

没有接口情况下,使用 CGLIB 动态代理

CGLIB动态代理,创建一个类子类的代理对象,该子类的代理对象会去调用父类中的方法,并且在子类代理对象调用其父类方法后做增强

2.4 静态代理与动态代理

很多同学在理解aop的代理时,很容易对静态代理与动态代理这个概念搞混,顾名思义,静态代理,简单理解就是代理的类或方法定义已经提前定义好了,在需要使用的地方直接调用即可,而动态代理,关键是理解动态这个概念,从程序的角度来说,就是在程序运行过程中,动态生成了目标对象的代理对象。

2.4.1 静态代理实现

定义一个接口

public interface LoginService {void login(String userId);
}

接口实现

public class LoginServiceImpl implements LoginService {@Overridepublic void login(String userId) {System.out.println("登录成功,userId:"+userId);}
}

实现了LoginService 接口的代理实现类

public class ProxyLoginService implements LoginService {private LoginServiceImpl loginServiceImpl;public ProxyLoginService(LoginServiceImpl loginServiceImpl){this.loginServiceImpl=loginServiceImpl;}@Overridepublic void login(String userId) {System.out.println("执行登录之前操作");loginServiceImpl.login("user");System.out.println("执行登录方法后操作");}
}

测试方法

public static void main(String[] args) {//创建电影院(静态代理)ProxyLoginService proxyLoginService = new ProxyLoginService(new LoginServiceImpl());proxyLoginService.login("user1");}

静态代理优点

  • 使得真实角色处理的业务更加纯粹,不再去关注一些公共的事情 ;
  • 公共的业务由代理来完成---实现业务的分工;
  • 公共业务发生扩展时变得更加集中和方便 ;

缺点:

  • 每一个代理类都必须实现一遍真实实现类(也就是realMovie)的接口,如果接口增加方法,则代理类也必须跟着修改;
  • 每一个代理类对应一个真实实现类(委托类),如果真实实现(委托类)非常多,则静态代理类就非常臃肿,难以胜任;

三、 jdk动态代理与cglib代理

3.1 jdk动态代理

与静态代理不同,动态代理是根据代理对象,动态创建代理类。这样就可以避免静态代理中代理类接口过多的问题。jdk动态代理是通过反射来实现的,借助Java自带的java.lang.reflect.Proxy,通过固定的规则生成。

jdk动态代理的使用步骤如下:

  • 创建一个需要动态代理的接口,即Movie接口;
  • 创建一个需要动态代理接口的真实实现,即RealMovie类;
  • 创建一个动态代理处理器,实现InvocationHandler接口,并重写invoke方法去增强真实实现中的目标方法;
  • 在测试类中,生成动态代理的对象;

3.1.1 jdk代理示例

自定义一个类,实现InvocationHandler接口,并重写里面的invoke方法

public class ProxyInvocationHandler implements InvocationHandler {//需要动态代理接口的真实实现类private Object object;//通过构造方法去给需要动态代理接口的真实实现类赋值public ProxyInvocationHandler(Object object) {this.object = object;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {//方法增强System.out.println("方法执行前操作");//object是真实实现,args是调用方法的参数//当代理对象调用真实实现的方法,那么这里就会将真实实现和方法参数传递过去,去调用真实实现的方法method.invoke(object,args);//方法增强System.out.println("方法执行后操作");return null;}
}

编写测试类,使用自定义的handler生成代理对象并调用目标方法

public class DynamicProxyTest {public static void main(String[] args) {//需要动态代理接口的真实实现LoginServiceImpl realMovie = new LoginServiceImpl();//动态代理处理类ProxyInvocationHandler handler = new ProxyInvocationHandler(realMovie);//获取动态代理对象//第一个参数:真实实现(被代理对象)的类加载器//第二个参数:真实实现类(被代理对象)它所实现的所有接口的数组//第三个参数:动态代理处理器LoginService loginService = (LoginService) Proxy.newProxyInstance(realMovie.getClass().getClassLoader(),realMovie.getClass().getInterfaces(),handler);loginService.login("user");}}

执行结果

3.1.2 jdk动态代理模拟实现

由于jdk的代理是在运行期间动态产生字节码,所以很难看到运行期间代理产生的源码,我们可以模拟其原理进行一个简单的实现,代码如下:

定义一个接口,作为被代理的对象,实现类也一并贴出

    interface MinuService {int addMinu();void reduceMinu();}//目标对象实现类static class Target implements MinuService {@Overridepublic int addMinu() {System.out.println("tager do addMinu");return 1;}@Overridepublic void reduceMinu() {System.out.println("tager do reduceMinu");}}

定义一个handler,handler的作用可理解为作为代理对象中实际执行方法调用的一个入口

    interface MyInvocationHandler {Object invoke(Object proxy,Method method, Object[] args);}

定义代理类,代理类是核心实现,在代理类中主要做的事情如下:

  • 引用自定义handler;
  • 引用代理类中待执行的目标方法(该方法其实是运行期间动态生成的);
  • 调用handler中的invoke反射调用目标方法,并得到执行结果;
import java.lang.reflect.Method;public class MyProxy implements JdkInner.MinuService {private JdkInner.MyInvocationHandler handler;public MyProxy(JdkInner.MyInvocationHandler handler) {this.handler = handler;}static Method addMinu;static Method reduceMinu;static {try {addMinu = JdkInner.MinuService.class.getMethod("addMinu");reduceMinu = JdkInner.MinuService.class.getMethod("reduceMinu");} catch (NoSuchMethodException e) {e.printStackTrace();}}@Overridepublic int addMinu() {Object invoke = null;invoke = handler.invoke(this, addMinu, new Object[0]);return Integer.parseInt(invoke.toString());}@Overridepublic void reduceMinu() {handler.invoke(this, reduceMinu, new Object[0]);}
}

编写测试类,有没有发现这个写法和上述使用jdk的动态代理很像

 public static void main(String[] args) {MinuService minuService = new MyProxy(new MyInvocationHandler() {@Overridepublic Object invoke(Object proxy,Method method, Object[] args) {System.out.println("before  invoke method");Object result = null;try {result = method.invoke(new Target(), args);System.out.println("result :" + result);} catch (IllegalAccessException e) {e.printStackTrace();} catch (InvocationTargetException e) {e.printStackTrace();}return result;}});System.out.println(minuService.getClass());minuService.addMinu();}

如果你想查看jdk动态代理时产生的源码,可以考虑使用arthars进行反编译查看

3.2 CGLIB 代理

CGLIB 代理是一个基于字节码操作的代理方式,它是一个强大的、高性能、高质量的 Code 生成类库,可以在运行期扩展 Java 类与实现 Java 接口,可以为没有实现接口的类创建代理对象。CGLIB 代理会在运行时生成一个目标对象的子类,并覆盖其中的方法,以实现AOP的功能。下面是 CGLIB 代理的实现代码:

public class CglibAopProxy implements AopProxy {private final AdvisedSupport advised;public CglibAopProxy(AdvisedSupport advised) {this.advised = advised;}@Overridepublic Object getProxy() {Enhancer enhancer = new Enhancer();enhancer.setSuperclass(advised.getTargetSource().getTargetClass());enhancer.setCallback(new DynamicAdvisedInterceptor(advised));return enhancer.create();}private static class DynamicAdvisedInterceptor implements MethodInterceptor {private final AdvisedSupport advised;public DynamicAdvisedInterceptor(AdvisedSupport advised) {this.advised = advised;}@Overridepublic Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {MethodInvocation methodInvocation = new CglibMethodInvocation(advised.getTargetSource().getTarget(),method,args,proxy,advised.getMethodInterceptor(),advised.getTargetSource().getTargetClass());return methodInvocation.proceed();}}
}

3.2.1 cglib代理代码示例

下面是一段使用cglib进行代理的代码

import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;import java.lang.reflect.Method;public class CglibTest {static class Target {public void get() {System.out.println("target get");}}public static void main(String[] args) {Target target = new Target();//拿到代理的类Target proxyTarget = (Target)Enhancer.create(Target.class, new MethodInterceptor() {@Overridepublic Object intercept(Object proxyClass, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {System.out.println("before....");Object result = method.invoke(target, args);System.out.println("after...");return result;}});//执行代理类的方法proxyTarget.get();}}

结果看起来和使用jdk代理类似

与jdk代理不同的是,jdk的代理仅能针对接口代理,而cglib生成的代理对象是一个子类,所以需要注意,使用cglib进行代理的时候,父类不能是final的,并且目标类中的代理方法也不能是final的。

3.2.2 cglib代理源码模拟实现

上面的案例了解了如何使用cglib进行代理以及代码的实现,下面来模拟一下其底层源码的实现

定义一个目标类

public class CglibTarget {public void add(){System.out.println("add()");}public void add(int num){System.out.println("add() :" + num);}public void reduce(int count){System.out.println("reduce() :" + count);}
}

定义代理类

通过上面的介绍了解到,cglib的代理对象是通过生成目标对象的子类实现的,所以代理类需要继承目标类

import org.springframework.cglib.proxy.MethodInterceptor;import java.lang.reflect.Method;public class CglibProxy extends CglibTarget {private MethodInterceptor methodInterceptor;public void setMethodInterceptor(MethodInterceptor methodInterceptor) {this.methodInterceptor = methodInterceptor;}static Method add_1;static Method add_2;static {try {add_1 = CglibTarget.class.getMethod("add");add_2 = CglibTarget.class.getMethod("add",int.class);} catch (NoSuchMethodException e) {e.printStackTrace();}}@Overridepublic void add() {try {methodInterceptor.intercept(this,add_1,new Object[0],null);} catch (Throwable throwable) {throwable.printStackTrace();}}public void add(int num){try {methodInterceptor.intercept(this,add_2,new Object[]{num},null);} catch (Throwable throwable) {throwable.printStackTrace();}}public void reduce(int count){super.reduce(count);}}

测类类

import com.congge.aop.cglib.CglibProxy;
import com.congge.aop.cglib.CglibTarget;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;import java.lang.reflect.Method;public class CglibTest {public static void main(String[] args) {CglibProxy proxy = new CglibProxy();CglibTarget target = new CglibTarget();proxy.setMethodInterceptor(new MethodInterceptor() {@Overridepublic Object intercept(Object targetObj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {System.out.println("before handle");Object result = method.invoke(target, args);System.out.println("after handle");return result;}});proxy.add(10);}
}

运行代码,得到如下效果

3.2.3 cglib代理补充说明

在使用cglib代码编码实现中,注意到在intercept方法参数中有一个Method的参数,这个参数是做什么用的呢?

不妨将代码修改成下面这样

public static void main(String[] args) {Target target = new Target();//拿到代理的类Target proxyTarget = (Target)Enhancer.create(Target.class, new MethodInterceptor() {@Overridepublic Object intercept(Object proxyClass, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {System.out.println("before....");//Object result = method.invoke(target, args);Object result = methodProxy.invoke(target, args);System.out.println("after...");return result;}});//执行代理类的方法proxyTarget.get();}

再次执行,发现仍然可以得到正确的结果,为什么会这样呢?

cglib代理底层通过这种方式为调用者提供了多一种选择,当选择使用methodProxy的invoke方法时,将不反射,而是退化为直接使用原始目标对象去调用方法,某些情况下,可以获得比反射更高的性能。

四、spring aop源码探究

spring底层在aop的代理上是怎么处理的呢,接下来让我们通过源码一探究竟。

4.1 环境准备

4.1.1 引入aop依赖包

只需要引入aspectjweaver即可

        <dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId><version>1.8.3</version></dependency>

4.1.2 自定义aop配置类

定义一个切面的配置类,这里扫描的是某个包路径下的所有方法,并且使用了环绕通知,在通知方法里,输出了方法实际执行耗时

@Component
@Aspect
public class AspectConfig {@Pointcut("execution(* com.congge.service.aop.*.*(..)))")private void pointcut(){}@Around("pointcut()")public Object around(ProceedingJoinPoint point) throws Throwable {long start = System.currentTimeMillis();Object proceed = point.proceed();long end = System.currentTimeMillis();System.out.println("消耗时间:" + (end-start));return proceed;}}

4.1.3 测试方法

编写测试方法,验证上述aop切面通知是否生效

@ComponentScan("com.congge.service.aop")
@EnableAspectJAutoProxy
public class SpringApp {public static void main(String[] args) {AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringApp.class);UserService userService = context.getBean(UserService.class);userService.getUser();}}

运行之后效果如下,说明aop的环绕通知配置生效了

4.2 spring aop切点表达式

在实际开发中,aop编程的一般步骤是,定义切面类,然后定义具体的切点表达式,然后再在具体的通知方法上面引入切点表达式即可。下面列举几种常用的几种自定义切点表达式的写法。

4.2.1 语法结构

例1:对扫描包中的某个类的方法增强

execution(* com.xxx.xxxClass.方法名(..))

例2:对扫描包中的某个类的所有方法增强

 execution(* com.xxx.xxxClass.* (..))

例3:对扫描包中所有类,类中所有方法增强

 execution(* com.xxx.service.*.* (..))

例4:所有带有某个注解的方法或类

@annotation(com.xxx.annotation.xxxAnnotation)

上面举的例子都是去切入具体的某个方法、类,也可以切到某个包下所有的方法,也可以去切某包下带有某注解的方法等等。  

4.2.2 常见的切点表达式示例

下面列举常用的几种切点表达式示例,便于后续的参考

 所有方法执行

execution(public * *(..))

名以"XXX"开头的所有方法执行

execution(* XXX*(..))

XXX接口中所有方法执行

 execution(* com.xxx.XXX.*(..))

service包下所有方法执行

 execution(* com.xxx.service..*.*(..))

service包下的所有连接点

 (仅在Spring AOP中执行方法)

within(com.xxx.service..*)

代理实现XXX接口的任何连接点

 (仅在Spring AOP中执行方法)

this(com.xxx.service.XXXService)

所有带有@XXX注解的方法或类

@annotation(com.xxx.annotation.XXX)

4.3 spring aop源码分析过程

通过断点,在真正执行userService.getUser()之前,我们可以看到,userService已经是一个代理对象,而且不难看出,这是一个jdk动态代理产生的对象(userService接口存在实现类)

于是可以断定,在真正执行目标方法的时候,是代理对象在帮我们执行了,那么代理对象在哪里产生的呢?代理对象是什么时候产生的呢?这就是接下来需要搞清的重点所在。

从getBean入手,一路往下走,通过getBean方法,最终获取到了代理对象

public <T> T getBean(Class<T> requiredType) throws BeansException {this.assertBeanFactoryActive();return this.getBeanFactory().getBean(requiredType);}

通过getBean方法走到下面这个doGetBean方法里,在这个方法中,注意有一个关键的位置:getSingleton,很多同学在看spring源码的时候发现调用栈非常深,经常陷入一种不知道从何看起的状态,这里说过小技巧,你只需要关注你期望得到的目标即可。比如在doGetBean这里,为了快速获取到userService,可以在此处设置条件断点,如下:

以上述getSingleton为例来说,通过这个方法,可能会得到代理对象,需不需要进去看呢,可以先走一步,如果得到的是代理对象,那么说明getSingleton这个方法中是产生代理对象的地方,按照这个思路,我们走一步看看,发现此刻得到的对象就是一个代理对象,说明确实是在getSingleton这个方法中产生了代理对象;

以getSingleton继续深入,进入该方法之后发现,从singletonObjects中拿到的就是代理对象了,这里不禁冒出一个问题来了,singletonObjects中是什么时候将代理对象放进去的呢?

这里不得不说另一个源码的调试和阅读技巧,就是直接看debug中的调用栈,从下面的调用栈可以发现,上一步singletonObjects中存储的这个代理对象就是在其中的某一步产生并放到容器中的;

 这样一来,至少可以说明,产生代理对象的时机在当前这个getSingleton之前,从spring的bean的生命周期来看,要经历解析,创建,初始化,实例化等一些步骤,所以可以将目光定位到创建的过程,即createBean阶段,于是从getBean方法入手作为突破点即可,再次断点进入,来到getBean方法中

通过getBean一路来到getSingleton方法中,该方法即创建初始的userService的核心代码,而且第一次进入的时候发现userService的bean对象还未创建出来,一直来到该方法的如下位置,利用上面的调试技巧发现就是在这个singletonFactory.getObject()方法得到了代理对象

继续进入这个singletonFactory.getObject()方法,就来到创建bean的方法中,即createBean

沿着该方法继续往下走,当走到doCreateBean这里时,发现这个方法执行结束后就成了代理对象

继续进入doCreateBean方法中,当走完initializeBean方法之后,发现就产生了代理对象

那么initializeBean里面发生了什么呢?可以断定这个方法的某个地方最终产生了代理对象,于是来到下面这个方法

进入到applyBeanPostProcessorsAfterInitialization方法中

来到postProcessAfterInitialization里面之后,沿着wrapIfNecessary继续进入

 

历经千辛万苦,最终来到这里,这个方法就是最终产生代理对象的地方,核心创建代理对象的代码就是下面这段

protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {return bean;} else if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {return bean;} else if (!this.isInfrastructureClass(bean.getClass()) && !this.shouldSkip(bean.getClass(), beanName)) {Object[] specificInterceptors = this.getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, (TargetSource)null);if (specificInterceptors != DO_NOT_PROXY) {this.advisedBeans.put(cacheKey, Boolean.TRUE);Object proxy = this.createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));this.proxyTypes.put(cacheKey, proxy.getClass());return proxy;} else {this.advisedBeans.put(cacheKey, Boolean.FALSE);return bean;}} else {this.advisedBeans.put(cacheKey, Boolean.FALSE);return bean;}}

 我们重点关注方法中createProxy这个方法即可

跳过中间的步骤,最终来到createAopProxy方法中,在这个方法中,展示了如何创建代理对象,以及采用哪种方式创建代理对象,即使用jdk动态代理呢?还是cglib方式呢?

从这段代码不难看出,这里做了一个判断,如果目标对象是接口,将采用jdk动态代理,如果目标对象是类,则使用cglib的代理,由于在前面的代码中目标对象是普通的类,所以将会产生一个cglib的代理对象

补充说明:  

在上面判断使用哪种代理方式时,有一个很重要的参数,即判断下面的这个参数的布尔值,通过源码点击进去发现,默认情况下该参数初始值为false

config.isProxyTargetClass()

该参数的意义在于,开发者可以通过强制指定这个参数的值,从而改变代理的方式强制使用cglib,如何修改呢,只需要将下面的注解中改为true即可

五、写在文末

本文通过较大的篇幅全面而深度的总结了aop代理相关的技术点,并且深入到源码层面解读了spring aop的完整过程,aop不仅是spring编程中的重要内容,也是日常开发中运用比较广泛的技术点,有必要对其做深入的理解和掌握。本篇到此结束,感谢观看。

相关文章:

【微服务】Spring Aop原理深入解析

目录 一、前言 二、aop概述 2.1 什么是AOP 2.2 AOP中的一些概念 2.2.1 aop通知类型 2.3 AOP实现原理 2.3.1 aop中的代理实现 2.4 静态代理与动态代理 2.4.1 静态代理实现 三、 jdk动态代理与cglib代理 3.1 jdk动态代理 3.1.1 jdk代理示例 3.1.2 jdk动态代理模拟实现…...

Spring Boot JSON中文文档

本文为官方文档直译版本。原文链接 Spring Boot JSON中文文档 引言Jackson自定义序列化器和反序列化器混入 GsonJSON-B 引言 Spring Boot 提供与三个 JSON 映射库的集成&#xff1a; GsonJacksonJSON-B Jackson 是首选的默认库。 Jackson Spring-boot-starter-json 提供了…...

Flink系列之:State Time-To-Live (TTL)

Flink系列之&#xff1a;State Time-To-Live TTL 一、TTL二、TTL实现代码三、过期状态的清理 一、TTL Flink的TTL&#xff08;Time-To-Live&#xff09;是一种数据过期策略&#xff0c;用于指定数据在流处理中的存活时间。TTL可以应用于Flink中的状态或事件时间窗口&#xff0…...

数据结构(Chapter Two -01)—线性表及顺序表

2.1 线性表 线性表是具有相同数据类型的n个数据元素的有限序列。第一个元素为表头元素&#xff0c;最后一个元素为表尾元素。除第一个元素&#xff0c;每个元素有且仅有一个直接前驱。除最后一个元素&#xff0c;每个元素都仅有一个直接后继。 其中线性表包括以下&#xff08;…...

【刷题笔记1】

笔记1 string s;while(cin>>s);cout<<s.length()<<endl;输入为hello nowcoder时&#xff0c;输出为8 &#xff08;nowcoder的长度&#xff09; 2.字符串的输入(有空格) string a;getline(cin, a);cout<<a<<endl;输入为ABCabc a 输出为ABCabc a …...

视频数据卡设计方案:120-基于PCIe的视频数据卡

一、产品概述 基于PCIe的一款视频数据收发卡&#xff0c;并通过PCIe传输到存储计算服务器&#xff0c;实现信号的采集、分析、模拟输出&#xff0c;存储。 产品固化FPGA逻辑&#xff0c;实现PCIe的连续采集&#xff0c;单次采集容量2GB&#xff0c;开源的PCIe QT客…...

Windows使用VNC Viewer远程桌面Ubuntu【内网穿透】

文章目录 前言1. ubuntu安装VNC2. 设置vnc开机启动3. windows 安装VNC viewer连接工具4. 内网穿透4.1 安装cpolar【支持使用一键脚本命令安装】4.2 创建隧道映射4.3 测试公网远程访问 5. 配置固定TCP地址5.1 保留一个固定的公网TCP端口地址5.2 配置固定公网TCP端口地址5.3 测试…...

javascript 数组处理的两个利器: `forEach` 和 `map`(上)

&#x1f90d; 前端开发工程师&#xff08;主业&#xff09;、技术博主&#xff08;副业&#xff09;、已过CET6 &#x1f368; 阿珊和她的猫_CSDN个人主页 &#x1f560; 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 &#x1f35a; 蓝桥云课签约作者、已在蓝桥云…...

【C语言】SCU安全项目1-FindKeys

目录 前言 命令行参数 16进制转字符串 extract_message1 process_keys12 extract_message2 main process_keys34 前言 因为这个学期基本都在搞CTF的web方向&#xff0c;C语言不免荒废。所幸还会一点指针相关的知识&#xff0c;故第一个安全项目做的挺顺利的&#xff0c…...

IDA pro软件 如何修改.exe小程序打开对话框显示的文字?

环境: Win10 专业版 IDA pro Version 7.5.201028 .exe小程序 问题描述: IDA pro软件 如何修改.exe小程序打开对话框显示的文字? 解决方案: 一、在IDA Python脚本中编写代码来修改.rdata段中的静态字符串可以使用以下示例代码作为起点(未成功) import idc# 定义要修…...

Ubuntu22.04切换用户

一、只有一个用户时没有切换用户菜单项 1、用户信息 cat /etc/passwd 2、系统菜单 二、添加用户 添加新用户ym&#xff0c;全名yang mi 三、有两个及以上的用户时出现切换用户菜单项 1、用户信息 cat /etc/passwd 2、系统菜单 四、切换用户 1、点击上图中Switch User …...

torch.gather(...)

1. Abstract 对于 pytorch 中的函数 torch.gather(input, # (Tensor) the source tensordim, # (int) the axis along which to indexindex, # (LongTensor) the indices of elements to gather*,sparse_gradFalse,outNone ) → Tensor有点绕&#xff0c;很多博客画各…...

vscode如何开发微信小程序?JS与TS的主要区别?

要在 VS Code 中编写微信小程序代码并同步到 Git&#xff0c;需要安装以下插件&#xff1a; 1. 微信小程序插件&#xff08;WeChat Mini Program&#xff09;&#xff1a;此插件提供了微信小程序的语法高亮、代码提示、调试、上传等功能。 2. Git 插件&#xff08;GitLens、…...

产品入门第五讲:Axure交互和情境

目录 一.Axure交互和情境的介绍 1.交互介绍 概念 常见的Axure交互设计技巧 2.情境介绍 概念 常见的Axure情境设计技巧&#xff1a; 二.实例展示 1.ERP登录页到主页的跳转 2.ERP的菜单跳转到各个页面 &#x1f4da;&#x1f4da; &#x1f3c5;我是默&#xff0c;一个…...

Python 自动化之收发邮件(一)

imapclient / smtplib 收发邮件 文章目录 imapclient / smtplib 收发邮件前言一、基本内容二、发送邮件1.整体代码 三、获取邮件1.整体代码 总结 前言 简单给大家写个如何用Python进行发邮件和查看邮件教程&#xff0c;希望对各位有所帮助。 一、基本内容 本文主要分为两部分…...

Flutter开发笔记 —— sqflite插件数据库应用

前言 今天在观阅掘金大佬文章的时候&#xff0c;了解到了该 sqflite 插件&#xff0c;结合官网教程和自己实践&#xff0c;由此总结出该文&#xff0c;希望对大家的学习有帮助&#xff01; 插件详情 Flutter的 SQLite 插件。支持 iOS、Android 和 MacOS。 支持事务和batch模式…...

OxLint 发布了,Eslint 何去何从?

由于最近的rust在前端领域的崛起&#xff0c;基于rust的前端生态链遭到rust底层重构&#xff0c;最近又爆出OxLint&#xff0c;是一款基于Rust的linter工具Oxlint在国外前端圈引起热烈讨论&#xff0c;很多大佬给出了高度评价&#xff1b;你或许不知道OxLint&#xff0c;相比ES…...

第一次使用ThreadPoolExecutor处理业务

通过对业务逻辑的分析&#xff0c;进行编码&#xff0c;先把第一条sql查出来的数据进行分组&#xff0c;然后分别使用不同的线程去查询数据返回&#xff0c;并添加到原来的数据中。 总感觉哪里写的不对&#xff0c;但是同事们都没用过这个&#xff0c;请大家指教一下&#xff…...

Sharding-Jdbc(6):Sharding-Jdbc日志分析

1 修改配置 将配置文件中的开启分片日志从false改为true Sharding-JDBC中的路由结果是通过分片字段和分片方法来确定的,如果查询条件中有 id 字段的情况还好&#xff0c;查询将会落到某个具体的分片&#xff1b;如果查询没有分片的字段&#xff0c;会向所有的db或者是表都会查…...

centos安装了curl却报 -bash: curl: command not found

前因 我服务器上想用curl下载docker-compress&#xff0c;发现没有curl命令&#xff0c;就去下载安装&#xff0c;安装完成之后&#xff0c;报-bash: curl: command not found 解决方法 [rootcentos ~]# rpm -e --nodeps curl warning: file /usr/bin/curl: remove failed: …...

Re58:读论文 REALM: Retrieval-Augmented Language Model Pre-Training

诸神缄默不语-个人CSDN博文目录 诸神缄默不语的论文阅读笔记和分类 论文名称&#xff1a;REALM: Retrieval-Augmented Language Model Pre-Training 模型名称&#xff1a;Retrieval-Augmented Language Model pre-training (REALM) 本文是2020年ICML论文&#xff0c;作者来自…...

java的json解析

import com.alibaba.fastjson.*; public class JsonParser { public static void main(String[] args) { String jsonStr "{\"name\":\"John\", \"age\":30}"; // JSON字符串示例 // 将JSON字符串转换为JSONObject对象 JSONObje…...

Spring事务失效的几种情况

Spring事务失效的几种情况 1、未被Spring管理的类中的方法 这种情况是指&#xff1a;没有在类上添加Service、Repository、Component等注解将类交由Spring管理&#xff0c;然后该类中还有加上了Transactional注解 例如&#xff1a; Service //如果没有添加Service这个注解…...

filter的用法与使用场景:筛选数据

//this.allCollectorList:后台给定的所有可供选择数据 //this.collectorData:目前已经存在选中列表中的数据//目前已经存在选中列表中的数据id getSelIdList() {let eIdList = []this.collectorData.forEach(row => {eIdList.push(row.id)})return eIdList },//在中的数据…...

ClickHouse(18)ClickHouse集成ODBC表引擎详细解析

文章目录 创建表用法示例资料分享参考文章 ODBC集成表引擎使得ClickHouse可以通过ODBC方式连接到外部数据库. 为了安全地实现 ODBC 连接&#xff0c;ClickHouse 使用了一个独立程序 clickhouse-odbc-bridge. 如果ODBC驱动程序是直接从 clickhouse-server中加载的&#xff0c;那…...

网络攻击(一)--安全渗透简介

1. 安全渗透概述 目标 了解渗透测试的基本概念了解渗透测试从业人员的注意事项 1.1. 写在前面的话 在了解渗透测试之前&#xff0c;我们先看看&#xff0c;信息安全相关的法律是怎么样的 中华人民共和国网络安全法 《中华人民共和国网络安全法》由全国人民代表大会常务委员会…...

视频号小店资金需要多少?

我是电商珠珠 视频号团队于22年7月发展了自己的电商平台-视频号小店&#xff0c;相比于抖音电商来讲&#xff0c;可以有效的将公域流量转化为私域&#xff0c;对于商家来说&#xff0c;是一件利好的事情。 可以有效的提高客户的黏性&#xff0c;增加店铺回头客。 有很多想要…...

机器学习项目精选 第一期:超完整数据科学资料合集

大噶吼&#xff0c;不说废话&#xff0c;分享一波我最近看过并觉得非常硬核的资源&#xff0c;包括Python、机器学习、深度学习、大模型等等。 1、超完整数据科学资料合集 地址&#xff1a;https://github.com/krishnaik06/The-Grand-Complete-Data-Science-Materials Pytho…...

档案数字化管理可以提供什么服务?

档案数字化管理提供了便捷、高效和安全的档案管理服务&#xff0c;帮助组织更好地管理和利用自己的档案资源。 具体来说&#xff0c;专久智能档案数字化管理可以提供以下服务&#xff1a; 1. 档案扫描和数字化&#xff1a;将纸质档案通过扫描仪转换为数字格式&#xff0c;包括文…...

第一周:AI产品经理跳槽准备工作

一、筛选意向行业 因素1:行业发展情况 1. 行业发展情况和政策 待补充 2. AI人才市场情况 报告下载:待补充 2023年2⽉,ChatGPT爆⽕在脉脉引发各界搜索和热议,当⽉,“AIGC”、“⼈⼯智能”、“ChatGPT”、“⼤模型”等相关词汇搜索指数达到459.31,同⽐增⻓超5.4倍,内…...

web个人博客网站/百度关键词竞价查询系统

大家好&#xff0c;我是燕衔泥。今天咱们来修改一份小红书的美妆报告&#xff0c;我会分享整套PPT的制作思路和一些实用技巧。先来看下原稿这份案例共5个页面&#xff0c;即封面页、目录页、图表页、内容页&#xff0c;这4种类型的页面也是我们日常工作中经常遇到的。确定风格1…...

镇江网站建设 的公司/站长工具端口

01 阿里中间件&#xff08;四面&#xff0c;Java岗&#xff09; 1.1 Java中间件一面 技术一面考察范围 重点问了Java线程锁&#xff1a;synchronized 和ReentrantLock相关的底层实现 线程池的底层实现以及常见的参数 数据结构基本都问了一遍&#xff1a;链表、队列等 Java内存…...

网站建设和托管哪家好/软文推广500字

tuple1 (1, 2, 3, 4, 5, 6, 7,8) temp ()temp1 (1, 2, 3) temp2 1, 2, 3 print(type(temp1)) print(type(temp2))# ,是tuple的关键 tuple不可以修改&#xff0c;可以通过切片和进行插入&#xff0c;通过del完全删除转载于:https://www.cnblogs.com/hotfeng/p/7768674.html...

做网店的网站/线上渠道推广有哪些方式

通过cygwin在WINDOWS系统上安装SSH服务 并使用scp命令进行上传和下载文件。按照步骤参考如下 https://jingyan.baidu.com/article/08b6a591a5048b14a809220b.html 拷贝数据的命令文件参考如下 http://www.360doc.com/content/13/0425/23/3810344_280963267.shtml转载于:h…...

企业网站建设的方法/保定seo排名外包

原文转载于:http://insaneguy.me/2015/04/05/cheshire_cat_in_cpp-pimpl_idiom/ C 中的“柴郡猫技术”(Cheshire Cat Idiom)&#xff0c;又称为 PIMPL(Pointer to IMPLementation) &#xff0c;Opaque Pointer 等&#xff0c;是一种在类中只定义接口&#xff0c;而将私有数据成…...

做视频投稿赚钱的网站/网络推广公司有哪些

2018 下半年网络工程师上午试题分析与解答试题(1)采用n位补码(包含一个符号位)表示数据&#xff0c;可以直接表示数值_ (1) 。试题分析采用n位补码(包含一个符号位)表示数据时&#xff0c;用1位(最高位)表示数的符号(0正1负)&#xff0c;其余n-1位表示数值部分。若表示整数&…...