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技术可行性:…...
浏览器访问 AWS ECS 上部署的 Docker 容器(监听 80 端口)
✅ 一、ECS 服务配置 Dockerfile 确保监听 80 端口 EXPOSE 80 CMD ["nginx", "-g", "daemon off;"]或 EXPOSE 80 CMD ["python3", "-m", "http.server", "80"]任务定义(Task Definition&…...

铭豹扩展坞 USB转网口 突然无法识别解决方法
当 USB 转网口扩展坞在一台笔记本上无法识别,但在其他电脑上正常工作时,问题通常出在笔记本自身或其与扩展坞的兼容性上。以下是系统化的定位思路和排查步骤,帮助你快速找到故障原因: 背景: 一个M-pard(铭豹)扩展坞的网卡突然无法识别了,扩展出来的三个USB接口正常。…...
Golang 面试经典题:map 的 key 可以是什么类型?哪些不可以?
Golang 面试经典题:map 的 key 可以是什么类型?哪些不可以? 在 Golang 的面试中,map 类型的使用是一个常见的考点,其中对 key 类型的合法性 是一道常被提及的基础却很容易被忽视的问题。本文将带你深入理解 Golang 中…...

.Net框架,除了EF还有很多很多......
文章目录 1. 引言2. Dapper2.1 概述与设计原理2.2 核心功能与代码示例基本查询多映射查询存储过程调用 2.3 性能优化原理2.4 适用场景 3. NHibernate3.1 概述与架构设计3.2 映射配置示例Fluent映射XML映射 3.3 查询示例HQL查询Criteria APILINQ提供程序 3.4 高级特性3.5 适用场…...
多场景 OkHttpClient 管理器 - Android 网络通信解决方案
下面是一个完整的 Android 实现,展示如何创建和管理多个 OkHttpClient 实例,分别用于长连接、普通 HTTP 请求和文件下载场景。 <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas…...

uniapp微信小程序视频实时流+pc端预览方案
方案类型技术实现是否免费优点缺点适用场景延迟范围开发复杂度WebSocket图片帧定时拍照Base64传输✅ 完全免费无需服务器 纯前端实现高延迟高流量 帧率极低个人demo测试 超低频监控500ms-2s⭐⭐RTMP推流TRTC/即构SDK推流❌ 付费方案 (部分有免费额度&#x…...
HTML前端开发:JavaScript 常用事件详解
作为前端开发的核心,JavaScript 事件是用户与网页交互的基础。以下是常见事件的详细说明和用法示例: 1. onclick - 点击事件 当元素被单击时触发(左键点击) button.onclick function() {alert("按钮被点击了!&…...
Redis:现代应用开发的高效内存数据存储利器
一、Redis的起源与发展 Redis最初由意大利程序员Salvatore Sanfilippo在2009年开发,其初衷是为了满足他自己的一个项目需求,即需要一个高性能的键值存储系统来解决传统数据库在高并发场景下的性能瓶颈。随着项目的开源,Redis凭借其简单易用、…...
Python网页自动化Selenium中文文档
1. 安装 1.1. 安装 Selenium Python bindings 提供了一个简单的API,让你使用Selenium WebDriver来编写功能/校验测试。 通过Selenium Python的API,你可以非常直观的使用Selenium WebDriver的所有功能。 Selenium Python bindings 使用非常简洁方便的A…...

从零开始了解数据采集(二十八)——制造业数字孪生
近年来,我国的工业领域正经历一场前所未有的数字化变革,从“双碳目标”到工业互联网平台的推广,国家政策和市场需求共同推动了制造业的升级。在这场变革中,数字孪生技术成为备受关注的关键工具,它不仅让企业“看见”设…...