Spring-事务源码解析2
上一篇文章我们介绍了事务开启注解@EnableTransactionManagement源码解析《Spring-事务源码解析1》 里面提到了2个关键组件,这里我们分析下Spring如何利用这2个组件来给Bean创建代理对象。
本篇文章我们看下当一个类里面包含了@Transactional注解,Spring如何利用上面说的2个组件来实例化Bean,如何让这个Bean带有事务功能。
下面三个部分用来介绍
1、Bean如何创建具有事务功能的代理类(这里就用到上面2个组件)
2、目标方法执行,如何被拦截事务拦截
3、完整的执行流程
一、Bean如何创建具有事务功能的代理类
1、创建目标类的代理对象
创建Bean当然是看Spring非常经典的doCreateBean方法,这里最终就得到目标类的代理对象,这中间就包含了Spring如何利用后置处理器控制"目标类代理对象"创建,如何利用切点匹配要添加通知的方法。因为doCreateBean内容有点多,这里我也就贴我认为比较重要的一部分代码。
//假设我们的业务类AService带有@Transactional注解,下面通过doCreateBean创建AService的代理对象
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) throws BeanCreationException {........1、下面一块代码就是通过反射得到AService的实例对象,这个还只是通过反射拿到了普通的实例对象不是代理对象。BeanWrapper instanceWrapper = null;if (mbd.isSingleton()) {instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);}if (instanceWrapper == null) {instanceWrapper = createBeanInstance(beanName, mbd, args);}Object bean = instanceWrapper.getWrappedInstance();................2、把普通实例对象封装ObjectFactory对象存入三级缓存,在AOP依赖注入和循环依赖会用到,这里我们先忽略。boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName));if (earlySingletonExposure) {addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));}................3、通过AService普通实例对象生成AService的代理对象,也利用了前面提到的2个组件,这里的代理对象就包含了事务功能。逻辑都在"初始化"Object exposedObject = bean;populateBean(beanName, mbd, instanceWrapper);//属性填充exposedObject = initializeBean(beanName, exposedObject, mbd);//初始化................4、利用三级缓存解决依赖注入和循环依赖,这里先忽略。if (earlySingletonExposure) {Object earlySingletonReference = getSingleton(beanName, false);if (earlySingletonReference != null) {if (exposedObject == bean) {exposedObject = earlySingletonReference;}}}........return exposedObject;//返回AService的代理对象
}
下面我们看下Bean的初始化逻辑,如何得到代理对象。
AServcie的普通实例对象初始化。
protected Object initializeBean(String beanName, Object bean, @Nullable RootBeanDefinition mbd) {Object wrappedBean = bean;//这里Bean还只是AService的普通实例对象//执行所有后置处理器的Before方法,本次忽略wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);//如果AServcie实现了InitializingBean接口,执行inint方法,本次忽略invokeInitMethods(beanName, wrappedBean, mbd);//重点:执行所有后置处理器的After方法,这里会生成AService的代理对象。wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);return wrappedBean;
}
Spring里面包含了很多后置处理器,那么生成AService代理类的后置处理器,也就是我们前面提到的2个组件
1、AutoProxyRegistrar组件会往Spring容器中注册InfrastructureAdvisorAutoProxyCreator后置处理器
2、ProxyTransactionManagementConfiguration组件会往Spring容器中注册切面进去
在执行到InfrastructureAdvisorAutoProxyCreator后置处理器的After方法里面,主要是为通过调用wrapIfNecessary方法来创建AService代理对象。
@Override
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {if (bean != null) {........return wrapIfNecessary(bean, beanName, cacheKey); //生成AService的代理对象}return bean;
}
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {//根据Bean的信息,来获取AService所有的Advisor,Advisor可以近似的把他理解成切面,当然Advisor和切面不是一个东西,为了方便理解就近似把他看做是切面。//这里拿到的切面,是符合AService的切面,就Spring会有很多事务相关的切面,每个切面有自己定义的切点规则,比如有的切面处理@X注解的,有的切面只处理@Transactional//这里只返回AService符合切点规则的那个切面。为什么要拿切面,因为Spring在创建代理类的时候,就是要基于切面里面的通知,来对目标方法进行拦截。Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);if (specificInterceptors != DO_NOT_PROXY) {//利用Advices来生成AService的代理对象。Object proxy = createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));return proxy;}return bean;
}
wrapIfNecessary分为2块内容
1、拿到符合目标类的Advisor或者说符合目标类的切面
2、创建代理对象
1.1 getAdvicesAndAdvisorsForBean是如何获取符合规则的Advisor
//获取切面
@Override
@Nullable
protected Object[] getAdvicesAndAdvisorsForBean(Class<?> beanClass, String beanName, @Nullable TargetSource targetSource) {List<Advisor> advisors = findEligibleAdvisors(beanClass, beanName);//获取切面if (advisors.isEmpty()) {return DO_NOT_PROXY;}return advisors.toArray();
}
protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) {//1、拿到所有事务的切面List<Advisor> candidateAdvisors = findCandidateAdvisors();//2、过滤出AService符合切点规则List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);return eligibleAdvisors;
}
上面包含2块内容,第一块是获取所有切面,第二块是过滤出符合规则的切面,下面我们分别看下如何获取所有切面。
//获取所有的Advisor,这里的Advisor就包含了,我们前面提到的组件ProxyTransactionManagementConfiguration,
//ProxyTransactionManagementConfiguration会往Spring容器里面注册3个Bean。 切面、切点、通知。
//其中切面BeanFactoryTransactionAttributeSourceAdvisor, 就是我们这里要拿到的。public List<Advisor> findAdvisorBeans() {String[] advisorNames = this.cachedAdvisorBeanNames;if (advisorNames == null) {advisorNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(this.beanFactory, Advisor.class, true, false);this.cachedAdvisorBeanNames = advisorNames;}List<Advisor> advisors = new ArrayList<>();for (String name : advisorNames) {........advisors.add(this.beanFactory.getBean(name, Advisor.class));//通过Name获取Advisor对应的Bean,这里就是BeanFactoryTransactionAttributeSourceAdvisor........}return advisors;
}
上面拿到了所有切面,下面开始过滤符合当前目标类的切面
//从Advisor集合中,挑选出beanClass符合的Advisor
protected List<Advisor> findAdvisorsThatCanApply(List<Advisor> candidateAdvisors, Class<?> beanClass, String beanName) {return AopUtils.findAdvisorsThatCanApply(candidateAdvisors, beanClass);
}public static List<Advisor> findAdvisorsThatCanApply(List<Advisor> candidateAdvisors, Class<?> clazz) {List<Advisor> eligibleAdvisors = new ArrayList<>();//定义符合规则的Advisor集合,同以最终返回。//引介切面过滤for (Advisor candidate : candidateAdvisors) {//如果Advisor是"引介切面",并且符合规则,引介切面是基于类层面判断是否符合规则if (candidate instanceof IntroductionAdvisor && canApply(candidate, clazz)) {eligibleAdvisors.add(candidate);}}//切点切面过滤boolean hasIntroductions = !eligibleAdvisors.isEmpty();for (Advisor candidate : candidateAdvisors) {if (candidate instanceof IntroductionAdvisor) {continue;}//进到这里说明这个Advisor是切点切面,切面切面是基于方法层面判断是否符合规则。if (canApply(candidate, clazz, hasIntroductions)) {eligibleAdvisors.add(candidate);}}return eligibleAdvisors;
}
canApply用来判断方法或者类是否匹配
public static boolean canApply(Advisor advisor, Class<?> targetClass, boolean hasIntroductions) {if (advisor instanceof IntroductionAdvisor) {//引介切面类型,通过类来匹配return ((IntroductionAdvisor) advisor).getClassFilter().matches(targetClass);} else if (advisor instanceof PointcutAdvisor) { //切点切面类型,通过方法判断PointcutAdvisor pca = (PointcutAdvisor) advisor;return canApply(pca.getPointcut(), targetClass, hasIntroductions);} else {return true;}
}
//通过方法层面判断,其实就是判断方法是否有@Transactional注解,当然每个切点的实现不一样,判断逻辑也会有差异
public static boolean canApply(Pointcut pc, Class<?> targetClass, boolean hasIntroductions) {MethodMatcher methodMatcher = pc.getMethodMatcher();if (methodMatcher == MethodMatcher.TRUE) {//判断是否是默认的return true;}//拿到类型Set<Class<?>> classes = new LinkedHashSet<>();if (!Proxy.isProxyClass(targetClass)) {classes.add(ClassUtils.getUserClass(targetClass));}classes.addAll(ClassUtils.getAllInterfacesForClassAsSet(targetClass));//拿到目标类for (Class<?> clazz : classes) {//获取所有方法Method[] methods = ReflectionUtils.getAllDeclaredMethods(clazz);for (Method method : methods) {//判断方法是否匹配,这里其实通过切点类判断是否方法是否包含特定注解。比如@Transactionalif (methodMatcher.matches(method, targetClass)) {return true;}}}return false;
}
1.1小结
这一段我们讲了如何利用getAdvicesAndAdvisorsForBean拿到目标类的advisor,因为Spring在整个启动过程中会存在很多advisor,不是所有的advisor都能给AService来用,我们需要通过切面中的切点规则来判断是否符合规则。
1.2 拿到符合规则的advisor,生成代理对象
再回到wrapIfNecessary方法的第二块内容生成代理对象。
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {//拿到符合规则的advisorObject[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);if (specificInterceptors != DO_NOT_PROXY) {//2、结合advisor,生成AService的代理对象。Object proxy = createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));return proxy;}return bean;
}protected Object createProxy(Class<?> beanClass, @Nullable String beanName, @Nullable Object[] specificInterceptors, TargetSource targetSource) {//创建ProxyFactory,用来生成代理对象ProxyFactory proxyFactory = new ProxyFactory();proxyFactory.copyFrom(this);//拿到切面,添加进去Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);proxyFactory.addAdvisors(advisors);proxyFactory.setTargetSource(targetSource);proxyFactory.setFrozen(this.freezeProxy);...省略部分代理return proxyFactory.getProxy(getProxyClassLoader());
}
上面最终调用getProxy方法来生成代理对象,那么JDK和Cglib都实现了getProxy方法,这里我们分别看下他们如何创建对象。
1.2.1 JDK代理的getProxy方法
@Override
public Object getProxy(@Nullable ClassLoader classLoader) {//拿到代理类需要实现的接口Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true);...省略部分代理//这里的this是JdkDynamicAopProxy//生成代理对象,代理对象需要实现proxiedInterfaces集合所有接口//当目标对象被调用的时候,会先进到JdkDynamicAopProxy的invoke方法里面return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
}
1.2.2 Cglib代理的getProxy方法
@Override
public Object getProxy(@Nullable ClassLoader classLoader) {//获取代理类需要继承的父类Class<?> rootClass = this.advised.getTargetClass();Class<?> proxySuperClass = rootClass;if (rootClass.getName().contains(ClassUtils.CGLIB_CLASS_SEPARATOR)) {proxySuperClass = rootClass.getSuperclass();Class<?>[] additionalInterfaces = rootClass.getInterfaces();for (Class<?> additionalInterface : additionalInterfaces) {this.advised.addInterface(additionalInterface);}}Enhancer enhancer = createEnhancer();enhancer.setSuperclass(proxySuperClass); //代理类的父类,也就是目标类AServcieenhancer.setInterfaces(AopProxyUtils.completeProxiedInterfaces(this.advised)); //代理类需要实现的接口enhancer.setStrategy(new ClassLoaderAwareGeneratorStrategy(classLoader));//添加回调方法,这些回调类都实现了MethodInterceptor接口,当AService的方法被调用时,会进到MethodInterceptor里面的intercept方法里面去Callback[] callbacks = getCallbacks(rootClass);Class<?>[] types = new Class<?>[callbacks.length];enhancer.setCallbackFilter(new ProxyCallbackFilter(this.advised.getConfigurationOnlyCopy(), this.fixedInterceptorMap, this.fixedInterceptorOffset));。。。。。。省略部分代码return createProxyClassAndInstance(enhancer, callbacks);
}
getCallbacks会拿到很多MethodInterceptor的实现类,作为回调添加到代理类里面去,当AService有方法被调用时,就会进被MethodInterceptor的intercept方法拦截,在intercept里面调用切面里面的通知方法。这里MethodInterceptor的实现类就包含DynamicAdvisedInterceptor。
1.2小结
通过JDK和Cglib创建代理类,并添加拦截方法,在AService方法被执行时,被invoke方法或者intercept方法拦截。
三、目标方法执行,如何被拦截事务拦截
上面我们介绍了,如何创建代理对象,并且在创建代理对象时拦截目标方法,这里我们看下当目标方法被执行,在拦截里面做了什么操作。
当目标方法被调用时,会被JdkDynamicAopProxy的invoke方法拦截,或者是MethodInterceptor的intercept方法拦截,在invoke和intercept里面会通过切面拿到通知,在挨个的执行通知最后在执行目标方法
1、Jdk的invoke
@Override
@Nullable
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {//获取符合目标方法的拦截器MethodInterceptor集合List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);if (chain.isEmpty()) {Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);//如果拦截器集合为空,就直接调用目标方法} else {//生成拦截器链MethodInvocation invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);retVal = invocation.proceed();}return retVal;
}
2、Cglib的intercept方法
@Override
@Nullable
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {target = targetSource.getTarget();Class<?> targetClass = (target != null ? target.getClass() : null);//1、获取符合目标方法的拦截器MethodInterceptor集合。这里是拿切里的通知,通知是MethodInterceptor类型List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);Object retVal;if (chain.isEmpty() && Modifier.isPublic(method.getModifiers())) {retVal = methodProxy.invoke(target, argsToUse);//如果拦截器集合为空,就直接调用目标方法} else {//2、生成拦截器链,使用的是责任链设计模式。在挨个调用拦截器。最后调用目标方法retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();}retVal = processReturnType(proxy, target, method, retVal);return retVal;
}
关于获取拦截器集合和调用拦截器链,可以看到我的这篇文章《代理生成拦截器和调用拦截器》
3、触发事务拦截器
然后执行拦截器链中的最开始的拦截器,后面一次遍历,但是此时的拦截器就只有一个就是TransactionInterceptor事务方法拦截器
进到TransactionInterceptor就会执行TransactionInterceptor的invoke方法,下面看下invoke的流程
1、先拿到事务管理器的事务类型:PROPAGATION_REQUIRED,ISOLATION_DEFAULT;
2、在拿到事务的管理器也就是DataSourceTransactionManager
@Override
public Object invoke(final MethodInvocation invocation) throws Throwable {//目标类 Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);//执行切面方法和目标方法return invokeWithinTransaction(invocation.getMethod(), targetClass, new InvocationCallback() {@Overridepublic Object proceedWithInvocation() throws Throwable {return invocation.proceed();}});
}protected Object invokeWithinTransaction(Method method, Class<?> targetClass, final InvocationCallback invocation) throws Throwable {//1、事务隔离级别final TransactionAttribute txAttr = getTransactionAttributeSource().getTransactionAttribute(method, targetClass);//2、事务管理器DataSourceTransactionManagerfinal PlatformTransactionManager tm = determineTransactionManager(txAttr);//生成事务txInfo,里面包含本次事务状态,隔离级别,事务处理器,当本次事务执行完后需要关闭本次事务TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);try {//执行进入下一个链,此时只有一个也就是目标方法retVal = invocation.proceedWithInvocation();}catch (Throwable ex) {//更改txInfo里面的状态completeTransactionAfterThrowing(txInfo, ex);throw ex;}finally {cleanupTransactionInfo(txInfo);}commitTransactionAfterReturning(txInfo);//根据状态是否commitreturn retVal;
}
总结
1、前面我们在Bean的实例化时,用到了第一个组件InfrastructureAdvisorAutoProxyCreator,用来触发wrapIfNecessary方法。
2、在wrapIfNecessary方法里面,我们利用了第二个组件ProxyTransactionManagementConfiguration生成的切面BeanFactoryTransactionAttributeSourceAdvisor,通过BeanFactoryTransactionAttributeSourceAdvisor的切点AnnotationTransactionAttributeSource,来判断AService是否符合切点规则,也就是解读AService类的方法是否包含@Transactional注解。
五、疑问
1、带有@Transaction注解的Bean实例化的时候,怎么如果判断该Bean带有@Transaction方法
1、InfrastructureAdvisorAutoProxyCreator#postProcessAfterInitialization
—>2、BeanFactoryTransactionAttributeSourceAdvisor
—>3、AnnotationTransactionAttributeSource
—>4、SpringTransactionAnnotationParser判断@Transaction
最终通过SpringTransactionAnnotationParser来判断Bean的方法是否带有@Transaction注解
拓展:
InfrastructureAdvisorAutoProxyCreator里面有个advisedBeans属性,在Bean的创建过程会判断Bean里面 是否是带有事务注解@Transaction的方法,如果有就会被加到advisedBeans里面去。
private final Map<Object, Boolean> advisedBeans = new ConcurrentHashMap<Object, Boolean>(256);
注意:
1、BeanFactoryTransactionAttributeSourceAdvisor事务增强器在advisedBeans是false的形式存在
2、transactionInterceptor也是false
2、带有@Transaction注解的代理Bean在哪个流程被创建
在执行BeanPostProcess处理器链的时候,由InfrastructureAdvisorAutoProxyCreator#postProcessAfterInitialization方法执行创建,该方法会调用wrapIfNecessary方法完成代理创建
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {.......//这里是拿到BeanFactoryTransactionAttributeSourceAdvisor也就是“事务增强器”Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);if (specificInterceptors != DO_NOT_PROXY) {this.advisedBeans.put(cacheKey, Boolean.TRUE);//key就是带有@Transaction的BeanNameObject proxy = createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));return proxy;//返回代理对象}return bean;
}
3、如何创建@Transaction注解的Bean
默认是使用JDK动态代理
1、先创建ProxyFactory对象,设置属性
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.setTargetSource(目标对象LLServcieImpl);
proxyFactory.setInterfaces(目标接口LLServcie);
proxyFactory.addAdvisor(事务增强器BeanFactoryTransactionAttributeSourceAdvisor);
proxyFactory.getProxy(getProxyClassLoader())//调用下面的代码
//2、生成动态代理对象
proxiedInterfaces=AopProxyUtils.completeProxiedInterfaces(事务增强器, true);
//设置代理对象要实现接口和InvocationHandler对象
Proxy.newProxyInstance(classLoader, proxiedInterfaces, JdkDynamicAopProxy类);
其中proxiedInterfaces包含接口
0 = {Class@3511} “interface cn.tedu.sample2.util.LLServcie”
1 = {Class@3676} “interface org.springframework.aop.SpringProxy”
2 = {Class@5780} “interface org.springframework.aop.framework.Advised”
3 = {Class@3897} “interface org.springframework.core.DecoratingProxy”
4、InfrastructureAdvisorAutoProxyCreator类的作用
1、postProcessBeforeInstantiation方法主要是针对所有要创建的Bean,判断存到advisedBeans里面是false还是true
2、postProcessAfterInitialization方法,处理带有@Transcation的Bean,创建代理Bean
六、总结
1、先通过@EnableTransactionManagement引入TransactionManagementConfigurationSelector
2、通过TransactionManagementConfigurationSelector,导入AutoProxyRegistrar,ProxyTransactionManagementConfiguration
3、AutoProxyRegistrar会向容器中注册InfrastructureAdvisorAutoProxyCreator
4、ProxyTransactionManagementConfiguration会向容器定义三个Bean:事务增强器、@Transaction注解解析器、事务方法拦截器
5、执行Bean的后置处理器时,通过InfrastructureAdvisorAutoProxyCreator的postProcessAfterInitialization方法创建代理对象
6、创建代理对象时,通过事务增强器BeanFactoryTransactionAttributeSourceAdvisor来得到代理类要实现的接口SpringProxy、Advised、DecoratingProxy,最终生成代理对象
7、当请求进来时,先进入JdkDynamicAopProxy的invoke方法
8、invoke里面会调用TransactionInterceptor的invoke方法,里面会调用invokeWithinTransaction方法
9、invokeWithinTransaction里面会在调用目标方法前开启事务,catch失败设置状态,然后finally根据状态来确认是否commit
相关文章:
Spring-事务源码解析2
上一篇文章我们介绍了事务开启注解EnableTransactionManagement源码解析《Spring-事务源码解析1》 里面提到了2个关键组件,这里我们分析下Spring如何利用这2个组件来给Bean创建代理对象。 本篇文章我们看下当一个类里面包含了Transactional注解,Spring如…...
基于ssm008医院门诊挂号系统+jsp【附PPT|开题|任务书|万字文档(LW)和搭建文档】
主要功能 后台登录:4个角色 管理员: ①个人中心、修改密码、个人信息 ②药房管理、护士管理、医生管理、病人信息管理、科室信息管理、挂号管理、诊断信息管理、病例库管理、开药信息管理、药品信息管理、收费信息管理 药房: ①个人中心、修…...
【Linux常用命令11】Linux文件与权限详解
权限 r :读权限,用数字4表示 w :写权限,用数字2表示 x :执行权限,用数字1表示 常用权限 644:代表所有者拥有读、写权限,而所属组和其他人拥有只读权限。 755:代表所有…...
BAT026:删除当前目录指定文件夹以外的文件夹
引言:编写批处理程序,实现删除当前目录指定文件夹以外的文件夹。 一、新建Windows批处理文件 参考博客: CSDNhttps://mp.csdn.net/mp_blog/creation/editor/132137544 二、写入批处理代码 1.右键新建的批处理文件,点击【编辑】…...
Python浏览器自动化
如果你正在进行手机爬虫的工作,并且希望通过模拟浏览器行为来抓取数据,那么Pyppeteer将会是你的理想选择。Pyppeteer是一个强大的Python库,它可以让你控制浏览器进行自动化操作,如点击按钮、填写表单等,从而实现数据的…...
基于tornado BELLE 搭建本地的web 服务
我的github 将BELLE 封装成web 后端服务,采用tornado 框架 import timeimport torch import torch.nn as nnfrom gptq import * from modelutils import * from quant import *from transformers import AutoTokenizer import sys import json #import lightgbm a…...
信息系统漏洞与风险管理制度
1、总则 1.1、目的 为了进一步规范XXXXX单位信息系统风险管理活动,提升风险管理工作的可操纵性和适用性,使信息网络正常运行,防止网络攻击,保证业务的正常进行,依据XXXXX单位员的相关规范和标准规定,特制…...
Hadoop3教程(十七):MapReduce之ReduceJoin案例分析
文章目录 (113)ReduceJoin案例需求分析(114)ReduceJoin案例代码实操 - TableBean(115)ReduceJoin案例代码实操 - TableMapper(116)ReduceJoin案例代码实操 - Reducer及Driver参考文献…...
BAT026:删除当前目录及子目录下的空文件夹
引言:编写批处理程序,实现批量删除当前目录及子目录下的空文件夹。 一、新建Windows批处理文件 参考博客: CSDNhttps://mp.csdn.net/mp_blog/creation/editor/132137544 二、写入批处理代码 1.右键新建的批处理文件,点击【编辑…...
nodejs+vue网课学习平台
目 录 摘 要 I ABSTRACT II 目 录 II 第1章 绪论 1 1.1背景及意义 1 1.2 国内外研究概况 1 1.3 研究的内容 1 第2章 相关技术 3 2.1 nodejs简介 4 2.2 express框架介绍 6 2.4 MySQL数据库 4 第3章 系统分析 5 3.1 需求分析 5 3.2 系统可行性分析 5 3.2.1技术可行性:…...
Can Language Models Make Fun? A Case Study in Chinese Comical Crosstalk
本文是LLM系列文章,针对《Can Language Models Make Fun? A Case Study in Chinese Comical Crosstalk》的翻译。 语言模型能制造乐趣吗?中国滑稽相声个案研究 摘要1 引言2 问题定义3 数据集4 使用自动评估生成基准5 人工评估6 讨论7 结论与未来工作 摘要 语言是…...
阿里云云服务器实例使用教学
目录 云服务器免费试用 详细步骤 Xshell 远程连接 云服务器免费试用 阿里云云服务器网址:阿里云免费试用 - 阿里云 详细步骤 访问阿里云免费试用。单击页面右上方的登录/注册按钮,并根据页面提示完成账号登录(已有阿里云账号)…...
promisify 是 Node.js 标准库 util 模块中的一个函数
promisify 是 Node.js 标准库 util 模块中的一个函数。它用于将遵循 Node.js 回调风格的函数转换为返回 Promise 的函数。这使得你可以使用 async/await 语法来等待异步操作完成,从而让异步代码看起来更像同步代码。 在 Node.js 的回调风格中,函数通常接…...
ArcGIS在VUE框架中的构建思想
项目快要上线了,出乎意料的有些空闲时间。想着就把其他公司开发的一期代码里面,把关于地图方面的代码给优化一下。试运行的时候,客户说控制台有很多飘红的报错,他们很在意,虽然很不情愿,但能改的就给改了吧…...
【Overload游戏引擎细节分析】视图投影矩阵计算与摄像机
本文只罗列公式,不做具体的推导。 OpenGL本身没有摄像机(Camera)的概念,但我们为了产品上的需求与编程上的方便,一般会抽象一个摄像机组件。摄像机类似于人眼,可以建立一个本地坐标系。相机的位置是坐标原点,摄像机的朝…...
什么是云原生?零基础学云原生难吗?
伴随着云计算的浪潮,云原生概念也应运而生,而且火得一塌糊涂,但真正谈起“云原生”,大多数非 IT 从业者的认知往往仅限于将服务应用放入云端,在云上处理业务。实际上,云原生远不止于此。 现在越来越多的企…...
Ubuntu18.04下载安装基于使用QT的pcl1.13+vtk8.2,以及卸载
一、QVTKWidget、QVTKWidget2、QVTKOpenGLWidget、QVTKOpenGLNativeWidget 区别 1.Qt版本 Qt5.4以前版本:QVTKWidget2/QVTKWidget。 Qt5.4以后版本:QVTKOpenGLWidget/QVTKOpenGLWidget。 2.VTK版本(Qt版本为5.4之后) 在VTK8.2以前的版本:QVT…...
7 使用Docker容器管理的tomcat容器中的项目连接mysql数据库
1、查看容器的IP 1)进入容器 docker exec -it mysql-test /bin/bash 2)显示hosts文件内容 cat /etc/hosts 这里容器的ip为172.17.0.2 除了上面的方法外,也可以在容器外使用docker inspect查看容器的IP docker inspect mysql-test 以下为…...
双节前把我的网站重构了一遍
赶在中秋国庆假期前,终于将我的网站(https://spacexcode.com/[1])结构定好了,如之前所说,这个网站的定位就是作为自己的前端知识沉淀。内容大致从:前端涉及的基础知识分类汇总(知识库࿰…...
基于 nodejs+vue网上考勤系统
目 录 摘 要 I ABSTRACT II 目 录 II 第1章 绪论 1 1.1背景及意义 1 1.2 国内外研究概况 1 1.3 研究的内容 1 第2章 相关技术 3 2.1 nodejs简介 4 2.2 express框架介绍 6 2.4 MySQL数据库 4 第3章 系统分析 5 3.1 需求分析 5 3.2 系统可行性分析 5 3.2.1技术可行性:…...
以数智化指标管理,驱动光伏能源行业的市场推进
近年来,碳中和、碳达峰等降低碳排放、提升环境健康度的政策和技术改进正在不断地被社会所认可和引起重视,也被越来越多的企业在生产运营和基础建设中列为重要目标之一。而光伏能源行业作为全球绿色能源、新能源的优秀解决方案,充分利用太阳能…...
lv8 嵌入式开发-网络编程开发 18 广播与组播的实现
目录 1 广播 1.1 什么是广播? 1.2 广播地址 1.3 广播的实现 2 组播 2.1 分类的IP地址 2.2 多播 IP 地址 2.3 组播的实现 1 广播 1.1 什么是广播? 数据包发送方式只有一个接受方,称为单播 如果同时发给局域网中的所有主机࿰…...
前端面试题个人笔记(后面继续更新完善)
文章目录 填空题部分简答题部分 if有好答案请各位大佬们在底下评论上,感谢 填空题部分 1、常见的css选择器 2、getElementById获取元素的(DOM)对象 简答题部分 1、介绍一下你对RESTful API的理解以及它的优势? 答: …...
软件设计之工厂方法模式
工厂方法模式指定义一个创建对象的接口,让子类决定实例化哪一个类。 结构关系如下: 可以看到,客户端创建了两个接口,一个AbstractFactory,负责创建产品,一个Product,负责产品的实现。ConcreteF…...
【Linux】shell运行原理及权限
主页点击直达:个人主页 我的小仓库:代码仓库 C语言偷着笑:C语言专栏 数据结构挨打小记:初阶数据结构专栏 Linux被操作记:Linux专栏 LeetCode刷题掉发记:LeetCode刷题 算法:算法专栏 C头疼…...
OA系统和ERP系统有什么区别?
在当今的企业管理领域,协同办公管理系统和ERP系统是两个非常重要的工具。它们在企业的日常运营中扮演着不同的角色,有着各自独特的功能和优势。那么,OA系统和ERP系统到底有什么区别呢?协同办公管理系统又是如何在这两者之间发挥协…...
c语言之strcat函数使用和实现
文章目录 前言一、strcat函数使用二、实现方法 前言 c语言之strcat 函数使用和实现 一、strcat函数使用 原型: char *strcat ( char * destination, const char * source );strcat追加拷贝,追加到目标空间后面,目标空间必须足够大,能容纳下…...
Halo-Theme-Hao文档:如何设置导航栏?
本篇文章会教你如何配置导航栏,最终效果参考如下。 感谢 Lanbin、小孙同学 等同学的贡献(语雀参与编辑)。 1标题 进入站点后台 点击左侧面板中的 主题 点击上方的 导航 修改 标题字段即可 2主菜单 主菜单即网站导航栏中间部分的菜单 进入站点…...
【Java学习之道】Java网络编程API介绍
引言 在Java中,进行网络编程的主要方式是通过Java网络编程API。这些API提供了一组类和接口,用于创建网络应用,如TCP和UDP通信、URL访问等。在这一节中,我们将带你领略Java网络编程API的魅力。 一、InetAddress InetAddress类是表…...
[论文笔记]SimCSE
引言 今天带来一篇当时引起轰动的论文SimCSE笔记,论文题目是 语句嵌入的简单对比学习。 SimCSE是一个简单的对比学习框架,它可以通过无监督和有监督的方式来训练。 对于无监督方式,输入一个句子然后在一个对比目标中预测它自己,仅需要标准的Dropout作为噪声。这种简单的…...
做pc端网站适配/网站运营主要做什么工作
分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.csdn.net/jiangjunshow也欢迎大家转载本篇文章。分享知识,造福人民,实现我们中华民族伟大复兴!1. Kafka整体结构图Kafka名词解释…...
wordpress底部CSS/app推广平台
据统计,全球范围内被投递的钓鱼邮件每天约达到1亿封,经常会遇到一些邮件发送方,被spammer利用于伪造各种钓鱼/诈骗邮件,如:伪造银行、保险等金融企业,支付宝、Paypal等支付商,知名网站、政府网站…...
wordpress优化公司/网站软文代写
DNS 是Domain Name System (域名系统) 的缩写,是一种按域层次结构组织计算机和网络的命名系统。DNS应用于TCP/IP构建的网络,主要用于Internet。在Internet上,用户记忆由数字组成的IP地址比较困难,所以引入了域名的概念。域名与IP地…...
网站色差表/软文代写公司
晚上帮同事解决一个merge问题的时候,发现了我去年在上一家公司时候写的一篇非常不错的关于git的文章,分享出来,有助于更加高效地使用git。 (1)配置lg2 git默认的日志查看命令是“git log”,界面显示如下&am…...
wordpress 幻灯片/新浪博客
我们大家都知道,JS有个很经典的浮点运算精度丢失问题,今天我们就来聊一聊这个问题产生的原因,以及该如何去解决它呢? 先来看下面的代码,0.10.2的结果不等于0.3,这是不是超出了我们之前的认知呢?毕竟0.10.20.3可是我们小学就已经学会了的东西,到这里怎么就不一样了呢? 0.1 …...
宁夏网站建设优化/百度网盘官网下载
在这篇 Linux 黑话解释中,你将了解 Linux 中的显示管理器。它是桌面环境的一部分吗?它的作用是什么?什么是 Linux 中的显示管理器?简单来说,显示管理器display manager(DM)是一个为你的 Linux 发行版提供图形登录功能的…...