【微服务】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编程中的重要内容,也是日常开发中运用比较广泛的技术点,有必要对其做深入的理解和掌握。本篇到此结束,感谢观看。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/228557.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【OpenCV】 OpenCV 源码编译并实现 CUDA 加速 (Windows)

OpenCV 源码编译并实现 CUDA 加速 Windows 1. 环境准备1.1 软件环境1. 2 源码下载 2. CMake编译项目2.1 创建cmake项目2.2 设置编译配置2.3 解决异常2.3.1 文件下载异常2.3.2 解决CUDA版本异常 2.4 编译项目 3. Visual Studio 编译项目4. 项目测试5. 总结 OpenCV是一个基于Apac…

Ubuntu 常用命令之 ln 命令用法介绍

ln命令在Ubuntu系统中用于创建硬链接或符号链接。硬链接是指向文件的物理地址&#xff0c;而符号链接&#xff08;也称为软链接&#xff09;是指向文件路径的引用。 命令格式&#xff1a;ln [选项]... [-T] 目标&#xff08;源文件&#xff09; 链接&#xff08;目标文件&…

【FPGA】Verilog:编码器 | 实现 4 到 2 编码器

0x00 编码器&#xff08;Encoder&#xff09; 编码器与解码器相反。当多台设备向计算机提供输入时&#xff0c;编码器会为每一个输入生成一个与设备相对应的信号&#xff0c;因此有多少比特就有多少输出&#xff0c;以数字形式表示输入的数量。 例如&#xff0c;如果有四个输…

NAS搭建WebDAV服务同步Zotero科研文献

文章目录 一、Zotero安装教程二、群晖NAS WebDAV设置三、Zotero设置四、使用公网地址同步Zotero文献库五、使用永久固定公网地址同步Zotero文献库 Zotero 是一款全能型 文献管理器,可以 存储、管理和引用文献&#xff0c;不但免费&#xff0c;功能还很强大实用。 ​ Zotero 支…

重新认识Word——尾注

重新认识Word——尾注 参考文献格式文献自动生成器插入尾注将数字带上方括号将参考文献中的标号改为非上标 多处引用一篇文献多篇文献被一处引用插入尾注有横线怎么删除&#xff1f;删除尾注 前面我们学习了如何给图片&#xff0c;公式自动添加编号&#xff0c;今天我们来看看毕…

LSTM ——作业

习题6-4 推导LSTM网络中参数的梯度&#xff0c; 并分析其避免梯度消失的效果 习题6-3P 编程实现下图LSTM运行过程 1. 使用Numpy实现LSTM算子 import numpy as np # 创建一个numpy数组x&#xff0c;它是一个4x4的矩阵&#xff0c;包含9个元素 x np.array([[1, 0, 0, 1],[3, …

Unity中URP下的菲涅尔效果实现(URP下的法线和视线向量怎么获取)

文章目录 前言一、实现思路二、实现原理我们可以由下图直观的感受到 N 与 L夹角越小&#xff0c;点积越接近&#xff08;白色&#xff09;1。越趋近90&#xff0c;点积越接近0&#xff08;黑色&#xff09; 三、实现URP下的菲涅尔效果1、我们新建一个Shader&#xff0c;修改为最…

安全密码(字符串)

#include <stdio.h> #include <stdbool.h> #include <string.h> bool is_secure_password(const char* password); int main() {int M;char password[51];// 读取输入中的密码数量 Mscanf("%d", &M);// 处理每个密码for (int i 0; i < M; …

Pytorch:Tensorboard简要学习

目录 一、TensorBoard简介二、TensorBoard的安装与启动Tensorboard的安装Tensorboard的启动 三、TensorBoard的简单使用3.1 SummaryWriter()3.2 add_scalar()和add_scalars()3.3 add_histogram()3.4 模型指标监控 四、总结参考博客 一、TensorBoard简介 TensorBoard 是Google开…

17.Oracle中instr()函数查询字符位置

1、instr()函数的格式 &#xff08;俗称&#xff1a;字符查找函数&#xff09; 格式一&#xff1a;instr( string1, string2 ) // instr(源字符串, 目标字符串) 格式二&#xff1a;instr( string1, string2 [, start_position [, nth_appearance ] ] ) // instr(源字符…

typescript使用解构传参

看下面这个函数 interface Student {id: number;name: string;class: string;sex: string;}function matriculation(student: Student) {//...}我们要调用它,就需要传递一个实现了Student约束的对象进去 interface Student {id: number;name: string;class: string;sex: string…

C语言:将三个数从大到小输出

#include<stdio.h> int main() {int a 0;int b 0;int c 0;printf("请输入abc的值&#xff1a;");scanf_s("%d%d%d", &a, &b, &c);if (b > a){int tmp a;a b;b tmp;}if (c > a){int tmp a;a c;c tmp;}if (b < c){int t…

app分发平台哪个好点?手机app应用内测分发平台支持负载均衡的重要性

随着互联网的快速发展&#xff0c;内测分发平台扮演着越来越重要的角色。而在现代应用程序的开发和运营过程中&#xff0c;负载均衡技术是不可或缺的一部分。内测分发平台支持负载均衡对于提高系统的稳定性、可靠性和性能至关重要。那么什么是负载均衡又有哪些重要性。 图片来源…

在线学习平台,云课堂云教育类网站源码,在线题库+随身携带的刷题神器+视频安装教程

源码介绍 在线题库&#xff1a;由传统的线下学习模式改为在线学习。能够实现学员在线学习、练习、考试 优点&#xff1a;方便、便宜、自我管理、选择性更多 、成人教育 &#xff08;1&#xff09;公考&#xff1a;国考、省考、事业单位… &#xff08;2&#xff09;升学&…

数据结构期末考题之001

算法复杂度就是n&#xff08;0&#xff09;

C++ Qt开发:自定义Dialog对话框组件

Qt 是一个跨平台C图形界面开发库&#xff0c;利用Qt可以快速开发跨平台窗体应用程序&#xff0c;在Qt中我们可以通过拖拽的方式将不同组件放到指定的位置&#xff0c;实现图形化开发极大的方便了开发效率&#xff0c;本章将重点介绍自定义Dialog组件的常用方法及灵活运用。 在…

【上海大学数字逻辑实验报告】七、中规模元件及综合设计

一、实验目的 掌握中规模时序元件的测试。学会在Quartus II上设计序列发生器。 二、实验原理 74LS161是四位可预置数二进制加计数器&#xff0c;采用16引脚双列直插式封装的中规模集成电路&#xff0c;其外形如下图所示&#xff1a; 其各引脚功能为&#xff1a; 异步复位输…

在做题中学习(33):只出现一次的数字 II

137. 只出现一次的数字 II - 力扣&#xff08;LeetCode&#xff09; 思路&#xff1a; 1.首先想到出现三次的数&#xff0c;它们仨的任意一位都是相同的&#xff08;1/0&#xff09; 2.可以发现出现三次的数的某一位和a某一位在所有情况下%3最后的结果都和a的那一位相同&…

PMP项目管理 - 风险管理

系列文章目录 PMP项目管理 - 质量管理 PMP项目管理 - 采购管理 PMP项目管理 - 资源管理 PMP项目管理 - 风险管理 现在的一切都是为将来的梦想编织翅膀&#xff0c;让梦想在现实中展翅高飞。 Now everything is for the future of dream weaving wings, let the dream fly in…

Java技术栈 —— Log4j 2、Logpack、SLF4j日志框架介绍

Log4j 2、Logpack、SLF4j日志框架介绍 Log4j 2、Logpack、SLF4j日志框架&#xff0c;及其区别1.1 Log4j 21.1.1 日志级别1.1.2 日志输出目标位置1.1.3 日志刷新机制1.1.4 结构化打印日志1.1.5 异步打印日志1.1.6 在Cloud云环境汇集日志信息 1.2 LogPack1.3 SLF4j1.4 区别 Log4j…