spring源码阅读--aop实现原理分析

aop实现原理简介

首先我们都知道aop的基本原理就是动态代理思想,在设计模式之代理模式中有介绍过这两种动态代理的使用与基本原理,再次不再叙述。

这里分析的是,在spring中是如何基于动态代理的思想实现aop的。为了方便了解接下来的源码分析,这里简单化了一个流程图分析aop的基本实现思想。
在这里插入图片描述

so,基于上面的流程,一步步分析spring源码中的aop实现方式。

采用一个简单的aop例子,利用基于注解配置方式的切面配置,分析一个简单的Before AOP例子。在spring boot下运行以下简单例子。

AOP的advisor和advice配置。

@Component
@Aspect
public class AopConfig {@Pointcut("execution(* com.garine.debug.testcase.model.AopObject..*(..))")public void mypoint(){//切面定义}@Before("mypoint()")public void doAround() throws Throwable {System.out.println("before logic");}
}

AopObject,被代理拦截对象。

@Component
public class AopObject {public void aoped(){System.out.println("logic");}
}

代理实现的处理器(BeanPostProcessor)

首先是第一步内容,对我们在AopConfig中的AOP配置内容进行解析并且保存到BeanFactory中,这个过程就是解析保存切面信息。

代理实现的源头–AnnotationAwareAspectJAutoProxyCreator

经过一遍的代码跟踪,我了解到注解方式的AOP配置,都离不开一个类–AnnotationAwareAspectJAutoProxyCreator,这个类继承了BeanPostProcessor接口,我们都知道BeanPostProcessor的实现类有多个执行处理节点,其中一个执行节点就是在Bean实例化之后。也就是在这个时机AnnotationAwareAspectJAutoProxyCreator拦截bean的初始化过程,根据提前解析得到的切面信息,对bean的方法进行尝试适配,如果有匹配则需要进行代理创建。

这里先分析的就是AnnotationAwareAspectJAutoProxyCreator,在bean实例化第一次查询所有切面信息时,就会解析保存Aop的信息到实例中,跟踪以下代码。

  • AbstractApplicationContext#refresh (上下文初始化主干方法)
    • AbstractApplicationContext#registerBeanPostProcessors (执行实例化并保存所有实现BeanPostProcessor接口的类

按照上面的逻辑,registerBeanPostProcessors 会比一般的bean实例化逻辑要早执行,因此我们接下来只需要分析AnnotationAwareAspectJAutoProxyCreator的初始化过程。

AnnotationAwareAspectJAutoProxyCreator的继承结构

在这里插入图片描述

通过上图可以知道,AnnotationAwareAspectJAutoProxyCreator是继承了BeanfactoryAware接口,所以在实例化时,会执行setFactory方法。而所有切面信息解析的执行者BeanFactoryAspectJAdvisorsBuilderAdapter初始化的时机也是在setFactory方法。

跟踪代码如下。

  • AbstractAdvisorAutoProxyCreator#setBeanFactory
    • AnnotationAwareAspectJAutoProxyCreator#initBeanFactory

在这个方法里面会新建一个BeanFactoryAspectJAdvisorsBuilderAdapter,这个对象会根据Beanfactory内的aop配置信息,进行解析保存。但是需要注意,此时虽然新建了BeanFactoryAspectJAdvisorsBuilderAdapter对象.但是此时还不会马上解析aop配置,需要在第一次个普通bean实例化时才执行解析aop配置。解析的方法就是

BeanFactoryAspectJAdvisorsBuilder#buildAspectJAdvisors,会在初次执行AnnotationAwareAspectJAutoProxyCreator调用postProcessBeforeInitialization时开始执行。

protected void initBeanFactory(ConfigurableListableBeanFactory beanFactory) {super.initBeanFactory(beanFactory);//aspectJAdvisorsBuilder#buildAspectJAdvisors就是解析配置入口this.aspectJAdvisorsBuilder =new BeanFactoryAspectJAdvisorsBuilderAdapter(beanFactory, this.aspectJAdvisorFactory);
}

代理对象(Proxy)的创建

解析并缓存切面

上面提到继承结构图中,AnnotationAwareAspectJAutoProxyCreator是实现了InstantiationAwareBeanPostProcessor接口的,InstantiationAwareBeanPostProcessor接口定义的postProcessBeforeInitialization方法是一个可以对已经注入依赖属性的bean对象实例进行编辑操作的接口,会在

  • AbstractAutowireCapableBeanFactory#doCreateBean
    • AbstractAutowireCapableBeanFactory#initializeBean(String, Object, RootBeanDefinition)
      • AbstractAutowireCapableBeanFactory#applyBeanPostProcessorsBeforeInstantiation

方法中执行InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation,初次初始化缓存切面信息的话就是在这个方法里面。。具体的调用链如上所示。这里的postProcessBeforeInstantiation方法实际上是AnnotationAwareAspectJAutoProxyCreator的实例进行调用,AnnotationAwareAspectJAutoProxyCreator实现InstantiationAwareBeanPostProcessor接口。

下面进入InstantiationAwareBeanPostProcessor#postProcessBeforeInitialization方法分析代码。

  • AbstractAutoProxyCreator#postProcessBeforeInstantiation
    • AspectJAwareAdvisorAutoProxyCreator#shouldSkip (关键代码)

进入如下代码AbstractAutoProxyCreator,这个实例也就是之前一开始初始化的AnnotationAwareAspectJAutoProxyCreator实例,进入实例的shouldSkip 方法,

	@Overrideprotected boolean shouldSkip(Class<?> beanClass, String beanName) {// TODO: Consider optimization by caching the list of the aspect names//预先解析缓存切面信息List<Advisor> candidateAdvisors = findCandidateAdvisors();for (Advisor advisor : candidateAdvisors) {if (advisor instanceof AspectJPointcutAdvisor) {if (((AbstractAspectJAdvice) advisor.getAdvice()).getAspectName().equals(beanName)) {return true;}}}return super.shouldSkip(beanClass, beanName);}
  • AnnotationAwareAspectJAutoProxyCreator#findCandidateAdvisors

方法findCandidateAdvisors代码如下,这里是预先解析缓存所有切面advisor信息,注意这一步操作是在AbstractAutoProxyCreator#postProcessBeforeInitialization处理,也就是开头提到的切面解析操作,解析完成就进行缓存。

@Override
protected List<Advisor> findCandidateAdvisors() {// Add all the Spring advisors found according to superclass rules.List<Advisor> advisors = super.findCandidateAdvisors();// Build Advisors for all AspectJ aspects in the bean factory.//这里就是前面提到的BeanFactoryAspectJAdvisorsBuilder解析所有切面信息的调用点advisors.addAll(this.aspectJAdvisorsBuilder.buildAspectJAdvisors());return advisors;
}

然后继续在这里先提前看一下是如何解析aop配置的。跟踪BeanFactoryAspectJAdvisorsBuilder#buildAspectJAdvisors

public List<Advisor> buildAspectJAdvisors() {List<String> aspectNames = null;synchronized (this) {aspectNames = this.aspectBeanNames;if (aspectNames == null) {List<Advisor> advisors = new LinkedList<Advisor>();aspectNames = new LinkedList<String>();//查询出Beanfactory中所有已经注册的BeanNameString[] beanNames =BeanFactoryUtils.beanNamesForTypeIncludingAncestors(this.beanFactory, Object.class, true, false);for (String beanName : beanNames) {if (!isEligibleBean(beanName)) {continue;}// We must be careful not to instantiate beans eagerly as in this// case they would be cached by the Spring container but would not// have been weavedClass<?> beanType = this.beanFactory.getType(beanName);if (beanType == null) {continue;}//判断Bean是否是切面Bean,isAspect方法判断[标注1]if (this.advisorFactory.isAspect(beanType)) {aspectNames.add(beanName);AspectMetadata amd = new AspectMetadata(beanType, beanName);if (amd.getAjType().getPerClause().getKind() == PerClauseKind.SINGLETON) {MetadataAwareAspectInstanceFactory factory =new BeanFactoryAspectInstanceFactory(this.beanFactory, beanName);//解析aop class的配置,包返回Advisor对象[标注2]List<Advisor> classAdvisors = this.advisorFactory.getAdvisors(factory);if (this.beanFactory.isSingleton(beanName)) {this.advisorsCache.put(beanName, classAdvisors);}else {this.aspectFactoryCache.put(beanName, factory);}advisors.addAll(classAdvisors);}else {// Per target or per this.if (this.beanFactory.isSingleton(beanName)) {throw new IllegalArgumentException("Bean with name '" + beanName +"' is a singleton, but aspect instantiation model is not singleton");}MetadataAwareAspectInstanceFactory factory =new PrototypeAspectInstanceFactory(this.beanFactory, beanName);this.aspectFactoryCache.put(beanName, factory);advisors.addAll(this.advisorFactory.getAdvisors(factory));}}}this.aspectBeanNames = aspectNames;return advisors;}}if (aspectNames.isEmpty()) {return Collections.emptyList();}List<Advisor> advisors = new LinkedList<Advisor>();for (String aspectName : aspectNames) {List<Advisor> cachedAdvisors = this.advisorsCache.get(aspectName);if (cachedAdvisors != null) {advisors.addAll(cachedAdvisors);}else {MetadataAwareAspectInstanceFactory factory = this.aspectFactoryCache.get(aspectName);advisors.addAll(this.advisorFactory.getAdvisors(factory));}}return advisors;
}

**[标注1]如何判断类是否是aop切面配置类? **

通过以下代码。

@Override
public boolean isAspect(Class<?> clazz) {return (hasAspectAnnotation(clazz) && !compiledByAjc(clazz));
}private boolean hasAspectAnnotation(Class<?> clazz) {//包含@Aspect注解return (AnnotationUtils.findAnnotation(clazz, Aspect.class) != null);
}

[标注2]如何解析为Advisor对象?

  • ReflectiveAspectJAdvisorFactory#getAdvisors 遍历所有没被@PointCut注解标注的方法,也就是遍历切面内容方法
    • ReflectiveAspectJAdvisorFactory#getAdvisor 处理所有没被@PointCut注解标注的方法,候选切面内容方法

代码如下。

@Override
public Advisor getAdvisor(Method candidateAdviceMethod, MetadataAwareAspectInstanceFactory aspectInstanceFactory,int declarationOrderInAspect, String aspectName) {validate(aspectInstanceFactory.getAspectMetadata().getAspectClass());//解析判断候选方法是否有@Before,@After,@Around等注解,如果有,就继续执行新建Advisor对象。AspectJExpressionPointcut expressionPointcut = getPointcut(candidateAdviceMethod, aspectInstanceFactory.getAspectMetadata().getAspectClass());if (expressionPointcut == null) {return null;}
//创建advisorreturn new InstantiationModelAwarePointcutAdvisorImpl(expressionPointcut, candidateAdviceMethod,this, aspectInstanceFactory, declarationOrderInAspect, aspectName);
}

最终循环解析,@Before,@After,@Around等标注的方法都会新建一个Advisor对象。新建的Advisor对象都保存在BeanFactoryAspectJAdvisorsBuilder#advisorsCache中,当AnnotationAwareAspectJAutoProxyCreator拦截bean的创建过程时,从这里面适配是否有切面可用。

这里解析得到的Advisor,大概有以下信息。下面的信息中,并没有对@PointCut注解做处理,pointCut属性只得出一个"mypoint()",此时还不知道Advisor实际对应的拦截表达式。
在这里插入图片描述

拦截表达式还是空的,会在AnnotationAwareAspectJAutoProxyCreator#postProcessAfterInstantiation第一次执行时解析拦截表达式。
在这里插入图片描述

适配切面

在AbstractAutoProxyCreator#postProcessAfterInitialization执行时,找到上面AbstractAutoProxyCreator#postProcessBeforeInitialization缓存的所有的切面信息,之后是如何进行切面适配,从而决定是否需要进行代理对象的创建呢?

在调用AbstractAutoProxyCreator#postProcessAfterInitialization方法时,进行切面适配,并且会根据适配创建代理对象。根据以下调用链。

  • AbstractAutoProxyCreator#postProcessAfterInitialization

    • AbstractAutoProxyCreator#wrapIfNecessary

      protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {if (beanName != null && this.targetSourcedBeans.contains(beanName)) {return bean;}if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {return bean;}if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {this.advisedBeans.put(cacheKey, Boolean.FALSE);return bean;}// Create proxy if we have advice.//查找匹配切面Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);if (specificInterceptors != DO_NOT_PROXY) {this.advisedBeans.put(cacheKey, Boolean.TRUE);//创建代理对象Object proxy = createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));this.proxyTypes.put(cacheKey, proxy.getClass());return proxy;}this.advisedBeans.put(cacheKey, Boolean.FALSE);return bean;
      }
      • AbstractAdvisorAutoProxyCreator#getAdvicesAndAdvisorsForBean
        • AbstractAdvisorAutoProxyCreator#findEligibleAdvisors
protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) {//从缓存取出所有切面信息List<Advisor> candidateAdvisors = findCandidateAdvisors();//根据advisor信息中的表达式进行方法对class的匹配List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);extendAdvisors(eligibleAdvisors);if (!eligibleAdvisors.isEmpty()) {eligibleAdvisors = sortAdvisors(eligibleAdvisors);}return eligibleAdvisors;
}

此时如果是第一次执行适配方法findAdvisorsThatCanApply的话,candidateAdvisors中的拦截表达式还是空的,需要进行表达式获取,也就是@Pointcut的value。spring的操作的在第一次执行findAdvisorsThatCanApply时解析获取拦截表达式的值,获得拦截表达式值之后就跟当前class的方法进行匹配看是否需要进行代理。继续往下跟踪代码

  • AopUtils#canApply(org.springframework.aop.Advisor, java.lang.Class<?>, boolean)
    • AopUtils#canApply(org.springframework.aop.Pointcut, java.lang.Class<?>, boolean)
      • AspectJExpressionPointcut#getClassFilter
        • AspectJExpressionPointcut#checkReadyToMatch
private void checkReadyToMatch() {if (getExpression() == null) {throw new IllegalStateException("Must set property 'expression' before attempting to match");}if (this.pointcutExpression == null) {this.pointcutClassLoader = (this.beanFactory instanceof ConfigurableBeanFactory ?((ConfigurableBeanFactory) this.beanFactory).getBeanClassLoader() :ClassUtils.getDefaultClassLoader());//解析得到拦截表达式,例如根据@Before的value来关联查询出对应的表达式this.pointcutExpression = buildPointcutExpression(this.pointcutClassLoader);}
}

最终解析完之后,advisor中的表达式信息结构如下图。包含在pointcut属性中,匹配时就根据pointcutExpression循环进行匹配class的方法。有兴趣的可以继续调试看看是如何实现匹配表达式的。
在这里插入图片描述

创建代理对象

如果在上面的匹配切面过程中,发现适配的切面,那就需要进行代理对象的创建了。

我们回到上面的AbstractAutoProxyCreator#wrapIfNecessary,主要看代码如下。

  // Create proxy if we have advice.//查找匹配切面Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);if (specificInterceptors != DO_NOT_PROXY) {this.advisedBeans.put(cacheKey, Boolean.TRUE);//创建代理对象  Object proxy = createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));this.proxyTypes.put(cacheKey, proxy.getClass());return proxy;}

所以,继续看

  • AbstractAutoProxyCreator#createProxy

的创建代理对象方法。设置ProxyFactory创建Proxy需要的一切信息。

protected Object createProxy(Class<?> beanClass, String beanName, Object[] specificInterceptors, TargetSource targetSource) {if (this.beanFactory instanceof ConfigurableListableBeanFactory) {AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory) this.beanFactory, beanName, beanClass);}//新建代理对象工厂ProxyFactory proxyFactory = new ProxyFactory();proxyFactory.copyFrom(this);//设置工厂代理类if (!proxyFactory.isProxyTargetClass()) {if (shouldProxyTargetClass(beanClass, beanName)) {proxyFactory.setProxyTargetClass(true);}else {evaluateProxyInterfaces(beanClass, proxyFactory);}}//设置拦截切面Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);for (Advisor advisor : advisors) {proxyFactory.addAdvisor(advisor);}//设置被代理对象proxyFactory.setTargetSource(targetSource);customizeProxyFactory(proxyFactory);proxyFactory.setFrozen(this.freezeProxy);if (advisorsPreFiltered()) {proxyFactory.setPreFiltered(true);}
//创建代理对象return proxyFactory.getProxy(getProxyClassLoader());
}

下面看ProxyFactory是如何创建代理对象,继续跟踪proxyFactory.getProxy(getProxyClassLoader());

public Object getProxy(ClassLoader classLoader) {return createAopProxy().getProxy(classLoader);
}

createAopProxy()作用是根据class的种类判断采用的代理方式,看如下实现

  • DefaultAopProxyFactory#createAopProxy

    @Override
    public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {Class<?> targetClass = config.getTargetClass();if (targetClass == null) {throw new AopConfigException("TargetSource cannot determine target class: " +"Either an interface or a target is required for proxy creation.");}if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {//采用jdk动态代理必须基于接口return new JdkDynamicAopProxy(config);}//基于cglib实现代理不需要接口return new ObjenesisCglibAopProxy(config);}else {return new JdkDynamicAopProxy(config);}
    }
    

所以在当前调试的例子中,使用cglib代理。所以执行如下代理。

@Override
public Object getProxy(ClassLoader classLoader) {//。。。。。。// Configure CGLIB Enhancer...Enhancer enhancer = createEnhancer();if (classLoader != null) {enhancer.setClassLoader(classLoader);if (classLoader instanceof SmartClassLoader &&((SmartClassLoader) classLoader).isClassReloadable(proxySuperClass)) {enhancer.setUseCache(false);}}enhancer.setSuperclass(proxySuperClass);enhancer.setInterfaces(AopProxyUtils.completeProxiedInterfaces(this.advised));enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);enhancer.setStrategy(new ClassLoaderAwareUndeclaredThrowableStrategy(classLoader));//获取拦截回调函数Callback[] callbacks = getCallbacks(rootClass);Class<?>[] types = new Class<?>[callbacks.length];for (int x = 0; x < types.length; x++) {types[x] = callbacks[x].getClass();}// fixedInterceptorMap only populated at this point, after getCallbacks call aboveenhancer.setCallbackFilter(new ProxyCallbackFilter(this.advised.getConfigurationOnlyCopy(), this.fixedInterceptorMap, this.fixedInterceptorOffset));enhancer.setCallbackTypes(types);// Generate the proxy class and create a proxy instance.//返回一个cglib代理对象return createProxyClassAndInstance(enhancer, callbacks);}catch (CodeGenerationException ex) {//、、、、、、}
}

getCallbacks(rootClass);在这个获取回调函数的方法中,普通的aop采用的回调函数是如下的方式。

// Choose an "aop" interceptor (used for AOP calls).
Callback aopInterceptor = new DynamicAdvisedInterceptor(this.advised);

cglib 的aop回调函数如下。

public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {Object oldProxy = null;boolean setProxyContext = false;Class<?> targetClass = null;Object target = null;try {//这里注入的advised就是之前创建的ProxyFactory对象if (this.advised.exposeProxy) {// Make invocation available if necessary.oldProxy = AopContext.setCurrentProxy(proxy);setProxyContext = true;}// May be null. Get as late as possible to minimize the time we// "own" the target, in case it comes from a pool...target = getTarget();if (target != null) {targetClass = target.getClass();}//根据切面信息创建切面内容调用链List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);Object retVal;// Check whether we only have one InvokerInterceptor: that is,// no real advice, but just reflective invocation of the target.if (chain.isEmpty() && Modifier.isPublic(method.getModifiers())) {// We can skip creating a MethodInvocation: just invoke the target directly.// Note that the final invoker must be an InvokerInterceptor, so we know// it does nothing but a reflective operation on the target, and no hot// swapping or fancy proxying.Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);retVal = methodProxy.invoke(target, argsToUse);}else {// We need to create a method invocation...//创建一个方法调用对象,具体调用实现没分析,Before逻辑大概是先调用切面,在反射调用目标方法retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();}retVal = processReturnType(proxy, target, method, retVal);return retVal;}finally {if (target != null) {releaseTarget(target);}if (setProxyContext) {// Restore old proxy.AopContext.setCurrentProxy(oldProxy);}}
}

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

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

相关文章

java muki_再次学习 java 类的编译

做JAVA开发的都知道myeclipse&#xff0c; 我们在myeclipse中新建一个类&#xff0c;然后保存&#xff0c; 如何正常的话&#xff0c;那么在项目指定的目录(也就是项目的output目录)就会生成同名的class文件&#xff0c;可是&#xff0c;我们都知道myeclipse中的类的编译的原理…

spring源码阅读--@Transactional实现原理

Transactional注解简介 Transactional是spring中声明式事务管理的注解配置方式&#xff0c;相信这个注解的作用大家都很清楚。Transactional注解可以帮助我们把事务开启、提交或者回滚的操作&#xff0c;通过aop的方式进行管理。通过Transactional注解就能让spring为我们管理事…

MySQL中实现并、交、差

简介 sql叫做结构化查询语言&#xff0c;本质利用的就是关系代数中的操作&#xff0c;比如常用的并、交、差、投影、选择等操作。 其中并、交、差是常用的操作&#xff0c;本文就看看MySQL中的sql语言是怎么提供对应的关系代数操作的。 并 并的符号是∪&#xff0c;含义就是…

java获取http状态码_java获取Json和http状态码

最近再做接口自动化测试&#xff0c;其中有几个方法比较重要1.获取http状态码/** 返回接口状态码**/public staticString getHttpCode(String url) {String code null;try{URL u newURL(url);URLConnection ucu.openConnection();HttpURLConnection huc(HttpURLConnection)uc;c…

MySQL 普通索引和唯一索引的区别详解

1 概念区分 普通索引和唯一索引 普通索引可重复&#xff0c;唯一索引和主键一样不能重复。 唯一索引可作为数据的一个合法验证手段&#xff0c;例如学生表的身份证号码字段&#xff0c;我们人为规定该字段不得重复&#xff0c;那么就使用唯一索引。&#xff08;一般设置学号字…

win8.1已阻止java_win8系统下打开java程序时出现应用程序已被安全设置阻止的解决方法...

今天和大家分享一下win7系统下打开java程序时出现应用程序已被安全设置阻止问题的解决方法&#xff0c;在使用win7系统的过程中经常不知道如何去解决win7系统下打开java程序时出现应用程序已被安全设置阻止的问题&#xff0c;有什么好的办法去解决win7系统下打开java程序时出现…

MySql常用函数大全

MySql常用函数大全 MySQL数据库中提供了很丰富的函数。MySQL函数包括数学函数、字符串函数、日期和时间函数、条件判断函数、系统信息函数、加密函数、格式化函数等。通过这些函数&#xff0c;可以简化用户的操作。例如&#xff0c;字符串连接函数可以很方便的将多个字符串连接…

android两个java文件内容_java – 在1个请求中将多个文件从Android上传...

我知道我可以使用multipart / form POST请求一次将1个文件上传到AppEngine. AppEngine也支持uploading multiple files,但你必须做一些运行的JSP东西才能工作.我有一个应用程序,要求我上传一些表单数据,2个图像和3个文本字段.这可以通过AppEngine完成吗&#xff1f;我一直在努力…

LINUX下用YUM安装nginx出现No package nginx available.的问题与解决方案

一、问题描述 运行命令 yum install nginx 之后出现如下图情况。 二、解决过程如下 根据问题描述可以看出&#xff0c;是yum源出了问题&#xff0c;因此我们需要捣鼓以下yum源配置。具体解决过程如下。 1.备份CentOS-Base.repo mv /etc/yum.repos.d/CentOS-Base.repo /et…

mysql开启yum search pt-mysql_Centos使用MySQL工具Percona Toolkit

Centos使用MySQL工具Percona Toolkit安装Percona Toolkit 的Repo 得以支持直接用yum 安装二进制包yum install -y https://www.percona.com/redir/downloads/percona-release/redhat/latest/percona-release-0.1-4.noarch.rpmyum install -y percona-toolkit改MySQL表结构DDL p…

Controller层使用@value注解获取不到properties属性值

说到Value注解&#xff0c;用过的应该都知道&#xff0c;这是Spring3的一个注解&#xff0c;通过value注解的方式获取properties文件中的配置值&#xff0c;大大简化了我们读取配置文件的代码。然而&#xff0c;最近在使用中发现在controller使用出现了获取不到值的问题 经过排…

spring中context:property-placeholder标签详解

spring中context:property-placeholder标签的使用说明 1&#xff0c;有些参数在某些阶段中是常量。 在开发阶段我们连接数据库时的url&#xff0c;username&#xff0c;password等信息 分布式应用中client端的server地址&#xff0c;端口等这些参数在不同阶段之间又住住需要改…

access mysql oracle数据库_Oracle Access 数据库连接 使用

直接代码吧&#xff1a;/// /// Oracle数据库连接/// /// 数据库连接串&#xff0c;例如&#xff1a;(DESCRIPTION (ADDRESS_LIST (ADDRESS (PROTOCOL TCP)(HOST IP)(PORT *)))(CONNECT_DATA (SERVICE_NAME *)))/// 用户名/// 用户密码/// Oracle数据库连接对象private st…

Jackson用法详解

Spring MVC 默认采用Jackson解析Json&#xff0c;尽管还有一些其它同样优秀的json解析工具&#xff0c;例如Fast Json、GSON&#xff0c;但是出于最小依赖的考虑&#xff0c;也许Json解析第一选择就应该是Jackson。 一、简介 Jackson 是当前用的比较广泛的&#xff0c;用来序列…

php7 cms,PHP7CMS 无条件前台GETSHELL

Version:2018-10-09//最新版中以修复此漏洞这个漏洞很简单&#xff0c;如果作者在写代码的时候考虑到一点点安全方面&#xff0c;其实都可以避免的。[PHP] 纯文本查看 复制代码// php7cms/Core/Controllers/Api/Api.php// 52~61 linepublic function save_form_data() {$rt \P…

php服务器怎么设置cookie,php服务器如何清除浏览器cookie

php服务器清除浏览器cookie的方法&#xff1a;1、设置cookie的过期时间&#xff1b;2、设置cookie的值为空&#xff0c;代码为【setcookie($cookiename, ) setcookie($cookiename, NULL);】。php服务器清除浏览器cookie的方法&#xff1a;一、设置cookie的过期时间//将过期时间…

Java面试——RabbitMQ系列总结

1.RabbitMQ是什么&#xff1f; RabbitMQ是一款开源的&#xff0c;Erlang编写的&#xff0c;基于AMQP&#xff08;高级消息队列协议&#xff09;协议的消息中间件。 2.为什么要使用消息队列&#xff1f; 从本质上来说是因为互联网的快速发展&#xff0c;业务不断扩张&#xff0c…

商城系统php功能模块,yershop商城系统的支付模块问题

这个商城系统是用ThinkPHP框架进行开发的&#xff0c;但是有很多毛病就不吐槽了。最崩溃的毛病是商城的核心功能??支付功能有语法错误导致无法支付。如图&#xff1a;求教大神&#xff0c;这该怎么改&#xff0c;纠结了好久好久。。。。。在这里数组中使用C方法就报错。代码片…

php ini 长连接秒数,php使用webSocket实现Echarts长连接自动刷新的解决方案(2):后端服务端代码返回json数据...

$address "127.0.0.1";$port 9090; //调试的时候&#xff0c;可以多换端口来测试程序&#xff01;set_time_limit(0);$sock socket_create(AF_INET, SOCK_STREAM, SOL_TCP);socket_set_block($sock);socket_bind($sock, $address, $port);socket_listen($sock, 4)…

php swoole udp,基于Swoole如何搭建UDP服务?

本节将会讲解如下2个问题&#xff1a;通过Swoole如何搭建UPD服务&#xff1f;对比TCP和UDP有什么不同&#xff1f;01通过Swoole如何搭建UPD服务新建一个文件命名为 udp_server.php&#xff0c;代码如下&#xff1a;在命令行执行如下命令就可以开启TCP服务&#xff1a;php udp_s…