
暂无个人介绍
能力说明:
了解变量作用域、Java类的结构,能够创建带main方法可执行的java应用,从命令行运行java程序;能够使用Java基本数据类型、运算符和控制结构、数组、循环结构书写和运行简单的Java程序。
阿里云技能认证
详细说明
在项目开发中遇到了这样的一个问题:有同事在BeanFactoryPostProcessor的实现方法中写了类似这样的一段代码: @Component public class BeanFactoryPostProcessorImpl implements BeanFactoryPostProcessor { @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { //就是这一段代码 beanFactory.getBean(GetBeanByType.class); } } 然后导致了一些实现了FactoryBean接口的Bean被提前创建了,当时感觉很奇怪,我只是根据传入的Class会获取Bean,为什么会导致一些实现了FactoryBean接口的Bean也被创建呢?其实即使我们是根据类型从Spring容器中获取Bean,但是Spring容器在实现的时候,最底层还是会调用getBean(java.lang.String, java.lang.Class, java.lang.Object...)这个方法获取Bean,那么当我们根据类型从Spring容器中获取Bean的时候就先找到这个类型对应的在Spring容器中的beanName,问题就出在获取这个beanName的地方。真正出问题的是AbstractBeanFactory#isTypeMatch(java.lang.String, org.springframework.core.ResolvableType)这个方法。下面我们来分一下获取beanName的这个过程。其入口方法为:DefaultListableBeanFactory#getBeanNamesForType(java.lang.Class<?>),代码如下: public String[] getBeanNamesForType(Class<?> type) { //这里传入的三个参数 type是bean的Class 第二个参数的意思是 是否包含非单例的bean //第三个参数很重要 这里传入的值为true 这里传入true 代表允许提前初始化Bean //如果这个值为false的话 代表不允许提前初始化Bean 问题就出在这个值为true!!! return getBeanNamesForType(type, true, true); } @Override public String[] getBeanNamesForType(Class<?> type, boolean includeNonSingletons, boolean allowEagerInit) { //如果是在容器启动过程中 这个值为false 如果在容器启动完成之后 这个值会变为true 这个值的更改在 //DefaultListableBeanFactory#freezeConfiguration 可以看一下它的调用关系 //下面的内容就很简单了 我们主要分析doGetBeanNamesForType这个方法 if (!isConfigurationFrozen() || type == null || !allowEagerInit) { return doGetBeanNamesForType(ResolvableType.forRawClass(type), includeNonSingletons, allowEagerInit); } //这里是从缓存中获取,includeNonSingletons是否包含非单例的方法 Map<Class<?>, String[]> cache = (includeNonSingletons ? this.allBeanNamesByType : this.singletonBeanNamesByType); String[] resolvedBeanNames = cache.get(type); if (resolvedBeanNames != null) { return resolvedBeanNames; } resolvedBeanNames = doGetBeanNamesForType(ResolvableType.forRawClass(type), includeNonSingletons, true); if (ClassUtils.isCacheSafe(type, getBeanClassLoader())) { cache.put(type, resolvedBeanNames); } return resolvedBeanNames; } DefaultListableBeanFactory#doGetBeanNamesForType private String[] doGetBeanNamesForType(ResolvableType type, boolean includeNonSingletons, boolean allowEagerInit) { List<String> result = new ArrayList<String>(); // Check all bean definitions. // 注意这里是循环Spring容器中所有的BeanDefinition for (String beanName : this.beanDefinitionNames) { // Only consider bean as eligible if the bean name // is not defined as alias for some other bean. //beanName不是别名 if (!isAlias(beanName)) { try { //这里你可以暂时理解为根据BeanName获取BeanDefinition RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName); // Only check bean definition if it is complete. //下面这个判断比较长 一个一个的来说一下 //mbd.isAbstract()判断这个类是不是抽象类 默认是false //allowEagerInit 是否允许提前初始化 这个值是传入的值 我们这里传入的值是true 即允许提前初始化 //mbd.hasBeanClass() 是否已经有BeanClass了 //mbd.isLazyInit()是否允许延迟初始化 默认false //isAllowEagerClassLoading() 是否允许提前加载 上面这四个值是或的关系 只有有一个为true就可以 //requiresEagerInitForType 检查特殊的Bean是否需要提前被初始化 这里传入的是工厂方法的名字 //我们把这个条件总结一下 如果这个Bean不是一个抽象类,并且(被允许提前初始化 或者 (这个Bean的 //BeanDefinition设置了BeanClass了 或者 这个Bean没有设置延迟加载 或者 即使这个Bean标注为延迟加载了,也可以被提前加载(全局变量) 并且 BeanDefinition中的FactoryBeanName是否允许被提前初始化)) //根据我们前面的分析 allowEagerInit 的值为true 这个Bean也不是个抽象类,所以会继续下面的流程 if (!mbd.isAbstract() && (allowEagerInit || ((mbd.hasBeanClass() || !mbd.isLazyInit() || isAllowEagerClassLoading())) && !requiresEagerInitForType(mbd.getFactoryBeanName()))) { // In case of FactoryBean, match object created by FactoryBean. // 判断是不是FactoryBean boolean isFactoryBean = isFactoryBean(beanName, mbd); BeanDefinitionHolder dbd = mbd.getDecoratedDefinition(); // 这个地方是判断是不是找到匹配的beanName 这里关键的是isTypeMatch这个方法 // 在这个方法中会导致一些FactoryBean被提前实例化 所以这里给我们的一个提示是: // 在Spring容器的启动阶段,调用isTypeMatch这个方法去判断Bean的类型的时候要慎重一些。 // 我们要重点分析isTypeMatch这个方法 boolean matchFound = (allowEagerInit || !isFactoryBean || (dbd != null && !mbd.isLazyInit()) || containsSingleton(beanName)) && (includeNonSingletons || (dbd != null ? mbd.isSingleton() : isSingleton(beanName))) && isTypeMatch(beanName, type); //如果类型不匹配 并且还是FactoryBean类型的BeanDefinition if (!matchFound && isFactoryBean) { // In case of FactoryBean, try to match FactoryBean instance itself next. //这里讲beanName变为&beanName beanName = FACTORY_BEAN_PREFIX + beanName; //继续判断类型匹配不匹配 matchFound = (includeNonSingletons || mbd.isSingleton()) && isTypeMatch(beanName, type); } //如果类型相匹配的话 则将beanName放入到结果集中 if (matchFound) { result.add(beanName); } } } catch (CannotLoadBeanClassException ex) { } catch (BeanDefinitionStoreException ex) { } } } //这一部分的处理流程大致相同 // Check manually registered singletons too. for (String beanName : this.manualSingletonNames) { try { // In case of FactoryBean, match object created by FactoryBean. if (isFactoryBean(beanName)) { if ((includeNonSingletons || isSingleton(beanName)) && isTypeMatch(beanName, type)) { result.add(beanName); // Match found for this bean: do not match FactoryBean itself anymore. continue; } // In case of FactoryBean, try to match FactoryBean itself next. beanName = FACTORY_BEAN_PREFIX + beanName; } // Match raw bean instance (might be raw FactoryBean). if (isTypeMatch(beanName, type)) { result.add(beanName); } } catch (NoSuchBeanDefinitionException ex) { } } return StringUtils.toStringArray(result); } 下面我们来看看isTypeMatch这个方法的内容 @Override public boolean isTypeMatch(String name, ResolvableType typeToMatch) throws NoSuchBeanDefinitionException { //这里是对beanName进行转换 如:如果传入的beanName是&beanName,则将&去掉。如果传入的beanName有别名的话,则替换为别名 String beanName = transformedBeanName(name); // Check manually registered singletons. //根据beanName获取bean的实例 前提是其对应的bean已经被实例化了 或者正在实例化中 Object beanInstance = getSingleton(beanName, false); if (beanInstance != null) { //如果是FactoryBean类型的实例 if (beanInstance instanceof FactoryBean) { //是否要获取在FactoryBean中创建的Bean实例 调用 getObject getObjectType方法 //这个地方的判断条件是 传入的beanName是否以&开头 如果以&开头,则返回true if (!BeanFactoryUtils.isFactoryDereference(name)) { //这里是调用FactoryBean的getObjectType方法 来获取bean的类型 Class<?> type = getTypeForFactoryBean((FactoryBean<?>) beanInstance); return (type != null && typeToMatch.isAssignableFrom(type)); } else { //直接获取FactoryBean类型的实例 return typeToMatch.isInstance(beanInstance); } } //判断beanName是否以&开头,如果以&开头的话 则返回true else if (!BeanFactoryUtils.isFactoryDereference(name)) { //如果beanName不是以&开头 则直接进行类型比较 if (typeToMatch.isInstance(beanInstance)) { // Direct match for exposed instance? return true; } //泛型的处理 else if (typeToMatch.hasGenerics() && containsBeanDefinition(beanName)) { // Generics potentially only match on the target class, not on the proxy... RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName); Class<?> targetType = mbd.getTargetType(); if (targetType != null && targetType != ClassUtils.getUserClass(beanInstance) && typeToMatch.isAssignableFrom(targetType)) { // Check raw class match as well, making sure it's exposed on the proxy. Class<?> classToMatch = typeToMatch.resolve(); return (classToMatch == null || classToMatch.isInstance(beanInstance)); } } } return false; } else if (containsSingleton(beanName) && !containsBeanDefinition(beanName)) { // null instance registered return false; } // No singleton instance found -> check bean definition. //这里是从父BeanFactory中进行查找匹配 查找匹配的过程是一样的。 BeanFactory parentBeanFactory = getParentBeanFactory(); if (parentBeanFactory != null && !containsBeanDefinition(beanName)) { // No bean definition found in this factory -> delegate to parent. return parentBeanFactory.isTypeMatch(originalBeanName(name), typeToMatch); } // Retrieve corresponding bean definition. //根据beanName重新检索BeanDefinition 这里返回一个RootBeanDefinition 如果父子容器中存在相同类型的bean会进行合并 RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName); //解析出 class的类型 Class<?> classToMatch = typeToMatch.resolve(); if (classToMatch == null) { classToMatch = FactoryBean.class; } //如果传入的class类型为FactoryBean类型 则直接返回FactoryBean类型的数组 否则会将传入的class的类型和FactoryBean组装在一起 Class<?>[] typesToMatch = (FactoryBean.class == classToMatch ? new Class<?>[] {classToMatch} : new Class<?>[] {FactoryBean.class, classToMatch}); // Check decorated bean definition, if any: We assume it'll be easier // to determine the decorated bean's type than the proxy's type. //这个地方是获取最原始的BeanDefinition 不是组装之后的 RootBeanDefinition BeanDefinitionHolder dbd = mbd.getDecoratedDefinition(); //如果dbd 存在并且 beanName不是以&开头 if (dbd != null && !BeanFactoryUtils.isFactoryDereference(name)) { RootBeanDefinition tbd = getMergedBeanDefinition(dbd.getBeanName(), dbd.getBeanDefinition(), mbd); //这里是取BeanDefinition中的bean类型 Class<?> targetClass = predictBeanType(dbd.getBeanName(), tbd, typesToMatch); if (targetClass != null && !FactoryBean.class.isAssignableFrom(targetClass)) { return typeToMatch.isAssignableFrom(targetClass); } } //获取目标类型 通常是去BeanDefinition中的resolvedTargetType Class<?> beanType = predictBeanType(beanName, mbd, typesToMatch); if (beanType == null) { return false; } // Check bean class whether we're dealing with a FactoryBean. //如果BeanDefinition中的beanType是FactoryBean类型 if (FactoryBean.class.isAssignableFrom(beanType)) { if (!BeanFactoryUtils.isFactoryDereference(name)) { // If it's a FactoryBean, we want to look at what it creates, not the factory class. //就是这个地方 会将FactoryBean类型的Bean进行实例化 //我们来分析这个方法 beanType = getTypeForFactoryBean(beanName, mbd); if (beanType == null) { return false; } } } else if (BeanFactoryUtils.isFactoryDereference(name)) { // Special case: A SmartInstantiationAwareBeanPostProcessor returned a non-FactoryBean // type but we nevertheless are being asked to dereference a FactoryBean... // Let's check the original bean class and proceed with it if it is a FactoryBean. //这个地方需要注意的是 SmartInstantiationAwareBeanPostProcessor类型的BeanPostProcessor会对返回的Bean类型做修改 beanType = predictBeanType(beanName, mbd, FactoryBean.class); if (beanType == null || !FactoryBean.class.isAssignableFrom(beanType)) { return false; } } ResolvableType resolvableType = mbd.targetType; if (resolvableType == null) { //工厂方法创建的Bean resolvableType = mbd.factoryMethodReturnType; } if (resolvableType != null && resolvableType.resolve() == beanType) { return typeToMatch.isAssignableFrom(resolvableType); } return typeToMatch.isAssignableFrom(beanType); } isTypeMatch这个方法就是进行各种Bean类型的判断,如是已经实例化的FactoryBean可能会调用它的getObjectType方法获取Bean的类型,或者从BeanDefinition中获取Bean的类型,并且如果是未实例化的FactoryBean,为了进行Bean类型的判断会导致FactoryBean的实例化。下面我们来看看org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#getTypeForFactoryBean这个方法的内容: protected Class<?> getTypeForFactoryBean(String beanName, RootBeanDefinition mbd) { //工程Bean和工厂方法名 根据工厂方法创建Bean String factoryBeanName = mbd.getFactoryBeanName(); String factoryMethodName = mbd.getFactoryMethodName(); if (factoryBeanName != null) { if (factoryMethodName != null) { // Try to obtain the FactoryBean's object type from its factory method declaration // without instantiating the containing bean at all. BeanDefinition fbDef = getBeanDefinition(factoryBeanName); if (fbDef instanceof AbstractBeanDefinition) { AbstractBeanDefinition afbDef = (AbstractBeanDefinition) fbDef; if (afbDef.hasBeanClass()) { Class<?> result = getTypeForFactoryBeanFromMethod(afbDef.getBeanClass(), factoryMethodName); if (result != null) { return result; } } } } // If not resolvable above and the referenced factory bean doesn't exist yet, // exit here - we don't want to force the creation of another bean just to // obtain a FactoryBean's object type... if (!isBeanEligibleForMetadataCaching(factoryBeanName)) { return null; } } // Let's obtain a shortcut instance for an early getObjectType() call... //getSingletonFactoryBeanForTypeCheck和getNonSingletonFactoryBeanForTypeCheck这两个方法是创建FactoryBean的实例了 FactoryBean<?> fb = (mbd.isSingleton() ? getSingletonFactoryBeanForTypeCheck(beanName, mbd) : getNonSingletonFactoryBeanForTypeCheck(beanName, mbd)); if (fb != null) { // Try to obtain the FactoryBean's object type from this early stage of the instance. //调用getObjectType方法 Class<?> result = getTypeForFactoryBean(fb); if (result != null) { return result; } else { // No type found for shortcut FactoryBean instance: // fall back to full creation of the FactoryBean instance. return super.getTypeForFactoryBean(beanName, mbd); } } if (factoryBeanName == null && mbd.hasBeanClass()) { // No early bean instantiation possible: determine FactoryBean's type from // static factory method signature or from class inheritance hierarchy... if (factoryMethodName != null) { return getTypeForFactoryBeanFromMethod(mbd.getBeanClass(), factoryMethodName); } else { return GenericTypeResolver.resolveTypeArgument(mbd.getBeanClass(), FactoryBean.class); } } return null; } 上面这个方法的内容是:如果是以工厂方法来创建Bean的话,则从工厂方法中返回bean类型,如果是FactoryBean类型的Bean的话,会实例化FactoryBean类型的Bean。实例化是在getSingletonFactoryBeanForTypeCheck或getNonSingletonFactoryBeanForTypeCheck方法中完成的。 private FactoryBean<?> getSingletonFactoryBeanForTypeCheck(String beanName, RootBeanDefinition mbd) { synchronized (getSingletonMutex()) { //先从缓存中获取 BeanWrapper bw = this.factoryBeanInstanceCache.get(beanName); if (bw != null) { return (FactoryBean<?>) bw.getWrappedInstance(); } //这个bean是不是正在创建中 if (isSingletonCurrentlyInCreation(beanName) || (mbd.getFactoryBeanName() != null && isSingletonCurrentlyInCreation(mbd.getFactoryBeanName()))) { return null; } Object instance = null; try { // Mark this bean as currently in creation, even if just partially. //判断是不是正在创建中的bean beforeSingletonCreation(beanName); // Give BeanPostProcessors a chance to return a proxy instead of the target bean instance. //Bean实例化之前的一些处理 由Spring容器中InstantiationAwareBeanPostProcessor类型的实例来完成 instance = resolveBeforeInstantiation(beanName, mbd); if (instance == null) { //创建Bean的实例 看到这里就很明了了 bw = createBeanInstance(beanName, mbd, null); instance = bw.getWrappedInstance(); } } finally { // Finished partial creation of this bean. afterSingletonCreation(beanName); } FactoryBean<?> fb = getFactoryBean(beanName, instance); if (bw != null) { this.factoryBeanInstanceCache.put(beanName, bw); } return fb; } } 按照我们上面的分析,在doGetBeanNamesForType这个方法中, private String[] doGetBeanNamesForType(ResolvableType type, boolean includeNonSingletons, boolean allowEagerInit) allowEagerInit是一个非常重要的参数,这个参数可以控制要不要提前实例化一些Bean。为什么这样说呢?因为要不要调用isTypeMatch这个方法在很大程度上是由这个条件控制的 if (!mbd.isAbstract() && (allowEagerInit ||((mbd.hasBeanClass() || !mbd.isLazyInit() || isAllowEagerClassLoading())) &&!requiresEagerInitForType(mbd.getFactoryBeanName()))) 通常我们的BeanDefinition都不是抽象类的,所以第一个条件为true,我们来看第二个条件,就是后面的那一大堆 (allowEagerInit ||((mbd.hasBeanClass() || !mbd.isLazyInit() || isAllowEagerClassLoading())) &&!requiresEagerInitForType(mbd.getFactoryBeanName())) 这个条件分为两部分,是一个或的条件。如果allowEagerInit 为true的话,则整个条件为true,如果allowEagerInit 为false呢?mbd.hasBeanClass() || !mbd.isLazyInit() || isAllowEagerClassLoading()这三个条件基本上也是为true但是这个地方是一个与的条件,那么还要判断一下requiresEagerInitForType(mbd.getFactoryBeanName())这个方法是返回的true还是false。 private boolean requiresEagerInitForType(String factoryBeanName) { //factoryBeanName不为null isFactoryBean(factoryBeanName) 是否为FactoryBeanBean //containsSingleton(factoryBeanName)) 如果这个Bean已经被实例化了 这个返回的值为true return (factoryBeanName != null && isFactoryBean(factoryBeanName) && !containsSingleton(factoryBeanName)); } 因为我们在这篇文章中的分析是根据类型获取Bean会导致FactoryBean类型的Bean被提前实例化,所以factoryBeanName不为null,isFactoryBean(factoryBeanName) 为true,!containsSingleton(factoryBeanName)同样为true。综合requiresEagerInitForType这个方法返回true,那么 ((mbd.hasBeanClass() || !mbd.isLazyInit() || isAllowEagerClassLoading())) &&!requiresEagerInitForType(mbd.getFactoryBeanName()) 会返回false,那这个这一大段的逻辑也就返回false了。所以我们在根据类型获取Bean的时候要谨慎一点,如果整个Spring容器已经启动完成之后,根据类型获取Bean是没有问题的,如果是在Spring容器的启动过程中根据类型获取Bean可能会导致一些意想不到的结果。
在这篇文章中我们接着上一篇的文章说。在上一篇文章中我们提到了getAdvicesAndAdvisorsForBean这个方法,这个方法的内容是为目标对象挑选合适的Advisor类,其源码如下: //targetSource 为null protected Object[] getAdvicesAndAdvisorsForBean(Class<?> beanClass, String beanName, TargetSource targetSource) { List<Advisor> advisors = findEligibleAdvisors(beanClass, beanName); //如果获取的Advisor为空的话,则直接返回DO_NOT_PROXY 返回这个值的时候 是不创建代理对象 if (advisors.isEmpty()) { return DO_NOT_PROXY; } return advisors.toArray(); } protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) { //这个方法我们在上一章中分析过了 获取Spring容器中的所有的通知方法 封装为Advisor集合 List<Advisor> candidateAdvisors = findCandidateAdvisors(); //这一步就是为目标对象挑选合适的Advisor 即目标对象和切点表达式相匹配 //此处的分析 请参考这里:https://blog.csdn.net/zknxx/article/details/79735405 List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName); //这个方法做的事是向 上一步获取到的Advisor中 插入ExposeInvocationInterceptor.ADVISOR 插在第一个位置 extendAdvisors(eligibleAdvisors); if (!eligibleAdvisors.isEmpty()) { //排序 很复杂的实现 不说了 如果你对一个目标对象使用多个 相同类型的通知的话 请把这些通知放到不同的Aspect中,并实现Order接口或者使用Ordered注解标注顺序 eligibleAdvisors = sortAdvisors(eligibleAdvisors); } return eligibleAdvisors; } 最后我们再看看目标对象的创建过程: //specificInterceptors 上面的方法中获取到的Advisor //targetSource 为 new SingletonTargetSource(bean) //将Spring容器中创建出来的bean封装为了SingletonTargetSource protected Object createProxy( Class<?> beanClass, String beanName, Object[] specificInterceptors, TargetSource targetSource) { //这里将目标Bean的Class设置为BeanDefinition中的一个属性 //在使用JDK动态代理的时候 可能会用到这个属性 https://stackoverflow.com/questions/45463757/what-is-interface-based-proxying if (this.beanFactory instanceof ConfigurableListableBeanFactory) { AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory) this.beanFactory, beanName, beanClass); } //这里创建了一个新的 ProxyFactory ProxyFactory proxyFactory = new ProxyFactory(); //copy原类的配置 重写赋值的动作 单例变多例 proxyFactory.copyFrom(this); //这里是 判断是否强制使用 cglib if (!proxyFactory.isProxyTargetClass()) { //这里 判断是 使用jdk动态代理 还是cglib代理 if (shouldProxyTargetClass(beanClass, beanName)) { proxyFactory.setProxyTargetClass(true); } else { evaluateProxyInterfaces(beanClass, proxyFactory); } } //这里又重写构建了一次 Advisor 看看是否 设置了通用的拦截 //这里讲所有的Advisor和配置的通用拦截都转换为了Advisor类型 Advisor[] advisors = buildAdvisors(beanName, specificInterceptors); for (Advisor advisor : advisors) { proxyFactory.addAdvisor(advisor); } //targetSource 里是包含目标对象的 proxyFactory.setTargetSource(targetSource); customizeProxyFactory(proxyFactory); proxyFactory.setFrozen(this.freezeProxy); if (advisorsPreFiltered()) { proxyFactory.setPreFiltered(true); } //创建代理对象 关于创建代理对象的分析 请参考:https://blog.csdn.net/zknxx/article/details/79764107 return proxyFactory.getProxy(getProxyClassLoader()); } 好了,到此关于AOP的分析就先暂时告一段落了。
在开始这个系列之前大家先想一下我们是怎么在项目中使用SpringAOP的(这里的分析都是基于AspectJ注解的)。我们需要在我们的Spring配置文件中引入SpringAOP的命名空间和标签,然后定义切面Bean,进行AOP配置。大概如下所示: <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" <!--AOP的命名空间--> xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <!--开启自动包扫描--> <context:component-scan base-package="com.zkn.spring.learn.aop.spring"/> <!--使用AspectJ注解--> <aop:aspectj-autoproxy/> </beans> 我们在使用Spring中不同的功能的时候可能会引入不同的命名空间比如xmlns:context,xmlns:aop,xmlns:tx等等。关于命名空间的东西我们这里先不多说。在Spring中定义了一个这样的抽象类专门用来解析不同的命名空间。这个类是NamespaceHandler,我们看一下这个和这个类相关的一些子类:在不同的命名空间实现类中定义了不同类型的实现类,这些实现类主要是用来初始化一些解析对应的标签的类。比如我们接下来要分析的AopNamespaceHandler这个类。在上面关于AOP的配置中,我们使用了一个AOP的标签: <aop:aspectj-autoproxy/> 为什么我们只要使用这个标签,就可以使用SpringAOP的功能呢?看一下AopNamespaceHandler这个类的内容你就会明白了: public class AopNamespaceHandler extends NamespaceHandlerSupport { @Override public void init() { // In 2.0 XSD as well as in 2.1 XSD. registerBeanDefinitionParser("config", new ConfigBeanDefinitionParser()); //我们看到了这样的一段代码 aspectj-autoproxy这个再加上aop 是不是就是 aop:aspectj-autoproxy呢 //这段代码的意思是使用AspectJAutoProxyBeanDefinitionParser来解析aop:aspectj-autoproxy标签 //AspectJAutoProxyBeanDefinitionParser这个类就是SpringAOP和Spring框架结合的关键 registerBeanDefinitionParser("aspectj-autoproxy", new AspectJAutoProxyBeanDefinitionParser()); registerBeanDefinitionDecorator("scoped-proxy", new ScopedProxyBeanDefinitionDecorator()); // Only in 2.0 XSD: moved to context namespace as of 2.1 registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser()); } } PS:每个框架对应的NamespaceHandler就是你分析Spring中的对应框架的关键入口。下面我们来分析一下AspectJAutoProxyBeanDefinitionParser这个类。先看一下它的parse方法。 public BeanDefinition parse(Element element, ParserContext parserContext) { //这个地方是向ApplicationContext中注入使用AspectJ注解自动创建代理对象的bean AopNamespaceUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(parserContext, element); extendBeanDefinition(element, parserContext); return null; } AopNamespaceUtils#registerAspectJAnnotationAutoProxyCreatorIfNecessary的方法内容如下: public static void registerAspectJAnnotationAutoProxyCreatorIfNecessary( ParserContext parserContext, Element sourceElement) { //parserContext.getRegistry()这个是获取到的全局的BeanDefinitionRegistry //我们看这个方法做了什么 BeanDefinition beanDefinition = AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary( parserContext.getRegistry(), parserContext.extractSource(sourceElement)); //这两个方法的内容一看就知道是什么意思了 就先不分析了 useClassProxyingIfNecessary(parserContext.getRegistry(), sourceElement); registerComponentIfNecessary(beanDefinition, parserContext); } AopConfigUtils#registerAspectJAnnotationAutoProxyCreatorIfNecessary public static BeanDefinition registerAspectJAnnotationAutoProxyCreatorIfNecessary(BeanDefinitionRegistry registry, Object source) { //这里传入了一个AnnotationAwareAspectJAutoProxyCreator的class return registerOrEscalateApcAsRequired(AnnotationAwareAspectJAutoProxyCreator.class, registry, source); } private static BeanDefinition registerOrEscalateApcAsRequired(Class<?> cls, BeanDefinitionRegistry registry, Object source) { Assert.notNull(registry, "BeanDefinitionRegistry must not be null"); //这里先判断是不是已经注入过name为org.springframework.aop.config.internalAutoProxyCreator的BeanDefinition了 if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) { //如果已经注入过了 判断之前注入的BeanDefinition的Class和传入的Class是不是一致 BeanDefinition apcDefinition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME); if (!cls.getName().equals(apcDefinition.getBeanClassName())) { //如果不一致的话 则取他们的优先级 int currentPriority = findPriorityForClass(apcDefinition.getBeanClassName()); int requiredPriority = findPriorityForClass(cls); //优先级高的生效 这个优先级 一会儿说 if (currentPriority < requiredPriority) { apcDefinition.setBeanClassName(cls.getName()); } } return null; } //如果之前没有向BeanDefinitionRegistry中注入过此beanname的BeanDefinition //那就创建一个新的BeanDefinition添加到BeanDefinitionRegistry中 RootBeanDefinition beanDefinition = new RootBeanDefinition(cls); beanDefinition.setSource(source); beanDefinition.getPropertyValues().add("order", Ordered.HIGHEST_PRECEDENCE); beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); registry.registerBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME, beanDefinition); return beanDefinition; } 我们上面提到了一个优先级的东西,那这个优先级是在哪儿定义的呢?在AopConfigUtils中有这样的一段代码: static { APC_PRIORITY_LIST.add(InfrastructureAdvisorAutoProxyCreator.class); APC_PRIORITY_LIST.add(AspectJAwareAdvisorAutoProxyCreator.class); APC_PRIORITY_LIST.add(AnnotationAwareAspectJAutoProxyCreator.class); } 在AopConfigUtils初始化的时候就会向APC_PRIORITY_LIST中添加这三个Class类。而上面提到的优先级就是他们在APC_PRIORITY_LIST中的位置。由此可见AspectJAwareAdvisorAutoProxyCreator会覆盖InfrastructureAdvisorAutoProxyCreatorBeanDefinition中的class,而AnnotationAwareAspectJAutoProxyCreator又会覆盖AspectJAwareAdvisorAutoProxyCreator的BeanDefinition中的class(当然也会覆盖InfrastructureAdvisorAutoProxyCreator的BeanDefinition)。而我们在上面传入的Class是AnnotationAwareAspectJAutoProxyCreator,即是优先级最大的Class。说了半天,这三个Class都有什么用呢?和我们今天说的SpringAOP又有什么关系呢?通过上面的分析,我们只知道使用 <aop:aspectj-autoproxy/> 会向BeanDefinitionRegistry中注入一个beanClass为AnnotationAwareAspectJAutoProxyCreator的Bean。我们先来看一下这三个类的关系:我们发现他们三个是同一个类的子类:AbstractAdvisorAutoProxyCreator,这个类的作用是创建代理对象并为每个代理对象找到合适的Advisor(这一部分的东西可以参考前面的博文),那么它的子类也具有相同的功能。AnnotationAwareAspectJAutoProxyCreator主要是为AspectJ注解服务的,InfrastructureAdvisorAutoProxyCreator是一个基础建设性的类,即识别不使用AspectJ注解的AOP配置(比如事务的实现)。OK,我们在上废话了一大堆其实就是说了一件事:那就是在Spring启动的时候会使用AopNamespaceHandler和AspectJAutoProxyBeanDefinitionParser来解析AOP标签,并注入对应的BeanDefinition(AnnotationAwareAspectJAutoProxyCreator和InfrastructureAdvisorAutoProxyCreator)。我们在项目中使用AspectJ注解比较多,所以我们在下篇文章中会分析一下AnnotationAwareAspectJAutoProxyCreator这个类。
我们在上一篇文章中说到了前置通知的方法调用AspectJMethodBeforeAdvice#before,在这个before方法中又调用了invokeAdviceMethod这个方法,invokeAdviceMethod这个方法在AspectJMethodBeforeAdvice的父类AbstractAspectJAdvice中。AbstractAspectJAdvice这个是Aspect的所有通知类型的共同父类。关于AbstractAspectJAdvice中的invokeAdviceMethod方法,有两个重载的方法。前置通知、后置通知、异常通知、后置返回通知都是用的AbstractAspectJAdvice#invokeAdviceMethod(org.aspectj.weaver.tools.JoinPointMatch, java.lang.Object, java.lang.Throwable)这个方法,环绕通知用的是:AbstractAspectJAdvice#invokeAdviceMethod(org.aspectj.lang.JoinPoint, org.aspectj.weaver.tools.JoinPointMatch, java.lang.Object, java.lang.Throwable)这个方法。这两个重载方法的区别是:后置通知调用的方法多了一个JoinPoint的参数。invokeAdviceMethod方法的源码如下: //这三个参数 JoinPointMatch 都是相同的 //returnValue 当执行后置返回通知的时候 传值 其他为null //Throwable 当执行后置异常通知的时候 传值,其他为null protected Object invokeAdviceMethod(JoinPointMatch jpMatch, Object returnValue, Throwable ex) throws Throwable { return invokeAdviceMethodWithGivenArgs(argBinding(getJoinPoint(), jpMatch, returnValue, ex)); } //重载的方法 这个 JoinPoint 是ProceedingJoinPoint protected Object invokeAdviceMethod(JoinPoint jp, JoinPointMatch jpMatch, Object returnValue, Throwable t) throws Throwable { return invokeAdviceMethodWithGivenArgs(argBinding(jp, jpMatch, returnValue, t)); } 我们先看getJoinPoint这个方法,其源码如下: protected JoinPoint getJoinPoint() { return currentJoinPoint(); } public static JoinPoint currentJoinPoint() { //这里就不用再多说了 获取当前的MethodInvocation 即ReflectiveMethodInvocation的实例 MethodInvocation mi = ExposeInvocationInterceptor.currentInvocation(); if (!(mi instanceof ProxyMethodInvocation)) { throw new IllegalStateException("MethodInvocation is not a Spring ProxyMethodInvocation: " + mi); } ProxyMethodInvocation pmi = (ProxyMethodInvocation) mi; //JOIN_POINT_KEY 的值 为 JoinPoint.class.getName() //从ReflectiveMethodInvocation中获取JoinPoint 的值 //这里在第一次获取的时候 获取到的 JoinPoint是null //然后把下面创建的MethodInvocationProceedingJoinPoint放入到ReflectiveMethodInvocation的userAttributes中 //这样在第二次获取的是 就会获取到这个 MethodInvocationProceedingJoinPoint JoinPoint jp = (JoinPoint) pmi.getUserAttribute(JOIN_POINT_KEY); if (jp == null) { jp = new MethodInvocationProceedingJoinPoint(pmi); pmi.setUserAttribute(JOIN_POINT_KEY, jp); } return jp; } 下面我们来看一下argBinding这个方法的作用和内容。从名字我们可以猜测这个方法的作用应该是进行参数绑定用的,我们来看一下: protected Object[] argBinding(JoinPoint jp, JoinPointMatch jpMatch, Object returnValue, Throwable ex) { calculateArgumentBindings(); // AMC start Object[] adviceInvocationArgs = new Object[this.parameterTypes.length]; int numBound = 0; //这个默认值是 -1 重新赋值是在calculateArgumentBindings中进行的 if (this.joinPointArgumentIndex != -1) { adviceInvocationArgs[this.joinPointArgumentIndex] = jp; numBound++; } else if (this.joinPointStaticPartArgumentIndex != -1) { adviceInvocationArgs[this.joinPointStaticPartArgumentIndex] = jp.getStaticPart(); numBound++; } //这里主要是取通知方法中的参数类型 是除了 JoinPoint和ProceedingJoinPoint参数之外的参数 //如异常通知参数 返回通知参数 if (!CollectionUtils.isEmpty(this.argumentBindings)) { // binding from pointcut match if (jpMatch != null) { PointcutParameter[] parameterBindings = jpMatch.getParameterBindings(); for (PointcutParameter parameter : parameterBindings) { String name = parameter.getName(); Integer index = this.argumentBindings.get(name); adviceInvocationArgs[index] = parameter.getBinding(); numBound++; } } // binding from returning clause //后置返回通知参数 if (this.returningName != null) { Integer index = this.argumentBindings.get(this.returningName); adviceInvocationArgs[index] = returnValue; numBound++; } // binding from thrown exception //异常通知参数 if (this.throwingName != null) { Integer index = this.argumentBindings.get(this.throwingName); adviceInvocationArgs[index] = ex; numBound++; } } if (numBound != this.parameterTypes.length) { throw new IllegalStateException("Required to bind " + this.parameterTypes.length + " arguments, but only bound " + numBound + " (JoinPointMatch " + (jpMatch == null ? "was NOT" : "WAS") + " bound in invocation)"); } return adviceInvocationArgs; } calculateArgumentBindings public synchronized final void calculateArgumentBindings() { // The simple case... nothing to bind. //通知方法没有参数直接返回 if (this.argumentsIntrospected || this.parameterTypes.length == 0) { return; } int numUnboundArgs = this.parameterTypes.length; Class<?>[] parameterTypes = this.aspectJAdviceMethod.getParameterTypes(); //从这里可以看出来我们的JoinPoint和ProceedingJoinPoint要放在通知方法的第一个参数 if (maybeBindJoinPoint(parameterTypes[0]) || maybeBindProceedingJoinPoint(parameterTypes[0])) { numUnboundArgs--; } else if (maybeBindJoinPointStaticPart(parameterTypes[0])) { numUnboundArgs--; } //这里是对其他的参数的处理 处理过程还是比较复杂一点的 这里不再多说了。 if (numUnboundArgs > 0) { // need to bind arguments by name as returned from the pointcut match bindArgumentsByName(numUnboundArgs); } this.argumentsIntrospected = true; } 这里还有再说一下AbstractAspectJAdvice这个类的构造函数,这个类只有这一个构造函数 public AbstractAspectJAdvice( Method aspectJAdviceMethod, AspectJExpressionPointcut pointcut, AspectInstanceFactory aspectInstanceFactory) { //通知方法不能为空 Assert.notNull(aspectJAdviceMethod, "Advice method must not be null"); //切面类 this.declaringClass = aspectJAdviceMethod.getDeclaringClass(); //通知方法的名字 this.methodName = aspectJAdviceMethod.getName(); //通知方法参数 this.parameterTypes = aspectJAdviceMethod.getParameterTypes(); //通知方法 this.aspectJAdviceMethod = aspectJAdviceMethod; //切点类 this.pointcut = pointcut; //切面实例的工厂类 this.aspectInstanceFactory = aspectInstanceFactory; } 在创建通知类实例的时候,进行了上面的赋值的动作,把和通知相关的方法都传了进来。最后我们来看一下invokeAdviceMethodWithGivenArgs这个方法的内容: protected Object invokeAdviceMethodWithGivenArgs(Object[] args) throws Throwable { Object[] actualArgs = args; //判断通知方法是否有参数 if (this.aspectJAdviceMethod.getParameterTypes().length == 0) { actualArgs = null; } try { ReflectionUtils.makeAccessible(this.aspectJAdviceMethod); // TODO AopUtils.invokeJoinpointUsingReflection //反射调用通知方法 //this.aspectInstanceFactory.getAspectInstance()获取的是切面的实例 return this.aspectJAdviceMethod.invoke(this.aspectInstanceFactory.getAspectInstance(), actualArgs); } catch (IllegalArgumentException ex) { throw new AopInvocationException("Mismatch on arguments to advice method [" + this.aspectJAdviceMethod + "]; pointcut expression [" + this.pointcut.getPointcutExpression() + "]", ex); } catch (InvocationTargetException ex) { throw ex.getTargetException(); } }
我们在上一篇文章中简单的说了调用动态代理对象方法的过程,也说了AOP拦截器执行链的生成过程。我们接着说AOP对目标对象的拦截过程。下面的代码是我们要分析的重点: //proxy:生成的动态代理对象 //target:目标对象 //method:目标方法 //args:目标方法参数 //targetClass:目标类对象 //chain: AOP拦截器执行链 是一个MethodInterceptor的集合 这个链条的获取过程参考我们上一篇文章的内容 invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain); //开始执行AOP的拦截过程 retVal = invocation.proceed(); //构造ReflectiveMethodInvocation对象 protected ReflectiveMethodInvocation( Object proxy, Object target, Method method, Object[] arguments, Class<?> targetClass, List<Object> interceptorsAndDynamicMethodMatchers) { this.proxy = proxy; this.target = target; this.targetClass = targetClass; //桥接方法 this.method = BridgeMethodResolver.findBridgedMethod(method); //转换参数 this.arguments = AopProxyUtils.adaptArgumentsIfNecessary(method, arguments); this.interceptorsAndDynamicMethodMatchers = interceptorsAndDynamicMethodMatchers; } public Object proceed() throws Throwable { // currentInterceptorIndex初始值为 -1 // 如果执行到链条的末尾 则直接调用连接点方法 即 直接调用目标方法 if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) { //下面会分析 return invokeJoinpoint(); } //获取集合中的 MethodInterceptor Object interceptorOrInterceptionAdvice = this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex); //如果是InterceptorAndDynamicMethodMatcher类型 if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) { InterceptorAndDynamicMethodMatcher dm = (InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice; //这里每一次都去匹配是否适用于这个目标方法 if (dm.methodMatcher.matches(this.method, this.targetClass, this.arguments)) { //如果匹配则直接调用 MethodInterceptor的invoke方法 //注意这里传入的参数是this 我们下面看一下 ReflectiveMethodInvocation的类型 return dm.interceptor.invoke(this); } else { //如果不适用于此目标方法 则继续执行下一个链条 //递归调用 return proceed(); } } else { //说明是适用于此目标方法的 直接调用 MethodInterceptor的invoke方法 传入this即ReflectiveMethodInvocation实例 //传入this进入 这样就可以形成一个调用的链条了 return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this); } } invokeJoinpoint方法 protected Object invokeJoinpoint() throws Throwable { //this.target 目标对象 //this.method 目标方法 this.arguments 目标方法参数信息 return AopUtils.invokeJoinpointUsingReflection(this.target, this.method, this.arguments); } public static Object invokeJoinpointUsingReflection(Object target, Method method, Object[] args) throws Throwable { // Use reflection to invoke the method. try { //设置方法可见性 ReflectionUtils.makeAccessible(method); //反射调用 最终是通过反射去调用目标方法 return method.invoke(target, args); } catch (InvocationTargetException ex) { // Invoked method threw a checked exception. // We must rethrow it. The client won't see the interceptor. throw ex.getTargetException(); } catch (IllegalArgumentException ex) { throw new AopInvocationException("AOP configuration seems to be invalid: tried calling method [" + method + "] on target [" + target + "]", ex); } catch (IllegalAccessException ex) { throw new AopInvocationException("Could not access method [" + method + "]", ex); } } ReflectiveMethodInvocation的UML类图如下:MethodInterceptorChainOK,我们在proceed()这个方法中看到了AOP对于目标方法的一个拦截的过程,其中很重要的一个点是调用MethodInterceptor的invoke方法。我们先看一下MethodInterceptor的主要UML类图(由于我们在开发中使用AspectJ注解的方式越来越多,所以我们这里说的基本上都是基于AspectJ注解的):从上图我们也可以看到不同的通知其实相当于不同的MethodInterceptor类型。像前置通知会交给:MethodBeforeAdviceInterceptor来进行处理,后置通知是由AspectJAfterAdvice来处理的,环绕通知是由AspectJAroundAdvice来处理的。我们也挑几个通知类型来说一下具体的调用过程。先说一下前置通知:MethodBeforeAdviceInterceptor //实现了MethodInterceptor接口 public class MethodBeforeAdviceInterceptor implements MethodInterceptor, Serializable { //这个对象的获取参考这个方法org.springframework.aop.aspectj.annotation.ReflectiveAspectJAdvisorFactory#getAdvice //在之前的文章中有说过 不再描述 //这个MethodBeforeAdvice是AspectJMethodBeforeAdvice实例 private MethodBeforeAdvice advice; public MethodBeforeAdviceInterceptor(MethodBeforeAdvice advice) { Assert.notNull(advice, "Advice must not be null"); this.advice = advice; } @Override public Object invoke(MethodInvocation mi) throws Throwable { //这里就会执行 前置通知的逻辑 这里的advice是 AspectJMethodBeforeAdvice this.advice.before(mi.getMethod(), mi.getArguments(), mi.getThis() ); //这里传入的MethodInvocation是ReflectiveMethodInvocation对象,即前面说的 传入this //相当于ReflectiveMethodInvocation.proceed() 递归调用。 return mi.proceed(); } } AspectJMethodBeforeAdvice#before代码如下: @Override public void before(Method method, Object[] args, Object target) throws Throwable { //这里传进来的目标对象、目标参数、目标方法都没有用到 invokeAdviceMethod(getJoinPointMatch(), null, null); } protected JoinPointMatch getJoinPointMatch() { //这里从线程上下文中获取MethodInvocation 看到这里你也许会感到奇怪 跟着文章分析来看 我们没有在设置过上下文的值啊 //这里是怎么获取到MethodInvocation 的对象的呢? //不知道你是否还记得 我们在获取Advisor的时候 调用过这样的一个方法org.springframework.aop.aspectj.AspectJProxyUtils#makeAdvisorChainAspectJCapableIfNecessary //在这个方法中会有这样的一段代码 // if (foundAspectJAdvice && //!advisors.contains(ExposeInvocationInterceptor.ADVISOR)) { // 如果获取到了 Advisor 则向 Advisor集合中添加第一个元素 即 ExposeInvocationInterceptor.ADVISOR //也就是说 我们的 Advisor列表中的第一个元素为ExposeInvocationInterceptor.ADVISOR 它是一个DefaultPointcutAdvisor的实例 // 对于任何的目标方法都返回true 它的Advice是ExposeInvocationInterceptor // advisors.add(0, ExposeInvocationInterceptor.ADVISOR); // return true; // } // 这样我们就不难理解了,在调用ReflectiveMethodInvocation#proceed的时候第一个调用的MethodInterceptor是ExposeInvocationInterceptor //ExposeInvocationInterceptor的invoke方法的内容如下: // public Object invoke(MethodInvocation mi) throws Throwable { // 先取出旧的MethodInvocation的值 // MethodInvocation oldInvocation = invocation.get(); // 这里设置新的 MethodInvocation 就是这里了!!! // invocation.set(mi); // try { // 递归调用 // return mi.proceed(); /// } // finally { // invocation.set(oldInvocation); // } // } MethodInvocation mi = ExposeInvocationInterceptor.currentInvocation(); if (!(mi instanceof ProxyMethodInvocation)) { throw new IllegalStateException("MethodInvocation is not a Spring ProxyMethodInvocation: " + mi); } //这里主要是获取 JoinPointMatch return getJoinPointMatch((ProxyMethodInvocation) mi); } 关于invokeAdviceMethod方法的内容我们在下一篇文章中继续分析
我们在上一篇文章中简单的说了一下SpringAOP使用JDK动态代理生成目标对象的过程,我们在这一篇文章中说一下SpringAOP对生成的动态代理对象的方法的拦截过程(即SpringAOP拦截过程),这个分析的过程可能会比较长。在上一篇文章中我们说的使用JDK创建动态代理对象是用的JdkDynamicAopProxy这个类,这个类同时实现了InvocationHandler这个接口,实现了它的invoke方法,熟悉JDK动态代理的同学都知道,当我们调用动态代理对象的方法的时候,会进入到生成代理对象时所传入的InvocationHandler实现类的invoke方法中,在这里也就是指JdkDynamicAopProxy的invoke方法,我们进入到这个invoke方法中看一下: @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { MethodInvocation invocation; Object oldProxy = null; boolean setProxyContext = false; //目标对象 TargetSource targetSource = this.advised.targetSource; Class<?> targetClass = null; Object target = null; try { //接口中没有定义 equals方法(这个方法定义形式和Object中保持一致 ),并且调用的方法是equals方法(即Object中定义的equals方法) if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) { //调用 JdkDynamicAopProxy 中写的equals方法 return equals(args[0]); }else if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) { //和上面的分析一样 return hashCode(); }else if (method.getDeclaringClass() == DecoratingProxy.class) { //没用过 留作以后再说 return AopProxyUtils.ultimateTargetClass(this.advised); }else if (!this.advised.opaque && method.getDeclaringClass().isInterface() && method.getDeclaringClass().isAssignableFrom(Advised.class)) { //如果 方法所在的类是接口 并且是Advised的子类,则直接调用下面的方法,这个方法在下面分析 return AopUtils.invokeJoinpointUsingReflection(this.advised, method, args); } Object retVal; //是否对外暴露代理对象 if (this.advised.exposeProxy) { // Make invocation available if necessary. //把之前创建的代理对象放到线程上下文中 //oldProxy为之前线程上下文中的对象 oldProxy = AopContext.setCurrentProxy(proxy); setProxyContext = true; } //从TargetSource中获取目标对象 target = targetSource.getTarget(); if (target != null) { targetClass = target.getClass(); } //从Advised中根据方法名和目标类获取 AOP拦截器执行链 重点要分析的内容 List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass); //如果这个执行链为空的话 if (chain.isEmpty()) { //直接进行方法调用 Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args); retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse); } else { //如果AOP拦截器执行链不为空 说明有AOP通知存在 invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain); //开始调用 retVal = invocation.proceed(); } //方法的返回值类型 Class<?> returnType = method.getReturnType(); if (retVal != null && retVal == target && returnType.isInstance(proxy) && !RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) { //return this retVal = proxy; } //返回值类型错误 else if (retVal == null && returnType != Void.TYPE && returnType.isPrimitive()) { throw new AopInvocationException( "Null return value from advice does not match primitive return type for: " + method); } return retVal; } finally { //如果目标对象不为空 且目标对象是可变的 如prototype类型 //通常我们的目标对象都是单例的 即targetSource.isStatic为true if (target != null && !targetSource.isStatic()) { //释放目标对象 targetSource.releaseTarget(target); } if (setProxyContext) { //线程上下文复位 AopContext.setCurrentProxy(oldProxy); } } } 在上面的方法中大致了说明了一下整个代理对象方法调用的执行过程,像equals和hashcode方法的调用,Advised子类的调用。重点就是在连接点处执行不同的通知类型的调用,即获取AOP拦截执行链的调用。下面我们要分析的就是这个过程:this.advised.getInterceptorsAndDynamicInterceptionAdvice。我们这里的advised是一个AdvisedSupport类型的实例,它可能是ProxyFactory的实例也可能是AspectJProxyFactory实例。我们进入到getInterceptorsAndDynamicInterceptionAdvice这个方法中去看一下: public List<Object> getInterceptorsAndDynamicInterceptionAdvice(Method method, Class<?> targetClass) { //创建一个method的缓存对象 在MethodCacheKey中实现了equals和hashcode方法同时还实现了compareTo方法 MethodCacheKey cacheKey = new MethodCacheKey(method); List<Object> cached = this.methodCache.get(cacheKey); //先从缓存中获取 如果缓存中获取不到 则再调用方法获取,获取之后放入到缓存中 if (cached == null) { //调用的是advisorChainFactory的getInterceptorsAndDynamicInterceptionAdvice方法 cached = this.advisorChainFactory.getInterceptorsAndDynamicInterceptionAdvice( this, method, targetClass); this.methodCache.put(cacheKey, cached); } return cached; } 上面的方法的调用过程是先从缓存中获取,缓存中获取不到的话,再交给AdvisorChainFactory,通过调用AdvisorChainFactory中的getInterceptorsAndDynamicInterceptionAdvice方法来获取拦截器执行链,放入到缓存中。AdvisorChainFactory在SpringAOP中只有一个默认的实现类:DefaultAdvisorChainFactory,所以我们去这个DefaultAdvisorChainFactory类中看一下这个方法的内容。 //在这个方法中传入了三个实例,一个是Advised的实例 一个是目标方法 一个是目标类可能为null //想想我们在前面的文章中说过的,在Advised中都有什么内容 @Override public List<Object> getInterceptorsAndDynamicInterceptionAdvice( Advised config, Method method, Class<?> targetClass) { //创建一个初始大小为 之前获取到的 通知个数的 集合 List<Object> interceptorList = new ArrayList<Object>(config.getAdvisors().length); //如果目标类为null的话,则从方法签名中获取目标类 Class<?> actualClass = (targetClass != null ? targetClass : method.getDeclaringClass()); //判断目标类是否存在引介增强 通常为false boolean hasIntroductions = hasMatchingIntroductions(config, actualClass); //这里用了一个单例模式 获取DefaultAdvisorAdapterRegistry实例 //在Spring中把每一个功能都分的很细,每个功能都会有相应的类去处理 符合单一职责原则的地方很多 这也是值得我们借鉴的一个地方 //AdvisorAdapterRegistry这个类的主要作用是将Advice适配为Advisor 将Advisor适配为对应的MethodInterceptor 我们在下面说明 AdvisorAdapterRegistry registry = GlobalAdvisorAdapterRegistry.getInstance(); //循环 目标方法匹配的 通知 for (Advisor advisor : config.getAdvisors()) { //如果是PointcutAdvisor类型的实例 我们大多数的Advisor都是PointcutAdvisor类型的 if (advisor instanceof PointcutAdvisor) { PointcutAdvisor pointcutAdvisor = (PointcutAdvisor) advisor; //如果提前进行过 切点的匹配了 或者当前的Advisor适用于目标类 if (config.isPreFiltered() || pointcutAdvisor.getPointcut().getClassFilter().matches(actualClass)) { //将Advisor适配为MethodInterceptor MethodInterceptor[] interceptors = registry.getInterceptors(advisor); MethodMatcher mm = pointcutAdvisor.getPointcut().getMethodMatcher(); //检测Advisor是否适用于此目标方法 if (MethodMatchers.matches(mm, method, actualClass, hasIntroductions)) { //MethodMatcher中的切点分为两种 一个是静态的 一种是动态的 //如果isRuntime返回true 则是动态的切入点 每次方法的调用都要去进行匹配 //而静态切入点则回缓存之前的匹配结果值 if (mm.isRuntime()) { //动态切入点 则会创建一个InterceptorAndDynamicMethodMatcher对象 //这个对象包含MethodInterceptor和MethodMatcher 的实例 for (MethodInterceptor interceptor : interceptors) { interceptorList.add(new InterceptorAndDynamicMethodMatcher(interceptor, mm)); } } else { //添加到列表中 interceptorList.addAll(Arrays.asList(interceptors)); } } } } //如果是引介增强 else if (advisor instanceof IntroductionAdvisor) { IntroductionAdvisor ia = (IntroductionAdvisor) advisor; if (config.isPreFiltered() || ia.getClassFilter().matches(actualClass)) { //将Advisor转换为Interceptor Interceptor[] interceptors = registry.getInterceptors(advisor); interceptorList.addAll(Arrays.asList(interceptors)); } } //以上两种都不是 else { //将Advisor转换为Interceptor Interceptor[] interceptors = registry.getInterceptors(advisor); interceptorList.addAll(Arrays.asList(interceptors)); } } return interceptorList; } 在上面这个方法中主要干了这几件事:1、循环目标方法的所有Advisor2、判断Advisor的类型如果是PointcutAdvisor的类型,则判断此Advisor是否适用于此目标方法如果是IntroductionAdvisor引介增强类型,则判断此Advisor是否适用于此目标方法如果以上都不是,则直接转换为Interceptor类型。在上面的三个步骤中都干了这样的一件事,将Advisor转换为Interceptor类型。这里用到了一个很重要的一个类:DefaultAdvisorAdapterRegistry。从类名我们可以看出这是一个Advisor的适配器注册类。它的源码如下: public class DefaultAdvisorAdapterRegistry implements AdvisorAdapterRegistry, Serializable { //初始化了一个size为3的集合 因为下面就添加了三个AdvisorAdapter private final List<AdvisorAdapter> adapters = new ArrayList<AdvisorAdapter>(3); /** * Create a new DefaultAdvisorAdapterRegistry, registering well-known adapters. */ public DefaultAdvisorAdapterRegistry() { //在SpringAOP中只默认提供了这三种通知类型的适配器 //为什么没有其他通知类型的呢?参考AbstractAspectJAdvice下面的几个通知类型 //前置通知适配器 registerAdvisorAdapter(new MethodBeforeAdviceAdapter()); //后置返回通知适配器 registerAdvisorAdapter(new AfterReturningAdviceAdapter()); //后置异常通知适配器 registerAdvisorAdapter(new ThrowsAdviceAdapter()); } //这个方法的作用主要是将Advice转换为Advisor的 @Override public Advisor wrap(Object adviceObject) throws UnknownAdviceTypeException { //如果传入的实例是Advisor 则直接返回 if (adviceObject instanceof Advisor) { return (Advisor) adviceObject; } //如果传入的实例不是 Advice类型 则直接抛出异常 if (!(adviceObject instanceof Advice)) { throw new UnknownAdviceTypeException(adviceObject); } Advice advice = (Advice) adviceObject; //如果这个Advice是MethodInterceptor类型的实例,则直接包装为DefaultPointcutAdvisor //DefaultPointcutAdvisor中的Pointcut为Pointcut.TRUE matches始终返回true if (advice instanceof MethodInterceptor) { return new DefaultPointcutAdvisor(advice); } //如果不是Advisor的实例 也不是MethodInterceptor类型的实例 //看看是不是 上面的那种通知类型适配器所支持的类型 for (AdvisorAdapter adapter : this.adapters) { // Check that it is supported. if (adapter.supportsAdvice(advice)) { return new DefaultPointcutAdvisor(advice); } } throw new UnknownAdviceTypeException(advice); } //这个方法是将 Advisor转换为 MethodInterceptor @Override public MethodInterceptor[] getInterceptors(Advisor advisor) throws UnknownAdviceTypeException { List<MethodInterceptor> interceptors = new ArrayList<MethodInterceptor>(3); //从Advisor中获取 Advice Advice advice = advisor.getAdvice(); if (advice instanceof MethodInterceptor) { interceptors.add((MethodInterceptor) advice); } for (AdvisorAdapter adapter : this.adapters) { if (adapter.supportsAdvice(advice)) { //转换为对应的 MethodInterceptor类型 //AfterReturningAdviceInterceptor MethodBeforeAdviceInterceptor ThrowsAdviceInterceptor interceptors.add(adapter.getInterceptor(advisor)); } } if (interceptors.isEmpty()) { throw new UnknownAdviceTypeException(advisor.getAdvice()); } return interceptors.toArray(new MethodInterceptor[interceptors.size()]); } //新增的 Advisor适配器 @Override public void registerAdvisorAdapter(AdvisorAdapter adapter) { this.adapters.add(adapter); } } 所以this.advised.getInterceptorsAndDynamicInterceptionAdvice这个方法获取的是目标方法的AOP拦截器执行链。
我们在之前的文章中说了Advisor的创建过程,Advice的创建过程以及为目标类挑选合适的Advisor的过程。通过之前的分析我们知道,SpringAOP将切面类中的通知方法都封装成了一个个的Advisor,这样就统一了拦截方法的调用过程。我们在这一篇文章中说一下SpringAOP中代理对象的创建过程。先看下面的一张图:在SpringAOP中提供了两种创建代理对象的方式,一种是JDK自带的方式创建代理对象,另一种是使用Cglib的方式创建代理对象。所以在SpringAOP中抽象了一个AopProxy接口,这个接口有两个实现类:JDKDynamicAopProxy和CglibAopProxy。从名字我们应该能看出来这两个类的作用了吧。在为目标类创建代理对象的时候,根据我们的目标类型和AOP的配置信息选择不同的创建代理对象的方式。在SpringAOP中创建代理对象没有直接依赖AopProxy,而是又抽象了一个AopProxyFactory的接口,通过这个接口(工厂模式)来创建代理对象。下面我们来看看具体的创建过程。在开篇的文章中我们写了这样的一段代码来获取代理对象: //aspectJProxyFactory是AspectJProxyFactory实例 AspectJService proxyService = aspectJProxyFactory.getProxy(); //从这段代码中我们可以看到这里又调用了createAopProxy()的方法 //通过调用createAopProxy()生成的对象调用getProxy()方法生成代理对象 public <T> T getProxy() { return (T) createAopProxy().getProxy(); } //这是一个同步方法 ProxyCreatorSupport#createAopProxy protected final synchronized AopProxy createAopProxy() { if (!this.active) { //这里会监听调用AdvisedSupportListener实现类的activated方法 activate(); } //获取AopProxyFactory //调用createAopProxy的时候传入了this对象 return getAopProxyFactory().createAopProxy(this); } //在SpringAOP中 AopProxyFactory只有一个实现类,这个实现类就是DefaultAopProxyFactory public AopProxyFactory getAopProxyFactory() { return this.aopProxyFactory; } 我们先看看一个关于Advised的UML类图:从上面的图中我们可以看到AdvisedSupport继承了ProxyConfig类实现了Advised接口。如果你去翻看这两个类的代码的话,会发现在Advised中定义了一些列的方法,而在ProxyConfig中是对这些接口方法的一个实现,但是Advised和ProxyConfig却是互相独立的两个类。但是SpringAOP通过AdvisedSupport将他们适配到了一起。AdvisedSupport只有一个子类,这个子类就是ProxyCreatorSupport。从这两个类的名字我们可以看到他们的作用:一个是为Advised提供支持的类,一个是为代理对象的创建提供支持的类。还记得在Advised中有什么内容吗?我们去DefaultAopProxyFactory中看一下调用createAopProxy(this)这个方法的时候发生了什么: //注意 这里传入了一个参数 AdvisedSupport public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException { //这段代码用来判断选择哪种创建代理对象的方式 //config.isOptimize() 是否对代理类的生成使用策略优化 其作用是和isProxyTargetClass是一样的 默认为false //config.isProxyTargetClass() 是否使用Cglib的方式创建代理对象 默认为false //hasNoUserSuppliedProxyInterfaces目标类是否有接口存在 且只有一个接口的时候接口类型不是 //SpringProxy类型 if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) { //上面的三个方法有一个为true的话,则进入到这里 //从AdvisedSupport中获取目标类 类对象 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."); } //判断目标类是否是接口 如果目标类是接口的话,则还是使用JDK的方式生成代理对象 //如果目标类是Proxy类型 则还是使用JDK的方式生成代理对象 if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) { return new JdkDynamicAopProxy(config); } //配置了使用Cglib进行动态代理 或者目标类没有接口 那么使用Cglib的方式创建代理对象 return new ObjenesisCglibAopProxy(config); } else { //上面的三个方法没有一个为true 那使用JDK的提供的代理方式生成代理对象 return new JdkDynamicAopProxy(config); } } 我们先看JdkDynamicAopProxy生成代理对象的方法。在上面的代理中创建JdkDynamicAopProxy对象的时候,传入了AdvisedSupport对象。 public JdkDynamicAopProxy(AdvisedSupport config) throws AopConfigException { Assert.notNull(config, "AdvisedSupport must not be null"); //如果不存在Advisor或者目标对象为空的话 抛出异常 if (config.getAdvisors().length == 0 && config.getTargetSource() == AdvisedSupport.EMPTY_TARGET_SOURCE) { throw new AopConfigException("No advisors and no TargetSource specified"); } this.advised = config; } //这两个方法的区别是否传入类加载器 @Override public Object getProxy() { //使用默认的类加载器 return getProxy(ClassUtils.getDefaultClassLoader()); } @Override public Object getProxy(ClassLoader classLoader) { if (logger.isDebugEnabled()) { logger.debug("Creating JDK dynamic proxy: target source is " + this.advised.getTargetSource()); } //获取AdvisedSupport类型对象的所有接口 Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true); //接口是否定义了 equals和hashcode方法 正常是没有的 findDefinedEqualsAndHashCodeMethods(proxiedInterfaces); //创建代理对象 this是JdkDynamicAopProxy //JdkDynamicAopProxy 同时实现了InvocationHandler 接口 //这里我们生成的代理对象可以向上造型为 任意 proxiedInterfaces 中的类型 return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this); } //AopProxyUtils#completeProxiedInterfaces //这个方法主要是获取目标类上的接口 并且判断是否需要添加SpringProxy Advised DecoratingProxy 接口 static Class<?>[] completeProxiedInterfaces(AdvisedSupport advised, boolean decoratingProxy) { //获取AdvisedSupport 类型中 目标类的接口 Class<?>[] specifiedInterfaces = advised.getProxiedInterfaces(); //如果目标类没有实现接口的话, if (specifiedInterfaces.length == 0) { // No user-specified interfaces: check whether target class is an interface. //获取目标类 Class<?> targetClass = advised.getTargetClass(); if (targetClass != null) { //如果目标类是接口 则把目标类添加到 AdvisedSupport的接口集合中 if (targetClass.isInterface()) { advised.setInterfaces(targetClass); } //如果是Proxy类型 else if (Proxy.isProxyClass(targetClass)) { advised.setInterfaces(targetClass.getInterfaces()); } //重新获取接口 specifiedInterfaces = advised.getProxiedInterfaces(); } } //接口中有没有 SpringProxy类型的接口 //是否需要添加 SpringProxy接口 boolean addSpringProxy = !advised.isInterfaceProxied(SpringProxy.class); //isOpaque 代表生成的代理是否避免转化为Advised类型 默认为false 如果目标类没有实现 Advised接口 //是否需要添加 Advised接口 boolean addAdvised = !advised.isOpaque() && !advised.isInterfaceProxied(Advised.class); //是否需要添加 DecoratingProxy接口 boolean addDecoratingProxy = (decoratingProxy && !advised.isInterfaceProxied(DecoratingProxy.class)); int nonUserIfcCount = 0; if (addSpringProxy) { nonUserIfcCount++; } if (addAdvised) { nonUserIfcCount++; } if (addDecoratingProxy) { nonUserIfcCount++; } Class<?>[] proxiedInterfaces = new Class<?>[specifiedInterfaces.length + nonUserIfcCount]; //扩展接口数组 System.arraycopy(specifiedInterfaces, 0, proxiedInterfaces, 0, specifiedInterfaces.length); int index = specifiedInterfaces.length; if (addSpringProxy) { //为目标对象接口中添加 SpringProxy接口 proxiedInterfaces[index] = SpringProxy.class; index++; } if (addAdvised) { //为目标对象接口中添加 Advised接口 proxiedInterfaces[index] = Advised.class; index++; } if (addDecoratingProxy) { //为目标对象接口中添加 DecoratingProxy接口 proxiedInterfaces[index] = DecoratingProxy.class; } return proxiedInterfaces; }
我们在之前的文章中分析了Advisor的生成过程以及在Advisor中生成Advise的过程。在这一篇文章中我们说一下为目标类挑选合适的Advisor的过程。通过之前的分析我们知道,一个切面类可以生成多个Advisor(多个切面类的话那就更多多的Advisor了),这些Advisor是否都能适用于我们的目标类呢?这就需要通过Advisor中所拥有的Pointcut来进行判断了。先回到我们最开始的例子: //手工创建一个实例 AspectJService aspectJService = new AspectJServiceImpl(); //使用AspectJ语法 自动创建代理对象 AspectJProxyFactory aspectJProxyFactory = new AspectJProxyFactory(aspectJService); 我们在我们的AspectJProxyFactory中传入了我们的目标对象。我们再回到AspectJProxyFactory的addAdvisorsFromAspectInstanceFactory方法中。 private void addAdvisorsFromAspectInstanceFactory(MetadataAwareAspectInstanceFactory instanceFactory) { //获取Advisor的过程我们在之前分析了 List<Advisor> advisors = this.aspectFactory.getAdvisors(instanceFactory); //这句代码的意思是为我们的目标类挑选合适的Advisor也是我们这一次要分析的内容 advisors = AopUtils.findAdvisorsThatCanApply(advisors, getTargetClass()); AspectJProxyUtils.makeAdvisorChainAspectJCapableIfNecessary(advisors); //为Advisor进行排序 AnnotationAwareOrderComparator.sort(advisors); addAdvisors(advisors); } 在AspectJProxyFactory中是通过调用AopUtils中的findAdvisorsThatCanApply方法来为目标类挑选合适的Advisor的或者是进判断哪些Advisor可以作用于目标类。在这个方法中传入了两个参数,一个参数是Advisor的集合,一个参数是目标类Class。我们看一下getTargetClass()这个方法的内容:AdvisedSupport#getTargetClass @Override public Class<?> getTargetClass() { //直接调用targetSource的getTargetClass方法 //其实也是相当于调用target.getClass() return this.targetSource.getTargetClass(); } OK,下面我们进入到AopUtils#findAdvisorsThatCanApply中看一下这个方法的内容 public static List<Advisor> findAdvisorsThatCanApply(List<Advisor> candidateAdvisors, Class<?> clazz) { //如果传入的Advisor集合为空的话,直接返回这个空集合 //这里没有判断candidateAdvisors不为null的情况 因为在获取Advisor的地方是先创建一个空的集合,再进行添加Advisor的动作 //不过还是加一下不为null的判断更好一点 if (candidateAdvisors.isEmpty()) { return candidateAdvisors; } //创建一个合适的Advisor的集合 eligible List<Advisor> eligibleAdvisors = new LinkedList<Advisor>(); //循环所有的Advisor for (Advisor candidate : candidateAdvisors) { //如果Advisor是IntroductionAdvisor 引介增强 可以为目标类 通过AOP的方式添加一些接口实现 //引介增强是一种比较我们接触的比较少的增强 我们可以在以后的文章单独做个分析 if (candidate instanceof IntroductionAdvisor && canApply(candidate, clazz)) { eligibleAdvisors.add(candidate); } } //是否有引介增强 boolean hasIntroductions = !eligibleAdvisors.isEmpty(); for (Advisor candidate : candidateAdvisors) { //如果是IntroductionAdvisor类型的话 则直接跳过 if (candidate instanceof IntroductionAdvisor) { // already processed continue; } //判断此Advisor是否适用于target if (canApply(candidate, clazz, hasIntroductions)) { eligibleAdvisors.add(candidate); } } return eligibleAdvisors; } canApply方法的内容 public static boolean canApply(Advisor advisor, Class<?> targetClass, boolean hasIntroductions) { //如果是IntroductionAdvisor的话,则调用IntroductionAdvisor类型的实例进行类的过滤 //这里是直接调用的ClassFilter的matches方法 if (advisor instanceof IntroductionAdvisor) { return ((IntroductionAdvisor) advisor).getClassFilter().matches(targetClass); } //通常我们的Advisor都是PointcutAdvisor类型 else if (advisor instanceof PointcutAdvisor) { PointcutAdvisor pca = (PointcutAdvisor) advisor; //这里从Advisor中获取Pointcut的实现类 这里是AspectJExpressionPointcut return canApply(pca.getPointcut(), targetClass, hasIntroductions); } else { // It doesn't have a pointcut so we assume it applies. return true; } } 重载canApply方法的内容。 public static boolean canApply(Pointcut pc, Class<?> targetClass, boolean hasIntroductions) { Assert.notNull(pc, "Pointcut must not be null"); //进行切点表达式的匹配最重要的就是 ClassFilter 和 MethodMatcher这两个方法的实现。 //MethodMatcher中有两个matches方法。一个参数是只有Method对象和targetclass,另一个参数有 //Method对象和targetClass对象还有一个Method的方法参数 他们两个的区别是: //两个参数的matches是用于静态的方法匹配 三个参数的matches是在运行期动态的进行方法匹配的 //先进行ClassFilter的matches方法校验 //首先这个类要在所匹配的规则下 if (!pc.getClassFilter().matches(targetClass)) { return false; } //再进行 MethodMatcher 方法级别的校验 MethodMatcher methodMatcher = pc.getMethodMatcher(); if (methodMatcher == MethodMatcher.TRUE) { // No need to iterate the methods if we're matching any method anyway... return true; } IntroductionAwareMethodMatcher introductionAwareMethodMatcher = null; if (methodMatcher instanceof IntroductionAwareMethodMatcher) { introductionAwareMethodMatcher = (IntroductionAwareMethodMatcher) methodMatcher; } Set<Class<?>> classes = new LinkedHashSet<Class<?>>(ClassUtils.getAllInterfacesForClassAsSet(targetClass)); classes.add(targetClass); for (Class<?> clazz : classes) { Method[] methods = ReflectionUtils.getAllDeclaredMethods(clazz); //只要有一个方法能匹配到就返回true //这里就会有一个问题:因为在一个目标中可能会有多个方法存在,有的方法是满足这个切点的匹配规则的 //但是也可能有一些方法是不匹配切点规则的,这里检测的是只有一个Method满足切点规则就返回true了 //所以在运行时进行方法拦截的时候还会有一次运行时的方法切点规则匹配 for (Method method : methods) { if ((introductionAwareMethodMatcher != null && introductionAwareMethodMatcher.matches(method, targetClass, hasIntroductions)) || methodMatcher.matches(method, targetClass)) { return true; } } } return false; } 从上面的代码来看,这次我们要分析的重点就在AspectJExpressionPointcut这个类中了。在AspectJExpressPointcut中预先初始化了这些内容:你能看出来这是什么内容吗? static { //execution SUPPORTED_PRIMITIVES.add(PointcutPrimitive.EXECUTION); //args SUPPORTED_PRIMITIVES.add(PointcutPrimitive.ARGS); SUPPORTED_PRIMITIVES.add(PointcutPrimitive.REFERENCE); //this SUPPORTED_PRIMITIVES.add(PointcutPrimitive.THIS); //target SUPPORTED_PRIMITIVES.add(PointcutPrimitive.TARGET); //within SUPPORTED_PRIMITIVES.add(PointcutPrimitive.WITHIN); //@annotation SUPPORTED_PRIMITIVES.add(PointcutPrimitive.AT_ANNOTATION); SUPPORTED_PRIMITIVES.add(PointcutPrimitive.AT_WITHIN); SUPPORTED_PRIMITIVES.add(PointcutPrimitive.AT_ARGS); SUPPORTED_PRIMITIVES.add(PointcutPrimitive.AT_TARGET); } 我们来看一下这段代码,这是要从AspectJExpressPointcut中获取ClassFilter if (!pc.getClassFilter().matches(targetClass)) { return false; } public ClassFilter getClassFilter() { checkReadyToMatch(); return this; } private void checkReadyToMatch() { //如果没有expression的值的话 直接抛出异常 if (getExpression() == null) { throw new IllegalStateException("Must set property 'expression' before attempting to match"); } if (this.pointcutExpression == null) { //选择类加载器 this.pointcutClassLoader = determinePointcutClassLoader(); //构建PointcutExpression的实例 this.pointcutExpression = buildPointcutExpression(this.pointcutClassLoader); } } //下面的这一段逻辑完全就是aspectj这个jar中的内容了 很复杂不多说了。。。。 private PointcutExpression buildPointcutExpression(ClassLoader classLoader) { //初始化一个PointcutParser的实例 PointcutParser aspectj中提供的类 PointcutParser parser = initializePointcutParser(classLoader); PointcutParameter[] pointcutParameters = new PointcutParameter[this.pointcutParameterNames.length]; for (int i = 0; i < pointcutParameters.length; i++) { pointcutParameters[i] = parser.createPointcutParameter( this.pointcutParameterNames[i], this.pointcutParameterTypes[i]); } //解析切点表达式 我们的切点表示有可能会这样写:在切面中定义一个专门的切面表达式方法 //在不同的通知类型中引入这个切点表达式的方法名 return parser.parsePointcutExpression(replaceBooleanOperators(getExpression()), this.pointcutDeclarationScope, pointcutParameters); } //这个方法 将表达式中的 and 替换为 && or 替换为 || not 替换为 ! private String replaceBooleanOperators(String pcExpr) { String result = StringUtils.replace(pcExpr, " and ", " && "); result = StringUtils.replace(result, " or ", " || "); result = StringUtils.replace(result, " not ", " ! "); return result; } 由于我们在项目开发中使用SpringAOP基本上都是用的AspectJ的注解的形式。AspectJ对于切点的匹配规则解析是一个比较复杂的过程,所以我们在使用的AspectJ中的切点表达式的时候要按照它的一个规则来进行书写。
我们在前面的文章中分析了从切面类中获取Advisor的过程,我们最后创建的Advisor实例为:InstantiationModelAwarePointcutAdvisorImpl,它是一个Advisor和PointcutAdvisor的实现类,所以我们可以从这个类中获取Advice和Pointcut。从之前的分析中我们也看到了Pointcut的赋值,在这一篇文章中我们将会具体分析Advice的创建过程。我们在上一篇文章的末尾说到了这一段代码可以实例化Advice。我们来看看这个方法的代码: this.instantiatedAdvice = instantiateAdvice(this.declaredPointcut); private Advice instantiateAdvice(AspectJExpressionPointcut pcut) { //入参为切点表达式类 //这里是通过调用aspectJAdvisorFactory来获取Advice //aspectJAdvisorFactory的实例是ReflectiveAspectJAdvisorFactory所以最终我们还是要到 //ReflectiveAspectJAdvisorFactory中去分析Advice的获取过程 //ReflectiveAspectJAdvisorFactory真是一个重要的类啊Advisor和Advice的获取都是在这个类中完成的 //入参为:通知方法、切点表达式类、切面实例、切面的一个顺序、切面类名 //倒腾了倒腾去基本上还是这几个参数 return this.aspectJAdvisorFactory.getAdvice(this.aspectJAdviceMethod, pcut, this.aspectInstanceFactory, this.declarationOrder, this.aspectName); } ReflectiveAspectJAdvisorFactory中getAdvice方法的代码如下 public Advice getAdvice(Method candidateAdviceMethod, AspectJExpressionPointcut expressionPointcut, MetadataAwareAspectInstanceFactory aspectInstanceFactory, int declarationOrder, String aspectName) { //切面类 带有@Aspect注解的类 Class<?> candidateAspectClass = aspectInstanceFactory.getAspectMetadata().getAspectClass(); //验证切面类 不再分析了 validate(candidateAspectClass); //又来一遍 根据通知方法 获取通知注解相关信息 //在获取Advisor的方法 我们已经见过这个调用。这个在Spring的AnnotationUtils中会缓存方法的注解信息 AspectJAnnotation<?> aspectJAnnotation = AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(candidateAdviceMethod); if (aspectJAnnotation == null) { return null; } //再去校验一遍 如果不是切面类 则抛出异常 if (!isAspect(candidateAspectClass)) { throw new AopConfigException("Advice must be declared inside an aspect type: " + "Offending method '" + candidateAdviceMethod + "' in class [" + candidateAspectClass.getName() + "]"); } AbstractAspectJAdvice springAdvice; //在上一篇文章的分析中 我们说过在AspectJAnnotation中会存放通知类型 switch (aspectJAnnotation.getAnnotationType()) { //如果是前置通知,则直接创建AspectJMethodBeforeAdvice实例 //入参为:通知方法、切点表达式、切面实例 case AtBefore: springAdvice = new AspectJMethodBeforeAdvice( candidateAdviceMethod, expressionPointcut, aspectInstanceFactory); break; //如果是后置通知,则直接创建AspectJAfterAdvice实例 //入参为:通知方法、切点表达式、切面实例 case AtAfter: springAdvice = new AspectJAfterAdvice( candidateAdviceMethod, expressionPointcut, aspectInstanceFactory); break; //如果是后置返回通知,则直接创建AspectJAfterReturningAdvice实例 //入参为:通知方法、切点表达式、切面实例 case AtAfterReturning: springAdvice = new AspectJAfterReturningAdvice( candidateAdviceMethod, expressionPointcut, aspectInstanceFactory); AfterReturning afterReturningAnnotation = (AfterReturning) aspectJAnnotation.getAnnotation(); //设置后置返回值的参数name if (StringUtils.hasText(afterReturningAnnotation.returning())) { springAdvice.setReturningName(afterReturningAnnotation.returning()); } break; //如果是后置异常通知,则直接创建AspectJAfterThrowingAdvice实例 //入参为:通知方法、切点表达式、切面实例 case AtAfterThrowing: springAdvice = new AspectJAfterThrowingAdvice( candidateAdviceMethod, expressionPointcut, aspectInstanceFactory); AfterThrowing afterThrowingAnnotation = (AfterThrowing) aspectJAnnotation.getAnnotation(); //设置后置异常通知 异常类型参数name if (StringUtils.hasText(afterThrowingAnnotation.throwing())) { springAdvice.setThrowingName(afterThrowingAnnotation.throwing()); } break; //如果是后置异常通知,则直接创建AspectJAfterThrowingAdvice实例 //入参为:通知方法、切点表达式、切面实例 case AtAround: springAdvice = new AspectJAroundAdvice( candidateAdviceMethod, expressionPointcut, aspectInstanceFactory); break; //如果是切点方法,则什么也不做 case AtPointcut: if (logger.isDebugEnabled()) { logger.debug("Processing pointcut '" + candidateAdviceMethod.getName() + "'"); } return null; //上面的那几种情况都不是的话,则抛出异常 default: throw new UnsupportedOperationException( "Unsupported advice type on method: " + candidateAdviceMethod); } // Now to configure the advice... //切面的名字 springAdvice.setAspectName(aspectName); springAdvice.setDeclarationOrder(declarationOrder); //通知注解中的参数名 String[] argNames = this.parameterNameDiscoverer.getParameterNames(candidateAdviceMethod); if (argNames != null) { springAdvice.setArgumentNamesFromStringArray(argNames); } //参数绑定 springAdvice.calculateArgumentBindings(); return springAdvice; } 上面即是获取Advice的过程。我们简单的看一下calculateArgumentBindings这个方法做了什么事:calculateArgumentBindings public synchronized final void calculateArgumentBindings() { //如果已经进行过参数绑定了 或者通知方法中没有参数 if (this.argumentsIntrospected || this.parameterTypes.length == 0) { return; } int numUnboundArgs = this.parameterTypes.length; //通知方法参数类型 Class<?>[] parameterTypes = this.aspectJAdviceMethod.getParameterTypes(); //如果第一个参数是JoinPoint或者ProceedingJoinPoint if (maybeBindJoinPoint(parameterTypes[0]) || //这个方法中还有一个校验 即只有在环绕通知中第一个参数类型才能是ProceedingJoinPoint maybeBindProceedingJoinPoint(parameterTypes[0])) { numUnboundArgs--; } //如果第一个参数是JoinPoint.StaticPart else if (maybeBindJoinPointStaticPart(parameterTypes[0])) { numUnboundArgs--; } if (numUnboundArgs > 0) { //进行参数绑定 绑定过程略复杂 //常见的场景是我们使用 后置返回通知和后置异常通知的时候 需要指定 returning和throwing的值 bindArgumentsByName(numUnboundArgs); } this.argumentsIntrospected = true; } 通过前面的分析我们可以了解到一个切面中的通知方法会生成一个Advisor实例(如InstantiationModelAwarePointcutAdvisorImpl,其实这个也是我们在SpringAOP中最常用的一个Advisor实现类),在生成这个Advisor实例的过程中会创建一个相应的Advice实例! 一个通知方法---->一个Advisor(包含Pointcut)------>一个Advice!PS:这里只是一个生成Advice的地方,在其他的地方也会生成Advice,我们在以后再分析。
我们在这一章中继续上一章的分析。我们在上一章中说到了获取到了切面类中所有不带@Pointcut注解的方法,我们看看Spring对我们获取到的这些方法进行了什么操作: for (Method method : getAdvisorMethods(aspectClass)) { //循环切面中所有不带@Pointcut注解的方法。 //method 切面中不带@Pointcut注解的方法 //lazySingletonAspectInstanceFactory中含有切面实例 是个单例 //advisors中的下标 切面的一个顺序 //aspectName 切面类的名字 //下面我们来看一下getAdvisor这个方法的内容 Advisor advisor = getAdvisor(method, lazySingletonAspectInstanceFactory, advisors.size(), aspectName); if (advisor != null) { advisors.add(advisor); } } getAdvisor public Advisor getAdvisor(Method candidateAdviceMethod, MetadataAwareAspectInstanceFactory aspectInstanceFactory, int declarationOrderInAspect, String aspectName) { //验证切面类 见我们上一章的分析 validate(aspectInstanceFactory.getAspectMetadata().getAspectClass()); //这里根据传入的方法和切面类获取 切点表达式 AspectJExpressionPointcut expressionPointcut = getPointcut( candidateAdviceMethod, aspectInstanceFactory.getAspectMetadata().getAspectClass()); //没有获取到切点表达式 直接返回null if (expressionPointcut == null) { return null; } //返回一个Advisor的实例 这个实例中包含了 一下内容 //切点表达式 AspectJExpressionPointcut //切点方法 //ReflectiveAspectJAdvisorFactory实例 //切面类实例 //切面类名字 return new InstantiationModelAwarePointcutAdvisorImpl(expressionPointcut, candidateAdviceMethod, this, aspectInstanceFactory, declarationOrderInAspect, aspectName); } 我们先来看一下AspectJExpressionPointcut这个类的UML类图:在AspectJExpressionPointcut这个类中主要实现了以下这四大接口:ClassFilter、BeanFactoryAware、MethodMatcher、Pointcut。Pointcut是SpringAOP中定义的接口,用来表示切面的抽象。BeanFactoryAware也是SpringIOC中一个常见的接口,用来设置BeanFactory实例。ClassFilter和MethodMatcher是SpringAOP中定义的进行Advisor匹配的接口。ClassFilter用来此Advisor是否使用于目标类。MethodMatcher用来匹配此Advisor是否可以作用于目标类中的目标方法。那么AspectJExpressionPointcut这个类就拥有了一下功能:从BeanFactory中获取Bean、拥有切点表达式、可以用来判断此切点表达式方法是否适用于目标类、此切点表达式方法是否适用于目标类中的方法。OK,我们继续看生成AspectJExpressionPointcut的过程。 private AspectJExpressionPointcut getPointcut(Method candidateAdviceMethod, Class<?> candidateAspectClass) { //方法上是否存在切面注解(通知类型注解) 即方法上是否有 //@Before, @Around, @After, @AfterReturning, @AfterThrowing, @Pointcut注解 //这里也提供了一种获取类上是否有我们想要的注解的一种方式 //返回一个AspectJAnnotation对象 //这里用了AnnotationUtils用来获取注解的相关信息 AspectJAnnotation<?> aspectJAnnotation = AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(candidateAdviceMethod); if (aspectJAnnotation == null) { return null; } //创建AspectJExpressionPointcut对象 AspectJExpressionPointcut ajexp = new AspectJExpressionPointcut(candidateAspectClass, new String[0], new Class<?>[0]); //设置切面表达式 在AspectJAnnotation中是可以获取到通知类型的,但是这里没有设置通知类型 ajexp.setExpression(aspectJAnnotation.getPointcutExpression()); ajexp.setBeanFactory(this.beanFactory); return ajexp; } 在这里我们要看一下AspectJAnnotation这个类。这个类是AbstractAspectJAdvisorFactory中的内部类。从这个类中可以获取切点表达式和通知类型。 protected static class AspectJAnnotation<A extends Annotation> { //切点表达式所在的属性 poincut会覆盖value的值 //其实这里指的是 通知类型注解中的属性 private static final String[] EXPRESSION_PROPERTIES = new String[] {"value", "pointcut"}; private static Map<Class<?>, AspectJAnnotationType> annotationTypes = new HashMap<Class<?>, AspectJAnnotationType>(); static { //初始化通知类型 annotationTypes.put(Pointcut.class,AspectJAnnotationType.AtPointcut); annotationTypes.put(After.class,AspectJAnnotationType.AtAfter); annotationTypes.put(AfterReturning.class,AspectJAnnotationType.AtAfterReturning); annotationTypes.put(AfterThrowing.class,AspectJAnnotationType.AtAfterThrowing); annotationTypes.put(Around.class,AspectJAnnotationType.AtAround); annotationTypes.put(Before.class,AspectJAnnotationType.AtBefore); } ....... public AspectJAnnotation(A annotation) { //注解信息 this.annotation = annotation; //根据注解类型获取通知类型 this.annotationType = determineAnnotationType(annotation); // We know these methods exist with the same name on each object, // but need to invoke them reflectively as there isn't a common interface. try { //从通知类型注解上面获取切点表达式 this.pointcutExpression = resolveExpression(annotation); //获取参数的名字 this.argumentNames = (String) annotation.getClass().getMethod("argNames").invoke(annotation); } catch (Exception ex) { throw new IllegalArgumentException(annotation + " cannot be an AspectJ annotation", ex); } } //从这个获取切点表达式的代码中我们可以看到 pointcut的属性会覆盖value的属性值 private String resolveExpression(A annotation) throws Exception { String expression = null; //循环上面的切点属性 for (String methodName : EXPRESSION_PROPERTIES) { Method method; try { method = annotation.getClass().getDeclaredMethod(methodName); } catch (NoSuchMethodException ex) { method = null; } if (method != null) { //获取切点表达式!!! String candidate = (String) method.invoke(annotation); if (StringUtils.hasText(candidate)) { expression = candidate; } } } return expression; } 我们把上面获取Advisor的过程总结一下:循环切面类中的所有不带@Pointcut注解的方法,接着判断切面类的方法上是否有:@Before, @Around, @After, @AfterReturning, @AfterThrowing, @Pointcut注解。如果没有的话,循环下一个方法。如果有这些注解的话,则从这些注解中获取切点表达式存放到AspectJExpressionPointcut对象中,最后将获取到的切点表达式类封装到InstantiationModelAwarePointcutAdvisorImpl这个类中。从上面的分析我们知道InstantiationModelAwarePointcutAdvisorImpl类至少拥有:切点表达式类、切面对象、带有切点表达式的方法、ReflectiveAspectJAdvisorFactory实例。那么我们最后来分析一下InstantiationModelAwarePointcutAdvisorImpl这个类。 public InstantiationModelAwarePointcutAdvisorImpl(AspectJExpressionPointcut declaredPointcut, Method aspectJAdviceMethod, AspectJAdvisorFactory aspectJAdvisorFactory, MetadataAwareAspectInstanceFactory aspectInstanceFactory, int declarationOrder, String aspectName) { //切点表达式类 AspectJExpressionPointcut this.declaredPointcut = declaredPointcut; //切面类 this.declaringClass = aspectJAdviceMethod.getDeclaringClass(); //切点表达式方法所在的方法名 这里指的是@Before、@After这些通知类型所在的方法名 this.methodName = aspectJAdviceMethod.getName(); //通知参数类型 this.parameterTypes = aspectJAdviceMethod.getParameterTypes(); //切面通知方法 this.aspectJAdviceMethod = aspectJAdviceMethod; //ReflectiveAspectJAdvisorFactory实例 this.aspectJAdvisorFactory = aspectJAdvisorFactory; //切面对象实例 this.aspectInstanceFactory = aspectInstanceFactory; //切面顺序 this.declarationOrder = declarationOrder; this.aspectName = aspectName; //根据切面元数据判断是否要延迟实例化 一般为否 if (aspectInstanceFactory.getAspectMetadata().isLazilyInstantiated()) { // Static part of the pointcut is a lazy type. Pointcut preInstantiationPointcut = Pointcuts.union( aspectInstanceFactory.getAspectMetadata().getPerClausePointcut(), this.declaredPointcut); this.pointcut = new PerTargetInstantiationModelPointcut( this.declaredPointcut, preInstantiationPointcut, aspectInstanceFactory); this.lazy = true; } else { // A singleton aspect. //切点表达式类 this.pointcut = this.declaredPointcut; this.lazy = false; //这里获取Advice实例 这里又拥有了Advice的实例!!!! //不得不说InstantiationModelAwarePointcutAdvisorImpl这个类真的是太强大了 this.instantiatedAdvice = instantiateAdvice(this.declaredPointcut); } } OK下面我们的分析就到了如果从InstantiationModelAwarePointcutAdvisorImpl中获取Advice的内容。
我们在这篇文章中接着上一篇文章的分析。我们在上一篇文章中分析了创建AspectJProxyFactory,并向AspectJProxyFactory中添加目标对象和获取目标对象的过程。我们在这一篇文章中分析调用addAspect方法添加切面的过程。在AspectJProxyFactory中有两个addAspect重载方法,一个入参是切面实例对象,一个入参是切面类对象。他们两个的区别是:传入实例对象的方法会将实例对象封装为一个单例不再进行切面对象的场景,传入切面类对象的方法需要创建切面对象实例。我们分析入参为切面类对象的方法。代码如下: public void addAspect(Class<?> aspectClass) { //全限定类名 String aspectName = aspectClass.getName(); //根据切面对象创建切面元数据类 AspectMetadata am = createAspectMetadata(aspectClass, aspectName); //根据传入的切面类创建 切面实例 将切面实例封装为切面实例工厂 MetadataAwareAspectInstanceFactory instanceFactory = createAspectInstanceFactory(am, aspectClass, aspectName); //从切面实例工厂中获取Advisor。 addAdvisorsFromAspectInstanceFactory(instanceFactory); } 上面的代码只调用了createAspectMetadata、createAspectInstanceFactory、addAdvisorsFromAspectInstanceFactory这三个方法,但是这个过程却是很复杂的。我们先看createAspectMetadata这个方法。我们先看看AspectMetadata 这个类是个什么东西。 public class AspectMetadata implements Serializable { /** * 切面的名字 可能是类的全限定类名 也可能是Spring容器中bean的名字 */ private final String aspectName; /** * 切面类 指带有切面注解的类 */ private final Class<?> aspectClass; /** * 类的类型 这个是AspectJ中定义的类 存储了aspectClass类的类相关信息 * 实现类为 AjTypeImpl */ private transient AjType<?> ajType; /** * Spring AOP 中的切点表达式 */ private final Pointcut perClausePointcut; } AspectMetadata这个类中主要存储了切面类的名字、切面类对象和AspectJ中定义的存储切面类Class对象的类以及SpringAOP中的切点表达式。createAspectMetadata方法的内容如下: private AspectMetadata createAspectMetadata(Class<?> aspectClass, String aspectName) { //直接调用 AspectMetadata的构造函数 创建对象 入参为:切面类和切面类的全限定类名 AspectMetadata am = new AspectMetadata(aspectClass, aspectName); //如果切面类不是切面则抛出异常 //这里判断我们传入的切面类是不是切面很简单,即判断切面类上是否存在@Aspect注解。 //这里判断一个类是不是切面类是这样进行判断的:如果我们传入的切面类上没有@Aspect注解的话,则去查找它的父类上 //是否存在@Aspect注解。一直查到父类为Object。如果一直没有找到带有@Aspect注解的类,则会抛出异常。 if (!am.getAjType().isAspect()) { throw new IllegalArgumentException("Class [" + aspectClass.getName() + "] is not a valid aspect type"); } return am; } AspectMetadata的构造函数: 在这个构造函数里主要是查找带有@Aspect注解的类。获取@Aspect类的PerClause类型。正常都是SINGLETON。 public AspectMetadata(Class<?> aspectClass, String aspectName) { //传入的切面类名直接赋值 this.aspectName = aspectName; Class<?> currClass = aspectClass; AjType<?> ajType = null; //这里循环查找 带有Aspect的类,一直找到父类为Object while (currClass != Object.class) { AjType<?> ajTypeToCheck = AjTypeSystem.getAjType(currClass); if (ajTypeToCheck.isAspect()) { //这里的AjType所持有的aspectClass为带有@Aspect注解的类。 //可能是我们传入的类,也可能是我们的传入类的父类 父父类。。。 ajType = ajTypeToCheck; break; } //查找父类 currClass = currClass.getSuperclass(); } //如果传入的类 没有@Aspect注解 则抛出异常 if (ajType == null) { throw new IllegalArgumentException("Class '" + aspectClass.getName() + "' is not an @AspectJ aspect"); } //这里是检查AspectJ的注解 这里一般我们也不会用到 可以忽略掉。 if (ajType.getDeclarePrecedence().length > 0) { throw new IllegalArgumentException("DeclarePrecendence not presently supported in Spring AOP"); } //带有@Aspect注解的类。 this.aspectClass = ajType.getJavaClass(); this.ajType = ajType; //正常我们的Aspect类 都是SINGLETON //其他的是AspectJ提供的一些高级的用法 我们这里先不展开 switch (this.ajType.getPerClause().getKind()) { case SINGLETON: this.perClausePointcut = Pointcut.TRUE; return; .............. 省略 } } 我们在看createAspectInstanceFactory这个方法的内容: private MetadataAwareAspectInstanceFactory createAspectInstanceFactory( AspectMetadata am, Class<?> aspectClass, String aspectName) { MetadataAwareAspectInstanceFactory instanceFactory; //前面我们分析过 我们在使用 @Aspect注解的时候 都是直接在类上添加@Aspect注解 if (am.getAjType().getPerClause().getKind() == PerClauseKind.SINGLETON) { // Create a shared aspect instance. //根据传入的切面类创建 切面对象 是一个单例 要求有无参构造函数 //这个获取 单例 切面对象的方式可以学习一下 Object instance = getSingletonAspectInstance(aspectClass); //将上一步创建的切面对象 封装到SingletonMetadataAwareAspectInstanceFactory中 //从名字我们也可以看出来 这是一个单例的带有切面元数据的切面实例工厂 instanceFactory = new SingletonMetadataAwareAspectInstanceFactory(instance, aspectName); } else { // Create a factory for independent aspect instances. //这里创建一个 SimpleMetadataAwareAspectInstanceFactory 传入切面类和切面名字 instanceFactory = new SimpleMetadataAwareAspectInstanceFactory(aspectClass, aspectName); } return instanceFactory; } 这个方法主要是创建了一个MetadataAwareAspectInstanceFactory 的子类。用来组合切面实例对象和切面元数据。面向接口编程的一个很好的体现(依赖倒转)。MetadataAwareAspectInstanceFactory有很多子类,在不同的场景下创建不同用途的实例。其UML类图如下:我们先看创建SingletonMetadataAwareAspectInstanceFactory的构造函数: public SingletonMetadataAwareAspectInstanceFactory(Object aspectInstance, String aspectName) { //将切面实例传入到父类构造函数中 super(aspectInstance); //创建切面元数据 和之前的过程一下 this.metadata = new AspectMetadata(aspectInstance.getClass(), aspectName); } addAdvisorsFromAspectInstanceFactory这个方法,应该是我们这次要分析的重点方法了,获取Advisor的逻辑都在这个方法中。其代码如下: private void addAdvisorsFromAspectInstanceFactory(MetadataAwareAspectInstanceFactory instanceFactory) { //使用ReflectiveAspectJAdvisorFactory从MetadataAwareAspectInstanceFactory中获取Advisor List<Advisor> advisors = this.aspectFactory.getAdvisors(instanceFactory); //从中挑出适用于目标对象的Advisor advisors = AopUtils.findAdvisorsThatCanApply(advisors, getTargetClass()); AspectJProxyUtils.makeAdvisorChainAspectJCapableIfNecessary(advisors); //对获取到的Advisor进行排序 AnnotationAwareOrderComparator.sort(advisors); //将获取到Advisor添加到advisors集合中 addAdvisors(advisors); } 对于上面的代码我们一步一步的分析。先从this.aspectFactory.getAdvisors这里开始。这里的aspectFactory为ReflectiveAspectJAdvisorFactory。在SpringAOP中从Aspect中获取Advisor都是使用的ReflectiveAspectJAdvisorFactory这个类。这个类是AspectJAdvisorFactory的子类,他们的关系如下:AbstractAspectJAdvisorFactory和ReflectiveAspectJAdvisorFactory中很重要的类。他们的重要性在我们后面的分析中会慢慢的体现出来。ReflectiveAspectJAdvisorFactory中的getAdvisors方法内容如下: public List<Advisor> getAdvisors(MetadataAwareAspectInstanceFactory aspectInstanceFactory) { //切面类 这个我们在上说过 是一个带有Aspect注解的类。不一定就是我们调用addAspect传入的类 可能是其父类 Class<?> aspectClass = aspectInstanceFactory.getAspectMetadata().getAspectClass(); //切面类名字 String aspectName = aspectInstanceFactory.getAspectMetadata().getAspectName(); //校验切面类 validate(aspectClass); //validate方法的内容如下 主要是校验 带有Aspect注解的类查看它的父类是否也有Aspect注解并且不是抽象类 //校验我们的切面类是否带有Aspect注解 public void validate(Class<?> aspectClass) throws AopConfigException { //如果我们的带有@Aspect注解的类的父类也带有@Aspect注解并且其还不是抽象类 则抛出异常 if (aspectClass.getSuperclass().getAnnotation(Aspect.class) != null && !Modifier.isAbstract(aspectClass.getSuperclass().getModifiers())) { throw new AopConfigException("[" + aspectClass.getName() + "] cannot extend concrete aspect [" + aspectClass.getSuperclass().getName() + "]"); } AjType<?> ajType = AjTypeSystem.getAjType(aspectClass); //再次校验 切面类是否带有 @Aspect注解 if (!ajType.isAspect()) { throw new NotAnAtAspectException(aspectClass); } //下面这两个正常开发中一般遇到不 if (ajType.getPerClause().getKind() == PerClauseKind.PERCFLOW) { throw new AopConfigException(aspectClass.getName() + " uses percflow instantiation model: " + "This is not supported in Spring AOP."); } if (ajType.getPerClause().getKind() == PerClauseKind.PERCFLOWBELOW) { throw new AopConfigException(aspectClass.getName() + " uses percflowbelow instantiation model: " + "This is not supported in Spring AOP."); } } 继续getAdvisors方法中下面的内容 //将我们上一步获取的MetadataAwareAspectInstanceFactory实例又包装为LazySingletonAspectInstanceFactoryDecorator //装饰模式的一个使用 //确保只能获取到一个切面实例 MetadataAwareAspectInstanceFactory lazySingletonAspectInstanceFactory = new LazySingletonAspectInstanceFactoryDecorator(aspectInstanceFactory); List<Advisor> advisors = new LinkedList<Advisor>(); //调用getAdvisorMethods方法来获取 切面类中 所有不包含Pointcut注解的方法。 for (Method method : getAdvisorMethods(aspectClass)) { //得到Advisor Advisor advisor = getAdvisor(method, lazySingletonAspectInstanceFactory, advisors.size(), aspectName); if (advisor != null) { advisors.add(advisor); } } //我们来看看getAdvisorMethods方法的内容 private List<Method> getAdvisorMethods(Class<?> aspectClass) { final List<Method> methods = new LinkedList<Method>(); //回调 ReflectionUtils.doWithMethods这个方法我们在Spring的代码中会经常看到 ReflectionUtils.doWithMethods(aspectClass, new ReflectionUtils.MethodCallback() { //ReflectionUtils.MethodCallback的匿名实现 @Override public void doWith(Method method) throws IllegalArgumentException { // Exclude pointcuts //不带Pointcut注解的方法 添加到methods集合中 if (AnnotationUtils.getAnnotation(method, Pointcut.class) == null) { methods.add(method); } } }); Collections.sort(methods, METHOD_COMPARATOR); return methods; } //ReflectionUtils.doWithMethods方法的内容 public static void doWithMethods(Class<?> clazz, MethodCallback mc, MethodFilter mf) { // Keep backing up the inheritance hierarchy. //获取类中所有的方法 //这里需要注意的是 这里支持JDK1.8中的新特性 可以获取到接口中的default方法 Method[] methods = getDeclaredMethods(clazz); for (Method method : methods) { //如果传入的MethodFilter 不等于null的话,则调用它的matches方法 根据匹配规则进行匹配 if (mf != null && !mf.matches(method)) { continue; } try { //回调前面我们定义的doWith方法 mc.doWith(method); } catch (IllegalAccessException ex) { throw new IllegalStateException("Not allowed to access method '" + method.getName() + "': " + ex); } } //父类中的方法 if (clazz.getSuperclass() != null) { doWithMethods(clazz.getSuperclass(), mc, mf); } //接口中的方法 else if (clazz.isInterface()) { for (Class<?> superIfc : clazz.getInterfaces()) { doWithMethods(superIfc, mc, mf); } } } 剩下的内容我们下一章继续分析。
终于要正式开始SpringAOP的分析工作了,在这之前先问个问题:关于AOP你认为最重要的是哪几个地方?我觉得有这样几个地方(理解的不对的地方欢迎指出):一个是AOP的配置(拦截规则的配置),一个是代理对象的创建,一个是统一的拦截调用过程。关于AOP的配置Spring是自己定义了一套规则同时集成了AspectJ的语法,抽象为了Pointcut类。代理对象的创建,SpringAOP定义了一个工厂类AopProxy,同时支持JDK动态代理和CGlib动态代理。统一的拦截调用过程则是使用了AOP联盟中定义的拦截过程:Advice----->Interceptor------->MethodInterceptor。但是SpringAOP在这个基础上做了一些扩展,形成了自己的一套体系。接下来先介绍一下长得很像三胞胎的三个类:Advice:SpringAOP联盟中定义的类。是一个标识性的接口。通知类型的接口。同时也是Interceptor、MethodInterceptor的父类。通知类型都有去实现的一个接口。Advisor:关联了Advice和Pointcut。在SpringAOP中是一个很关键的类。上起到了连接点的匹配下起到了通知类型的调用。统一了拦截的调用过程。Advised:关联了Advisor和TargetSource的类。也是AOP中一个很关键的类。AOP进行方法拦截的时候,就是从它里面获取的拦截调用链。一个大致的关系如下:下面我们先写一个使用SpringAOP的小例子,这里使用了AspectJ中的语法。小例子如下:先定义一个切面类和一个前置通知: @Aspect public class AopAdviceConfig { @Before("execution(* com.zkn.spring.learn.aop.program.service..*.*(..))") public void beforeAdvice(JoinPoint joinPoint) { System.out.println(joinPoint.getThis()); System.out.println("我是前置通知...."); } } //定义一个接口 public interface AspectJService { /** * 测试前置通知 */ void beforeAdvice(); /** * 测试后置通知 */ void afterAdvice(); } //实现类 public class AspectJServiceImpl implements AspectJService { @Override public void beforeAdvice() { System.out.println("测试前置通知,我是第一个Service。。。。。。"); } /** * 测试后置通知 */ @Override public void afterAdvice() { System.out.println("测试AspectJ后置通知。。。。"); } } 我们用编程的方式去进行一个AOP的拦截功能。 public class AspectJProxyFactoryLearn { public static void main(String[] args) { //手工创建一个实例 AspectJService aspectJService = new AspectJServiceImpl(); //使用AspectJ语法 自动创建代理对象 AspectJProxyFactory aspectJProxyFactory = new AspectJProxyFactory(aspectJService); //添加切面和通知类 aspectJProxyFactory.addAspect(AopAdviceConfig.class); //创建代理对象 AspectJService proxyService = aspectJProxyFactory.getProxy(); //进行方法调用 proxyService.beforeAdvice(); } } 我们看一下输出结果:从上面的输出结果来看,我们的AOP拦截功能是生效了。但是对于这样的操作你可能会很奇怪,感觉SpringAOP还能这样玩?上面的AspectJProxyFactory这个类又是什么鬼?怎么只调用了一下addAspect方法,然后调用getProxy方法生成代理对象,这样就能进行AOP拦截了?我们在下面的文章中慢慢为你解谜。在进行AspectJProxyFactory分析之前先来看一下AspectJProxyFactory的UML类图:AspectJProxyFactory的类图如上所示,我们可以看到它是Advised的一个子类。先把这个图印在脑子里。我们先来看第一段代码: AspectJProxyFactory aspectJProxyFactory = new AspectJProxyFactory(aspectJService); //对应的AspectJProxyFactory构造函数的内容 public AspectJProxyFactory(Object target) { Assert.notNull(target, "Target object must not be null"); setInterfaces(ClassUtils.getAllInterfaces(target)); setTarget(target); } 当我们调用AspectJProxyFactory的有参构造函数时,它做了这几件事,检测目标对象不能为null,设置目标对象的所有的接口,设置目标对象。获取类上的所有的接口是通过调用ClassUtils.getAllInterfaces来获取的。这个方法可以获取类上的所有接口,包括父类上的接口,但是它不能获取接口的接口。意思是如果:类A继承了类B,类B实现了接口C,接口C继承了接口D,如果传入的参数是类A,这里是可以获取到接口C,但是获取不到接口D的。 //AdvisedSupport中添加接口信息 public void setInterfaces(Class<?>... interfaces) { Assert.notNull(interfaces, "Interfaces must not be null"); //先清空原来的接口信息 是一个List this.interfaces.clear(); for (Class<?> ifc : interfaces) { addInterface(ifc); } } //AdvisedSupport中的方法 public void addInterface(Class<?> intf) { Assert.notNull(intf, "Interface must not be null"); //如果不是接口 抛出异常 if (!intf.isInterface()) { throw new IllegalArgumentException("[" + intf.getName() + "] is not an interface"); } if (!this.interfaces.contains(intf)) { this.interfaces.add(intf); adviceChanged(); } } //ProxyCreatorSupport中的方法 protected void adviceChanged() { super.adviceChanged(); synchronized (this) { if (this.active) { //给Advised的监听器发送通知 通知Advised的变化 //在Spring中没有默认的实现 for (AdvisedSupportListener listener : this.listeners) { listener.adviceChanged(this); } } } } //AdvisedSupport中的方法 protected void adviceChanged() { //清空缓存的方法信息 这里可以思考一下为什么当Interface变化的时候,会清空methodCache //Map<MethodCacheKey, List<Object>> methodCache //为什么这个类名是adviceChanged??? this.methodCache.clear(); } 设置目标对象 public void setTarget(Object target) { //注意这里是将目标对象封装为了 SingletonTargetSource 是一个单例的 //这里一定要记着 SingletonTargetSource中存放的是我们的目标对象 不是代理对象 //这里调用的是AdvisedSupport中的方法 setTargetSource这个方法是Advised中定义的方法 setTargetSource(new SingletonTargetSource(target)); } // AdvisedSupport public void setTargetSource(TargetSource targetSource) { this.targetSource = (targetSource != null ? targetSource : EMPTY_TARGET_SOURCE); } 剩下的我们在下一章分析。
在开始Spring的AOP分析之前,先来看一个很老的一个问题。。。假设你在开发的过程中,需要在类A中的方法之前执行一些逻辑(我们称为逻辑A),你可能的一个做法是直接修改类A中的方法,在类A中的方法的开始处写上要添加的代码,你还可能会给类A生成一个代理类,去对调用方法进行拦截,在代理类里面去执行相应的逻辑(逻辑A)。直接修改类A中的方法一般是我们不推荐的方式(存在改动量大、不易扩展等问题),我们通常采用的做法是为类A生成一个代理对象,在执行的时候去执行我们的代理对象。小例子如下(这里使用的JDK动态代理的方式): /** * @author zkn * @date 2018/3/18 */ public interface ProxyService { /** * 测试方法 */ void testProxy(); } /** * @author zkn * @date 2018/3/18 */ public class ProxyServiceImpl implements ProxyService { /** * 测试方法 */ @Override public void testProxy() { System.out.println("我是ProxyService中的测试方法......"); } } /** * @author zkn * @date 2018/3/18 */ public class LogicClassFir { /** * 逻辑方法A */ public void logicMethodFir() { System.out.println("我是第一个逻辑方法的内容........"); } } import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; /** * @author zkn * @date 2018/3/18 */ public class ProxyCreator implements InvocationHandler { private Object proxy; private LogicClassFir logicObj; public ProxyCreator(Object proxy, LogicClassFir logicObj) { this.proxy = proxy; this.logicObj = logicObj; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { logicObj.logicMethodFir(); return method.invoke(this.proxy, args); } } public class ProxyTest { public static void main(String[] args) { LogicClassFir logicClassFir = new LogicClassFir(); ProxyService targetService = new ProxyServiceImpl(); ProxyService proxyService = (ProxyService) Proxy.newProxyInstance(ProxyCreator.class.getClassLoader(), new Class[]{ProxyService.class}, new ProxyCreator(targetService, logicClassFir)); proxyService.testProxy(); } } 输出结果如下: 现在又来问题:我们只想对类A中的部分方法执行逻辑A,相对类A中的另一部分执行逻辑B,这个时候我们应该怎么做呢?如果我们想对类A中的方法先执行一个类B中的逻辑A,接着再执行类C中的逻辑B这个时候我们应该怎么做呢?如果我们想对类A中的方法先执行类B中的逻辑A方法,再执行类A中的方法,再接着执行类C中的逻辑B方法,这个时候我们又改怎么做呢?还有其他的各种不同的拦截需求。那么这个时候我们可能首先需要定义一种拦截的规则(什么样的方法应该被拦截、是在方法的执行前还是执行后进行拦截等),然后拦截要执行的逻辑如果在不同的类中,那么我们还需要将这些逻辑适配成一个统一的调用形式。这些内容对应到SpringAOP中是:Pointcut(切点表达式)、Advice(通知类型BeforeAdvice、AfterAdvice等)、Interceptor(统一的方法拦截MethodInterceptor等)、Invocation(组装链式调用MethodInvocation等)、AopProxy(创建代理对象JdkDynamicAopProxy、CglibAopProxy)。我们将在下面的系列文章中对这些内容进行一下分析。
在这篇文章中我将对自己了解的AOP中的基本主要类做一个概述,可能不包括一些AOP高级用法的类以及是自己还不了解的类。会不定期的进行补充和修改。 SpringAOP基础解析类 类名 作用概述 AopNamespaceHandler AOP命名空间解析类。我们在用AOP的时候,会在Spring配置文件的beans标签中引入:xmlns:aop AspectJAutoProxyBeanDefinitionParser 解析<aop:aspectj-autoproxy />标签的类。在AopNamespaceHandler中创建的类。 ConfigBeanDefinitionParser 解析<aop:config /> 标签的类。同样也是在AopNamespaceHandler中创建的类。 AopNamespaceUtils AOP命名空间解析工具类,在上面两个中被引用。 AopConfigUtils AOP配置工具类。主要是向Spring容器中注入可以生成Advisor和创建代理对象的bean AOP联盟中定义的一些类: 类名 作用概述 Advice AOP联盟中的一个标识接口。通知和Interceptor顶级类。我们说的各种通知类型都要实现这个接口。 Interceptor AOP联盟中进行方法拦截的一个标识接口。是Advice的子类。 MethodInterceptor 方法拦截器。是Interceptor的一个重要子类。主要方法:invoke。入参为:MethodInvocation ConstructorInterceptor 构造方法拦截器。是Interceptor的另一个重要的子类。在AOP联盟中是可以对构造方法进行拦截的。这样的场景我们应该很少用到。主要方法为:construct入参为ConstructorInvocation Joinpoint AOP联盟中的连接点类。主要的方法是:proceed()执行下一个拦截器。getThis()获取目标对象。 Invocation AOP拦截的执行类。是Joinpoint的子类。主要方法:getArguments()获取参数。 MethodInvocation Invocation的一个重要实现类。真正执行AOP方法的拦截。主要方法:getMethod()目标方法。 ConstructorInvocation Invocation的另一个重要实现类。执行构造方法的拦截。主要方法:getConstructor()返回构造方法。 SpringAOP中定义的类 类名 作用概述 Advisor SpringAOP中的核心类。组合了Advice。 PointcutAdvisor SpringAOP中Advisor的重要子类。组合了切点(Pointcut)和Advice。 InstantiationModelAwarePointcutAdvisorImpl PointcutAdvisor的一个重要实现子类。 DefaultPointcutAdvisor PointcutAdvisor的另一个重要实现子类。可以将Advice包装为Advisor。在SpringAOP中是以Advisor为主线。向Advice靠拢。 Pointcut SpringAOP中切点的顶级抽象类。 TruePointcut Pointcut的一个重要实现类。在DefaultPointcutAdvisor中使用的是TruePointcut。在进行切点匹配的时候永远返回true AspectJExpressionPointcut Pointcut的一个重要实现类。AspectJ语法切点类。同时实现了MethodMatcher,AspectJ语法切点的匹配在这个类中完成。 AnnotationMatchingPointcut Pointcut的一个重要实现类。注解语法的切点类。 JdkRegexpMethodPointcut Pointcut的一个重要实现类。正则语法的切点类。 MethodMatcher 切点匹配连接点的地方。即类中的某个方法和我们定义的切点表达式是否匹配、能不能被AOP拦截 TrueMethodMatcher 用于返回true AnnotationMethodMatcher 带有注解的方法的匹配器 JdkRegexpMethodPointcut 正则表达式 Advised SpringAOP中的又一个核心类。它组合了Advisor和TargetSource即目标对象 ProxyConfig SpringAOP中的一个核心类。在Advised中定义了一系列的配置接口,像:是否暴露对象、是否强制使用CGlib等。ProxyConfig是对这些接口的实现,但是ProxyConfig却不是Advised的实现类 AdvisedSupport Advised的一个实现类。SpringAOP中的一个核心类。继承了ProxyConfig实现了Advised。 ProxyCreatorSupport AdvisedSupport的子类。引用了AopProxyFactory用来创建代理对象。 ProxyFactory ProxyCreatorSupport的子类。用来创建代理对象。在SpringAOP中用的最多。 ProxyFactoryBean ProxyCreatorSupport的子类。用来创建代理对象。它实现了BeanFactoryAware、FactoryBean接口 AspectJProxyFactory ProxyCreatorSupport的子类。用来创建代理对象。使用AspectJ语法。 ProxyFactory、ProxyFactoryBean、AspectJProxyFactory这三个类的使用场景各不相同。 但都是生成Advisor和TargetSource、代理对象的关系。 ProxyProcessorSupport ProxyConfig的子类 AbstractAutoProxyCreator ProxyProcessorSupport的重要子类。SpringAOP中的核心类。实现了SmartInstantiationAwareBeanPostProcessor、BeanFactoryAware接口。自动创建代理对象的类。我们在使用AOP的时候基本上都是用的这个类来进程Bean的拦截,创建代理对象。 AbstractAdvisorAutoProxyCreator AbstractAutoProxyCreator的子类。SpringAOP中的核心类。用来创建Advisor和代理对象。 AspectJAwareAdvisorAutoProxyCreator AbstractAdvisorAutoProxyCreator的子类。使用AspectJ语法创建Advisor和代理对象。 AnnotationAwareAspectJAutoProxyCreator AspectJAwareAdvisorAutoProxyCreator的子类。使用AspectJ语法创建Advisor和代理对象的类。<aop:aspectj-autoproxy />标签默认注入到SpringAOP中的BeanDefinition。 InfrastructureAdvisorAutoProxyCreator AbstractAdvisorAutoProxyCreator的子类。SpringAOP中的核心类。基础建设类。Spring事务默认的创建代理对象的类。 TargetSource 持有目标对象的接口。 SingletonTargetSource TargetSource的子类。适用于单例目标对象。 HotSwappableTargetSource TargetSource的子类。支持热交换的目标对象 AbstractRefreshableTargetSource TargetSource的子类。支持可刷新的热部署的目标对象。 AbstractBeanFactoryBasedTargetSource TargetSource的子类。实现了BeanFactoryAware接口。 SimpleBeanTargetSource AbstractBeanFactoryBasedTargetSource的子类。从BeanFactory中获取单例Bean。 LazyInitTargetSource AbstractBeanFactoryBasedTargetSource的子类。从BeanFactory中获取单例Bean。支持延迟初始化。 AbstractPrototypeBasedTargetSource AbstractBeanFactoryBasedTargetSource的子类。对Prototype类型的Bean的支持。 ThreadLocalTargetSource AbstractPrototypeBasedTargetSource的子类。和线程上下文相结合的类。 PrototypeTargetSource AbstractPrototypeBasedTargetSource的子类。从BeanFacory中获取Prototype类型的Bean。 AopProxy 生成AOP代理对象的类。 JdkDynamicAopProxy AopProxy的子类。使用JDK的方式创建代理对象。它持有Advised对象。 CglibAopProxy AopProxy的子类。使用Cglib的方法创建代理对象。它持有Advised对象。 ObjenesisCglibAopProxy CglibAopProxy的子类。使用Cglib的方式创建代理对象。它持有Advised对象。 AopProxyFactory 创建AOP代理对象的工厂类。选择使用JDK还是Cglib的方式来创建代理对象。 DefaultAopProxyFactory AopProxyFactory的子类,也是SpringAOP中唯一默认的实现类。 AdvisorChainFactory 获取Advisor链的接口。 DefaultAdvisorChainFactory AdvisorChainFactory的实现类。也是SpringAOP中唯一默认的实现类。 AdvisorAdapterRegistry Advisor适配注册器类。用来将Advice适配为Advisor。将Advisor适配为MethodInterceptor。 DefaultAdvisorAdapterRegistry AdvisorAdapterRegistry的实现类。也是SpringAOP中唯一默认的实现类。持有:MethodBeforeAdviceAdapter、AfterReturningAdviceAdapter、ThrowsAdviceAdapter实例。 AutoProxyUtils SpringAOP自动创建代理对象的工具类。 BeforeAdvice 前置通知类。直接继承了Advice接口。 MethodBeforeAdvice BeforeAdvice的子类。定义了方法before。执行前置通知。 MethodBeforeAdviceInterceptor MethodBefore前置通知Interceptor。实现了MethodInterceptor接口。持有MethodBefore对象。 AfterAdvice 后置通知类。直接继承了Advice接口。 ThrowsAdvice 后置异常通知类。直接继承了AfterAdvice接口。 AfterReturningAdvice 后置返回通知类。直接继承了AfterAdvice接口。 AfterReturningAdviceInterceptor 后置返回通知Interceptor。实现了MethodInterceptor和AfterAdvice接口。持有AfterReturningAdvice实例 ThrowsAdviceInterceptor 后置异常通知Interceptor。实现了MethodInterceptor和AfterAdvice接口。要求方法名为:afterThrowing AdvisorAdapter Advisor适配器。判断此接口的是不是能支持对应的Advice。五种通知类型,只有三种通知类型适配器。这里可以想一下为什么只有三种。 MethodBeforeAdviceAdapter 前置通知的适配器。支持前置通知类。有一个getInterceptor方法:将Advisor适配为MethodInterceptor。Advisor持有Advice类型的实例,获取MethodBeforeAdvice,将MethodBeforeAdvice适配为MethodBeforeAdviceInterceptor。AOP的拦截过程通过MethodInterceptor来完成。 AfterReturningAdviceAdapter 后置返回通知的适配器。支持后置返回通知类。有一个getInterceptor方法:将Advisor适配为MethodInterceptor。Advisor持有Advice类型的实例,获取AfterReturningAdvice,将AfterReturningAdvice适配为AfterReturningAdviceInterceptor。AOP的拦截过程通过MethodInterceptor来完成。 ThrowsAdviceAdapter 后置异常通知的适配器。支持后置异常通知类。有一个getInterceptor方法:将Advisor适配为MethodInterceptor。Advisor持有Advice类型的实例,获取ThrowsAdvice,将ThrowsAdvice适配为ThrowsAdviceInterceptor。AOP的拦截过程通过MethodInterceptor来完成。 AbstractAspectJAdvice 使用AspectJ注解的通知类型顶级父类 AspectJMethodBeforeAdvice 使用AspectJ Before注解的前置通知类型。实现了MethodBeforeAdvice继承了AbstractAspectJAdvice。 AspectJAfterAdvice 使用AspectJ After注解的后置通知类型。实现了MethodInterceptor、AfterAdvice接口。继承了AbstractAspectJAdvice。 AspectJAfterReturningAdvice 使用AspectJ AfterReturning注解的后置通知类型。实现了AfterReturningAdvice、AfterAdvice接口。继承了AbstractAspectJAdvice。 AspectJAroundAdvice 使用AspectJ Around注解的后置通知类型。实现了MethodInterceptor接口。继承了AbstractAspectJAdvice。 AspectJAfterThrowingAdvice 使用AspectJ Around注解的后置通知类型。实现了MethodInterceptor、AfterAdvice接口。继承了AbstractAspectJAdvice。 AspectJAdvisorFactory 使用AspectJ注解 生成Advisor工厂类 AbstractAspectJAdvisorFactory AspectJAdvisorFactory的子类。使用AspectJ注解 生成Advisor的工厂类 ReflectiveAspectJAdvisorFactory AbstractAspectJAdvisorFactory的子类。使用AspectJ注解 生成Advisor的具体实现类。 AspectMetadata 使用AspectJ Aspect注解的切面元数据类。 BeanFactoryAspectJAdvisorsBuilder 工具类。负责构建Advisor、Advice。SpringAOP核心类 AspectInstanceFactory Aspect实例工厂类 MetadataAwareAspectInstanceFactory AspectInstanceFactory的子类。含有Aspect注解元数据 Aspect切面实例工厂类。 BeanFactoryAspectInstanceFactory MetadataAwareAspectInstanceFactory的子类。持有BeanFactory实例。从BeanFactory中获取Aspect实例。 PrototypeAspectInstanceFactory BeanFactoryAspectInstanceFactory的子类。获取Prototype类型的Aspect实例。 SimpleMetadataAwareAspectInstanceFactory MetadataAwareAspectInstanceFactory的实例。在AspectJProxyFactory中有使用。 SingletonMetadataAwareAspectInstanceFactory MetadataAwareAspectInstanceFactory的子类。继承了SimpleAspectInstanceFactory。单例Aspect实例类。在AspectJProxyFactory中有使用。 SimpleBeanFactoryAwareAspectInstanceFactory AspectInstanceFactory的子类。实现了BeanFactoryAware接口。和<aop:config>配合使用的类。 ProxyMethodInvocation 含有代理对象的。MethodInvocation的子类。 ReflectiveMethodInvocation ProxyMethodInvocation的子类。AOP拦截的执行入口类。 CglibMethodInvocation ReflectiveMethodInvocation的子类。对Cglib反射调用目标方法进行了一点改进。
我们在上一篇文章中说了一下FactoryBean类型的Bean的getObjectType方法被使用到的一个地方,我们在这一篇文章中会说一下FactoryBean是怎么让Spring容器管理调用它的getObject所生成的Bean的。在这篇文章中我们从getBean方法开始说起(我们这里还是要说一下我们现在的beanName的值为:factoryBeanLearn,Class类型为:FactoryBeanService.class ):getBean(beanName, requiredType, args)方法,这个方法又调用了doGetBean方法,doGetBean可以说是Spring容器中一个很核心的一个类,里面的功能很多很复杂,我们在这篇文章中只关注和FactoryBean相关的内容。思路是:先分析大内容里面的具体的点,再由点及面的分析。 public <T> T getBean(String name, Class<T> requiredType, Object... args) throws BeansException { return doGetBean(name, requiredType, args, false); } protected <T> T doGetBean( final String name, final Class<T> requiredType, final Object[] args, boolean typeCheckOnly) throws BeansException { //转换传入的BeanName的值,如&name变为name 以及别名(alias)的转换 final String beanName = transformedBeanName(name); Object bean; //调用getSingleton方法 从Spring容器中获取单例Bean 具体的获取过程见下面分析 //我们在上一篇文章中分析过 这里的beanName为factoryBeanLearn //beanName为factoryBeanLearn的Bean 已经在Spring容器中创建过了(创建过程见AbstractApplicationContext的refresh方法) //所以这里会获取到一个FactoryBeanLearn实例 //这里根据beanName获取bean实例的方法 beanName都是经过处理之后的beanName Object sharedInstance = getSingleton(beanName); if (sharedInstance != null && args == null) { //这个方法是从实例自身获取对象 像这个实例是一个FactoryBean类型的实例 //我们在下面分析这个方法 //这里要注意的是:我们在这个方法中传入了一个name 又传入了一个beanName //这里为什么要传入两个beanName?可以想一下 bean = getObjectForBeanInstance(sharedInstance, name, beanName, null); } getSingleton从Spring容器中获取单例Bean protected Object getSingleton(String beanName, boolean allowEarlyReference) { //先从singletonObjects中获取单例Bean singletonObjects是一个ConcurrentHashMap //key是beanName value是单例Bean Object singletonObject = this.singletonObjects.get(beanName); //如果没有获取到,则判断是不是当前在创建中的单例Bean if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { //这里加锁 synchronized (this.singletonObjects) { //提前暴露创建的Bean中是否存在beanName的单例Bean singletonObject = this.earlySingletonObjects.get(beanName); //如果没有获取到 并且允许提前引用响应的Bean if (singletonObject == null && allowEarlyReference) { //singletonFactories是一个HashMap key是beanName,value是ObjectFactory 又一个Factory //在Spring中很多地方用到了ObjectFactory 包括我们在之前的文章中分析的 Autowired Request ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName); if (singletonFactory != null) { //从ObjectFactory中获取Bean实例 singletonObject = singletonFactory.getObject(); //放入earlySingletonObjects这个Map中 this.earlySingletonObjects.put(beanName, singletonObject); this.singletonFactories.remove(beanName); } } } } return (singletonObject != NULL_OBJECT ? singletonObject : null); } getObjectForBeanInstance的分析 protected Object getObjectForBeanInstance( Object beanInstance, String name, String beanName, RootBeanDefinition mbd) { //这里判断 name是不是以&开头,不是经过处理的beanName 并且这个bean实例 不是FactoryBean类型的 //如果是&开头 并且不是FactoryBean类型 则抛出异常 if (BeanFactoryUtils.isFactoryDereference(name) && !(beanInstance instanceof FactoryBean)) { throw new BeanIsNotAFactoryException(transformedBeanName(name), beanInstance.getClass()); } //不是FactoryBean类型 或者name以&开头 直接返回bean实例 //想一下我们关于FactoryBean的知识:如果要根据beanName获取真正的FactoryBean实例的时候 //需要在beanName前面加上& 这里就可以看到为什么要这样做了。 if (!(beanInstance instanceof FactoryBean) || BeanFactoryUtils.isFactoryDereference(name)) { return beanInstance; } Object object = null; if (mbd == null) { //factoryBeanObjectCache 看看是不是在缓存中存在 //factoryBeanObjectCache object = getCachedObjectForFactoryBean(beanName); } //如果没有 if (object == null) { //如果能走到这里来 这个bean实例是FactoryBean类型的 FactoryBean<?> factory = (FactoryBean<?>) beanInstance; if (mbd == null && containsBeanDefinition(beanName)) { mbd = getMergedLocalBeanDefinition(beanName); } boolean synthetic = (mbd != null && mbd.isSynthetic()); //从这个方法的名字我们可以看到这个方法的意思是:从FactoryBean中 //获取对象 //getObjectFromFactoryBean的分析在下面 object = getObjectFromFactoryBean(factory, beanName, !synthetic); } return object; } getObjectFromFactoryBean的分析 protected Object getObjectFromFactoryBean(FactoryBean<?> factory, String beanName, boolean shouldPostProcess) { //FactoryBean类型的实例 调用isSingleton方法返回的是true //所传入的bean实例也要求是单例类型的 //对应我们这里就是FactoryBeanLearn中的方法返回true if (factory.isSingleton() && containsSingleton(beanName)) { //加锁 synchronized (getSingletonMutex()) { //再从缓存中获取一次 Object object = this.factoryBeanObjectCache.get(beanName); if (object == null) { //调用doGetObjectFromFactoryBean方法从FactoryBean中获取bean对象 //这里是调用的FactoryBean的getObject方法来获取的 object = doGetObjectFromFactoryBean(factory, beanName); //再从缓存中获取一次 Object alreadyThere = this.factoryBeanObjectCache.get(beanName); //如果上一步的缓存中获取到了 则用缓存中的替代我们 //我们从FactoryBean中获取的bean if (alreadyThere != null) { object = alreadyThere; } else { if (object != null && shouldPostProcess) { try { //调用BeanPostProcessor中的postProcessAfterInitialization方法进行处理 //这里只会调用BeanPostProcessor的postProcessAfterInitialization方法了 // object = postProcessObjectFromFactoryBean(object, beanName); } //抛出异常 } //放入到factoryBeanObjectCache中缓存起来 key为beanName this.factoryBeanObjectCache.put(beanName, (object != null ? object : NULL_OBJECT)); } } return (object != NULL_OBJECT ? object : null); } } else { //非单例 Object object = doGetObjectFromFactoryBean(factory, beanName); if (object != null && shouldPostProcess) { try { object = postProcessObjectFromFactoryBean(object, beanName); } catch (Throwable ex) { throw new BeanCreationException(beanName, "Post-processing of FactoryBean's object failed", ex); } } return object; } } doGetObjectFromFactoryBean的分析 private Object doGetObjectFromFactoryBean(final FactoryBean<?> factory, final String beanName) throws BeanCreationException { Object object; try { if (System.getSecurityManager() != null) { AccessControlContext acc = getAccessControlContext(); try { object = AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() { @Override public Object run() throws Exception { //调用FactoryBean中的getObject()方法获取bean return factory.getObject(); } }, acc); } catch (PrivilegedActionException pae) { throw pae.getException(); } } else { //看这里 调用FactoryBean中的getObject()方法获取bean object = factory.getObject(); } } if (object == null && isSingletonCurrentlyInCreation(beanName)) { throw new BeanCurrentlyInCreationException( beanName, "FactoryBean which is currently in creation returned null from getObject"); } return object; } 整个流程图大致如下: 流程简化起来就是: 循环Spring容器中所有的beanNames,再根据beanName获取对应的Bean实例,判断获取的Bean实例是不是FactoryBean类型的Bean,如果是,则调用Bean的getObjectType方法获取Class,将获取到的Class和传入的Class进行匹配,如果匹配到,则将此beanName和传入的Class建立一个映射关系。再根据beanName获取到Spring容器中对应的Bean,调用Bean的getObject方法来获取对应的实例。
在我们的开发工作中应该都见过或使用过FactoryBean这个类,也许你会看成了BeanFactory这个类。FactoryBean和BeanFactory虽然长的很像,但是他们的作用确实完全不像。这里你可以想象一下,你会在什么样的场景下使用FactoryBean这个接口?FactoryBean是一个工厂Bean,可以生成某一个类型Bean实例,它最大的一个作用是:可以让我们自定义Bean的创建过程。BeanFactory是Spring容器中的一个基本类也是很重要的一个类,在BeanFactory中可以创建和管理Spring容器中的Bean,它对于Bean的创建有一个统一的流程。下面我们先看一下FactoryBean中有什么东西: public interface FactoryBean<T> { //返回的对象实例 T getObject() throws Exception; //Bean的类型 Class<?> getObjectType(); //true是单例,false是非单例 在Spring5.0中此方法利用了JDK1.8的新特性变成了default方法,返回true boolean isSingleton(); } 从上面的代码中我们发现在FactoryBean中定义了一个Spring Bean的很重要的三个特性:是否单例、Bean类型、Bean实例,这也应该是我们关于Spring中的一个Bean最直观的感受。虽然没有返回BeanName的值,但是我们也知道BeanName的值。下面我们来写一个关于FactoryBean的小例子,看看我们是怎么使用FactoryBean的,然后再从源码的角度看看Spring是怎么解析FactoryBean中的Bean的。 //FactoryBean接口的实现类 @Component public class FactoryBeanLearn implements FactoryBean { @Override public Object getObject() throws Exception { //这个Bean是我们自己new的,这里我们就可以控制Bean的创建过程了 return new FactoryBeanServiceImpl(); } @Override public Class<?> getObjectType() { return FactoryBeanService.class; } @Override public boolean isSingleton() { return true; } } //接口 public interface FactoryBeanService { /** * 测试FactoryBean */ void testFactoryBean(); } //实现类 public class FactoryBeanServiceImpl implements FactoryBeanService { /** * 测试FactoryBean */ @Override public void testFactoryBean() { System.out.println("我是FactoryBean的一个测试类。。。。"); } } //单测 @Test public void test() { ClassPathXmlApplicationContext cac = new ClassPathXmlApplicationContext("classpath:com/zkn/spring/learn/base/applicationContext.xml"); FactoryBeanService beanService = cac.getBean(FactoryBeanService.class); beanService.testFactoryBean(); } 我们的输出结果如下:从上面的代码中我们可以看到我们从Spring容器中获取了FactoryBeanService类型的Bean。那么这个获取Bean的过程Spring是怎么处理的呢?它是怎么从FactoryBean中获取我们自己创建的Bean实例的呢?我们先从getBean这个方法看起,因为在Spring的AbstractApplicationContext中有很多重载的getBean方法,这里我们调用的是根据Type(指Class类型)来获取的Bean信息。我们传入的type是FactoryBeanService类型。 getBean AbstractApplicationContext#getBean(java.lang.Class) @Override public <T> T getBean(Class<T> requiredType) throws BeansException { //检测BeanFactory的激活状态 assertBeanFactoryActive(); //getBeanFactory()获取到的是一个DefaultListableBeanFactory的实例 //所以我们去DefaultListableBeanFactory中看一下getBean这个方法 return getBeanFactory().getBean(requiredType); } DefaultListableBeanFactory#getBean(java.lang.Class) @Override public <T> T getBean(Class<T> requiredType) throws BeansException { return getBean(requiredType, (Object[]) null); } @Override public <T> T getBean(Class<T> requiredType, Object... args) throws BeansException { //解析Bean NamedBeanHolder<T> namedBean = resolveNamedBean(requiredType, args); if (namedBean != null) { return namedBean.getBeanInstance(); } //如果当前Spring容器中没有获取到相应的Bean信息,则从父容器中获取 //SpringMVC是一个很典型的父子容器 BeanFactory parent = getParentBeanFactory(); if (parent != null) { //一个重复的调用过程,只不过BeanFactory的实例变了 return parent.getBean(requiredType, args); } //如果都没有获取到,则抛出异常 throw new NoSuchBeanDefinitionException(requiredType); } 在上面的代码中,我们重点关注的是resolveNamedBean这个方法: private <T> NamedBeanHolder<T> resolveNamedBean(Class<T> requiredType, Object... args) throws BeansException { Assert.notNull(requiredType, "Required type must not be null"); //这个方法是根据传入的Class类型来获取BeanName,因为我们有一个接口有多个实现类的情况(多态), //所以这里返回的是一个String数组。这个过程也比较复杂。 //这里需要注意的是,我们调用getBean方法传入的type为com.zkn.spring.learn.service.FactoryBeanService类型,但是我们没有在Spring容器中注入FactoryBeanService类型的Bean //正常来说我们在这里是获取不到beanName呢。但是事实是不是这样呢?看下面我们对getBeanNamesForType的分析 String[] candidateNames = getBeanNamesForType(requiredType); //如果有多个BeanName,则挑选合适的BeanName if (candidateNames.length > 1) { List<String> autowireCandidates = new ArrayList<String>(candidateNames.length); for (String beanName : candidateNames) { if (!containsBeanDefinition(beanName) || getBeanDefinition(beanName).isAutowireCandidate()) { autowireCandidates.add(beanName); } } if (!autowireCandidates.isEmpty()) { candidateNames = autowireCandidates.toArray(new String[autowireCandidates.size()]); } } //如果只有一个BeanName 我们调用getBean方法来获取Bean实例来放入到NamedBeanHolder中 //这里获取bean是根据beanName,beanType和args来获取bean //这里是我们要分析的重点 在下一篇文章中有介绍 if (candidateNames.length == 1) { String beanName = candidateNames[0]; return new NamedBeanHolder<T>(beanName, getBean(beanName, requiredType, args)); } //如果合适的BeanName还是有多个的话 else if (candidateNames.length > 1) { Map<String, Object> candidates = new LinkedHashMap<String, Object>(candidateNames.length); for (String beanName : candidateNames) { //看看是不是已经创建多的单例Bean if (containsSingleton(beanName)) { candidates.put(beanName, getBean(beanName, requiredType, args)); } else { //调用getType方法继续获取Bean实例 candidates.put(beanName, getType(beanName)); } } //有多个Bean实例的话 则取带有Primary注解或者带有Primary信息的Bean String candidateName = determinePrimaryCandidate(candidates, requiredType); if (candidateName == null) { //如果没有Primary注解或者Primary相关的信息,则去优先级高的Bean实例 candidateName = determineHighestPriorityCandidate(candidates, requiredType); } if (candidateName != null) { Object beanInstance = candidates.get(candidateName); //Class类型的话 继续调用getBean方法获取Bean实例 if (beanInstance instanceof Class) { beanInstance = getBean(candidateName, requiredType, args); } return new NamedBeanHolder<T>(candidateName, (T) beanInstance); } //都没有获取到 抛出异常 throw new NoUniqueBeanDefinitionException(requiredType, candidates.keySet()); } return null; } 在上面的代码中我们说我们传入的type是com.zkn.spring.learn.service.FactoryBeanService类型,但是在我们的Spring容器中却没有FactoryBeanService类型的Bean,那么我们是怎么从getBeanNamesForType获取到beanName的呢?getBeanNamesForType的分析 public String[] getBeanNamesForType(Class<?> type, boolean includeNonSingletons, boolean allowEagerInit) { if (!isConfigurationFrozen() || type == null || !allowEagerInit) { return doGetBeanNamesForType(ResolvableType.forRawClass(type), includeNonSingletons, allowEagerInit); } //先从缓存中获取 Map<Class<?>, String[]> cache = (includeNonSingletons ? this.allBeanNamesByType : this.singletonBeanNamesByType); String[] resolvedBeanNames = cache.get(type); if (resolvedBeanNames != null) { return resolvedBeanNames; } //调用doGetBeanNamesForType方法获取beanName resolvedBeanNames = doGetBeanNamesForType(ResolvableType.forRawClass(type), includeNonSingletons, true); //所传入的类能不能被当前类加载加载 if (ClassUtils.isCacheSafe(type, getBeanClassLoader())) { //放入到缓存中,解析一次以后从缓存中获取 //这里对应到我们这里 key是FactoryBeanService Value是beanFactoryLearn cache.put(type, resolvedBeanNames); } return resolvedBeanNames; } doGetBeanNamesForType的分析 private String[] doGetBeanNamesForType(ResolvableType type, boolean includeNonSingletons, boolean allowEagerInit) { List<String> result = new ArrayList<String>(); //循环所有的beanName 这个是在Spring容器启动解析Bean的时候放入到这个List中的 for (String beanName : this.beanDefinitionNames) { //不是别名 if (!isAlias(beanName)) { try { //根据beanName获取RootBeanDefinition RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName); //RootBeanDefinition中的Bean不是抽象类、非延迟初始化 if (!mbd.isAbstract() && (allowEagerInit || ((mbd.hasBeanClass() || !mbd.isLazyInit() || isAllowEagerClassLoading())) && !requiresEagerInitForType(mbd.getFactoryBeanName()))) { //是不是FactoryBean的子类 boolean isFactoryBean = isFactoryBean(beanName, mbd); BeanDefinitionHolder dbd = mbd.getDecoratedDefinition(); //这里我们其他的几个变量的意思都差不多 我们需要重点关注的是isTypeMatch这个方法 //如果isTypeMatch这个方法返回true的话,我们会把这个beanName即factoryBeanLearn 放入到result中返回 boolean matchFound = (allowEagerInit || !isFactoryBean || (dbd != null && !mbd.isLazyInit()) || containsSingleton(beanName)) && (includeNonSingletons || (dbd != null ? mbd.isSingleton() : isSingleton(beanName))) && isTypeMatch(beanName, type); if (!matchFound && isFactoryBean) { //如果不匹配,还是FactoryBean的子类 这里会把beanName变为 &beanName beanName = FACTORY_BEAN_PREFIX + beanName; //判断类型匹配不匹配 matchFound = (includeNonSingletons || mbd.isSingleton()) && isTypeMatch(beanName, type); } if (matchFound) { result.add(beanName); } } } } } //这里的Bean是Spring容器创建的特殊的几种类型的Bean 像Environment for (String beanName : this.manualSingletonNames) { try { // In case of FactoryBean, match object created by FactoryBean. if (isFactoryBean(beanName)) { if ((includeNonSingletons || isSingleton(beanName)) && isTypeMatch(beanName, type)) { result.add(beanName); // Match found for this bean: do not match FactoryBean itself anymore. continue; } // In case of FactoryBean, try to match FactoryBean itself next. beanName = FACTORY_BEAN_PREFIX + beanName; } // Match raw bean instance (might be raw FactoryBean). if (isTypeMatch(beanName, type)) { result.add(beanName); } } } return StringUtils.toStringArray(result); } 在上面的代码中,我们可以知道的是我们的FactoryBeanLearn是一个FactoryBean类型的类。所以在上面的代码中会调用isTypeMatch这个方法来判断FactoryBeanLearn是不是和我们传入的类型相匹配。这里是值:FactoryBeanService类。我们去isTypeMatch方法中去看一下它是怎么进行类型匹配判断的:由于isTypeMatch方法很长,这里我们只看和我们这次分析相关的一部分代码 public boolean isTypeMatch(String name, ResolvableType typeToMatch) throws NoSuchBeanDefinitionException { //转换beanName 这里我们可以知道我们的beanName为factoryBeanLearn 因为上面是循环了Spring容器中的所有的Bean String beanName = transformedBeanName(name); //因为我们这里是用的AbstractApplicationContext的子类来从Spring容器中获取Bean //获取beanName为factoryBeanLearn的Bean实例 这里是可以获取到Bean实例的 //这里有一个问题:使用AbstractApplicationContext的子类从Spring容器中获取Bean和 //使用BeanFactory的子类从容器中获取Bean有什么区别?这个可以思考一下 Object beanInstance = getSingleton(beanName, false); if (beanInstance != null) { //factoryBeanLearn是FactoryBean的一个实现类 if (beanInstance instanceof FactoryBean) { //这里判断beanName是不是以&开头 这里明显不是 这里可以想一下什么情况下会有&开头的Bean if (!BeanFactoryUtils.isFactoryDereference(name)) { //这里就是从factoryBeanLearn中获type类型 我们在下面会分析一下这个类 Class<?> type = getTypeForFactoryBean((FactoryBean<?>) beanInstance); //从factoryBeanLearn中获取到的type类型和我们传入的类型是不是同一种类型 是的话直接返回 return (type != null && typeToMatch.isAssignableFrom(type)); } else { return typeToMatch.isInstance(beanInstance); } } getTypeForFactoryBean protected Class<?> getTypeForFactoryBean(final FactoryBean<?> factoryBean) { try { if (System.getSecurityManager() != null) { return AccessController.doPrivileged(new PrivilegedAction<Class<?>>() { @Override public Class<?> run() { return factoryBean.getObjectType(); } }, getAccessControlContext()); } else { //看到这里是不是很熟悉了 调用FactoryBean实例的getObjectType()方法 return factoryBean.getObjectType(); } } } 我们在调用factoryBeanLearn的getObjectType方法的时候,获取到的值为:com.zkn.spring.learn.service.FactoryBeanService和我们传入的type是一样的类型。所以这里返回true,根据我们上面说的如果isTypeMatch返回true的话,我们返回的beanName为factoryBeanLearn。上面的分析总结起来是:我们调用getBean(Class requiredType)方法根据类型来获取容器中的bean的时候,对应我们的例子就是:根据类型com.zkn.spring.learn.service.FactoryBeanService来从Spring容器中获取Bean(首先明确的一点是在Spring容器中没有FactoryBeanService类型的BeanDefinition。但是却有一个Bean和FactoryBeanService这个类型有一些关系)。Spring在根据type去获取Bean的时候,会先获取到beanName。获取beanName的过程是:先循环Spring容器中的所有的beanName,然后根据beanName获取对应的BeanDefinition,如果当前bean是FactoryBean的类型,则会从Spring容器中根据beanName获取对应的Bean实例,接着调用获取到的Bean实例的getObjectType方法获取到Class类型,判断此Class类型和我们传入的Class是否是同一类型。如果是则返回测beanName,对应到我们这里就是:根据factoryBeanLearn获取到FactoryBeanLearn实例,调用FactoryBeanLearn的getObjectType方法获取到返回值FactoryBeanService.class。和我们传入的类型一致,所以这里获取的beanName为factoryBeanLearn。换句话说这里我们把factoryBeanLearn这个beanName映射为了:FactoryBeanService类型。即FactoryBeanService类型对应的beanName为factoryBeanLearn这是很重要的一点。在这里我们也看到了FactoryBean中三个方法中的一个所发挥作用的地方。我们在下一篇文章中着重分析:getBean(String name, Class requiredType, Object... args)这个方法。
我们在用SpringBoot进行项目开发的时候,基本上都使用过@ConfigurationProperties这个注解,我们在之前的文章中也说过ConfigurationPropertiesBindingPostProcessor会对标注@ConfigurationProperties注解的Bean进行属性值的配置,但是我们之前没有说ConfigurationPropertiesBindingPostProcessor这个Bean是什么时候注入到Spring容器中的。在Spring容器中如果没有这个Bean存在的话,那么通过@ConfigurationProperties注解对Bean属性值的配置行为将无从谈起。我们在本篇文章中将会说明SpringBoot在什么时机会将ConfigurationPropertiesBindingPostProcessor注入到SpringBoot容器中,但它不是我们这篇文章的主角,我们的主角是EnableConfigurationProperties注解。下面我们来请EnableConfigurationProperties登场。关于EnableConfigurationProperties,在SpringBoot的注释中是这样说明的:为带有@ConfigurationProperties注解的Bean提供有效的支持。这个注解可以提供一种方便的方式来将带有@ConfigurationProperties注解的类注入为Spring容器的Bean。你之前可能听说过类似这样的说法@ConfigurationProperties注解可以生效是因为在SpringBoot中有一个类叫ConfigurationPropertiesAutoConfiguration,它为@ConfigurationProperties注解的解析提供了支持的工作,这种说法更准确一点的说法是在这个类上还存在了@Configuration和@EnableConfigurationProperties这两个注解!我们去ConfigurationPropertiesAutoConfiguration这个类中看一下这个类中的内容: @Configuration @EnableConfigurationProperties public class ConfigurationPropertiesAutoConfiguration { } 我们会发现这个类没有任何内容,它其实只是一个标识性的类,用来标识ConfigurationProperties自动配置,重要的就是@Configuration和@EnableConfigurationProperties。这个类在SpringBoot中唯一被引用到的位置是在spring.factories中,关于@EnableAutoConfiguration的内容我们先不展开,你现在先记着由于在@EnableAutoConfiguration中存在 @Import(EnableAutoConfigurationImportSelector.class) Import这个注解,这个注解中又引入了EnableAutoConfigurationImportSelector这个类,而这个类会在某一个时机某一个地方(org.springframework.context.annotation.ConfigurationClassParser#processImports)被创建并调用它的selectImports方法,在它的selectImports方法中会获取到ConfigurationPropertiesAutoConfiguration这个类,而在ConfigurationPropertiesAutoConfiguration这个类上存在@EnableConfigurationProperties这个注解,在EnableConfigurationProperties这个注解中又存在@Import这个注解,在这个注解中又引入了EnableConfigurationPropertiesImportSelector.class这个类,所以这个类同样会在某一个地方(org.springframework.context.annotation.ConfigurationClassParser#processImports)被实例化,然后调用它的selectImports方法。关于@Import这个注解中所引入的类可以分为三种类型,第一种是实现了ImportSelector接口的类,ImportSelector接口的实现类又可以分为两种,一种是直接实现ImportSelector接口的类,另一种是实现了DeferredImportSelector接口的类,DeferredImportSelector是ImportSelector接口的子类,也只有直接实现了ImportSelector接口的类,才会在ConfigurationClassParser#processImports中被调用它的selectImports方法;第二种是实现了ImportBeanDefinitionRegistrar接口的类,实现了ImportBeanDefinitionRegistrar接口的类将会调用它的registerBeanDefinitions方法,向Spring容器中注册Bean定义;最后一种是只要不属于上面那两种的都是第三种。 @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Import(EnableConfigurationPropertiesImportSelector.class) public @interface EnableConfigurationProperties { /** * Convenient way to quickly register {@link ConfigurationProperties} annotated beans * with Spring. Standard Spring Beans will also be scanned regardless of this value. * @return {@link ConfigurationProperties} annotated beans to register */ Class<?>[] value() default {}; } 我们在上面说了会调用EnableConfigurationPropertiesImportSelector这个类的selectImports方法,我们去看看这个方法的内容: class EnableConfigurationPropertiesImportSelector implements ImportSelector { @Override public String[] selectImports(AnnotationMetadata metadata) { MultiValueMap<String, Object> attributes = metadata.getAllAnnotationAttributes( EnableConfigurationProperties.class.getName(), false); Object[] type = attributes == null ? null : (Object[]) attributes.getFirst("value"); if (type == null || type.length == 0) { return new String[] { ConfigurationPropertiesBindingPostProcessorRegistrar.class .getName() }; } return new String[] { ConfigurationPropertiesBeanRegistrar.class.getName(), ConfigurationPropertiesBindingPostProcessorRegistrar.class.getName() }; } ......... 在selectImports中会先通过 //false的意思是 不将 获取到的注解中的值转换为String metadata.getAllAnnotationAttributes(EnableConfigurationProperties.class.getName(), false); 来获取类上的EnableConfigurationProperties注解中的value值,注意这里返回的值MultiValueMap,我们用过Map的都了解,通常Map中一个key对应一个value,而MultiValueMap是一个key对应多个value的map(底层是通过List搞定的)。这里为什么用MultiValueMap呢?因为我们的注解中的元数据可能是多个值的(好像一句废话。。。)。 //如果前面没有获取到EnableConfigurationProperties注解的话 则type为null,获取到EnableConfigurationProperties注解的则取value这个元数据的值 Object[] type = attributes == null ? null: (Object[]) attributes.getFirst("value"); //如果上一步获取到的type为null或者是一个空数组的话则返回ConfigurationPropertiesBindingPostProcessorRegistrar的全限定类名 if (type == null || type.length == 0) { return new String[] { ConfigurationPropertiesBindingPostProcessorRegistrar.class.getName() }; } //如果在上一步中获取到了EnableConfigurationProperties注解中的value元数据的值,则返回ConfigurationPropertiesBeanRegistrar和ConfigurationPropertiesBindingPostProcessorRegistrar这两个类的全限定类名 return new String[] { ConfigurationPropertiesBeanRegistrar.class.getName(), ConfigurationPropertiesBindingPostProcessorRegistrar.class.getName() }; 如果我们的类上只是标注了EnableConfigurationProperties注解,如ConfigurationPropertiesAutoConfiguration这个类的用法。那么就会返回ConfigurationPropertiesBindingPostProcessorRegistrar的全限定类名。按照我们之前说的,这个类将会在某一个地方(org.springframework.context.annotation.ConfigurationClassParser#processImports)被实例化, public class ConfigurationPropertiesBindingPostProcessorRegistrar implements ImportBeanDefinitionRegistrar 请注意这个类是一个实现了ImportBeanDefinitionRegistrar接口的类,所以将会在某一个地方(org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader#loadBeanDefinitions)调用它的registerBeanDefinitions方法。我们先去看下这个类中registerBeanDefinitions方法的内容: @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { // BINDER_BEAN_NAME 的值是:' ConfigurationPropertiesBindingPostProcessor.class.getName() //如果在Spring Bean注册器中=不存在beanBame为org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor的Bean定义 if (!registry.containsBeanDefinition(BINDER_BEAN_NAME)) { BeanDefinitionBuilder meta = BeanDefinitionBuilder .genericBeanDefinition(ConfigurationBeanFactoryMetaData.class); //ConfigurationPropertiesBindingPostProcessor bean定义的构造器 BeanDefinitionBuilder bean = BeanDefinitionBuilder.genericBeanDefinition( ConfigurationPropertiesBindingPostProcessor.class); bean.addPropertyReference("beanMetaDataStore", METADATA_BEAN_NAME); //将beanName为org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor的bean定义放入到Springbean注册器 //如果之前看过Spring的源码的话,看到这里你就会会心一笑了。 //在这里就将我们开篇说的ConfigurationPropertiesBindingPostProcessor注入到Spring容器中了 registry.registerBeanDefinition(BINDER_BEAN_NAME, bean.getBeanDefinition()); registry.registerBeanDefinition(METADATA_BEAN_NAME, meta.getBeanDefinition()); } } 我们再看如果我们的类上标注了@EnableConfigurationProperties注解,并在注解中value赋值的话,它会返回ConfigurationPropertiesBeanRegistrar 和ConfigurationPropertiesBindingPostProcessorRegistrar这两个类的全限定名,关于ConfigurationPropertiesBindingPostProcessorRegistrar我们已经说过了,下面说ConfigurationPropertiesBeanRegistrar 这个类。首先EnableConfigurationPropertiesImportSelector这个类是EnableConfigurationPropertiesImportSelector中的一个静态内部类,同样的它也实现了ImportBeanDefinitionRegistrar这个接口(ImportBeanDefinitionRegistrar这个接口注意是对注解的Bean定义进行处理)。 public static class ConfigurationPropertiesBeanRegistrar implements ImportBeanDefinitionRegistrar @Override public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { //这里再次从metadata中获取EnableConfigurationProperties注解相关的东西 //这里的metadata即是我们带有EnableConfigurationProperties类的Bean的元数据 MultiValueMap<String, Object> attributes = metadata .getAllAnnotationAttributes( EnableConfigurationProperties.class.getName(), false); //这个是用的attributes.get("value")获取value的值 而前面是用的attributes.getFirst("value") //将获取到的value转换为List List<Class<?>> types = collectClasses(attributes.get("value")); for (Class<?> type : types) { //这里是获取EnableConfigurationProperties注解中value中的类的ConfigurationProperties的prefix //说的太绕了。。。。 String prefix = extractPrefix(type); //如果ConfigurationProperties中的prefix有值的话,则将beanName拼接为prefix+"-"+类的全限定名 String name = (StringUtils.hasText(prefix) ? prefix + "-" + type.getName() : type.getName()); //如果Spring bean注册器中不存在这个beanName的bean定义的话,则注入到Spring bean注册器中 if (!registry.containsBeanDefinition(name)) { registerBeanDefinition(registry, type, name); } } } private void registerBeanDefinition(BeanDefinitionRegistry registry, Class<?> type, String name) { //bean定义的构造器 BeanDefinitionBuilder builder = BeanDefinitionBuilder .genericBeanDefinition(type); //获取bean定义 AbstractBeanDefinition beanDefinition = builder.getBeanDefinition(); //注入到Spring bean注册器中 registry.registerBeanDefinition(name, beanDefinition); //获取EnableConfigurationProperties value引入的类上的ConfigurationProperties注解 ConfigurationProperties properties = AnnotationUtils.findAnnotation(type, ConfigurationProperties.class); //如果EnableConfigurationProperties value引入的类上没有ConfigurationProperties注解的话则抛出异常 //这个检测为什么不放到方法的开头处呢???? Assert.notNull(properties, "No " + ConfigurationProperties.class.getSimpleName() + " annotation found on '" + type.getName() + "'."); } 通过这篇文章你应该了解了ConfigurationPropertiesAutoConfiguration这个类什么会使ConfigurationProperties注解生效了(因为这个类放到了spring.factories中,作为org.springframework.boot.autoconfigure.EnableAutoConfiguration的一个value值,在SpringBoot启动的时候会先被加载,确保能获取到ConfigurationPropertiesAutoConfiguration这个类,并解析它上面的注解!),也应该明白了@EnableConfigurationProperties这个注解的作用,同时也应该了解了ConfigurationPropertiesBindingPostProcessorRegistrar这个类是怎么被注入到Spring容器中的。下面以一个小例子结束这篇文章: import com.zkn.springboot.analysis.domain.EnableConfigurationPropertiesDomain; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.stereotype.Component; /** * * @author zkn * @date 2018/1/28 */ @Component @EnableConfigurationProperties(value = EnableConfigurationPropertiesDomain.class) public class EnableConfigurationPropertiesBean { } /** * @author zkn * @date 2018/1/28 */ //这里只有ConfigurationProperties注解 @ConfigurationProperties(prefix = "configuration.properties") public class EnableConfigurationPropertiesDomain implements Serializable { private static final long serialVersionUID = -3485524455817230192L; private String name; //省略getter setter @Override public String toString() { return "EnableConfigurationPropertiesDomain{" + "name='" + name + '\'' + '}'; } } configuration: properties: name: This is EnableConfigurationProperties! @Autowired private EnableConfigurationPropertiesDomain propertiesDomain; @RequestMapping("index") public String index() { System.out.println(propertiesDomain); return "success"; } 我们通过浏览器访问一下,输出结果如下:
我们在之前的文章中说过怎么去修改TomCat的端口号(SpringBoot修改默认端口号),我们在这篇文章中简单的说一下SpringBoot是怎么实现修改TomCat端口号的。修改TomCat的端口号大概可以分为这样的两类吧,一种是用配置项的方式,另一种是用程序实现的方式。配置项包含:设置命令行参数、系统参数、虚拟机参数、SpringBoot默认的application.properties(或者是application.yml等类似的方式)。用程序实现的方式,则需要实现EmbeddedServletContainerCustomizer接口,并将此实现类注入为Spring的Bean。我们先说配置项的方式。通常我们用配置项的方式来修改TomCat端口号的时候,需要进行这样的配置(或类似的方式): server.port=8081 看到这样的一个配置项再结合我们自己在使用ConfigurationProperties的时候所进行的设置,我们可以推断一下应该会存在一个这样的JavaBean,在这个JavaBean上使用了ConfigurationProperties注解,并且它的prefix的值为server。既然有了一个这样的推想,那么我们就要去证明这个推想。在SpringBoot中也确实存在了我们所推想的这样的一个JavaBean:ServerProperties。ServerProperties这个类的UML如下所示:ServerProperties实现了EnvironmentAware接口,说明它可以获取Environment中的属性值,它也实现了Ordered接口,这个这里先记着,我们在后面再说,它也实现了EmbeddedServletContainerCustomizer接口,我们在上面说的第二种修改TomCat端口号的方式就是实现EmbeddedServletContainerCustomizer接口,并注入为Spring的Bean,而ServerProperties就实现了这个接口。但是这里还有一个问题,在这个类上没有添加Component注解(或者是相同作用的注解)。但是我们在SpringBoot中还发现了这样的一个类:ServerPropertiesAutoConfiguration。从名字我们可以猜出这个类应该是为ServerProperties提供自动配置的一个类,这个类也确实是这样的一个作用,关于SpringBoot的自动配置功能比较复杂,我们这里先不展开,有这方面疑问的童鞋可以在下面留言。我们去ServerPropertiesAutoConfiguration这个类中看一下这个类的代码: //Configuration相当于<beans>标签 //EnableConfigurationProperties使ConfigurationProperties注解生效,并将EnableConfigurationProperties这个注解中执行的类注入为Spring的Bean //ConditionalOnWebApplication 必须是在web开发环境中 @Configuration @EnableConfigurationProperties @ConditionalOnWebApplication public class ServerPropertiesAutoConfiguration { //如果在当前容器中 不存在ServerProperties类型的Bean,则创建ServerProperties Bean @Bean @ConditionalOnMissingBean(search = SearchStrategy.CURRENT) public ServerProperties serverProperties() { return new ServerProperties(); } //这个Bean也实现了 EmbeddedServletContainerCustomizer 接口,它同时还实现了ApplicationContextAware 接口,说明在这个类中可以获取到Spring容器的应用上下文 这个类的作用是检测在Spring 容器中是否有多于一个ServerProperties类型的Bean存在 如果是则抛出异常 @Bean public DuplicateServerPropertiesDetector duplicateServerPropertiesDetector() { return new DuplicateServerPropertiesDetector(); } private static class DuplicateServerPropertiesDetector implements EmbeddedServletContainerCustomizer, Ordered, ApplicationContextAware { private ApplicationContext applicationContext; @Override public int getOrder() { return 0; } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; } @Override public void customize(ConfigurableEmbeddedServletContainer container) { // ServerProperties handles customization, this just checks we only have // a single bean 主要作用就是检测当前容器中是否存在多于一个ServerProperties类型的Bean存在 String[] serverPropertiesBeans = this.applicationContext .getBeanNamesForType(ServerProperties.class); Assert.state(serverPropertiesBeans.length == 1, "Multiple ServerProperties beans registered " + StringUtils .arrayToCommaDelimitedString(serverPropertiesBeans)); } } } 通过上面的分析,我们看到了在哪里将ServerProperties包装为Spring容器的Bean的。下面我们来简单的说一下ServerProperties这个类中都为我们提供了什么东西: /** * 启动端口号 * Server HTTP port. */ private Integer port; /** * ServletConetxt上下文路径 * Context path of the application. */ private String contextPath; /** * DispatcherServlet 主要的 servlet Mapping * Path of the main dispatcher servlet. */ private String servletPath = "/"; /** * ServletContext参数 * ServletContext parameters. */ private final Map<String, String> contextParameters = new HashMap<String, String>(); 以及它的内部类,分别用来做和TomCat设置相关的内容、Jetty设置相关的内容、Undertow设置相关的内容以及Session设置相关的内容。关于ServerProperties中的属性值的设置请参考之前的文章,这里就不再多说了。在ServerProperties中最重要的一个方法是customize方法,这个方法是用来设置容器相关的内容的。 //这里的ConfigurableEmbeddedServletContainer 请看这个类中的内容EmbeddedServletContainerAutoConfiguration,看完你应该就会明白它是什么了 public void customize(ConfigurableEmbeddedServletContainer container) { //端口号 if (getPort() != null) { container.setPort(getPort()); } //IP地址 if (getAddress() != null) { container.setAddress(getAddress()); } //ContextPath if (getContextPath() != null) { container.setContextPath(getContextPath()); } if (getDisplayName() != null) { container.setDisplayName(getDisplayName()); } //Session超时时间 if (getSession().getTimeout() != null) { container.setSessionTimeout(getSession().getTimeout()); } container.setPersistSession(getSession().isPersistent()); container.setSessionStoreDir(getSession().getStoreDir()); //SSL if (getSsl() != null) { container.setSsl(getSsl()); } //JspServlet if (getJspServlet() != null) { container.setJspServlet(getJspServlet()); } if (getCompression() != null) { container.setCompression(getCompression()); } container.setServerHeader(getServerHeader()); //如果是TomCat服务器 if (container instanceof TomcatEmbeddedServletContainerFactory) { getTomcat().customizeTomcat(this, (TomcatEmbeddedServletContainerFactory) container); } //如果是Jetty服务器 if (container instanceof JettyEmbeddedServletContainerFactory) { getJetty().customizeJetty(this, (JettyEmbeddedServletContainerFactory) container); } //如果是Undertow服务器 if (container instanceof UndertowEmbeddedServletContainerFactory) { getUndertow().customizeUndertow(this, (UndertowEmbeddedServletContainerFactory) container); } container.addInitializers(new SessionConfiguringInitializer(this.session)); //ServletContext 参数 container.addInitializers(new InitParameterConfiguringServletContextInitializer( getContextParameters())); } 现在的关键问题是customize这个方法是在什么时候被调用的呢?通过翻开它的调用链,我们在SpringBoot中发现了这样的一个类:EmbeddedServletContainerCustomizerBeanPostProcessor在这个类中有这样的一个方法, //这个方法 如果你对Spring中的生命周期熟悉的话,那么你看到这个方法的时候一定不会陌生,同时这个类应该是实现了BeanPostProcessor 那么现在还存在的一个问题是,只有这个类是一个Spring中的Bean的时候,它才会被调用到,那么这个类是什么时候被注入到Spring的容器中的呢 @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { //如果是ConfigurableEmbeddedServletContainer类型 才会继续下面的动作 //所以这里是对我们在应用程序中所使用的应用服务器进行设置 if (bean instanceof ConfigurableEmbeddedServletContainer) { postProcessBeforeInitialization((ConfigurableEmbeddedServletContainer) bean); } return bean; } private void postProcessBeforeInitialization( ConfigurableEmbeddedServletContainer bean) { //getCustomizers()获取容器中的EmbeddedServletContainerCustomizer的实现类,ServerProperties当然算是一个,我们上面提到的DuplicateServerPropertiesDetector 也是一个 for (EmbeddedServletContainerCustomizer customizer : getCustomizers()) { //调用EmbeddedServletContainerCustomizer的实现类中的customize方法 customizer.customize(bean); } } 如果你对Spring中的生命周期熟悉的话,那么你看到postProcessBeforeInitialization这个方法的时候一定不会陌生,首先应该想到它应该是实现了BeanPostProcessor这个接口。那么现在还存在的一个问题是,只有这个类是一个Spring中的Bean的时候,它才会被调用到,那么这个类是什么时候被注入到Spring的容器中的呢 ?答案就在EmbeddedServletContainerAutoConfiguration这个类中。这个类的作用是自动配置嵌入式的Servlet容器。在这个类上用了这样的一个注解: @Import(BeanPostProcessorsRegistrar.class) Import这个注解在实现SpringBoot的自动配置功能的时候起到了非常重要的作用!Import这个注解中的value所指定的Class可以分为这样的三类:一类是实现了ImportSelector接口,一类是实现了ImportBeanDefinitionRegistrar接口,不属于前面说的这两种的就是第三种,具体的可以看一下这个方法org.springframework.context.annotation.ConfigurationClassParser#processImports。而上面所提到的BeanPostProcessorsRegistrar这个类就是实现了ImportBeanDefinitionRegistrar这个接口的。我们看一下这个类中的内容(EmbeddedServletContainerAutoConfiguration的内部类): //这个类实现了ImportBeanDefinitionRegistrar接口,同时也实现了BeanFactoryAware 接口 public static class BeanPostProcessorsRegistrar implements ImportBeanDefinitionRegistrar, BeanFactoryAware { private ConfigurableListableBeanFactory beanFactory; @Override public void setBeanFactory(BeanFactory beanFactory) throws BeansException { if (beanFactory instanceof ConfigurableListableBeanFactory) { this.beanFactory = (ConfigurableListableBeanFactory) beanFactory; } } //注入Bean定义 注意这里的Bean 都是用注解的方法注入的bean 如标注Component注解的Bean //这个方法的调用链先不介绍的 @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { if (this.beanFactory == null) { return; } //注入EmbeddedServletContainerCustomizerBeanPostProcessor registerSyntheticBeanIfMissing(registry, "embeddedServletContainerCustomizerBeanPostProcessor", EmbeddedServletContainerCustomizerBeanPostProcessor.class); //注入ErrorPageRegistrarBeanPostProcessor registerSyntheticBeanIfMissing(registry, "errorPageRegistrarBeanPostProcessor", ErrorPageRegistrarBeanPostProcessor.class); } private void registerSyntheticBeanIfMissing(BeanDefinitionRegistry registry, String name, Class<?> beanClass) { //如果容器中不存在指定类型的Bean定义 if (ObjectUtils.isEmpty( this.beanFactory.getBeanNamesForType(beanClass, true, false))) { //创一个RootBean定义 RootBeanDefinition beanDefinition = new RootBeanDefinition(beanClass); //合成的Bean 不是应用自己创建的基础建设角色的Bean beanDefinition.setSynthetic(true); //注入Spring容器中 registry.registerBeanDefinition(name, beanDefinition); } } } 到现在为止,关于SpringBoot设置TomCat启动端口号的简单分析就算是结束了。但是这里还有一个问题,如果我们既用配置项的形式设置了TomCat的端口号,同时又自定义了一个实现了EmbeddedServletContainerCustomizer接口的Bean,并且没有Order相关的设置,那么最终生效的会是哪个配置呢?答案是实现了EmbeddedServletContainerCustomizer接口的Spring Bean。在org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizerBeanPostProcessor#postProcessBeforeInitialization(org.springframework.boot.context.embedded.ConfigurableEmbeddedServletContainer)中的方法的内容如下: private void postProcessBeforeInitialization( ConfigurableEmbeddedServletContainer bean) { //getCustomizers() 从当前的Spirng容器中获取所有EmbeddedServletContainerCustomizer 类型的Bean //getCustomizers() 中的Bean是进行过排序之后的 所以这里Order值大的会覆盖Order值小的设置 for (EmbeddedServletContainerCustomizer customizer : getCustomizers()) { customizer.customize(bean); } } private Collection<EmbeddedServletContainerCustomizer> getCustomizers() { if (this.customizers == null) { // Look up does not include the parent context //从当前的Spring容器中获取EmbeddedServletContainerCustomizer 类型的Bean this.customizers = new ArrayList<EmbeddedServletContainerCustomizer>( this.beanFactory .getBeansOfType(EmbeddedServletContainerCustomizer.class, false, false) .values()); //将获取到的Bean进行排序 排序是根据Order的值进行排序的 如果你的Bean没有进行过任何关于Order值的设置的话,那么你的Bean将位于最后的位置了 Collections.sort(this.customizers, AnnotationAwareOrderComparator.INSTANCE); this.customizers = Collections.unmodifiableList(this.customizers); } return this.customizers; }
在上一篇文章的结尾处我们简单的说了一下PropertiesConfigurationFactory中的bindPropertiesToTarget这个方法的内容,在这个方法中有这样的一段代码: //获取PropertyValues 重点要分析的 PropertyValues propertyValues = getPropertySourcesPropertyValues(names,relaxedTargetNames); 首先先看一下我们在这个地方进行debug所看到的现象:这里的names是SpringBoot根据我们在ConfigurationProperties中设置的prefix的值和属性的值所能兼容的配置项的key,这里一共有98种key存在。上图中的是我们在ConfigurationProperties中设置的prefix所能匹配到的值的情况。我们进入到getPropertySourcesPropertyValues这个方法中看一下这个方法的内容: private PropertyValues getPropertySourcesPropertyValues(Set<String> names, Iterable<String> relaxedTargetNames) { //属性名字匹配器 1) PropertyNamePatternsMatcher includes = getPropertyNamePatternsMatcher(names, relaxedTargetNames); //重点要分析的内容 2) return new PropertySourcesPropertyValues(this.propertySources, names, includes, this.resolvePlaceholders); } 1)处的代码如下: 这里主要做的是获取配置项key的匹配器 private PropertyNamePatternsMatcher getPropertyNamePatternsMatcher(Set<String> names, Iterable<String> relaxedTargetNames) { //ignoreUnknownFields忽略未知的属性 isMapTarget 目标属性是不是Map类型 if (this.ignoreUnknownFields && !isMapTarget()) { // EXACT_DELIMITERS { '_', '.', '[' } return new DefaultPropertyNamePatternsMatcher(EXACT_DELIMITERS, true, names); } //如果上面的条件都不满足的话 则走下面的逻辑 //从上面的图中我们可以看到这里的relaxedTargetNames不为null 但是我们的属性不是为null if (relaxedTargetNames != null) { Set<String> relaxedNames = new HashSet<String>(); for (String relaxedTargetName : relaxedTargetNames) { relaxedNames.add(relaxedTargetName); } //TARGET_NAME_DELIMITERS { '_', '.' } return new DefaultPropertyNamePatternsMatcher(TARGET_NAME_DELIMITERS, true, relaxedNames); } //匹配所有的类型 return PropertyNamePatternsMatcher.ALL; } 2)处的代码是我们要分析的一个重点,我们进入到PropertySourcesPropertyValues这个类的构造方法中看一下: PropertySourcesPropertyValues(PropertySources propertySources, Collection<String> nonEnumerableFallbackNames, PropertyNamePatternsMatcher includes, boolean resolvePlaceholders) { //这个否则函数中有四个参数propertySources是我们从环境变量中获取到的MutablePropertySources的实例 nonEnumerableFallbackNames是所有的配置项的key值 includes是我们上一步获取到的配置项的key的匹配器 resolvePlaceholders 是否解析占位符 如${} 这里的值是true //再一次的判断 propertySources 不能为null Assert.notNull(propertySources, "PropertySources must not be null"); Assert.notNull(includes, "Includes must not be null"); this.propertySources = propertySources; this.nonEnumerableFallbackNames = nonEnumerableFallbackNames; this.includes = includes; this.resolvePlaceholders = resolvePlaceholders; //PropertySource属性解析器 这个类算是配置项解析这个大的体系中的一个类 PropertySourcesPropertyResolver resolver = new PropertySourcesPropertyResolver( propertySources); //循环之前获取到的propertySources for (PropertySource<?> source : propertySources) { processPropertySource(source, resolver); } } 这里给出一个propertySources的debug截图信息:注意看黑框中的内容,看看黑框中的顺序你会有什么发现呢?在上面的代码中我们可以发现,这里会循环propertySources中的PropertySource来进行处理。processPropertySource的内容如下: private void processPropertySource(PropertySource<?> source, PropertySourcesPropertyResolver resolver) { //如果PropertySource 为CompositePropertySource if (source instanceof CompositePropertySource) { processCompositePropertySource((CompositePropertySource) source, resolver); } //如果为EnumerablePropertySource类型 上面的CompositePropertySource是EnumerablePropertySource的子类 我们这里所提到的PropertySource大多都是EnumerablePropertySource的子类 可被列举的PropertySource else if (source instanceof EnumerablePropertySource) { processEnumerablePropertySource((EnumerablePropertySource<?>) source, resolver, this.includes); } else { processNonEnumerablePropertySource(source, resolver); } } 我们直接进入到processEnumerablePropertySource中看一下: private void processEnumerablePropertySource(EnumerablePropertySource<?> source, PropertySourcesPropertyResolver resolver, PropertyNamePatternsMatcher includes) { //如果有属性值 即有配置项的key存在 if (source.getPropertyNames().length > 0) { for (String propertyName : source.getPropertyNames()) { //配置项的key是否和之前预设的的key是否匹配 if (includes.matches(propertyName)) { //根据配置项的key获取 相应的value值 Object value = getEnumerableProperty(source, resolver, propertyName); putIfAbsent(propertyName, value, source); } } } } 如果你从上面看到现在的话,那么在这里是不是会有一个疑问呢?因为这里是循环propertySources来获取配置项的值的,如果这样的话,那么后面的propertySources中的值岂不是会覆盖前面的propertySources中的值吗?但是我们看到的现象却又不是这样的,这又是怎么一回事呢?秘密就在getEnumerableProperty中。 Object value = getEnumerableProperty(source, resolver, propertyName); private Object getEnumerableProperty(EnumerablePropertySource<?> source, PropertySourcesPropertyResolver resolver, String propertyName) { try { if (this.resolvePlaceholders) { return resolver.getProperty(propertyName, Object.class); } } return source.getProperty(propertyName); } //org.springframework.core.env.PropertySourcesPropertyResolver#getProperty(java.lang.String, java.lang.Class<T>) @Override public <T> T getProperty(String key, Class<T> targetValueType) { return getProperty(key, targetValueType, true); } //org.springframework.core.env.PropertySourcesPropertyResolver#getProperty(java.lang.String, java.lang.Class<T>, boolean) protected <T> T getProperty(String key, Class<T> targetValueType, boolean resolveNestedPlaceholders) { if (this.propertySources != null) { //奥秘就在这里!!!这里有循环了一次propertySources for (PropertySource<?> propertySource : this.propertySources) { //如果能取到值的话 就直接返回获取到的值 //所以如果你在前面的PropertySource中获取到值的话,那么后面的PropertySource中的值就不会再进行获取了!!!!!!! Object value = propertySource.getProperty(key); if (value != null) { //解析占位符,这里就不再多说了, if (resolveNestedPlaceholders && value instanceof String) { value = resolveNestedPlaceholders((String) value); } //是否需要转换服务,这里就不再多说了 因为这里就是另外一个话题了 是否需要进行转换 如日期类型转换 return convertValueIfNecessary(value, targetValueType); } } } return null; } 所以到这里我们可以看明白为什么优先级高的配置项会最终生效的原因。以上代码简化之后就是这样的: for (PropertySource<?> propertySource : this.propertySources) { for (PropertySource<?> propertySource : this.propertySources) { //如果获取到值就直接return } } 在processEnumerablePropertySource中还有一个这样的方法: putIfAbsent(propertyName, value, source); private PropertyValue putIfAbsent(String propertyName, Object value, PropertySource<?> source) { //如果获取到了value值 并且在propertyValues不存在的话 if (value != null && !this.propertyValues.containsKey(propertyName)) { //大家可以看一下关于List这样的属性是怎么进行属性值的设置的 PropertySource<?> collectionOwner = this.collectionOwners.putIfAbsent( COLLECTION_PROPERTY.matcher(propertyName).replaceAll("[]"), source); //如果没有获取过这个属性值的话 则放入到propertyValues中 if (collectionOwner == null || collectionOwner == source) { PropertyValue propertyValue = new OriginCapablePropertyValue(propertyName, value, propertyName, source); this.propertyValues.put(propertyName, propertyValue); return propertyValue; } } return null; } 真正的属性值的设置的动作是在org.springframework.boot.bind.PropertiesConfigurationFactory#doBindPropertiesToTarget这个方法中的这句话中完成的: dataBinder.bind(propertyValues);
我们在之前的文章中简单的说了一下SpringBoot对于默认的配置文件的解析过程,在这一篇文章中我们再简单的分析一下SpringBoot是怎么将解析到的配置属性信息设置到相应的Bean上的。既然是用SpringBoot的属性配置方式,那么我们在这里会在对应的类上加上ConfigurationProperties和Component(或是和Component相同功能的)注解。我们定义的Bean如下: @Component @ConfigurationProperties(prefix ="person.info") public class PersonInfoDomain { /** * 用户名 */ private String userName; } 配置文件如下:其内容依次如下: person.info.user-name=lisi person: info: user-name: zhangzhang person.info.user-name=zhangsan person: info: user-name: lilisisi 首先问一个问题,如果是你来实现这样的功能的话,你会在什么时机来进行属性值设置的功能呢?在创建Bean的时候进行属性的设置应该是一个比较合适的时机吧?Spring也是这样做的。在Spring中提供了各种不同功能的接口来让我们在Bean创建的过程中做一些不同功能的扩展,我们先称为"生命周期"的接口(可以在这里进行查看:Spring Bean的生命周期小析(一) Spring Bean的生命周期小析(二)),在Spring在有这样一个类:AbstractAutowireCapableBeanFactory这个类主要的一个功能是串联Spring的生命周期。我们先看一下这个类中的这个方法:applyBeanPostProcessorsBeforeInitialization(不要问我为什么知道这个方法。。。放一下调用链的截图) @Override public Object applyBeanPostProcessorsBeforeInitialization(Object existingBean, String beanName) throws BeansException { Object result = existingBean; //这里的getBeanPostProcessors()这个方法获取到的是Spring 容器中实现BeanPostProcessor接口的Bean 在这个Bean中有一个Bean叫ConfigurationPropertiesBindingPostProcessor 从这个Bean的名字我们可以感觉这个Bean应该是和ConfigurationProperties相关的类,而事实也确是如此 其他的我们先不说了,放一下SpringBoot中内置的一些BeanPostProcessor 的Bean,我们直接进入到 ConfigurationPropertiesBindingPostProcessor 这个类中 for (BeanPostProcessor beanProcessor : getBeanPostProcessors()) { result = beanProcessor.postProcessBeforeInitialization(result, beanName); if (result == null) { return result; } } return result; } @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { //获取类上的ConfigurationProperties注解 用AnnotationUtils来对类上的注解进行处理是很方便,有兴趣的可以看一下AnnotationUtils中的方法的源码实现 ConfigurationProperties annotation = AnnotationUtils .findAnnotation(bean.getClass(), ConfigurationProperties.class); //如果类上有ConfigurationProperties 注解 if (annotation != null) { //主要处理方法 postProcessBeforeInitialization(bean, beanName, annotation); } //方法上的ConfigurationProperties注解 annotation = this.beans.findFactoryAnnotation(beanName, ConfigurationProperties.class); if (annotation != null) { postProcessBeforeInitialization(bean, beanName, annotation); } return bean; } org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor#postProcessBeforeInitialization private void postProcessBeforeInitialization(Object bean, String beanName, ConfigurationProperties annotation) { //我们需要设置属性的目标bean Object target = bean; //新建一个PropertiesConfigurationFactory类 这个类来完成属性的赋值的工作 PropertiesConfigurationFactory<Object> factory = new PropertiesConfigurationFactory<Object>( target); //propertySources 1) factory.setPropertySources(this.propertySources); //验证器 factory.setValidator(determineValidator(bean)); //转换服务 factory.setConversionService(this.conversionService == null ? getDefaultConversionService() : this.conversionService); if (annotation != null) { //类上 ConfigurationProperties注解的信息 factory.setIgnoreInvalidFields(annotation.ignoreInvalidFields()); factory.setIgnoreUnknownFields(annotation.ignoreUnknownFields()); factory.setExceptionIfInvalid(annotation.exceptionIfInvalid()); factory.setIgnoreNestedProperties(annotation.ignoreNestedProperties()); //属性前缀 if (StringUtils.hasLength(annotation.prefix())) { factory.setTargetName(annotation.prefix()); } } try { //主要方法 factory.bindPropertiesToTarget(); } } 对于1)处的PropertySources我们应该不陌生了,前面我们一直在提到这个类。我们看一下这个PropertySources是在什么时候进行赋值的。在ConfigurationPropertiesBindingPostProcessor中有一个这样的方法afterPropertiesSet: public void afterPropertiesSet() throws Exception { if (this.propertySources == null) { //寻找PropertySources this.propertySources = deducePropertySources(); } //validator的赋值 if (this.validator == null) { this.validator = getOptionalBean(VALIDATOR_BEAN_NAME, Validator.class); } //转换服务 if (this.conversionService == null) { this.conversionService = getOptionalBean( ConfigurableApplicationContext.CONVERSION_SERVICE_BEAN_NAME, ConversionService.class); } } private PropertySources deducePropertySources() { //从Spring 容器中获取 PropertySourcesPlaceholderConfigurer PropertySourcesPlaceholderConfigurer configurer = getSinglePropertySourcesPlaceholderConfigurer(); if (configurer != null) { //configurer.getAppliedPropertySources() 这里获取到的就是我们之前一直提到的MutablePropertySources return new FlatPropertySources(configurer.getAppliedPropertySources()); } //如果Spring 容器中 没有PropertySourcesPlaceholderConfigurer的话 则从ConfigurableEnvironment中获取 if (this.environment instanceof ConfigurableEnvironment) { MutablePropertySources propertySources = ((ConfigurableEnvironment) this.environment) .getPropertySources(); return new FlatPropertySources(propertySources); } //还获取不到的话,则新建一个MutablePropertySources return new MutablePropertySources(); } private PropertySourcesPlaceholderConfigurer getSinglePropertySourcesPlaceholderConfigurer() { if (this.beanFactory instanceof ListableBeanFactory) { //从Spring 容器中获取 PropertySourcesPlaceholderConfigurer ListableBeanFactory listableBeanFactory = (ListableBeanFactory) this.beanFactory; Map<String, PropertySourcesPlaceholderConfigurer> beans = listableBeanFactory .getBeansOfType(PropertySourcesPlaceholderConfigurer.class, false, false); if (beans.size() == 1) { return beans.values().iterator().next(); } //如果有多于一个存在的话,则返回null if (beans.size() > 1 && logger.isWarnEnabled()) { } } return null; } OK,我们进入到PropertiesConfigurationFactory看一下factory.bindPropertiesToTarget();这个方法的内容: public void bindPropertiesToTarget() throws BindException { //propertySources不能为null Assert.state(this.propertySources != null, "PropertySources should not be null"); try { this.hasBeenBound = true; doBindPropertiesToTarget(); } } private void doBindPropertiesToTarget() throws BindException { //targetName PropertiesConfigurationFactory注解上的前缀值 //target 目标bean RelaxedDataBinder dataBinder = (this.targetName != null ? new RelaxedDataBinder(this.target, this.targetName) : new RelaxedDataBinder(this.target)); //校验器 if (this.validator != null && this.validator.supports(dataBinder.getTarget().getClass())) { dataBinder.setValidator(this.validator); } //转换服务 if (this.conversionService != null) { dataBinder.setConversionService(this.conversionService); } //集合的最大值 dataBinder.setAutoGrowCollectionLimit(Integer.MAX_VALUE); dataBinder.setIgnoreNestedProperties(this.ignoreNestedProperties); dataBinder.setIgnoreInvalidFields(this.ignoreInvalidFields); dataBinder.setIgnoreUnknownFields(this.ignoreUnknownFields); //自定义Binder 这里是一个空实现 customizeBinder(dataBinder); //下面这两段代码是获取 Spring支持的配置的名字 支持格式超出你的想象 可以调试自己看一下 Iterable<String> relaxedTargetNames = getRelaxedTargetNames(); Set<String> names = getNames(relaxedTargetNames); //获取PropertyValues 重点要分析的 PropertyValues propertyValues = getPropertySourcesPropertyValues(names, relaxedTargetNames); //属性值的绑定工作 dataBinder.bind(propertyValues); if (this.validator != null) { //属性值的校验 dataBinder.validate(); } checkForBindingErrors(dataBinder); }
我们接着上一篇的文章继续分析。我们来看这一段代码: //在上一篇文章中我们分析了getSearchNames()这个方法,这个方法默认返回 只有一个元素 application的List for (String name : getSearchNames()) { //我们分析的重点 profile 为null load(location, name, profile); } String group = "profile=" + (profile == null ? "" : profile); //这里的name是application 所以不会走这里 if (!StringUtils.hasText(name)) { // Try to load directly from the location loadIntoGroup(group, location, profile); } else { // Search for a file with the given name //获取所有的扩展名 1) for (String ext : this.propertiesLoader.getAllFileExtensions()) { //我们的profile为null 所以这里的逻辑可以先剥离出去不看 直接看下面的汉字的部分 if (profile != null) { // Try the profile-specific file loadIntoGroup(group, location + name + "-" + profile + "." + ext, null); for (Profile processedProfile : this.processedProfiles) { if (processedProfile != null) { loadIntoGroup(group, location + name + "-" + processedProfile + "." + ext, profile); } } // Sometimes people put "spring.profiles: dev" in // application-dev.yml (gh-340). Arguably we should try and error // out on that, but we can be kind and load it anyway. loadIntoGroup(group, location + name + "-" + profile + "." + ext, profile); } // Also try the profile-specific section (if any) of the normal file //主要调用方法 group : profile= profile:null loadIntoGroup(group, location + name + "." + ext, profile); } } 我们先来看1)的代码:this.propertiesLoader.getAllFileExtensions()这一句。 public Set<String> getAllFileExtensions() { Set<String> fileExtensions = new LinkedHashSet<String>(); //循环this.loaders 1) for (PropertySourceLoader loader : this.loaders) { //将得到的 扩展名 放到 Set中 这是一个 LinkedHashSet 2) fileExtensions.addAll(Arrays.asList(loader.getFileExtensions())); } return fileExtensions; } 在代码1)处 this.loaders是什么呢?在ConfigFileApplicationListener.Loader#load()这个方法中有这样一段代码: this.propertiesLoader = new PropertySourcesLoader(); public PropertySourcesLoader() { //这里创建一个MutablePropertySources 赋给了PropertySourcesLoader中的 propertySources this(new MutablePropertySources()); } public PropertySourcesLoader(MutablePropertySources propertySources) { Assert.notNull(propertySources, "PropertySources must not be null"); this.propertySources = propertySources; //这一段代码看着就很眼熟了吧 说过很多次了 //在spring.factories中的值为:org.springframework.boot.env.PropertiesPropertySourceLoader //和 ' org.springframework.boot.env.YamlPropertySourceLoader //这两个属性资源loader 一个适用于 properties 一个适用于 yml(或者yaml) this.loaders = SpringFactoriesLoader.loadFactories(PropertySourceLoader.class, getClass().getClassLoader()); } 所以1处的this.loaders 是 元素为 PropertiesPropertySourceLoader 和YamlPropertySourceLoader的List。2)处的loader.getFileExtensions()的代码分别如下: public String[] getFileExtensions() { //properties和xml后缀 return new String[] { "properties", "xml" }; } public String[] getFileExtensions() { //yml和yaml后缀 return new String[] { "yml", "yaml" }; } 所以getAllFileExtensions() 得到的扩展名为含有 “properties”, “xml” ,”yml”, “yaml” 这个四个元素的一个集合。所以我们接着上一章简化的代码,在这里进一步的简化代码为: List<String> listString = Arrays.asList("file:./config/,file:./,classpath:/config/,classpath:/".split(",")); List<String> listName = Arrays.asList("application"); List<String> fileExtensions = Arrays.asList("properties","xml","yml","yaml"); for (String location : listString) { for(String name : listName){ for(String ext : fileExtensions){ loadIntoGroup(group, location + name + "." + ext, profile); } } } 看到上面的代码,读者应该明白SpringBoot加载的配置文件的位置和文件类型了吧?我们继续到loadIntoGroup看一下它的代码: private PropertySource<?> loadIntoGroup(String identifier, String location, Profile profile) { try { return doLoadIntoGroup(identifier, location, profile); } } //去掉了无用的代码 private PropertySource<?> doLoadIntoGroup(String identifier, String location, Profile profile) throws IOException { //从不同的地方进行资源文件的加载 有兴趣的同学可以看一下Spring是怎么适配不同位置的资源的加载的 不再多说 //可以参考一下这里:[Spring学习之资源管理器(Resource)](http://blog.csdn.net/zknxx/article/details/72383848) Resource resource = this.resourceLoader.getResource(location); PropertySource<?> propertySource = null; if (resource != null && resource.exists()) { String name = "applicationConfig: [" + location + "]"; String group = "applicationConfig: [" + identifier + "]"; //加载解析资源文件 propertySource = this.propertiesLoader.load(resource, group, name, (profile == null ? null : profile.getName())); if (propertySource != null) { //Profile的处理 handleProfileProperties(propertySource); } } } org.springframework.boot.env.PropertySourcesLoader#load public PropertySource<?> load(Resource resource, String group, String name, String profile) throws IOException { //判断该资源是不是文件类型 这样的代码 在写自己的框架的时候是可以借鉴直接使用的 if (isFile(resource)) { String sourceName = generatePropertySourceName(name, profile); for (PropertySourceLoader loader : this.loaders) { //这里判断能不能加载 文件 是根据 每个loader 对应的扩展类型 进行匹配的 是不是相应的 扩展名 结尾 //PropertiesPropertySourceLoader "properties", "xml" //YamlPropertySourceLoader "yml", "yaml" if (canLoadFileExtension(loader, resource)) { //对应 properties和xml文件的加载 大家都比较熟悉一点,感兴趣的可以看一下对于yaml文件加载过程 //PropertiesPropertySourceLoader加载的文件封装为了PropertiesPropertySource //YamlPropertySourceLoader加载的文件封装为了MapPropertySource PropertySource<?> specific = loader.load(sourceName, resource, profile); //这一段代码有必要说明一下 addPropertySource(group, specific, profile); return specific; } } } return null; } private void addPropertySource(String basename, PropertySource<?> source, String profile) { //如果source为null 直接返回 if (source == null) { return; } //如果basename为null将resource添加到最后 if (basename == null) { this.propertySources.addLast(source); return; } //根据名字获取EnumerableCompositePropertySource EnumerableCompositePropertySource group = getGeneric(basename); //将前面得到的source放入到EnumerableCompositePropertySource中 //EnumerableCompositePropertySource 内部维护了一个LinkedHashSet group.add(source); //如果之前有这个name的话,则进行替换的工作 if (this.propertySources.contains(group.getName())) { this.propertySources.replace(group.getName(), group); } else { //如果之前没有这个name的话,则插入到头部 this.propertySources.addFirst(group); } } 如果我们的配置文件是这样的: 那么我们最终得到的EnumerableCompositePropertySource 是什么样的呢? 注意了!!!注意了!!!是properties在前,yml在后说明properties的优先级比yml高!!!!! 我们再来看一下这一段的代码,在org.springframework.boot.context.config.ConfigFileApplicationListener.Loader#load()中 //如果你一直跟着我说的在对着源码进行分析的话,那么对于this.propertiesLoader.getPropertySources()这个方法得到的PropertySources一定不陌生。它就是在PropertySourcesLoader中创建的MutablePropertySources,它的元素为上面图中的元素 addConfigurationProperties(this.propertiesLoader.getPropertySources()); private void addConfigurationProperties(MutablePropertySources sources) { //将之前得到的MutablePropertySources中的PropertySource转换为List<PropertySource<?>> List<PropertySource<?>> reorderedSources = new ArrayList<PropertySource<?>>(); for (PropertySource<?> item : sources) { reorderedSources.add(item); } //将上面得到的List<PropertySource<?>> 包装为ConfigurationPropertySources //它的name为applicationConfigurationProperties addConfigurationProperties(new ConfigurationPropertySources(reorderedSources)); } private void addConfigurationProperties(ConfigurationPropertySources configurationSources) { //获取环境变量中的MutablePropertySources 这个MutablePropertySources包含之前得到的 命令行的PropertySource、ServletConfig的、ServletContext、虚拟机的、系统的PropertySource MutablePropertySources existingSources = this.environment.getPropertySources(); //如果有defaultProperties if (existingSources.contains(DEFAULT_PROPERTIES)) { //则将之前得到的ConfigurationPropertySources插入到defaultProperties的前面 我们之前分析中得知 defaultProperties元素的位置位于最后 existingSources.addBefore(DEFAULT_PROPERTIES, configurationSources); } else { //将将之前得到的ConfigurationPropertySources插入到最后 existingSources.addLast(configurationSources); } } } 讲过上面的分析,我们可得到一个不同的配置项在环境变量中所处的一个位置情况:commandLineArgs、servletConfigInitParams 、servletContextInitParams 、jndiProperties 、systemProperties 、systemEnvironment、random、applicationConfigurationProperties、defaultProperties。 applicationConfigurationProperties中元素的位置顺序 file:./config/、file:./、classpath:/config/、classpath:/,其中properties后缀的文件在前,yml在后。
我们在上一篇文章中简单的说了一些SpringBoot配置属性相关的一些内容,我们在这篇文章中接着上一篇的文章继续进行分析。我们在上一篇文章中提到了这样一个类:ConfigFileApplicationListener,从类名来看的话这是一个配置文件应用监听器,这个类主要的一个作用是在 refresh context之前解析默认的配置文件。首先我们来看一下它的UML类图: ConfigFileApplicationListener实现了EnvironmentPostProcessor和SmartApplicationListener这两个接口,从上图中我们可以看到SmartApplicationListener其实是继承了ApplicationListener这个接口的。SmartApplicationListener这个类相比于ApplicationListener多了两个方法,一个是用来检测是否支持给定的类型,一个是用来检测得到的source type。EnvironmentPostProcessor这个接口的作用是在refresh application context之前定制化应用的环境配置信息,需要说明的是,实现这个接口的类必须要在spring.factories进行配置。大家可以想一下这里为什么要这样。既然ConfigFileApplicationListener也是一个监听器类,那么它肯定会监听某些动作的发生,我们在之前的文章中说过,在org.springframework.boot.SpringApplication#run(java.lang.String… args)这个方法中会负责SpringBoot的整个启动工作,在这里有这样的一段代码: SpringApplicationRunListeners listeners = getRunListeners(args); listeners.starting(); 顺着listeners.starting();这个方法往下扒的话,你就会找到调用org.springframework.boot.context.config.ConfigFileApplicationListener#onApplicationEvent这个方法的地方,这个调用链不算很负责,这里就不再多说了。我们直接进入到ConfigFileApplicationListener#onApplicationEvent这个方法中: public void onApplicationEvent(ApplicationEvent event) { if (event instanceof ApplicationEnvironmentPreparedEvent) { onApplicationEnvironmentPreparedEvent( (ApplicationEnvironmentPreparedEvent) event); } if (event instanceof ApplicationPreparedEvent) { onApplicationPreparedEvent(event); } } 但是啊但是,在onApplicationEvent中有两个条件判断,你通过listeners.starting()这个调用链一直跟踪到这里的话你会发现,这两个条件都不满足listeners.starting()的调用,那么是在什么地方触发这个监听事件的呢?在org.springframework.boot.SpringApplication#prepareEnvironment这个方法中,我们在上一篇文章中介绍了其中的一些代码,这里再看一段代码: listeners.environmentPrepared(environment); org.springframework.boot.SpringApplicationRunListeners#environmentPrepared public void environmentPrepared(ConfigurableEnvironment environment) { //这里的listeners是从spring.factories中得到的 for (SpringApplicationRunListener listener : this.listeners) { listener.environmentPrepared(environment); } } org.springframework.boot.context.event.EventPublishingRunListener#environmentPrepared public void environmentPrepared(ConfigurableEnvironment environment) { //这里的ApplicationEvent是ApplicationEnvironmentPreparedEvent满足我们上面的提到的两个条件中的一个 this.initialMulticaster.multicastEvent(new ApplicationEnvironmentPreparedEvent( this.application, this.args, environment)); } 从上面的代码中我们可以看到在执行listeners.environmentPrepared(environment); 的时候,会调用ConfigFileApplicationListener#onApplicationEvent这个方法,并且所传入的ApplicationEvent是ApplicationEnvironmentPreparedEvent,所以这里会调用ConfigFileApplicationListener#onApplicationEvent中的org.springframework.boot.context.config.ConfigFileApplicationListener#onApplicationEnvironmentPreparedEvent方法。我们进入到onApplicationEnvironmentPreparedEvent方法中看一下: private void onApplicationEnvironmentPreparedEvent( ApplicationEnvironmentPreparedEvent event) { //从spring.factories中获取key为org.springframework.boot.env.EnvironmentPostProcessor的配置信息不再细说 List<EnvironmentPostProcessor> postProcessors = loadPostProcessors(); //前面说过ConfigFileApplicationListener也实现了EnvironmentPostProcessor接口 postProcessors.add(this); //排序 AnnotationAwareOrderComparator.sort(postProcessors); //循环上一步得到的List,分别调用各自的postProcessEnvironment方法,在这里我们只关注ConfigFileApplicationListener这个类的postProcessEnvironment方法即可 for (EnvironmentPostProcessor postProcessor : postProcessors) { postProcessor.postProcessEnvironment(event.getEnvironment(), event.getSpringApplication()); } } postProcessEnvironment的源码如下: public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) { //添加PropertySource 这个是我们要分析的重点 addPropertySources(environment, application.getResourceLoader()); //JavaBean内省的配置 configureIgnoreBeanInfo(environment); bindToSpringApplication(environment, application); } 在上面的代码中,我们目前需要重点关注的就是ConfigFileApplicationListener#addPropertySources这个方法: protected void addPropertySources(ConfigurableEnvironment environment, ResourceLoader resourceLoader) { //添加RandomValuePropertySource RandomValuePropertySource.addToEnvironment(environment); //加载默认配置信息,这个environment就是我们在SpringApplication中创建的StandardServletEnvironment new Loader(environment, resourceLoader).load(); } RandomValuePropertySource.addToEnvironment public static void addToEnvironment(ConfigurableEnvironment environment) { //MutablePropertySources#addAfter 这个方法我们在上一章中说过了这里就不再多说了。这段代码的意思是将RandomValuePropertySource的顺序添加到systemEnvironment的后面 environment.getPropertySources().addAfter( //systemEnvironment StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, //random new RandomValuePropertySource(RANDOM_PROPERTY_SOURCE_NAME)); } 上面这段代码的意思就将RandomValuePropertySource添加到systemEnvironment的后面,到目前为止我们的MutablePropertySources#propertySourceList中的元素顺序为:commandLineArgs、servletConfigInitParams 、servletContextInitParams 、jndiProperties 、systemProperties 、systemEnvironment、random。我们接着分析new Loader(environment, resourceLoader).load();这段代码,这个代码中的内容比较多,我们也不是全部都要关注,这里我们先把Profile相关的内容先剥离出去。我们直接到这里: while (!this.profiles.isEmpty()) { Profile profile = this.profiles.poll(); //获取配置文件的位置 1) for (String location : getSearchLocations()) { //如果不是以/结尾的话则调用下面的方法 if (!location.endsWith("/")) { // location is a filename already, so don't search for more // filenames load(location, null, profile); } else { //获取配置文件的名称 2) for (String name : getSearchNames()) { load(location, name, profile); } } } this.processedProfiles.add(profile); } 我们先来看上面标记为1)处的代码: private Set<String> getSearchLocations() { Set<String> locations = new LinkedHashSet<String>(); // User-configured settings take precedence, so we do them first //如果配置了spring.config.location这个属性值,则先解析这个属性值 if (this.environment.containsProperty(CONFIG_LOCATION_PROPERTY)) { //多个值以 , 进行分割 for (String path : asResolvedSet( this.environment.getProperty(CONFIG_LOCATION_PROPERTY), null)) { if (!path.contains("$")) { path = StringUtils.cleanPath(path); if (!ResourceUtils.isUrl(path)) { path = ResourceUtils.FILE_URL_PREFIX + path; } } locations.add(path); } } //通常我们都不会设置 ' spring.config.location ' 这个属性值 这里添加默认位置 //默认位置为 "classpath:/,classpath:/config/,file:./,file:./config/" //asResolvedSet会用 , 分割上述字符串 并且还有一个作用将上传得到的List进行 翻转 注意这个很重要!!! 即使你在上面设置了 ' spring.config.location ' 它的位置也是在系统默认的位置的后面 locations.addAll( asResolvedSet(ConfigFileApplicationListener.this.searchLocations, DEFAULT_SEARCH_LOCATIONS)); return locations; } 我们接着看2)处的代码: private Set<String> getSearchNames() { //如果配置了 spring.config.name 属性值 if (this.environment.containsProperty(CONFIG_NAME_PROPERTY)) { return asResolvedSet(this.environment.getProperty(CONFIG_NAME_PROPERTY), null); } //解析默认的文件名 application 这里同样会对获取到的List进行翻转 return asResolvedSet(ConfigFileApplicationListener.this.names, DEFAULT_NAMES); } 所以上面while的这一段代码我们可以简化为这样(极其简化): List<String> listString = (Arrays.asList("file:./config/,file:./,classpath:/config/,classpath:/".split(","))); List<String> listName = Arrays.asList("application"); for (String location : listString) { for(String name : listName){ load(location, name, profile); } }
在我们的开发工作总是离不了配置项相关的配置工作,SpringBoot也为我们提供了@ConfigurationProperties注解来进行配置项信息的配置工作,同时也提供了几个配置文件的默认加载位置,如:classpath:application.properties、classpath:application.yml、classpath:application.yaml、classpath:/config/application.properties、classpath:/config/application.yml、classpath:/config/application.yaml等。另外我们还可以在命令行中、系统属性中、虚拟机参数中、Servlet上下文中进行配置项的配置,既然有这么多的配置位置,程序在加载配置项的时候总得有一个先后顺序吧,要不然系统不就乱套了。在SpringBoot中大概有这样的一个先后加载顺序(优先级高的会覆盖优先级低的配置): 命令行参数。 Servlet初始参数 ServletContext初始化参数 JVM系统属性 操作系统环境变量 随机生成的带random.*前缀的属性 应用程序以外的application.yml或者appliaction.properties文件 classpath:/config/application.yml或者classpath:/config/application.properties 通过@PropertySource标注的属性源 默认属性 那么SpringBoot是怎么创建这样的一个优先顺序的呢?默认的application.properties是怎么被加载的呢?在本章中我将把其中奥秘慢慢道出: 我在之前的SpringBoot启动流程简析的文章中说过,SpringBoot在启动的过程中会从spring.factories中加载一些ApplicationListener,在这些ApplicationListener中其中就有一个我们今天要说的ConfigFileApplicationListener;我们之前也说过在启动过程中会创建ConfigurableEnvironment,也会进行命令行参数的解析工作。在org.springframework.boot.SpringApplication#prepareEnvironment这个方法中有这样的一段代码: 先创建应用可配置的环境变量,为命令行进行环境变量配置工作: protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) { //将命令行参数转换为org.springframework.core.env.PropertySource configurePropertySources(environment, args); //Profile的配置,这里先不说明 configureProfiles(environment, args); } protected void configurePropertySources(ConfigurableEnvironment environment, String[] args) { //从上面创建的ConfigurableEnvironment实例中获取MutablePropertySources实例 MutablePropertySources sources = environment.getPropertySources(); //如果有defaultProperties属性的话,则把默认属性添加为最后一个元素 if (this.defaultProperties != null && !this.defaultProperties.isEmpty()) { sources.addLast( new MapPropertySource("defaultProperties", this.defaultProperties)); } //这里addCommandLineProperties默认为true 如果有命令行参数的数 if (this.addCommandLineProperties && args.length > 0) { //name为:commandLineArgs String name = CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME; //如果之前的MutablePropertySources中有name为commandLineArgs的PropertySource的话,则把当前命令行参数转换为CompositePropertySource类型,和原来的PropertySource进行合并,替换原来的PropertySource if (sources.contains(name)) { PropertySource<?> source = sources.get(name); CompositePropertySource composite = new CompositePropertySource(name); composite.addPropertySource(new SimpleCommandLinePropertySource( name + "-" + args.hashCode(), args)); composite.addPropertySource(source); sources.replace(name, composite); } else { //如果之前没有name为commandLineArgs的PropertySource的话,则将其添加为MutablePropertySources中的第一个元素,注意了这里讲命令行参数添加为ConfigurableEnvironment中MutablePropertySources实例的第一个元素,且永远是第一个元素 sources.addFirst(new SimpleCommandLinePropertySource(args)); } } } 从上面的代码中我们可以看到,SpringBoot把命令行参数转换为PropertySource,并添加为环境变量中的第一个元素!这里简单的提一下MutablePropertySources 这个类,它的UML如下所示: 从上面的UML中我们可以看到,MutablePropertySources实现了Iterable接口,是一个可迭代的类,在这个类中有这样的一个属性: private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<PropertySource<?>>(); 一个类型为PropertySource的CopyOnWriteArrayList,这里用的是CopyOnWriteArrayList,而不是ArrayList、LinkedList,大家可以想一下这里为什么用了CopyOnWriteArrayList。 MutablePropertySources中的这些方法都是通过CopyOnWriteArrayList中的方法来实现的。我们在之前的文章中说明,SpringBoot创建的ConfigurableEnvironment实例是StandardServletEnvironment,其UML类图如下: ,其在实例化的过程中,会调用父类的构造函数先实例化父类,其父类StandardEnvironment为默认无参构造函数,AbstractEnvironment中的无参构造函数如下: public AbstractEnvironment() { //调用customizePropertySources方法进行定制PropertySource customizePropertySources(this.propertySources); } 在StandardServletEnvironment和StandardEnvironment分别重写了这个方法,其调用为StandardServletEnvironment中的customizePropertySources方法,其源码如下: protected void customizePropertySources(MutablePropertySources propertySources) { //SERVLET_CONFIG_PROPERTY_SOURCE_NAME 为 servletConfigInitParams 添加servletConfigInitParams 的PropertySource propertySources.addLast(new StubPropertySource(SERVLET_CONFIG_PROPERTY_SOURCE_NAME)); //SERVLET_CONTEXT_PROPERTY_SOURCE_NAME为servletContextInitParams 添加servletContextInitParams 的PropertySource propertySources.addLast(new StubPropertySource(SERVLET_CONTEXT_PROPERTY_SOURCE_NAME)); //如果有JNDI if (JndiLocatorDelegate.isDefaultJndiEnvironmentAvailable()) { //JNDI_PROPERTY_SOURCE_NAME 为 jndiProperties 添加jndiProperties 的PropertySource propertySources.addLast(new JndiPropertySource(JNDI_PROPERTY_SOURCE_NAME)); } //调用父类的StandardEnvironment中的customizePropertySources super.customizePropertySources(propertySources); } protected void customizePropertySources(MutablePropertySources propertySources) { //SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME 为 systemProperties 添加systemProperties 的PropertySource propertySources.addLast(new MapPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties())); //SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME 为 systemEnvironment 添加systemEnvironment的PropertySource propertySources.addLast(new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment())); } 从上面的分析中我们可以看到在创建StandardServletEnvironment的实例的时候,会向org.springframework.core.env.AbstractEnvironment#propertySources中按顺序添加:name分别为:servletConfigInitParams、servletContextInitParams、jndiProperties 、systemProperties、systemEnvironment 的PropertySource,再按照我们前面的分析将name为commandLineArgs的PropertySource放到第一位,则org.springframework.core.env.AbstractEnvironment#propertySources的顺序到现在为:commandLineArgs、servletConfigInitParams 、servletContextInitParams 、jndiProperties 、systemProperties 、systemEnvironment,是不是和我们前面说的对照起来了?我们一直在说PropertySource,也一直在说PropertySource中的name,对于PropertySource我们可以理解为带name的、存放 name/value 的property pairs;那么其中的name我们应该如何理解呢?在org.springframework.core.env.MutablePropertySources中有这样一个方法:addAfter,其作用是将某个PropertySource的实例添加到某个name的PropertySource的后面,其源码如下所示: public void addAfter(String relativePropertySourceName, PropertySource<?> propertySource) { //确定所传入的relativePropertySourceName和所传入的propertySource的name不相同 assertLegalRelativeAddition(relativePropertySourceName, propertySource); //如果之前添加过此PropertySource 则移除 removeIfPresent(propertySource); //获取所传入的relativePropertySourceName的位置 int index = assertPresentAndGetIndex(relativePropertySourceName); //将传入的propertySource添加到相应的位置 addAtIndex(index + 1, propertySource); } 在上面的代码中有assertPresentAndGetIndex这样的一段代码比较重要: int index = assertPresentAndGetIndex(relativePropertySourceName); private int assertPresentAndGetIndex(String name) { //获取name为某个值的PropertySource的位置, int index = this.propertySourceList.indexOf(PropertySource.named(name)); if (index == -1) { throw new IllegalArgumentException("PropertySource named '" + name + "' does not exist"); } return index; } 在上面的代码中获取PropertySource的元素的位置的时候,是调用List中的indexOf方法来进行查找的,但是其参数为PropertySource.named(name)产生的对象,我们之前往propertySourceList中放入的明明是PropertySource类型的对象,这里在查找的时候为什么要用PropertySource.named(name)产生的对象来进行索引位置的查找呢?PropertySource.named(name)产生的对象又是什么呢? public static PropertySource<?> named(String name) { return new ComparisonPropertySource(name); } PropertySource.named(name)产生的对象是ComparisonPropertySource的实例,它也是PropertySource的一个子类,那么为什么用它也能查找到之前放入到propertySourceList中的元素的位置呢?通过翻开indexOf这个方法的源码我们知道,它是通过调用元素的equals方法来判断是否是同一个元素的,而凑巧的是在PropertySource中重写了equals这个方法: public boolean equals(Object obj) { return (this == obj || (obj instanceof PropertySource && ObjectUtils.nullSafeEquals(this.name, ((PropertySource<?>) obj).name))); } 到这里就很明显了,PropertySource中的name属性是用来判断是否是同一个元素的,即是否是同一个PropertySource的实例!我们在创建PropertySource类型的子类的时候都会传入一个name,直接用我们创建的PropertySource来进行位置的查找不就可以了吗?为什么还要创建出来一个ComparisonPropertySource类呢?通过翻看ComparisonPropertySource这个类的源码我们可以发现,在这个类中调用getSource、containsProperty、getProperty方法都会抛出异常,并且除了这三个方法之外没有多余的方法,如果直接用我们创建的PropertySource的话,保不齐你会重写它的equals方法,是不是?用ComparisonPropertySource的话,即使你在别的PropertySource实现类重写了PropertySource方法,在查找其顺序是也要按照Spring定义的规则来,并且ComparisonPropertySource只能做干查找元素位置这一件事,其他的事它什么也干不了,这又是不是设计模式中的某一个原则的体现呢?
在我们之前的web开发中,通常都是将应用打成war包或者将编译之后的应用放到tomcat的webapps目录下(其他的web服务器放到相应的目录下),但是我们在用SpringBoot进行web开发的时候,只是启动了一个main类,然后就会神奇的发现tomcat竟然也被启动了(SpringBoot也内置了Jetty),SpringBoot是怎么做到的呢?下面我将慢慢揭开它的神秘面纱: 我们之前说过在SpringBoot中web的上下文是AnnotationConfigEmbeddedWebApplicationContext这个类,我们先看简单的一下这个类的UML图: AnnotationConfigEmbeddedWebApplicationContext这个类继承了EmbeddedWebApplicationContext类,GenericWebApplicationContext类(这个要注意),它还实现了BeanDefinitionRegistry这个接口,还实现了ResourceLoader这个接口,这个类可以说是一个全能类了,这个个类我们先不多说,主要是看它的父类EmbeddedWebApplicationContext这个类。在这个类中重写了refresh方法、onRefresh方法、onClose方法、finishRefresh方法。这里我们先看onRefresh这个方法, @Override protected void onRefresh() { //调用父类的onRefresh方法(GenericWebApplicationContext) super.onRefresh(); try { //创建嵌入式的Servlet容器 createEmbeddedServletContainer(); } catch (Throwable ex) { throw new ApplicationContextException("Unable to start embedded container", ex); } } 这里我们要重点分析的就是createEmbeddedServletContainer这个方法。 private void createEmbeddedServletContainer() { //先获取embeddedServletContainer EmbeddedServletContainer localContainer = this.embeddedServletContainer; //获取Servlet上下文 ServletContext localServletContext = getServletContext(); //EmbeddedWebApplicationContext没有为EmbeddedServletContainer和ServletContext赋初值,也之前也没有调用set方法,所以这里都是为null if (localContainer == null && localServletContext == null) { //获取EmbeddedServletContainerFactory的实例 1) EmbeddedServletContainerFactory containerFactory = getEmbeddedServletContainerFactory(); //获取EmbeddedServletContainer 2) this.embeddedServletContainer = containerFactory .getEmbeddedServletContainer(getSelfInitializer()); } else if (localServletContext != null) { try { getSelfInitializer().onStartup(localServletContext); } catch (ServletException ex) { throw new ApplicationContextException("Cannot initialize servlet context", ex); } } //初始化属性信息3) initPropertySources(); } 我们先看1)处的代码: protected EmbeddedServletContainerFactory getEmbeddedServletContainerFactory() { // Use bean names so that we don't consider the hierarchy //从BeanFactory中获取EmbeddedServletContainerFactory类型的Bean,这里没有考虑父BeanFactory String[] beanNames = getBeanFactory() .getBeanNamesForType(EmbeddedServletContainerFactory.class); //如果没有获取到EmbeddedServletContainerFactory类型的Bean,则抛出异常 if (beanNames.length == 0) { throw new ApplicationContextException( "Unable to start EmbeddedWebApplicationContext due to missing " + "EmbeddedServletContainerFactory bean."); } //如果有一个以上的EmbeddedServletContainerFactory类型的Bean,则抛出异常 if (beanNames.length > 1) { throw new ApplicationContextException( "Unable to start EmbeddedWebApplicationContext due to multiple " + "EmbeddedServletContainerFactory beans : " + StringUtils.arrayToCommaDelimitedString(beanNames)); } //从BeanFactory中获取EmbeddedServletContainerFactory类型的Bean return getBeanFactory().getBean(beanNames[0], EmbeddedServletContainerFactory.class); } 上面的这一段代码,干了这样三件事,查看Spring容器中是否有EmbeddedServletContainerFactory类型的Bean,EmbeddedServletContainerFactory类型的Bean是否多于一个,从Spring容器中获取EmbeddedServletContainerFactory类型的Bean,那么EmbeddedServletContainerFactory的Bean是什么时候被注入到Spring容器中的呢?我们先看一下EmbeddedServletContainerFactory的层次结构: 从上图中我们可以看到,在SpringBoot中为我们内置了三种Web服务器的实现类,TomCat、Jetty、Undertow(没接触过)。我们之前说SpringBoot的四大神器,其中之一是自动配置的功能,关于自动配置的内容我们不做更多的展开,在SpringBoot中有这样一个类EmbeddedServletContainerAutoConfiguration 这个类配置在spring.factories中,它的key为org.springframework.boot.autoconfigure.EnableAutoConfiguration,这里你需要知道,在SpringBoot启动的时候会加载这个类,并且这个类上带有Configuration这个注解,所以这个类会被注入到Spring容器中,然后这个类要生效还要有一个条件,即当前环境是web环境!在EmbeddedServletContainerAutoConfiguration中有这样的一段代码: @Configuration @ConditionalOnClass({ Servlet.class, Tomcat.class }) @ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT) public static class EmbeddedTomcat { @Bean public TomcatEmbeddedServletContainerFactory tomcatEmbeddedServletContainerFactory() { return new TomcatEmbeddedServletContainerFactory(); } } 如果当前类路径下有Servlet和Tomcat这两个类,且在当前上下文中没有EmbeddedServletContainerFactory类型的Bean存在,则创建TomcatEmbeddedServletContainerFactory对象并注入到Spring容器中。SpringBoot中内置了TomCat相关的jar(spring-boot-starter-tomcat)。所以这里注入到Spring容器中的EmbeddedServletContainerFactory类型的Bean是TomcatEmbeddedServletContainerFactory。下面我们接着看2)处的代码: this.embeddedServletContainer = containerFactory .getEmbeddedServletContainer(getSelfInitializer()); 这里的containerFactory是TomcatEmbeddedServletContainerFactory的实例,所以这里调用的也是TomcatEmbeddedServletContainerFactory中的getEmbeddedServletContainer方法, @Override public EmbeddedServletContainer getEmbeddedServletContainer( ServletContextInitializer... initializers) { //创建一个TomCat对象 Tomcat tomcat = new Tomcat(); //创建web容器base目录 File baseDir = (this.baseDirectory != null ? this.baseDirectory : createTempDir("tomcat")); tomcat.setBaseDir(baseDir.getAbsolutePath()); //创建一个连接器 Connector connector = new Connector(this.protocol); //向tomcat的service中添加连接器 tomcat.getService().addConnector(connector); //定制化Connector customizeConnector(connector); tomcat.setConnector(connector); //自动部署设置为false tomcat.getHost().setAutoDeploy(false); //配置Engine configureEngine(tomcat.getEngine()); //想tomcat中添加其他的Connector for (Connector additionalConnector : this.additionalTomcatConnectors) { tomcat.getService().addConnector(additionalConnector); } //准备ServletContext的上下文 prepareContext(tomcat.getHost(), initializers); //创建TomcatEmbeddedServletContainer return getTomcatEmbeddedServletContainer(tomcat); } 上面的代码是不是看到很晕乎?准确的说上面的内容是TomCat的核心结构部分了。我们先从createTempDir这个简单的方法来开始: protected File createTempDir(String prefix) { try { //创建临时文件夹 File tempDir = File.createTempFile(prefix + ".", "." + getPort()); //如果之前这个文件夹存在的话,则删除这个文件 tempDir.delete(); //创建临时文件夹 tempDir.mkdir(); //当虚拟机退出的时候删除这个临时文件夹 tempDir.deleteOnExit(); return tempDir; } } 大家第一次看到TomCat这个类的时候是不是有点奇怪,怎么会有一个类叫做TomCat呢?既然敢叫TomCat,那肯定不一般,这个类也确实不一般,它整合了TomCat的整个生命周期,串联了TomCat的各个组件。我们接下来再继续说。
我们在上一节中说了SpringBoot的应用上下文的对象是AnnotationConfigEmbeddedWebApplicationContext,通过名字直译就是注解配置的可嵌入的web应用上下文。我们对它先不做过多的介绍,在不远的文章中我们就会对它进行一下简单的分析。 //上下文的一些准备动作 prepareContext(context, environment, listeners, applicationArguments,printedBanner); private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment, SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) { //将之前创建的环境变量的对象放入到应用上下文中 context.setEnvironment(environment); //像Spring容器中先行注入BeanNameGenerator和ResourceLoader的实例 postProcessApplicationContext(context); //调用之前在SpringBoot中加载的ApplicationContextInitializer的实现类,先进行一些初始化的动作 //在之前的文章中我分析过在SpringBoot启动的过程中会从META-INF/spring.factories中加载ApplicationContextInitializer的对象 applyInitializers(context); //这里是一个空实现 listeners.contextPrepared(context); if (this.logStartupInfo) { logStartupInfo(context.getParent() == null); logStartupProfileInfo(context); } //// Add boot specific singleton beans //将之前得到的的应用参数解析器注入到Spring容器中 context.getBeanFactory().registerSingleton("springApplicationArguments", applicationArguments); //将之前得到的的springBootBanner注入到Spring容器中 if (printedBanner != null) { context.getBeanFactory().registerSingleton("springBootBanner", printedBanner); } // Load the sources // 加载sources 这里是指我们的启动类 Set<Object> sources = getSources(); //sources不能为空 Assert.notEmpty(sources, "Sources must not be empty"); load(context, sources.toArray(new Object[sources.size()])); //监听上下文的变化 listeners.contextLoaded(context); } load(context, sources.toArray(new Object[sources.size()])); protected void load(ApplicationContext context, Object[] sources) { //创建BeanDefinition的加载器 直接new BeanDefinitionLoader BeanDefinitionLoader loader = createBeanDefinitionLoader( getBeanDefinitionRegistry(context), sources); //如果beanNameGenerator 对象不为空的话,则在BeanDefinitionLoader中set beanNameGenerator if (this.beanNameGenerator != null) { loader.setBeanNameGenerator(this.beanNameGenerator); } //如果resourceLoader 对象不为空的话,则在BeanDefinitionLoader中set resourceLoader if (this.resourceLoader != null) { loader.setResourceLoader(this.resourceLoader); } //如果environment 对象不为空的话,则在BeanDefinitionLoader中set environment if (this.environment != null) { loader.setEnvironment(this.environment); } //用BeanDefinitionLoader进行加载 loader.load(); } private BeanDefinitionRegistry getBeanDefinitionRegistry(ApplicationContext context) { //由于AnnotationConfigEmbeddedWebApplicationContext实现了BeanDefinitionRegistry接口,所以这里直接返回AnnotationConfigEmbeddedWebApplicationContext的实例 if (context instanceof BeanDefinitionRegistry) { return (BeanDefinitionRegistry) context; } //DefaultListableBeanFactory也实现了BeanDefinitionRegistry的接口 if (context instanceof AbstractApplicationContext) { return (BeanDefinitionRegistry) ((AbstractApplicationContext) context) .getBeanFactory(); } throw new IllegalStateException("Could not locate BeanDefinitionRegistry"); } public int load() { int count = 0; //这里的sources即是我们上面说的sources for (Object source : this.sources) { //load source count += load(source); } return count; } private int load(Object source) { Assert.notNull(source, "Source must not be null"); //如果是Class类型 if (source instanceof Class<?>) { return load((Class<?>) source); } //如果是Resource类型 if (source instanceof Resource) { return load((Resource) source); } if (source instanceof Package) { return load((Package) source); } if (source instanceof CharSequence) { return load((CharSequence) source); } //不是上面说的四种类型,则抛出异常 throw new IllegalArgumentException("Invalid source type " + source.getClass()); } private int load(Class<?> source) { //如果是Groovy环境 if (isGroovyPresent()) { // Any GroovyLoaders added in beans{} DSL can contribute beans here if (GroovyBeanDefinitionSource.class.isAssignableFrom(source)) { GroovyBeanDefinitionSource loader = BeanUtils.instantiateClass(source, GroovyBeanDefinitionSource.class); load(loader); } } //判断当前Class上面是否有Component注解 if (isComponent(source)) { //将启动应用主类注入到Spring容器中 由于SpringBoot的自动配置的特性,所以这里在注入Spring容器的过程中会判断应不应该将这个类注入到Spring容器中 this.annotatedReader.register(source); return 1; } return 0; } //这个方法的调用算是Spring容器开始启动工作了,这个过程是相当复杂我们以后会断断续续的分析点 refreshContext(context); 在这一篇文章中主要是分析了SpringBoot应用上下文的创建,和加载预先定义的几个Bean,像DefaultApplicationArguments、SpringBootBanner启动主类等。
在这篇文章中,我们接着上一篇的内容接着分析。 public ConfigurableApplicationContext run(String... args) { //启动应用的检测 StopWatch stopWatch = new StopWatch(); stopWatch.start(); //SpringBoot的上下文 ConfigurableApplicationContext context = null; //失败分析报告 FailureAnalyzers analyzers = null; configureHeadlessProperty(); //SpringBoot的runlistener SpringApplicationRunListeners listeners = getRunListeners(args); listeners.starting(); try { //参数解析 ApplicationArguments applicationArguments = new DefaultApplicationArguments( args); //配置环境变量 ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments); //输出Banner信息 Banner printedBanner = printBanner(environment); //创建应用上下文 context = createApplicationContext(); analyzers = new FailureAnalyzers(context); //refresh上下文之前的准备 prepareContext(context, environment, listeners, applicationArguments, printedBanner); refreshContext(context); afterRefresh(context, applicationArguments); listeners.finished(context, null); stopWatch.stop(); if (this.logStartupInfo) { new StartupInfoLogger(this.mainApplicationClass) .logStarted(getApplicationLog(), stopWatch); } return context; } catch (Throwable ex) { handleRunFailure(context, listeners, analyzers, ex); throw new IllegalStateException(ex); } } SpringApplication中的run方法的内容如上所示,上面就是整个SpringBoot应用启动的主要调用方法,run方法中的参数即是我们的应用参数。下面我们来简单的分析一下这个启动过程。 StopWatch主要是监控启动过程,统计启动时间,检测应用是否已经启动或者停止。 SpringApplicationRunListeners listeners = getRunListeners(args); private SpringApplicationRunListeners getRunListeners(String[] args) { Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class }; return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances( SpringApplicationRunListener.class, types, this, args)); } 通过在上一篇文章中的问题,对于getSpringFactoriesInstances这个方法你应该不陌生来吧。这里也是从META-INF/spring.factories中获取类型为org.springframework.boot.SpringApplicationRunListener的配置值,这个默认的配置值为:org.springframework.boot.context.event.EventPublishingRunListener。我们进入到EventPublishingRunListener这个类看一下它的构造函数 public EventPublishingRunListener(SpringApplication application, String[] args) { this.application = application; this.args = args; //创建一个SimpleApplicationEventMulticaster this.initialMulticaster = new SimpleApplicationEventMulticaster(); //把之前在SpringApplication中获取到的listener循环放入到SimpleApplicationEventMulticaster中 for (ApplicationListener<?> listener : application.getListeners()) { this.initialMulticaster.addApplicationListener(listener); } } 通过上面的分析,我们可以看到EventPublishingRunListener把SpringApplication中的监听器,都放到了SimpleApplicationEventMulticaster中,进行了统一的管理。listeners.starting();启动事件监听,这里以后我们单独开章节详细说明. //创建应用参数解析器 ApplicationArguments applicationArguments = new DefaultApplicationArguments( args); 我们看一下DefaultApplicationArguments的构造函数的内容: public DefaultApplicationArguments(String[] args) { //首先判断不能为null,这里大家可以想一下可变参数如果不传值的话看看是什么内容 Assert.notNull(args, "Args must not be null"); //调用Source对应用参数进行解析 this.source = new Source(args); this.args = args; } Source(String[] args) { //调用父类的构造函数 Source的继承关系如下图所示 super(args); } public SimpleCommandLinePropertySource(String... args) { //对应参数进行解析的工作 super(new SimpleCommandLineArgsParser().parse(args)); } 大家在配置应用参数的时候,是这样这样配置的 - -key=value,为什么要以- -开头呢?在SimpleCommandLineArgsParser的parse方法中你会找到答案。并且Source这个类还继承类PropertySource这个类。 //准备环境变量 ConfigurableEnvironment environment = prepareEnvironment(listeners,applicationArguments); private ConfigurableEnvironment prepareEnvironment( SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments) { //获取环境变量 ConfigurableEnvironment environment = getOrCreateEnvironment(); //将应用参数放入到环境变量持有对象中 configureEnvironment(environment, applicationArguments.getSourceArgs()); //监听器监听环境变量对象的变化 listeners.environmentPrepared(environment); //如果非web环境,则转换为StandardEnvironment对象 if (!this.webEnvironment) { environment = new EnvironmentConverter(getClassLoader()) .convertToStandardEnvironmentIfNecessary(environment); } return environment; } private ConfigurableEnvironment getOrCreateEnvironment() { //如果已经创建过存放环境变量的对象了,则直接返回 if (this.environment != null) { return this.environment; } //如果是web环境则创建StandardServletEnvironment对象 if (this.webEnvironment) { return new StandardServletEnvironment(); } //非web环境,创建StandardEnvironment return new StandardEnvironment(); } StandardServletEnvironment的UML图如下所示,StandardServletEnvironment集成了系统变量、环境变量、配置属性信息等内容。这些内容我们以后单开一个篇章来说一下。 //这句话是输出SpringBoot的Banner信息,可以从指定的位置加载信息,可以输出为文字形式,也可以输出为图片形式,如我们常见的SpringBoot的logo就是在这里输出的 Banner printedBanner = printBanner(environment); Banner的UML类图如下所示: 我们最常见的SpringBoot的logo“图像”就在SpringBootBanner这个类中定义的,这个也是SpringBoot默认的Banner类。 //创建SpringBoot的应用上下文 context = createApplicationContext(); protected ConfigurableApplicationContext createApplicationContext() { Class<?> contextClass = this.applicationContextClass; if (contextClass == null) { try { //DEFAULT_WEB_CONTEXT_CLASS = org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext /// DEFAULT_CONTEXT_CLASS = org.springframework.context.annotation.AnnotationConfigApplicationContext contextClass = Class.forName(this.webEnvironment ? DEFAULT_WEB_CONTEXT_CLASS : DEFAULT_CONTEXT_CLASS); } } return (ConfigurableApplicationContext) BeanUtils.instantiate(contextClass); } 因为我们是web开发环境,所以这里我们的web上下文是AnnotationConfigEmbeddedWebApplicationContext这个对象,一定要记住这个类,要仔细的看看它的UML类图。
我想很多人已经在项目中使用SpringBoot做项目开发的工作了,创建SpringBoot和启动SpringBoot应用都会较简单一点,下面我以SpringBoot官网上的Demo来简单的分析一些SpringBoot的启动流程,我们的启动主类代码如下: @SpringBootApplication public class SpringBootAnalysisApplication { public static void main(String[] args) { SpringApplication.run(SpringBootAnalysisApplication.class, args); } } 我们先来看一下SpringBootApplication这个注解上的注解: @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @SpringBootConfiguration @EnableAutoConfiguration @ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) }) @Inherited这个注解的意思是这个注解所在的类的子类可以继承这个注解。 @EnableAutoConfiguration这个注解的意思是开启自动配置。SpringBoot的自动配置功能是SpringBoot的四大神器之一。 @ComponentScan扫描包路径。 @SpringBootConfiguration这个注解的意思是使用SpringBootConfiguration这个注解相当用使用@Configuration这个注解(使用这个注解的类相当于beans)。 好了,言归正传,下面进入到我们的重点。我们首先进入到SpringApplication的run方法中看一下这个方法的内容: public static ConfigurableApplicationContext run(Object source, String... args) { //这里我们的第一个参数source的值是:SpringBootAnalysisApplication.class,在重载的run方法中将 //传入的SpringBootAnalysisApplication.class封装成了数组,也就是说我们可以调用重载的run方法,传入一个Object[],第二个参数是一个可变参数,是我们传入的启动参数。 return run(new Object[] { source }, args); } 在调用run方法启动SpringBoot容器的时候还有一点需要注意的是,调用run方法的时候会返回一个Spring上下文 ConfigurableApplicationContext的实例。 public static ConfigurableApplicationContext run(Object[] sources, String[] args) { return new SpringApplication(sources).run(args); } 上面这个run方法,干了两件事创建SpringApplication对象和调用另一个重载的run方法。我们先去看看SpringApplication的构造函数的内容: public SpringApplication(Object... sources) { //调用initialize方法进行一些初始化的动作。 initialize(sources); } private void initialize(Object[] sources) { if (sources != null && sources.length > 0) { //如果传入的sources有值的话,将Object[]对象转换为List。这里的sources是 //private final Set<Object> sources = new LinkedHashSet<Object>(); //是一个Set集合。 this.sources.addAll(Arrays.asList(sources)); } //判断是否是web环境 1) this.webEnvironment = deduceWebEnvironment(); //加载ApplicationContextInitializer类型的对象 2) setInitializers((Collection) getSpringFactoriesInstances( ApplicationContextInitializer.class)); //加载ApplicationListener类型的对象 3) setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)); //寻找启动主类 4) this.mainApplicationClass = deduceMainApplicationClass(); } 我们先看看看1)处的deduceWebEnvironment方法 private boolean deduceWebEnvironment() { //private static final String[] WEB_ENVIRONMENT_CLASSES = { "javax.servlet.Servlet", // "org.springframework.web.context.ConfigurableWebApplicationContext" }; //WEB_ENVIRONMENT_CLASSES是一个数组内容如上 for (String className : WEB_ENVIRONMENT_CLASSES) { //如果加载不到任何一个类就返回false if (!ClassUtils.isPresent(className, null)) { return false; } } return true; } 这里判断是不是web开发环境也很简单,就是看类路径下是能加载到javax.servlet.Servlet和org.springframework.web.context.ConfigurableWebApplicationContext这两个两类,如果能加载到则是web环境,否则非web环境。 我们接着看2)的代码: private <T> Collection<? extends T> getSpringFactoriesInstances(Class<T> type) { return getSpringFactoriesInstances(type, new Class<?>[] {}); } private <T> Collection<? extends T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) { //线程上下文加载器 ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); // Use names and ensure unique to protect against duplicates Set<String> names = new LinkedHashSet<String>( //关键代码 SpringFactoriesLoader.loadFactoryNames(type, classLoader)); //根据上一步获取到的类,创建实例对象,这里没什么多说的 List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names); //排序也没有什么多说的 AnnotationAwareOrderComparator.sort(instances); return instances; } 在上面的代码中最重要的就是SpringFactoriesLoader.loadFactoryNames(type, classLoader)这一句话。 loadFactoryNames的第一个方法是要加载的类的类型,第二个参数是类加载器。loadFactoryNames方法的内容如下所示: public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) { //获取要加载的类的全限定名这里是org.springframework.context.ApplicationContextInitializer String factoryClassName = factoryClass.getName(); try { //加载资源 从加载的是哪个资源呢?META-INF/spring.factories这个文件 //注意这里会加载所有类路径下的/META-INF/spring.factories,在Spring的相关jar包中基本上都有这个文件存在,当然也可以在自己的工程中自定义。 Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) : ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION)); List<String> result = new ArrayList<String>(); while (urls.hasMoreElements()) { URL url = urls.nextElement(); Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url)); //获取键为前面获取到的全限定类名的值,多个值用 , 分割 String factoryClassNames = properties.getProperty(factoryClassName); //用, 分割上面获取到的值 result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames))); } return result; } } 这里我们以org.springframework.context.ApplicationContextInitializer为例,看一下META-INF/spring.factories中键为ApplicationContextInitializer的配置情况: 从上图中我们发现在SpringBoot和SpringBootAutoConfigure中都有键为org.springframework.context.ApplicationContextInitializer的类存在并且值没有相同的,所以这里会加载到6个类型为org.springframework.context.ApplicationContextInitializer的实例。 通过我们的调试会发现,确实是加载到了六个org.springframework.context.ApplicationContextInitializer的实例。这几个上下文初始类,我们在后面再介绍。 对于3)的过程和2)处的过程一样,请参考上面的步骤。下面我们来看4)处的内容: private Class<?> deduceMainApplicationClass() { try { //获取运行时方法调用栈的信息 StackTraceElement[] stackTrace = new RuntimeException().getStackTrace(); for (StackTraceElement stackTraceElement : stackTrace) { //找到方法调用链上方法名为main的类 if ("main".equals(stackTraceElement.getMethodName())) { //返回main方法所在的类对象这里是 com.zkn.springboot.analysis.SpringBootAnalysisApplication return Class.forName(stackTraceElement.getClassName()); } } } return null; } 今天我们就先分析到这里,这篇文章中主要说了在启动SpringBoot的过程中创建SpringApplication的实例,并调用它的初始化方法来判断当前环境是不是web环境,获取主应用类,存放传入的sources类,加载org.springframework.context.ApplicationContextInitializer和org.springframework.context.ApplicationListener类型的对象。
我们在之前的文章中对HandlerMethodReturnValueHandler进行了简单的分析(SpringMVC之分析HandlerMethodReturnValueHandler(一)), 在 这篇文章中我们继续分析一下HandlerMethodReturnValueHandler这个类。有时候我们的请求映射处理方 法的返回值是View对象,当返回值是View对象时,会被ViewMethodReturnValueHandler这个类进行处 理;有时候我们方法的返回值是ModelAndView,返回值是ModelAndView时,会被ModelAndViewMethodReturnValueHandler类进行处理,返回值的类型有很多种,下面我用表格的形式列举一下: 返回值类型 处理类 String(方法上无ResponseBody注解) ViewNameMethodReturnValueHandler View ViewMethodReturnValueHandler ModelAndView ModelAndViewMethodReturnValueHandle Model ModelMethodProcessor Map MapMethodProcessor HttpHeaders HttpHeadersReturnValueHandler ModelAttribute(或者是自定义对象) ModelAttributeMethodProcessor HttpEntity HttpEntityMethodProcessor ResponseEntity ResponseBodyEmitterReturnValueHandler ResponseBody注解 RequestResponseBodyMethodProcessor 还有其他的几个异步的,不是很了解,这里就先不说了。这里我们需要重点说一下的是RequestResponseBodyMethodProcessor和ModelAttributeMethodProcessor这两个类,其他的看一下代码应该都能看的差不多。这两个类都是Processor结尾的类,不是ReturnValueHandler结尾的类,以Processor结尾的类有什么特殊的地方呢?特殊之处在于它同时实现了HandlerMethodArgumentResolver、HandlerMethodReturnValueHandler这两个接口。
在我们的开发中,NullPointerException可谓是随时随处可见,为了避免空指针异常,我们常常需要进行一 些防御式的检查,所以在代码中常常可见if(obj != null) 这样的判断。幸好在JDK1.8中,java为我们提供了 一个Optional类,Optional类能让我们省掉繁琐的非空的判断。下面先说一下Optional中为我们提供的方法 方法 描述 of 把指定的值封装为Optional对象,如果指定的值为null,则抛出NullPointerException empty 创建一个空的Optional对象 ofNullable 把指定的值封装为Optional对象,如果指定的值为null,则创建一个空的Optional对象 get 如果创建的Optional中有值存在,则返回此值,否则抛出NoSuchElementException orElse 如果创建的Optional中有值存在,则返回此值,否则返回一个默认值 orElseGet 如果创建的Optional中有值存在,则返回此值,否则返回一个由Supplier接口生成的值 orElseThrow 如果创建的Optional中有值存在,则返回此值,否则抛出一个由指定的Supplier接口生成的异常 filter 如果创建的Optional中的值满足filter中的条件,则返回包含该值的Optional对象,否则返回一个空的Optional对象 map 如果创建的Optional中的值存在,对该值执行提供的Function函数调用 flagMap 如果创建的Optional中的值存在,就对该值执行提供的Function函数调用,返回一个Optional类型的值,否则就返回一个空的Optional对象 isPresent 如果创建的Optional中的值存在,返回true,否则返回false ifPresent 如果创建的Optional中的值存在,则执行该方法的调用,否则什么也不做 下面我们写几个例子来具体看一下每个方法的作用: of //创建一个值为张三的String类型的Optional Optional<String> ofOptional = Optional.of("张三"); //如果我们用of方法创建Optional对象时,所传入的值为null,则抛出NullPointerException如下图所示 Optional<String> nullOptional = Optional.of(null); ofNullable //为指定的值创建Optional对象,不管所传入的值为null不为null,创建的时候都不会报错 Optional<String> nullOptional = Optional.ofNullable(null); Optional<String> nullOptional = Optional.ofNullable("lisi"); empty //创建一个空的String类型的Optional对象 Optional<String> emptyOptional = Optional.empty(); get 如果我们创建的Optional对象中有值存在则返回此值,如果没有值存在,则会抛出 NoSuchElementException异常。小demo如下: Optional<String> stringOptional = Optional.of("张三"); System.out.println(stringOptional.get()); Optional<String> emptyOptional = Optional.empty(); System.out.println(emptyOptional.get()); orElse 如果创建的Optional中有值存在,则返回此值,否则返回一个默认值 Optional<String> stringOptional = Optional.of("张三"); System.out.println(stringOptional.orElse("zhangsan")); Optional<String> emptyOptional = Optional.empty(); System.out.println(emptyOptional.orElse("李四")); orElseGet 如果创建的Optional中有值存在,则返回此值,否则返回一个由Supplier接口生成的值 Optional<String> stringOptional = Optional.of("张三"); System.out.println(stringOptional.orElseGet(() -> "zhangsan")); Optional<String> emptyOptional = Optional.empty(); System.out.println(emptyOptional.orElseGet(() -> "orElseGet")); orElseThrow 如果创建的Optional中有值存在,则返回此值,否则抛出一个由指定的Supplier接口生成的异常 Optional<String> stringOptional = Optional.of("张三"); System.out.println(stringOptional.orElseThrow(CustomException::new)); Optional<String> emptyOptional = Optional.empty(); System.out.println(emptyOptional.orElseThrow(CustomException::new)); private static class CustomException extends RuntimeException { private static final long serialVersionUID = -4399699891687593264L; public CustomException() { super("自定义异常"); } public CustomException(String message) { super(message); } } filter 如果创建的Optional中的值满足filter中的条件,则返回包含该值的Optional对象,否则返回一个空的Optional对象 Optional<String> stringOptional = Optional.of("zhangsan"); System.out.println(stringOptional.filter(e -> e.length() > 5).orElse("王五")); stringOptional = Optional.empty(); System.out.println(stringOptional.filter(e -> e.length() > 5).orElse("lisi")); 注意Optional中的filter方法和Stream中的filter方法是有点不一样的,Stream中的filter方法是对一堆元素进 行过滤,而Optional中的filter方法只是对一个元素进行过滤,可以把Optional看成是最多只包含一个元素 的Stream。 map 如果创建的Optional中的值存在,对该值执行提供的Function函数调用 //map方法执行传入的lambda表达式参数对Optional实例的值进行修改,修改后的返回值仍然是一个Optional对象 Optional<String> stringOptional = Optional.of("zhangsan"); System.out.println(stringOptional.map(e -> e.toUpperCase()).orElse("失败")); stringOptional = Optional.empty(); System.out.println(stringOptional.map(e -> e.toUpperCase()).orElse("失败")); flagMap 如果创建的Optional中的值存在,就对该值执行提供的Function函数调用,返回一个Optional类型的值,否 则就返回一个空的Optional对象.flatMap与map(Funtion)方法类似,区别在于flatMap中的mapper返回 值必须是Optional,map方法的mapping函数返回值可以是任何类型T。调用结束时,flatMap不会对结果 用Optional封装。 //map方法中的lambda表达式返回值可以是任意类型,在map函数返回之前会包装为Optional。 //但flatMap方法中的lambda表达式返回值必须是Optionl实例 Optional<String> stringOptional = Optional.of("zhangsan"); System.out.println(stringOptional.flatMap(e -> Optional.of("lisi")).orElse("失败")); stringOptional = Optional.empty(); System.out.println(stringOptional.flatMap(e -> Optional.empty()).orElse("失败")); ifPresent //ifPresent方法的参数是一个Consumer的实现类,Consumer类包含一个抽象方法,该抽象方法对传入的值进行处理,只处理没有返回值。 Optional<String> stringOptional = Optional.of("zhangsan"); stringOptional.ifPresent(e-> System.out.println("我被处理了。。。"+e));
我们在之前的文章中简单的说过SpringBoot的CommandLineRunner和ApplicationRunner这两个接口 SpringBoot之CommandLineRunner接口和ApplicationRunner接口,这篇文章中我们从源码上简单的分析一下这两个 接口。在org.springframework.boot.SpringApplication#run()这个方法中有这样一段代码: afterRefresh(context, applicationArguments); 方法内容如下: protected void afterRefresh(ConfigurableApplicationContext context, ApplicationArguments args) { callRunners(context, args); } SpringBoot的注释中说,在上下文刷新完之后调用这个方法。在调用这个方法的时候Spring容器已经启动完 成了。这里的context的真正对象是:AnnotationConfigEmbeddedWebApplicationContext,这个类贯 穿着SpringBoot的整个启动过程。我们看一下callRunners这个方法的内容: private void callRunners(ApplicationContext context, ApplicationArguments args) { List<Object> runners = new ArrayList<Object>(); //从Spring容器中查找类型为ApplicationRunner的Bean runners.addAll(context.getBeansOfType(ApplicationRunner.class).values()); //从Spring容器中查找类型为CommandLineRunner的Bean runners.addAll(context.getBeansOfType(CommandLineRunner.class).values()); //将上一步得到的Bean进行排序 AnnotationAwareOrderComparator.sort(runners); for (Object runner : new LinkedHashSet<Object>(runners)) { //如果是ApplicationRunner的实例 if (runner instanceof ApplicationRunner) { callRunner((ApplicationRunner) runner, args); } //如果是CommandLineRunner的实例 if (runner instanceof CommandLineRunner) { callRunner((CommandLineRunner) runner, args); } } } callRunner方法的内容就很简单了直接调用run方法。 private void callRunner(ApplicationRunner runner, ApplicationArguments args) { try { (runner).run(args); } catch (Exception ex) { throw new IllegalStateException("Failed to execute ApplicationRunner", ex); } } ApplicationRunner和CommandLineRunner的区别就是run方法参数不同,ApplicationRunner中run方法 的参数是ApplicationArguments,CommandLineRunner中run方法的参数是String类型的可变参数。。
SpringBoot自动配置的特性,很大程度上解放了我们繁琐的配置的工作,但是也向我们屏蔽了很多内部运行 的细节,好在SpringBoot为我们提供了Actuator,Actuator在应用程序里提供了众多的Web端点,通过它 们,我们可以了解应用程序运行时的内部状况。我们可以了解Bean的组装信息,获取环境配置信息,等等 Actuator为我们提供了如下的一些端口 HTTP方法 路径 描述 Sensitive Default GET /autoconfig 自动配置报告,记录哪些自动配置条件通过了,哪些没通过 true GET /configprops 自动配置的属性信息 true GET /beans 应用程序上下文里全部的Bean,以及它们的关系 true GET /dump 获取线程活动的快照 true GET /env 获取全部环境属性,包含环境变量和配置属性信息 true GET /env/{name} 根据名称获取特定的环境属性值 true GET /health 报告应用程序的健康指标,这些值由HealthIndicator的实现类提供 false GET /info 获取应用程序的定制信息,这些信息由info打头的属性提供 false GET /mappings 全部的URI路径,以及它们和控制器(包含Actuator端点)的映射关系 true GET /metrics 报告各种应用程序度量信息,比如内存用量和HTTP请求计数 true GET /metrics/{name} 报告指定名称的应用程序度量值 true GET /shutdown 优雅的关闭应用程序(endpoints.shutdown.enabled设置为true才会生效) true GET /trace 提供基本的HTTP请求跟踪信息(时间戳、HTTP头等) true GET /loggers 展示应用程序中的日志配置信息 true GET /heapdump 当访问这个请求的时候,会下载一个压缩的hprof的堆栈信息文件 true 我们如果要使用Actuator的话也很简单,我们首先在pom文件中引入Actuator的依赖就行了,如下: <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> 然后再添加一个配置项即可: management: security: enabled: false 下面我们访问几个来看一下功能(端口号换成自己应用的端口号): 请求:http://localhost:8003/beans 效果如下: 在上面的图片中,Actuator为我们展示了一个Bean的信息,Bean的名字(bean)、别名(aliases)、 scope(singleton or prototype)、类型(type)、位置(resource绝对路径)、依赖(dependencies) 请求:http://localhost:8003/autoconfig 效果如下: SpringBoot的另一个重要的特性是自动配置,当我们访问/autoconfig的时候,Actuator为我们输出了 autoconfig的一些信息,自动配置这个bean需要什么样的条件。 其他的一些Actuator端点也很有意思,例如:/configprops、/mappings、/heapdump等。当然我们也可以自定义Actuator来满足自己的功能需要,demo如下所示: package com.zkn.springboot.exercise.endpoint; import org.springframework.boot.actuate.endpoint.AbstractEndpoint; import org.springframework.stereotype.Component; import java.lang.management.*; import java.util.HashMap; import java.util.Map; /** * @author zkn * @date 2017/11/15 22:50 */ @Component public class ThreadInfoEndpoint extends AbstractEndpoint<Map<String, String>> { public ThreadInfoEndpoint() { //id super("threadInfo"); } /** * Called to invoke the endpoint. * * @return the results of the invocation */ @Override public Map<String, String> invoke() { ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean(); //获取所有的线程信息 ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(true, true); if (threadInfos != null && threadInfos.length > 0) { Map<String, String> map = new HashMap<>(threadInfos.length); for (ThreadInfo threadInfo : threadInfos) { map.put(threadInfo.getThreadName(), getThreadDumpString(threadInfo)); } return map; } return null; } /** * 组装线程信息 * * @param threadInfo */ private String getThreadDumpString(ThreadInfo threadInfo) { StringBuilder sb = new StringBuilder("threadName:" + threadInfo.getThreadName() + ",threadId:" + threadInfo.getThreadId() + ",threadStatus:" + threadInfo.getThreadState()); //锁的名字 if (threadInfo.getLockName() != null) { sb.append(",lockName:" + threadInfo.getLockName()); } //锁的持有者 if (threadInfo.getLockOwnerName() != null) { sb.append(",lockOwnerName:" + threadInfo.getLockOwnerName()); } //线程中断 if (threadInfo.isSuspended()) { sb.append(",suspended:" + threadInfo.isSuspended()); } if (threadInfo.isInNative()) { sb.append(",inNative:" + threadInfo.isInNative()); } sb.append("\n"); StackTraceElement[] stackTraceElementst = threadInfo.getStackTrace(); MonitorInfo[] monitorInfos = threadInfo.getLockedMonitors(); StackTraceElement stackTraceElement; if (stackTraceElementst != null) { int i; for (i = 0; i < stackTraceElementst.length; i++) { stackTraceElement = stackTraceElementst[i]; sb.append(",stackTraceElement:" + i + ";" + stackTraceElement.toString()); if (i == 0 && threadInfo.getLockInfo() != null) { Thread.State ts = threadInfo.getThreadState(); switch (ts) { case BLOCKED: sb.append("\t- blocked on " + threadInfo.getLockInfo()); sb.append('\n'); break; case WAITING: sb.append("\t- waiting on " + threadInfo.getLockInfo()); sb.append('\n'); break; case TIMED_WAITING: sb.append("\t- waiting on " + threadInfo.getLockInfo()); sb.append('\n'); break; default: } } for (MonitorInfo mi : monitorInfos) { if (mi.getLockedStackDepth() == i) { sb.append("\t- locked " + mi); sb.append('\n'); } } } if (i < stackTraceElementst.length) { sb.append("\t..."); sb.append('\n'); } LockInfo[] locks = threadInfo.getLockedSynchronizers(); if (locks.length > 0) { sb.append("\n\tNumber of locked synchronizers = " + locks.length); sb.append('\n'); for (LockInfo li : locks) { sb.append("\t- " + li); sb.append('\n'); } } sb.append('\n'); } return sb.toString(); } } 关键点是:一:继承AbstractEndpoint这个类(注意泛型类型),二:写一个无参的构造函数,调用父类的一 个有参构造函数,传入一个id描述(id描述会映射为响应的请求),三:重写invoke方法。在 AbstractEndpoint中注入Environment,所以你可以通过Environment获取系统环境变量中的值。
之前同事在做一个功能的时候,需要导出一些数据,但是组装数据量的过程比较麻烦一点,所以这里给了一个建议 看看能不能使用多线程的方法来进行处理。顺便写了两个demo,一种方式是批量分段去往一个sheet页中写入数 据,另一种方式是往多个sheet也中写入数据。demo如下: 批量分段导出数据 package com.zkn.newlearn.opensource.poi; /** * Created by zkn on 2017/11/15. */ import com.google.common.util.concurrent.ThreadFactoryBuilder; import org.apache.poi.hssf.usermodel.*; import org.apache.poi.ss.usermodel.CellStyle; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.util.concurrent.*; /** * @author zkn * @date 2017/11/15 23:34 */ public class CreateMultipleSheet { public static void main(String[] args) { //处理器核心数 int processor = Runtime.getRuntime().availableProcessors(); //HSSFWorkbook 一个sheet页只能写入六万多条数据 HSSFWorkbook workBook = new HSSFWorkbook(); //创建格式 CellStyle style = workBook.createCellStyle(); //居中格式 style.setAlignment(HSSFCellStyle.ALIGN_CENTER); //创建sheet页 HSSFSheet sheet = workBook.createSheet(); //创建一行 HSSFRow hssfRow = sheet.createRow(0); HSSFCell hssfCell = hssfRow.createCell(0); hssfCell.setCellStyle(style); hssfCell.setCellValue("第" + 1 + "个sheet页,第一行,第一个单元格"); hssfCell = hssfRow.createCell(1); hssfCell.setCellStyle(style); hssfCell.setCellValue("第" + 1 + "个sheet页,第一行,第二个单元格"); hssfCell = hssfRow.createCell(2); hssfCell.setCellStyle(style); hssfCell.setCellValue("第" + 1 + "个sheet页,第一行,第三个单元格"); //手工创建线程池 ExecutorService executorService = new ThreadPoolExecutor(processor, processor, 1000, TimeUnit.MILLISECONDS, new LinkedBlockingDeque(), new ThreadFactoryBuilder().setNameFormat("poi-task-%d").build()); //计数器 等待线程池中的线程执行完毕 CountDownLatch countDownLatch = new CountDownLatch(processor); for (int i = 1; i <= processor; i++) { int start = (i - 1) * 100 + 1; int end = i * 100; //放入线程池中 executorService.execute(() -> createRows(sheet, start, end, countDownLatch)); } try { //等待所有线程执行完毕 countDownLatch.await(); //关闭线程池 executorService.shutdown(); } catch (InterruptedException e) { e.printStackTrace(); } FileOutputStream fou = null; try { fou = new FileOutputStream("D:\\LearnVideo\\multiSheet.xls"); workBook.write(fou); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { if (fou != null) { try { fou.close(); } catch (IOException e) { e.printStackTrace(); } } } } private static void createRows(HSSFSheet hSSFSheet, int startRow, int endRow, CountDownLatch countDownLatch) { HSSFRow hssfRows; HSSFCell hSSFCells; int i = startRow; try { while (i <= endRow) { hssfRows = getRows(hSSFSheet, i); hSSFCells = hssfRows.createCell(0); hSSFCells.setCellValue("第" + (i + 1) + "行,第一个单元格"); hSSFCells = hssfRows.createCell(1); hSSFCells.setCellValue("第" + (i + 1) + "行,第一个单元格"); hSSFCells = hssfRows.createCell(2); hSSFCells.setCellValue("第" + (i + 1) + "行,第一个单元格"); ++i; } } finally { countDownLatch.countDown(); } } /** * 创建表格 这里要加锁 * * @param hSSFSheet * @param row * @return */ private static HSSFRow getRows(HSSFSheet hSSFSheet, int row) { synchronized (Object.class) { return hSSFSheet.createRow(row); } } } 向多个sheet页中写入数据 package com.zkn.newlearn.opensource.poi; import com.google.common.util.concurrent.ThreadFactoryBuilder; import org.apache.poi.hssf.usermodel.*; import org.apache.poi.ss.usermodel.CellStyle; import org.apache.poi.xssf.streaming.SXSSFCell; import org.apache.poi.xssf.streaming.SXSSFRow; import org.apache.poi.xssf.streaming.SXSSFSheet; import org.apache.poi.xssf.streaming.SXSSFWorkbook; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.util.concurrent.*; /** * @author zkn * @date 2017/11/15 23:34 */ public class CreateMultipleSheetNew { private final static Object object = new Object(); public static void main(String[] args) { //处理器核心数 int processor = Runtime.getRuntime().availableProcessors(); //XSSFWorkbook 一次只能写入六万多条数据,所以这里最好使用SXSSFWorkbook SXSSFWorkbook workBook = new SXSSFWorkbook(); //创建格式 CellStyle style = workBook.createCellStyle(); //居中格式 style.setAlignment(HSSFCellStyle.ALIGN_CENTER); //手工创建线程池 ExecutorService executorService = new ThreadPoolExecutor(processor, processor, 1000, TimeUnit.MILLISECONDS, new LinkedBlockingDeque(), new ThreadFactoryBuilder().setNameFormat("poi-task-%d").build()); //计数器 等待线程池中的线程执行完毕 CountDownLatch countDownLatch = new CountDownLatch(processor); for (int i = 0; i < processor; i++) { int sheetId = i; //放入线程池中 executorService.execute(() -> createSheet(workBook, style, sheetId, countDownLatch)); } try { //等待所有线程执行完毕 countDownLatch.await(); executorService.shutdown(); } catch (InterruptedException e) { e.printStackTrace(); } FileOutputStream fou = null; try { fou = new FileOutputStream("D:\\LearnVideo\\multiSheetNew.xls"); workBook.write(fou); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { if (fou != null) { try { fou.close(); } catch (IOException e) { e.printStackTrace(); } } } } private static void createSheet(SXSSFWorkbook workBook, CellStyle style, int sheetId, CountDownLatch countDownLatch) { try { SXSSFSheet hSSFSheet; //这个地方一定要加锁,要不然会出现问题 synchronized (object) { //创建sheet页 hSSFSheet = workBook.createSheet(String.format("第%d个sheet页", sheetId)); } //创建一行 SXSSFRow hssfRow = hSSFSheet.createRow(0); SXSSFCell hssfCell = hssfRow.createCell(0); hssfCell.setCellStyle(style); hssfCell.setCellValue("第" + sheetId + "个sheet页,第一行,第一个单元格"); hssfCell = hssfRow.createCell(1); hssfCell.setCellStyle(style); hssfCell.setCellValue("第" + sheetId + "个sheet页,第一行,第二个单元格"); hssfCell = hssfRow.createCell(2); hssfCell.setCellStyle(style); hssfCell.setCellValue("第" + sheetId + "个sheet页,第一行,第三个单元格"); SXSSFRow hssfRows; SXSSFCell hSSFCells; for (int i = 1; i < 3; i++) { hssfRows = hSSFSheet.createRow(i); hSSFCells = hssfRows.createCell(0); hSSFCells.setCellStyle(style); hSSFCells.setCellValue("第" + sheetId + "个sheet页,第" + (i + 1) + "行,第一个单元格"); hSSFCells = hssfRows.createCell(1); hSSFCells.setCellStyle(style); hSSFCells.setCellValue("第" + sheetId + "个sheet页,第一个单元格"); hSSFCells = hssfRows.createCell(2); hSSFCells.setCellStyle(style); hSSFCells.setCellValue("第" + sheetId + "个sheet页,第一个单元格"); } } finally { //计数器减一 countDownLatch.countDown(); } } } 这里说一下,对于HSSFWorkbook一个sheet页最多只能写入65535条数据,如果你导出的数量比较大的话,可以导出到多个sheet页,或者改用SXSSFWorkbook,另外可能会出现内存溢出的问题。
我们在用SpringMVC做web开发的时候,有时候处理完一个请求之后会返回一个页面,有时候会返回一个字符串,有时候会返回一个json对象。通过分析源码我们知道在调用请求处理器映射方法的时候走的是同一段代码,如下: org.springframework.web.method.support.InvocableHandlerMethod#doInvoke protected Object doInvoke(Object... args) throws Exception { //反射设置setAccessible为true ReflectionUtils.makeAccessible(getBridgedMethod()); try { //反射调用请求处理器映射方法 return getBridgedMethod().invoke(getBean(), args); } ............ }那么SpringMVC是在哪里进行不同返回对象的映射的呢?在org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod#invokeAndHandle中有这样一段代码: try { this.returnValueHandlers.handleReturnValue( returnValue, getReturnValueType(returnValue), mavContainer, webRequest); }这里的returnValueHandlers是HandlerMethodReturnValueHandlerComposite的实例(参考之前对HandlerMethodArgumentResolver的分析)。getReturnValueType(returnValue)获取到的是ReturnValueMethodParameter对象。这一段代码是用来处理不同的请求返回值的。所以我们不同请求的返回是由HandlerMethodReturnValueHandler的不同实现类来进行处理的。我们先看一下HandlerMethodReturnValueHandler这个接口的UML类图: 在HandlerMethodReturnValueHandler接口中有这样的两个方法: //支持的返回值类型 boolean supportsReturnType(MethodParameter returnType); //处理返回值 void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception; 接下来我试着分析一下常用的几个不同返回值类型的场景。 首先我们先进入到org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#invokeHandlerMethod这个方法中,在这个方法中有这样的一句话: invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers); 通过之前的分析我们知道这个returnValueHandlers是在RequestMappingHandlerAdapter的afterPropertiesSet方法中实例化的HandlerMethodReturnValueHandlerComposite。(参考SpringMVC之分析RequestMappingHandlerAdapter(二))。接着会调用org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod#invokeAndHandle方法,从而调用org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite#handleReturnValue方法,我们进入到handleReturnValue这个方法中看一下: public void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception { HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);//选择对应的HandlerMethodReturnValueHandler if (handler == null) { throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName()); }//对返回值进行处理 handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest); } private HandlerMethodReturnValueHandler selectHandler(Object value, MethodParameter returnType) { boolean isAsyncValue = isAsyncReturnValue(value, returnType); for (HandlerMethodReturnValueHandler handler : this.returnValueHandlers) {//循环之前放入的HandlerMethodReturnValueHandler的实现类 if (isAsyncValue && !(handler instanceof AsyncHandlerMethodReturnValueHandler)) { continue; }//调用supportsReturnType方法判断是否支持对返回值的处理 if (handler.supportsReturnType(returnType)) { return handler; } } return null; }在上面的这两个方法中通过调用HandlerMethodReturnValueHandler的supportsReturnType方法来判断支持返回值类型的处理类,通过调用HandlerMethodReturnValueHandler的handleReturnValue方法来对返回值进行处理。下面我们具体的分析几个场景。ViewNameMethodReturnValueHandler 在分析这个类之前,我们先看一个例子: @Controller @RequestMapping("/returnValueHandler") public class ReturnValueHandlerController { @RequestMapping("/viewNameMethod") public String viewNameMethodMapping() { return "viewNameMethod"; } }我们访问一下这个请求:http://localhost:8086/returnValueHandler/viewNameMethod 这个错误很多人应该都见过吧。请求处理的结果是404。错误提示信息是:在/WEB-INF/jsp这个目录下找不到viewNameMethod.jsp这个文件。我们返回的是一个字符串,为什么这里是要去寻找viewNameMethod.jsp这个页面呢?秘密就在ViewNameMethodReturnValueHandler这个类中。在之前的分析中(SpringMVC之分析RequestMappingHandlerAdapter(二))我们知道,在RequestMappingHandlerAdapter中默认添加了一些列的HandlerMethodReturnValueHandler实现类,ViewNameMethodReturnValueHandler就在其中。我们看一下ViewNameMethodReturnValueHandler的supportsReturnType方法的内容: @Override public boolean supportsReturnType(MethodParameter returnType) { Class<?> paramType = returnType.getParameterType(); return (void.class == paramType || CharSequence.class.isAssignableFrom(paramType)); }如果返回值是void或者是CharSequence类型(通常是值字符串),则可以使用ViewNameMethodReturnValueHandler这个类来处理。现在我们的返回值是String类型的,ViewNameMethodReturnValueHandler支持对String返回值的处理,所以我们这里获取到的HandlerMethodReturnValueHandler的实现类为ViewNameMethodReturnValueHandler。接下来我们看一下handleReturnValue这个方法的源码: @Override public void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception { if (returnValue instanceof CharSequence) {//如果是字符类型 String viewName = returnValue.toString();//获取字符串的值 mavContainer.setViewName(viewName);//设置viewName的值 if (isRedirectViewName(viewName)) {//判断是不是重定向 mavContainer.setRedirectModelScenario(true);//设置为重定向 } } else if (returnValue != null){//如果不是字符串类型,则抛出异常 // should not happen throw new UnsupportedOperationException("Unexpected return type: " + returnType.getParameterType().getName() + " in method: " + returnType.getMethod()); } }在上面的分析中我们可以看到,ViewNameMethodReturnValueHandler将我们返回的字符串映射为了视图名,所以这里就会走视图解析的流程,查找相应的视图,如果我们没有创建这个视图的话,就会报404的异常了。在上面的代码中还有一句isRedirectViewName。这个方法是判断请求是不是重定向到另外一个请求上。isRedirectViewName的源码如下: protected boolean isRedirectViewName(String viewName) { return (PatternMatchUtils.simpleMatch(this.redirectPatterns, viewName) || viewName.startsWith("redirect:")); }如果返回的字符串是redirect:开头的,会认为这个请求会重定向到另外一个请求上面。
我们在之前的文章中对这篇文章中AnnotationDrivenBeanDefinitionParser的parse方法进行了一些分析,我们在这篇文章中接着分析AnnotationDrivenBeanDefinitionParser的parse方法的内容。 ManagedList<?> messageConverters = getMessageConverters(element, source, parserContext); private ManagedList<?> getMessageConverters(Element element, Object source, ParserContext parserContext) { //解析message-converters的标签 //格式如下: /* <mvc:annotation-driven> <mvc:message-converters> <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"/> <bean class="org.springframework.http.converter.StringHttpMessageConverter"> <property name="supportedMediaTypes"> <list> <value>text/html;charset=UTF-8</value> <value>application/javascript;charset=UTF-8</value> </list> </property> </bean> </mvc:message-converters> </mvc:annotation-driven> */ Element convertersElement = DomUtils.getChildElementByTagName(element, "message-converters"); //存放自己配置的HttpMessageConverter ManagedList<? super Object> messageConverters = new ManagedList<Object>(); if (convertersElement != null) { messageConverters.setSource(source); //解析自己配置的HttpMessageConverter的bean for (Element beanElement : DomUtils.getChildElementsByTagName(convertersElement, "bean", "ref")) { Object object = parserContext.getDelegate().parsePropertySubElement(beanElement, null); messageConverters.add(object); } } //如果没有配置<mvc:message-converters>或者register-defaults的值为true 默认为true if (convertersElement == null || Boolean.valueOf(convertersElement.getAttribute("register-defaults"))) { messageConverters.setSource(source); //添加ByteArrayHttpMessageConverter的BeanDefinition messageConverters.add(createConverterDefinition(ByteArrayHttpMessageConverter.class, source)); //添加StringHttpMessageConverter的BeanDefinition RootBeanDefinition stringConverterDef = createConverterDefinition(StringHttpMessageConverter.class, source); stringConverterDef.getPropertyValues().add("writeAcceptCharset", false); messageConverters.add(stringConverterDef); //添加ResourceHttpMessageConverter的BeanDefinition messageConverters.add(createConverterDefinition(ResourceHttpMessageConverter.class, source)); //添加SourceHttpMessageConverter的BeanDefinition messageConverters.add(createConverterDefinition(SourceHttpMessageConverter.class, source)); //添加AllEncompassingFormHttpMessageConverter的BeanDefinition messageConverters.add(createConverterDefinition(AllEncompassingFormHttpMessageConverter.class, source)); //如果有Classpath下面有 com.rometools.rome.feed.WireFeed,则添加 //AtomFeedHttpMessageConverter和RssChannelHttpMessageConverter的BeanDefinition if (romePresent) { messageConverters.add(createConverterDefinition(AtomFeedHttpMessageConverter.class, source)); messageConverters.add(createConverterDefinition(RssChannelHttpMessageConverter.class, source)); } //如果有Classpath下面有 com.fasterxml.jackson.dataformat.xml.XmlMapper,则添加 //MappingJackson2XmlHttpMessageConverter和RssChannelHttpMessageConverter的BeanDefinition if (jackson2XmlPresent) { RootBeanDefinition jacksonConverterDef = createConverterDefinition(MappingJackson2XmlHttpMessageConverter.class, source); //创建Jackson2ObjectMapperFactoryBean的BeanDefinition GenericBeanDefinition jacksonFactoryDef = createObjectMapperFactoryDefinition(source); jacksonFactoryDef.getPropertyValues().add("createXmlMapper", true); jacksonConverterDef.getConstructorArgumentValues().addIndexedArgumentValue(0, jacksonFactoryDef); messageConverters.add(jacksonConverterDef); } //如果有Classpath下面有 javax.xml.bind.Binder,则添加 //Jaxb2RootElementHttpMessageConverter的BeanDefinition else if (jaxb2Present) { messageConverters.add(createConverterDefinition(Jaxb2RootElementHttpMessageConverter.class, source)); } //如果有Classpath下面有 com.fasterxml.jackson.databind.ObjectMapper和Jaxb2RootElementHttpMessageConverter的BeanDefinition,则添加 //MappingJackson2HttpMessageConverter的BeanDefinition if (jackson2Present) { RootBeanDefinition jacksonConverterDef = createConverterDefinition(MappingJackson2HttpMessageConverter.class, source); //创建Jackson2ObjectMapperFactoryBean的BeanDefinition GenericBeanDefinition jacksonFactoryDef = createObjectMapperFactoryDefinition(source); //添加到MappingJackson2HttpMessageConverter的构造函数中 jacksonConverterDef.getConstructorArgumentValues().addIndexedArgumentValue(0, jacksonFactoryDef); messageConverters.add(jacksonConverterDef); } //如果有Classpath下面有 com.google.gson.Gson,则添加 //GsonHttpMessageConverter的BeanDefinition else if (gsonPresent) { messageConverters.add(createConverterDefinition(GsonHttpMessageConverter.class, source)); } } return messageConverters; }上面的这段内容主要是添加一些自己配置的HttpMessageConverter的BeanDefinition或者是一些初始化的HttpMessageConverter的BeanDefinitionManagedList<?> argumentResolvers = getArgumentResolvers(element, parserContext); 这一段的内容主要是获取自定义的一些参数解析器(HandlerMethodArgumentResolver的实现类) ManagedList<?> returnValueHandlers = getReturnValueHandlers(element, parserContext);这一段是配置自定义的HandlerMethodReturnValueHandler的实现类,没什么可说的。标签如下: <mvc:return-value-handlers> </mvc:return-value-handlers> String asyncTimeout = getAsyncTimeout(element); RuntimeBeanReference asyncExecutor = getAsyncExecutor(element); ManagedList<?> callableInterceptors = getCallableInterceptors(element, source, parserContext); ManagedList<?> deferredResultInterceptors = getDeferredResultInterceptors(element, source, parserContext);上面这一段是配置异步的请求处理信息。 RootBeanDefinition handlerAdapterDef = new RootBeanDefinition(RequestMappingHandlerAdapter.class); handlerAdapterDef.setSource(source); handlerAdapterDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); handlerAdapterDef.getPropertyValues().add("contentNegotiationManager", contentNegotiationManager); handlerAdapterDef.getPropertyValues().add("webBindingInitializer", bindingDef); handlerAdapterDef.getPropertyValues().add("messageConverters", messageConverters);上面这一段是创建RequestMappingHandlerAdapter的BeanDefinition。并给webBindingInitializer属性和messageConverters属性赋值,赋值的内容是前面获取的的Bean对象。 addRequestBodyAdvice(handlerAdapterDef); protected void addRequestBodyAdvice(RootBeanDefinition beanDef) { //如果Classpath下面有com.fasterxml.jackson.databind.ObjectMapper和 //com.fasterxml.jackson.core.JsonGenerator这两个类的话,则添加JsonViewRequestBodyAdvice到 //requestResponseBodyAdvice集合中 if (jackson2Present) { beanDef.getPropertyValues().add("requestBodyAdvice", new RootBeanDefinition(JsonViewRequestBodyAdvice.class)); } }上面的这段代码中是给RequestHandlerMappingAdapter的requestResponseBodyAdvice属性中添加值。 addResponseBodyAdvice(handlerAdapterDef); protected void addResponseBodyAdvice(RootBeanDefinition beanDef) { //如果Classpath下面有com.fasterxml.jackson.databind.ObjectMapper和 //com.fasterxml.jackson.core.JsonGenerator这两个类的话,则添加JsonViewResponseBodyAdvice到 //requestResponseBodyAdvice集合中 if (jackson2Present) { beanDef.getPropertyValues().add("responseBodyAdvice", new RootBeanDefinition(JsonViewResponseBodyAdvice.class)); } }-上面的这段代码中是给RequestHandlerMappingAdapter的requestResponseBodyAdvice属性中添加值。 if (argumentResolvers != null) { handlerAdapterDef.getPropertyValues().add("customArgumentResolvers", argumentResolvers); } if (returnValueHandlers != null) { handlerAdapterDef.getPropertyValues().add("customReturnValueHandlers", returnValueHandlers); }给RequestHandlerMappingAdapter中的customArgumentResolvers添加自定义的参数解析器。给RequestHandlerMappingAdapter中的customReturnValueHandlers添加自定义的参数返回处理器。readerContext.getRegistry().registerBeanDefinition(HANDLER_ADAPTER_BEAN_NAME , handlerAdapterDef);将创建的RequestMappingHandlerAdapter注册到IOC容器中。bean的name为org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter。 在AnnotationDrivenBeanDefinitionParser中还添加了其他的一些Bean到IOC容器中,下面我们挑一些我们熟悉的说一下: RootBeanDefinition exceptionHandlerExceptionResolver = new RootBeanDefinition(ExceptionHandlerExceptionResolver.class); exceptionHandlerExceptionResolver.setSource(source); exceptionHandlerExceptionResolver.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); exceptionHandlerExceptionResolver.getPropertyValues().add("contentNegotiationManager", contentNegotiationManager); exceptionHandlerExceptionResolver.getPropertyValues().add("messageConverters", messageConverters); exceptionHandlerExceptionResolver.getPropertyValues().add("order", 0); addResponseBodyAdvice(exceptionHandlerExceptionResolver); if (argumentResolvers != null) { exceptionHandlerExceptionResolver.getPropertyValues().add("customArgumentResolvers", argumentResolvers); } if (returnValueHandlers != null) { exceptionHandlerExceptionResolver.getPropertyValues().add("customReturnValueHandlers", returnValueHandlers); }添加ExceptionHandlerExceptionResolver,处理异常信息的。 RootBeanDefinition responseStatusExceptionResolver = new RootBeanDefinition(ResponseStatusExceptionResolver.class); responseStatusExceptionResolver.setSource(source); responseStatusExceptionResolver.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); responseStatusExceptionResolver.getPropertyValues().add("order", 1); String responseStatusExceptionResolverName = readerContext.registerWithGeneratedName(responseStatusExceptionResolver);异常信息的另外一种处理方式,将异常信息映射为HTTP状态码。 RootBeanDefinition defaultExceptionResolver = new RootBeanDefinition(DefaultHandlerExceptionResolver.class); defaultExceptionResolver.setSource(source); defaultExceptionResolver.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); defaultExceptionResolver.getPropertyValues().add("order", 2); String defaultExceptionResolverName = readerContext.registerWithGeneratedName(defaultExceptionResolver); 默认的异常信息解析器。这三个异常信息解析器的处理顺序是:ExceptionHandlerExceptionResolver最先,ResponseStatusExceptionResolver次之,DefaultHandlerExceptionResolver顺序为最后。 MvcNamespaceUtils.registerDefaultComponents(parserContext, source);这这一段代码总主要是添加默认的几个Bean,分别是:BeanNameUrlHandlerMapping、HttpRequestHandlerAdapter、SimpleControllerHandlerAdapter。
在上一篇文章中我们简单的分析了一下AnnotationDrivenBeanDefinitionParser,在这一篇的文章中我们继续分析AnnotationDrivenBeanDefinitionParser的内容。AnnotationDrivenBeanDefinitionParser这个类最最重要的一个方法就是parse方法。在这篇文章中我们简单的分析一下parse方法的内容。先看parse中的这一段代码: RootBeanDefinition handlerMappingDef = new RootBeanDefinition(RequestMappingHandlerMapping.class); handlerMappingDef.setSource(source); handlerMappingDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);//基础建设类的角色 handlerMappingDef.getPropertyValues().add("order", 0);//order的顺序 handlerMappingDef.getPropertyValues().add("contentNegotiationManager", contentNegotiationManager); if (element.hasAttribute("enable-matrix-variables")) {//是否支持Matrix variables变量。 Boolean enableMatrixVariables = Boolean.valueOf(element.getAttribute("enable-matrix-variables")); handlerMappingDef.getPropertyValues().add("removeSemicolonContent", !enableMatrixVariables); } else if (element.hasAttribute("enableMatrixVariables")) { Boolean enableMatrixVariables = Boolean.valueOf(element.getAttribute("enableMatrixVariables")); handlerMappingDef.getPropertyValues().add("removeSemicolonContent", !enableMatrixVariables); }在上面的这一段代码中我们创建了一个RequestMappingHandlerMapping的BeanDefinition其实也相当于是默认配置了RequestMappingHandlerMapping。在这个BeanDefinition中我们设置了bean的role的值,order的顺序。同时还取了enable-matrix-variables这个属性的值。这个属性值是一个boolean类型,这个属性的作用是支持Matrix variables。通常在我们的URL中是会把带分号的数据移除掉的,如果我们配置了这个属性的值为true的话,就可以在URL的后面添加分号分割的数据。如:/test/name;userName=zhangsan这样。请求映射器的处理是需要在请求映射处理方法上添加@MatrixVariable注解,参数解析器为:MatrixVariableMethodArgumentResolver或MatrixVariableMapMethodArgumentResolver。有关Matrix variables更多的内容可以参考《看透springMvc源代码分析与实践》214页。我们下面接着分析: configurePathMatchingProperties(handlerMappingDef, element, parserContext); private void configurePathMatchingProperties(RootBeanDefinition handlerMappingDef, Element element, ParserContext parserContext) { /* *获取path-matching的元素,配置形式大概如下 * <mvc:annotation-driven> * <mvc:path-matching> * </mvc:path-matching> * </mvc:annotation-driven> * 这个配置是个可选配置,用来配置请求路径匹配模式的 */ Element pathMatchingElement = DomUtils.getChildElementByTagName(element, "path-matching"); if (pathMatchingElement != null) { Object source = parserContext.extractSource(element); if (pathMatchingElement.hasAttribute("suffix-pattern")) { //如果suffix-pattern这个属性的话,则取这个属性值。这个属性值默认是:true //这个属性值的作用是:是否配置.*这种模式 //例如我们在一个RequestMapping中配置的URL为:/listUserInfo,则/listUserInfo.json这样的请求也会被 //这个请求映射处理器方法所处理,即/listUserInfo相当于/listUserInfo.* Boolean useSuffixPatternMatch = Boolean.valueOf(pathMatchingElement.getAttribute("suffix-pattern")); handlerMappingDef.getPropertyValues().add("useSuffixPatternMatch", useSuffixPatternMatch); } //这个属性值默认为true。 匹配/users的请求也匹配 /users/ if (pathMatchingElement.hasAttribute("trailing-slash")) { Boolean useTrailingSlashMatch = Boolean.valueOf(pathMatchingElement.getAttribute("trailing-slash")); handlerMappingDef.getPropertyValues().add("useTrailingSlashMatch", useTrailingSlashMatch); } //现在 后缀匹配模式 避免 . 这个字符引起的歧义 //默认为false if (pathMatchingElement.hasAttribute("registered-suffixes-only")) { Boolean useRegisteredSuffixPatternMatch = Boolean.valueOf(pathMatchingElement.getAttribute("registered-suffixes-only")); handlerMappingDef.getPropertyValues().add("useRegisteredSuffixPatternMatch", useRegisteredSuffixPatternMatch); } RuntimeBeanReference pathHelperRef = null; //如果配置这个的话 需要继承UrlPathHelper这个类 寻找请求路径用的 //默认为UrlPathHelper if (pathMatchingElement.hasAttribute("path-helper")) { pathHelperRef = new RuntimeBeanReference(pathMatchingElement.getAttribute("path-helper")); } pathHelperRef = MvcNamespaceUtils.registerUrlPathHelper(pathHelperRef, parserContext, source); handlerMappingDef.getPropertyValues().add("urlPathHelper", pathHelperRef); RuntimeBeanReference pathMatcherRef = null; //路径匹配模式 //目前所使用的为AntPathMatcher。可以自定义 //Ant风格的URL。如:/user/*/createUser 匹配:/user/aaa/createUser、 /user/bbb/createUser // /user/**/createUser 匹配 /user/createUser 、/user/aaa/bbb/createUser // /user/{userId} 匹配 /user/123 、/user/1455 if (pathMatchingElement.hasAttribute("path-matcher")) { pathMatcherRef = new RuntimeBeanReference(pathMatchingElement.getAttribute("path-matcher")); } pathMatcherRef = MvcNamespaceUtils.registerPathMatcher(pathMatcherRef, parserContext, source); handlerMappingDef.getPropertyValues().add("pathMatcher", pathMatcherRef); } } 这一段逻辑主要是解析<mvc:path-matching>标签。我们接着往下看: RuntimeBeanReference conversionService = getConversionService(element, source, parserContext); private RuntimeBeanReference getConversionService(Element element, Object source, ParserContext parserContext) { RuntimeBeanReference conversionServiceRef; //如果配置了conversion-service属性的值,则使用所指定的bean,这个bean必须是ConversionService的子类。 if (element.hasAttribute("conversion-service")) { conversionServiceRef = new RuntimeBeanReference(element.getAttribute("conversion-service")); } else { //如果没有配置如果配置了conversion-service属性的值,则使用默认的ConversionService //这里使用的FormattingConversionServiceFactoryBean,但是这个类没有实现ConversionService这个接口,但是这个类 //实现了FactoryBean这个接口,在它的getObject方法中返回的是在afterPropertiesSet这个方法中创建的DefaultFormattingConversionService //所以默认的ConversionService是DefaultFormattingConversionService这个类。 //在这个类中大概初始化了118个Converters RootBeanDefinition conversionDef = new RootBeanDefinition(FormattingConversionServiceFactoryBean.class); conversionDef.setSource(source); conversionDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); String conversionName = parserContext.getReaderContext().registerWithGeneratedName(conversionDef); parserContext.registerComponent(new BeanComponentDefinition(conversionDef, conversionName)); conversionServiceRef = new RuntimeBeanReference(conversionName); } return conversionServiceRef; } 在上面的代码中进行了ConversionService的配置,默认配置了DefaultFormattingConversionService这个类型转换服务类。 RuntimeBeanReference validator = getValidator(element, source, parserContext); private RuntimeBeanReference getValidator(Element element, Object source, ParserContext parserContext) { if (element.hasAttribute("validator")) { return new RuntimeBeanReference(element.getAttribute("validator")); } //如果类路径中有javax.validation.Validator这个类的话,则配置OptionalValidatorFactoryBean //作为默认的校验器类 组合JSR-303 Validation规范和Spring Validator else if (javaxValidationPresent) { RootBeanDefinition validatorDef = new RootBeanDefinition( "org.springframework.validation.beanvalidation.OptionalValidatorFactoryBean"); validatorDef.setSource(source); validatorDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); String validatorName = parserContext.getReaderContext().registerWithGeneratedName(validatorDef); parserContext.registerComponent(new BeanComponentDefinition(validatorDef, validatorName)); return new RuntimeBeanReference(validatorName); } else { return null; } }在上面的代码中进行数据校验器的配置,默认配置了OptionalValidatorFactoryBean数据校验器。 RuntimeBeanReference messageCodesResolver = getMessageCodesResolver(element); private RuntimeBeanReference getMessageCodesResolver(Element element) { //默认的信息转换器是:DefaultMessageCodesResolver //用来转换校验失败信息。 if (element.hasAttribute("message-codes-resolver")) { return new RuntimeBeanReference(element.getAttribute("message-codes-resolver")); } else { return null; } }在上面的代码中进行MessageCode是Resolver的配置,默认配置的是DefaultMessageCodesResolver。 RootBeanDefinition bindingDef = new RootBeanDefinition(ConfigurableWebBindingInitializer.class); bindingDef.setSource(source); bindingDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); bindingDef.getPropertyValues().add("conversionService", conversionService); bindingDef.getPropertyValues().add("validator", validator); bindingDef.getPropertyValues().add("messageCodesResolver", messageCodesResolver);在上面的这段代码中默认配置了ConfigurableWebBindingInitializer这个bean,并设置了三个属性值conversionService、validator、messageCodesResolver。
在上一篇文章中我们使用了SpringMVC结合Servlet3.0的新特性进行web开发。在这一篇文章中我们用最原始的Servlet的方式来进行一个简单的web开发的工作。在我们之前的web开发中我们会在web.xml中进行Servlet、Filter、初始化参数等信息的配置。在Servlet3.0中为我们提供了一个接口:javax.servlet.ServletContainerInitializer。我们可以在这个接口的实现类中进行Servlet、Filter等信息的配置。它的作用和web.xml是很像,可以用来取代web.xml。和ServletContainerInitializer可能同时使用的还有一个javax.servlet.annotation.HandlesTypes注解,这个注解可以把指定的类和它的子类作为参数传入到ServletContainerInitializer的onStartup方法中,可以让我们进行一些其他的扩展功能。主要功能代码如下: javax.servlet.annotation.HandlesTypes注解指定的类: package com.zkn.servlet3.initializer; import javax.servlet.ServletContext; /** * Created by zkn on 2017/10/30. */ public interface WebApplicationContextInitializer { /** * 容器初始化的时候做一些其他动作 * * @param servletContext */ void onStartup(ServletContext servletContext); } package com.zkn.servlet3.initializer; import javax.servlet.ServletContext; /** * Created by zkn on 2017/10/30. */ public class ContextWebApplicationContextInitializer implements WebApplicationContextInitializer { /** * 容器初始化的时候做一些其他动作 * * @param servletContext */ @Override public void onStartup(ServletContext servletContext) { System.out.println("我是ContextWebApplicationContextInitializer"); } } package com.zkn.servlet3.initializer; import javax.servlet.ServletContext; /** * Created by zkn on 2017/10/30. */ public class ServletWebApplicationContextInitializer implements WebApplicationContextInitializer { /** * 容器初始化的时候做一些其他动作 * * @param servletContext */ @Override public void onStartup(ServletContext servletContext) { System.out.println("我是ServletWebApplicationContextInitializer"); } } 实现javax.servlet.ServletContainerInitiali接口的类 package com.zkn.servlet3; import com.zkn.servlet3.filter.DemoFilter; import com.zkn.servlet3.initializer.WebApplicationContextInitializer; import javax.servlet.*; import javax.servlet.annotation.HandlesTypes; import java.lang.reflect.Modifier; import java.util.EnumSet; import java.util.Set; /** * Created by zkn on 2017/10/30. */ @HandlesTypes(WebApplicationContextInitializer.class) public class StartServletContainerInitializer implements ServletContainerInitializer { @Override public void onStartup(Set<Class<?>> classSet, ServletContext servletContext) throws ServletException { if (classSet != null && !classSet.isEmpty()) { classSet.forEach(e -> { //不是接口,也不是抽象类 if (!e.isInterface() && !Modifier.isAbstract(e.getModifiers()) && WebApplicationContextInitializer.class.isAssignableFrom(e)) { try { WebApplicationContextInitializer webApplication = (WebApplicationContextInitializer) e.newInstance(); webApplication.onStartup(servletContext); } catch (InstantiationException e1) { e1.printStackTrace(); } catch (IllegalAccessException e1) { e1.printStackTrace(); } } }); } //添加Servlet ServletRegistration.Dynamic dynamicServlet = servletContext.addServlet("demoServlet", new DemoStartServlet()); //请求路径 dynamicServlet.addMapping("/demo"); //Servlet InitParam dynamicServlet.setInitParameter("demo", "demo"); dynamicServlet.setLoadOnStartup(1); //添加过滤器 FilterRegistration.Dynamic dynamicFilter = servletContext.addFilter("filter", new DemoFilter()); dynamicFilter.addMappingForServletNames(EnumSet.of(DispatcherType.REQUEST), true, "demoServlet"); } }在ServletContainerInitializer 的实现类中我们指定了Servlet、Filter、并且指定了Servlet的启动顺序。 指定的Servlet如下: package com.zkn.servlet3; import com.alibaba.fastjson.JSON; import javax.servlet.ServletConfig; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Enumeration; import java.util.List; /** * Created by zkn on 2017/10/30. */ public class DemoStartServlet extends HttpServlet { /** * 处理GET请求 * * @param request * @param response */ @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) { Enumeration<String> parameters = request.getParameterNames(); String paramterName; List<String> parameterValue = new ArrayList<>(); parameterValue.add("请求参数:"); if (parameters != null) { while (parameters.hasMoreElements()) { paramterName = parameters.nextElement(); parameterValue.add(paramterName + "=" + request.getParameter(paramterName) + ";"); } } String resultString = JSON.toJSONString(parameterValue); try { PrintWriter printWriter = new PrintWriter(response.getOutputStream()); printWriter.write(resultString); printWriter.close(); } catch (IOException e) { e.printStackTrace(); } } @Override public void init(ServletConfig config) throws ServletException { System.out.println("我是Servlet,我被初始化了、、、、、"); super.init(config); } }指定的Filter如下: package com.zkn.servlet3.filter; import javax.servlet.*; import java.io.IOException; /** * Created by zkn on 2017/10/30. */ public class DemoFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { System.out.println("初始化过滤器!"); } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { System.out.println("执行一个过滤器!"); chain.doFilter(request, response); } @Override public void destroy() { System.out.println("销毁过滤器!"); } } 但是现在我们缩写的功能还不能生效呢,要想使它生效的话,我们还需要进行一些简单的配置:在META-INF下面建立一个services的文件夹,在这个文件夹下面创建一个javax.servlet.ServletContainerInitializer文件,这个文件的内容如下:com.zkn.servlet3.StartServletContainerInitializer。内容就是我们缩写的ServletContainerInitializer的实现类的类路径。到此我们的一系列准备工作是完成了,下面我们在TomCat中启动一下,一些启动日志如下所示: 从上面的日志中我们可以看出来:HandlesTypes中所指定的类的先与过滤器被初始化,过滤器先与Servlet被初始化。还有一点需要注意的是,因为HandlesTypes所指定的类是做为StartServletContainerInitializer中的onStartup方法的参数的,但是这个参数的类型是set类型的,所以HandlesTypes指定的类的顺序可能是乱的,如果我们想让它们顺序指定的话,我们还需要手动的指定一个初始化的顺序。 下面让我们写一个请求来测试一下: http://localhost:8080/demo?userName=%E5%BC%A0%E4%B8%89&address=wwwe2323 完整代码路径如下:https://github.com/zhangconan/JavaWeb/tree/master/Servlet3Web
在我们之前进行web开发的时候,通常都会有一个web.xml存在,我们会在web.xml里面配置Servlet、Filter等一些web开发相关的东西。但是由于Servlet3.0的规范和Spring3.1功能的增强,现在我们在进行web开发的时候可以不用再使用web.xml了,转而全部使用java注解就可以了。下面是一个例子: 首先我们先创建一个StrartWebApplicationInitializer类,这个类继承AbstractAnnotationConfigDispatcherServletInitializer这个类,代码如下: package com.zkn.springmvc.annotationanalysis; import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer; import javax.servlet.ServletContext; /** * Create By ZKN * * @date 2017/10/28 * @time 上午9:53 */ public class StrartWebApplicationInitializer extends AbstractAnnotationConfigDispatcherServletInitializer { /** * SpringContext中相关的bean * * @return */ @Override protected Class<?>[] getRootConfigClasses() { return new Class[]{SpringRootConfig.class}; } /** * DispatcherServlet中上下文相关的Bean * * @return */ @Override protected Class<?>[] getServletConfigClasses() { return new Class[]{WebMvcConfig.class}; } /** * Servlet请求映射路径 * * @return */ @Override protected String[] getServletMappings() { return new String[]{ "/" }; } @Override public void registerDispatcherServlet(ServletContext servletContext) { //配置profile,激活不同的环境 servletContext.setInitParameter("spring.profiles.active", "jsp"); super.registerDispatcherServlet(servletContext); } }这里需要注意一下,getRootConfigClasses这个方法返回的类对应的是Spring上下文相关的一些配置,getServletConfigClasses这个方法返回的类是SpringMVC相关的一些类的配置。我们看一下SpringRootConfig和WebMvcConfig这两个类中的配置项: package com.zkn.springmvc.annotationanalysis; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; /** * Create By ZKN * * @date 2017/10/28 * @time 上午9:55 */ @Configuration @ComponentScan(basePackages = "com.zkn.springmvc.annotationanalysis.service") public class SpringRootConfig { } package com.zkn.springmvc.annotationanalysis; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Profile; import org.springframework.http.MediaType; import org.springframework.http.converter.StringHttpMessageConverter; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; import org.springframework.web.context.ServletContextAware; import org.springframework.web.servlet.HandlerAdapter; import org.springframework.web.servlet.ViewResolver; import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter; import org.springframework.web.servlet.view.InternalResourceViewResolver; import org.springframework.web.servlet.view.JstlView; import org.thymeleaf.TemplateEngine; import org.thymeleaf.spring4.SpringTemplateEngine; import org.thymeleaf.spring4.view.ThymeleafViewResolver; import org.thymeleaf.templatemode.TemplateMode; import org.thymeleaf.templateresolver.ITemplateResolver; import org.thymeleaf.templateresolver.ServletContextTemplateResolver; import javax.servlet.ServletContext; import java.util.Arrays; /** * Create By ZKN * * @date 2017/10/28 * @time 上午10:04 */ @EnableWebMvc @Configuration @ComponentScan(basePackages = "com.zkn.springmvc.annotationanalysis.controller") public class WebMvcConfig extends WebMvcConfigurerAdapter implements ServletContextAware { private ServletContext servletContext; @Bean @Profile("jsp") public ViewResolver jspViewResolver() { InternalResourceViewResolver viewResolver = new InternalResourceViewResolver(); viewResolver.setPrefix("WEB-INF/resources/jsp/"); viewResolver.setSuffix(".jsp"); viewResolver.setViewClass(JstlView.class); viewResolver.setExposePathVariables(true); return viewResolver; } /** * 视图解析器 * * @return */ @Bean @Profile(value = "thymeleaf") public ViewResolver viewResolver() { ThymeleafViewResolver viewResolver = new ThymeleafViewResolver(); viewResolver.setTemplateEngine(templateEngine()); return viewResolver; } /** * 模版引擎 * * @return */ @Bean @Profile("thymeleaf") public TemplateEngine templateEngine() { SpringTemplateEngine templateEngine = new SpringTemplateEngine(); templateEngine.setTemplateResolver(templateResolver()); return templateEngine; } /** * 模版引擎解析器 * * @return */ @Bean @Profile("thymeleaf") public ITemplateResolver templateResolver() { ServletContextTemplateResolver templateResolver = new ServletContextTemplateResolver(servletContext); templateResolver.setPrefix("WEB-INF/resources/thymeleaf/"); templateResolver.setSuffix(".html"); templateResolver.setTemplateMode(TemplateMode.HTML); return templateResolver; } @Override public void setServletContext(ServletContext servletContext) { this.servletContext = servletContext; } /** * 配置静态资源 * * @param configurer */ @Override public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) { configurer.enable(); } /** * 处理静态资源 * @param registry */ @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/js/**").addResourceLocations("/WEB-INF/resources/js/"); registry.addResourceHandler("/img/**").addResourceLocations("/WEB-INF/resources/img/"); super.addResourceHandlers(registry); } @Bean public HandlerAdapter requestMappingHandlerAdapter(){ RequestMappingHandlerAdapter handlerAdapter = new RequestMappingHandlerAdapter(); StringHttpMessageConverter messageConverter = new StringHttpMessageConverter(); messageConverter.setSupportedMediaTypes(Arrays.asList(MediaType.valueOf("text/html;charset=UTF-8"))); handlerAdapter.getMessageConverters().add(messageConverter); handlerAdapter.getMessageConverters().add(new MappingJackson2HttpMessageConverter()); return handlerAdapter; } }下面我们写个Controller来测试一下: package com.zkn.springmvc.annotationanalysis.controller; import com.zkn.springmvc.annotationanalysis.result.DataResult; import com.zkn.springmvc.annotationanalysis.vo.PersonInfoVO; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * Create By ZKN * * @date 2017/10/28 * @time 下午3:00 */ @RestController @RequestMapping("rest") public class RestfulController { /** * 返回一个字符串 * * @return */ @GetMapping("resultString") public String resultString() { return "我是一个字符串!"; } /** * 返回一个json数据 * * @return */ @GetMapping("resultJson") public DataResult<PersonInfoVO> resultJson() { DataResult<PersonInfoVO> dataResult = new DataResult<>(); PersonInfoVO personInfoVO = new PersonInfoVO(); personInfoVO.setUserName("我是张三"); personInfoVO.setPassWord("woshizhangsan"); dataResult.setData(personInfoVO); dataResult.setSuccess(true); return dataResult; } }当我们发送的请求为:http://localhost:8082/rest/resultString时,结果如下: 当我们发送的请求为:http://localhost:8082/rest/resultJson时,结果如下: 在上面的demo中我们使用了两种视图技术,一种是JSP,一种是Thymeleaf,通过激活不同的profile来切换不同的环境。完整代码请点击这里: https://github.com/zhangconan/JavaWeb/tree/master/springmvc-annotation-analysis 是不是感觉有点神奇呢?不需要web.xml只需要这样配置一下我们就可以进行web开发了。我会在近期的文章中简单的说明一下我们为什么可以这样去做,敬请期待。
首先我们会想一下,我们在进行SpringMVC配置的时候是怎样配置的(不是web.xml)?我们会在SpringMVC的配置文件中添加这样的一些东西: xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation=" http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.3.xsd"然后再配置一下: <mvc:annotation-driven/>就可以进行简单的Web开发工作了。那么Spring、SpringMVC框架是怎么做的呢? 我们注意到在SpringMVC工程的META-INF下面有spring.handlers和spring.schemas这两个文件 如果是jar包的话,则目录结构是这样的。 我们可以看看这两个文件中的内容是什么: spring.handlers: http\://www.springframework.org/schema/mvc=org.springframework.web.servlet.config.MvcNamespaceHandlerspring.schemas: http\://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd=org/springframework/web/servlet/config/spring-mvc.xsd http\://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd=org/springframework/web/servlet/config/spring-mvc.xsd http\://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd=org/springframework/web/servlet/config/spring-mvc.xsd http\://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd=org/springframework/web/servlet/config/spring-mvc.xsd http\://www.springframework.org/schema/mvc/spring-mvc-4.1.xsd=org/springframework/web/servlet/config/spring-mvc.xsd http\://www.springframework.org/schema/mvc/spring-mvc-4.2.xsd=org/springframework/web/servlet/config/spring-mvc.xsd http\://www.springframework.org/schema/mvc/spring-mvc-4.3.xsd=org/springframework/web/servlet/config/spring-mvc.xsd http\://www.springframework.org/schema/mvc/spring-mvc.xsd=org/springframework/web/servlet/config/spring-mvc.xsd这两个文件中的key看起来是不是很熟悉?看看文件开头的内容。Spring在对配置文件进行解析的时候会读取META-INF/spring.handlers这个文件来获取一个org.springframework.beans.factory.xml.NamespaceHandler的实现类。这个解析过程比较复杂,我们先不展开的(想了解的可以先看一下这个方法:org.springframework.beans.factory.xml.DefaultNamespaceHandlerResolver#resolve),你只需要记住Spring在解析xml配置文件的时候可以读取到org.springframework.web.servlet.config.MvcNamespaceHandler这个类就行了。我们先看一下NamespaceHandler这个类的UML类图: 在上面这张图中我们不仅看到了MvcNamespaceHandler、还有AopNamespaceHandler、TxNamespaceHandler、ContextNamespaceHandler等等(套路都是一样的)。spring.schemas中的xsd文件描述了xml文档的一些信息,比如有哪些根节点、根节点下面有哪些子节点、有哪些属性信息、数据类型等等。我们先来看一下MvcNamespaceHandler这个类的内容: @Override public void init() { //解析<mvc:annotation-driven>标签 registerBeanDefinitionParser("annotation-driven", new AnnotationDrivenBeanDefinitionParser()); //解析<mvc:default-servlet-handler>标签 registerBeanDefinitionParser("default-servlet-handler", new DefaultServletHandlerBeanDefinitionParser()); //解析<mvc:interceptors>标签 registerBeanDefinitionParser("interceptors", new InterceptorsBeanDefinitionParser()); registerBeanDefinitionParser("resources", new ResourcesBeanDefinitionParser()); registerBeanDefinitionParser("view-controller", new ViewControllerBeanDefinitionParser()); registerBeanDefinitionParser("redirect-view-controller", new ViewControllerBeanDefinitionParser()); registerBeanDefinitionParser("status-controller", new ViewControllerBeanDefinitionParser()); registerBeanDefinitionParser("view-resolvers", new ViewResolversBeanDefinitionParser()); registerBeanDefinitionParser("tiles-configurer", new TilesConfigurerBeanDefinitionParser()); registerBeanDefinitionParser("freemarker-configurer", new FreeMarkerConfigurerBeanDefinitionParser()); registerBeanDefinitionParser("velocity-configurer", new VelocityConfigurerBeanDefinitionParser()); registerBeanDefinitionParser("groovy-configurer", new GroovyMarkupConfigurerBeanDefinitionParser()); registerBeanDefinitionParser("script-template-configurer", new ScriptTemplateConfigurerBeanDefinitionParser()); registerBeanDefinitionParser("cors", new CorsBeanDefinitionParser()); }在这个方法中注册了很多的BeanDefinitionParser,,而<mvc:annotation-driven/>这个标签是通过AnnotationDrivenBeanDefinitionParser这个类来解析的。这个类中我们要分析的重点就是parse这个方法,在后面的文章中我们会挑一些重点的地方来分析一下这个parse方法中的内容。
接上篇文章,我们在这一篇文章中继续对RequestMappingHandlerAdapter这个类进行分析。在上篇文章中我们说到afterPropertiesSet这个方法中添加的一些MethodHandlerResolver,我们继续分析这个方法中的其他代码: if (this.initBinderArgumentResolvers == null) { List<HandlerMethodArgumentResolver> resolvers = getDefaultInitBinderArgumentResolvers(); this.initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers); }在我们做表单提交的时候可能需要对一些特殊值进行转换,比如时间格式,金钱等,这个时候我们可能会用到InitBinder这个注解,来添加一些特殊的编辑器。上面这几段代码的意思是,需要对哪些类型的请求映射处理方法的参数注册一些特殊的编辑器。如果我们没有配置过initBinderArgumentResolvers 这个属性的值的话,那么它会去getDefaultInitBinderArgumentResolvers这个方法中默认的一些参数解析器。如下所示: private List<HandlerMethodArgumentResolver> getDefaultInitBinderArgumentResolvers() { List<HandlerMethodArgumentResolver> resolvers = new ArrayList<HandlerMethodArgumentResolver>(); // Annotation-based argument resolution resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false)); resolvers.add(new RequestParamMapMethodArgumentResolver()); resolvers.add(new PathVariableMethodArgumentResolver()); resolvers.add(new PathVariableMapMethodArgumentResolver()); resolvers.add(new MatrixVariableMethodArgumentResolver()); resolvers.add(new MatrixVariableMapMethodArgumentResolver()); resolvers.add(new ExpressionValueMethodArgumentResolver(getBeanFactory())); resolvers.add(new SessionAttributeMethodArgumentResolver()); resolvers.add(new RequestAttributeMethodArgumentResolver()); // Type-based argument resolution resolvers.add(new ServletRequestMethodArgumentResolver()); resolvers.add(new ServletResponseMethodArgumentResolver()); //自定义的参数解析器 // Custom arguments if (getCustomArgumentResolvers() != null) { resolvers.addAll(getCustomArgumentResolvers()); } // Catch-all resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true)); return resolvers; }自定义的参数解析器,也会被添加到这个集合中,initBinderArgumentResolvers 这个类也是为托给HandlerMethodArgumentResolverComposite这个类来处理的。如果我们设置了initBinderArgumentResolvers 的话,那么它只会取我们设置的参数解析器。 下面我们再看一下returnValueHandlers返回值的处理器。我们在做项目的时候,有时候返回的是一个页面,有时候返回的是一个字符串,有时候返回的是JSON对象(静态资源除外),这些不同的返回值的处理是通过一些HandlerMethodReturnValueHandler的实现类来做的,这个我们以后会找几个常用的HandlerMethodReturnValueHandler实现类来分析一下,SpringMVC大概给我们默认了这么多的返回值处理器: if (this.returnValueHandlers == null) { List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers(); this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers); }SpringMVC默认的returnValueHandler private List<HandlerMethodReturnValueHandler> getDefaultReturnValueHandlers() { List<HandlerMethodReturnValueHandler> handlers = new ArrayList<HandlerMethodReturnValueHandler>(); // Single-purpose return value types //返回一个ModelAndView handlers.add(new ModelAndViewMethodReturnValueHandler()); handlers.add(new ModelMethodProcessor()); handlers.add(new ViewMethodReturnValueHandler()); handlers.add(new ResponseBodyEmitterReturnValueHandler(getMessageConverters())); handlers.add(new StreamingResponseBodyReturnValueHandler()); handlers.add(new HttpEntityMethodProcessor(getMessageConverters(), this.contentNegotiationManager, this.requestResponseBodyAdvice)); handlers.add(new HttpHeadersReturnValueHandler()); handlers.add(new CallableMethodReturnValueHandler()); handlers.add(new DeferredResultMethodReturnValueHandler()); handlers.add(new AsyncTaskMethodReturnValueHandler(this.beanFactory)); // Annotation-based return value types handlers.add(new ModelAttributeMethodProcessor(false)); //有RequestBody和ResponseBody注解的方法 handlers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(), this.contentNegotiationManager, this.requestResponseBodyAdvice)); // Multi-purpose return value types //直接返回一个字符,注意这个字符串是不带ResponseBody注解的 handlers.add(new ViewNameMethodReturnValueHandler()); handlers.add(new MapMethodProcessor()); // Custom return value types //自定义的返回值处理器 if (getCustomReturnValueHandlers() != null) { handlers.addAll(getCustomReturnValueHandlers()); } // Catch-all if (!CollectionUtils.isEmpty(getModelAndViewResolvers())) { handlers.add(new ModelAndViewResolverMethodReturnValueHandler(getModelAndViewResolvers())); } else { handlers.add(new ModelAttributeMethodProcessor(true)); } return handlers; } 这个returnValueHandlers 是HandlerMethodReturnValueHandlerComposite这个对象的实例。 在上面的分析中有一点需要我们注意的是,我们将HandlerMethodArgumentResolver、HandlerMethodReturnValueHandler、HttpMessageConverter等放到了相应的集合中,这个放入的顺序是很重要的。 我们在做SpringMVC开发的时候,只需要在SpringMVC的配置文件中添加这么一句话 <mvc:annotation-driven/>,就可以进行正常的Web开发的工作。那么RequestMappingHandlerAdapter、RequestMappingHandlerMapping等这些类是什么时候注入到Spring的Bean容器的呢?我们在下一篇的文章中会进行说明。
RequestMappingHandlerAdapter请求映射处理适配器,在SpringMVC中它是一个非常重要的类,对请求处理方法的调用主要是通过这个类来完成的(这段代码mv = ha.handle(processedRequest, response, mappedHandler.getHandler());)。下面我们就简单的分析一下这个类。首先我们先看一下它的UML类图结构: 画红线的部分是需要我们注意的一些类,如果你对Spring的Bean生命在周期熟悉的话,你会发现这些都是Spring的Bean生命周期相关的一些类(Spring Bean的生命周期小析(一)和Spring Bean的生命周期小析(二))。我们分析RequestMappingHandlerAdapter的时候也会从这些接口的实现方法中开始。首先我们先看一下它的构造函数: public RequestMappingHandlerAdapter() { StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter(); stringHttpMessageConverter.setWriteAcceptCharset(false); // see SPR-7316 this.messageConverters = new ArrayList<HttpMessageConverter<?>>(4); this.messageConverters.add(new ByteArrayHttpMessageConverter()); this.messageConverters.add(stringHttpMessageConverter); this.messageConverters.add(new SourceHttpMessageConverter<Source>()); this.messageConverters.add(new AllEncompassingFormHttpMessageConverter()); }从上面的代码中我们可以看到这里默认添加了四种类型的Http数据转换器。其中我们需要关注的时候AllEncompassingFormHttpMessageConverter这个转换器,我们也去它的构造函数中看一下: public AllEncompassingFormHttpMessageConverter() { addPartConverter(new SourceHttpMessageConverter<Source>()); //如果Classpath下面有javax.xml.bind.Binder类, //没有com.fasterxml.jackson.dataformat.xml.XmlMapper类的话 //则添加Jaxb2RootElementHttpMessageConverter转换器 if (jaxb2Present && !jackson2XmlPresent) { addPartConverter(new Jaxb2RootElementHttpMessageConverter()); } //如果Classpath下有com.fasterxml.jackson.databind.ObjectMapper //和com.fasterxml.jackson.core.JsonGenerator的话,则添加 //MappingJackson2HttpMessageConverter转换器 if (jackson2Present) { addPartConverter(new MappingJackson2HttpMessageConverter()); } //如果Classpath下面有com.google.gson.Gson类的话,则添加 //GsonHttpMessageConverter转换器 else if (gsonPresent) { addPartConverter(new GsonHttpMessageConverter()); } //如果Classpath下有com.fasterxml.jackson.dataformat.xml.XmlMapper类的话, //则添加MappingJackson2XmlHttpMessageConverter转换器 if (jackson2XmlPresent) { addPartConverter(new MappingJackson2XmlHttpMessageConverter()); } }其实在它的父类中还添加了三种类型的Convert: public FormHttpMessageConverter() { //application/x-www-form-urlencoded this.supportedMediaTypes.add(MediaType.APPLICATION_FORM_URLENCODED); //multipart/form-data this.supportedMediaTypes.add(MediaType.MULTIPART_FORM_DATA); //ByteArrayHttpMessageConverter this.partConverters.add(new ByteArrayHttpMessageConverter()); StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter(); stringHttpMessageConverter.setWriteAcceptCharset(false); //StringHttpMessageConverter this.partConverters.add(stringHttpMessageConverter); //ResourceHttpMessageConverter this.partConverters.add(new ResourceHttpMessageConverter()); applyDefaultCharset(); }按照Spring的Bean的生命周期的执行顺序,这里会先调用BeanFactoryAware#setBeanFactory方法、接着调用ApplicationContextAware#setApplicationContext方法,最后调用InitializingBean#afterPropertiesSet方法。我们先看一下重写之后的setBeanFactory方法的源码: @Override public void setBeanFactory(BeanFactory beanFactory) { if (beanFactory instanceof ConfigurableBeanFactory) { this.beanFactory = (ConfigurableBeanFactory) beanFactory; } }这个方法的内容很简单,就是设置一下beanFactory的属性值(其实这里的beanFactory是DefaultListableBeanFactory的实例)。 下面我们看一下setApplicationContext这个方法的源码,这个方法藏的比较深,在它的父类ApplicationObjectSupport#setApplicationContext方法中。 public final void setApplicationContext(ApplicationContext context) throws BeansException { //isContextRequired()方法返回true if (context == null && !isContextRequired()) { // Reset internal context state. this.applicationContext = null; this.messageSourceAccessor = null; } else if (this.applicationContext == null) { // Initialize with passed-in context. //所传入的context如果不能被实例化,则抛出异常 if (!requiredContextClass().isInstance(context)) { throw new ApplicationContextException( "Invalid application context: needs to be of type [" + requiredContextClass().getName() + "]"); } this.applicationContext = context; //国际化 this.messageSourceAccessor = new MessageSourceAccessor(context); //初始化ApplicationContext initApplicationContext(context); } else { // Ignore reinitialization if same context passed in. if (this.applicationContext != context) { throw new ApplicationContextException( "Cannot reinitialize with different application context: current one is [" + this.applicationContext + "], passed-in one is [" + context + "]"); } } }我们主要看一下WebApplicationObjectSupport#initApplicationContext这个方法的内容: @Override protected void initApplicationContext(ApplicationContext context) { //调用父类中的initApplicationContext方法 super.initApplicationContext(context); if (this.servletContext == null && context instanceof WebApplicationContext) { this.servletContext = ((WebApplicationContext) context).getServletContext(); if (this.servletContext != null) { //初始化ServletContext initServletContext(this.servletContext); } } }这里先调用父类中的initApplicationContext方法,然后会初始ServletContext。先看一下ApplicationObjectSupport#initApplicationContext方法: protected void initApplicationContext(ApplicationContext context) throws BeansException { initApplicationContext(); }在我们的这个类的继承体系中,initApplicationContext();是一个空实现。同样initServletContext也是一个空实现。接下来我们要分析的一个重点InitializingBean#afterPropertiesSet的方法,它的源码内容如下,下面我们一点一点的分析这个类: public void afterPropertiesSet() { // Do this first, it may add ResponseBody advice beans initControllerAdviceCache(); if (this.argumentResolvers == null) { List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers(); this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers); } if (this.initBinderArgumentResolvers == null) { List<HandlerMethodArgumentResolver> resolvers = getDefaultInitBinderArgumentResolvers(); this.initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers); } if (this.returnValueHandlers == null) { List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers(); this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers); } }我们先看initControllerAdviceCache这个方法: private void initControllerAdviceCache() { //通过上面的分析我们知道,这个方法的内容不为null if (getApplicationContext() == null) { return; } //获取所有带ControllerAdvice注解的类 List<ControllerAdviceBean> beans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext()); //对获取到的ControllerAdvice注解的类进行排序,排序的规则是基于实现PriorityOrdered接口或者带有Order注解 AnnotationAwareOrderComparator.sort(beans); List<Object> requestResponseBodyAdviceBeans = new ArrayList<Object>(); //循环获取到的ControllerAdviceBean for (ControllerAdviceBean bean : beans) { //获取所有ModelAttribute注解的方法,并且没有RequestMapping注解的方法 Set<Method> attrMethods = MethodIntrospector.selectMethods(bean.getBeanType(), MODEL_ATTRIBUTE_METHODS); if (!attrMethods.isEmpty()) { this.modelAttributeAdviceCache.put(bean, attrMethods); if (logger.isInfoEnabled()) { logger.info("Detected @ModelAttribute methods in " + bean); } } //获取所有带InitBinder注解的方法 Set<Method> binderMethods = MethodIntrospector.selectMethods(bean.getBeanType(), INIT_BINDER_METHODS); if (!binderMethods.isEmpty()) { this.initBinderAdviceCache.put(bean, binderMethods); if (logger.isInfoEnabled()) { logger.info("Detected @InitBinder methods in " + bean); } } //如果实现了RequestBodyAdvice接口 if (RequestBodyAdvice.class.isAssignableFrom(bean.getBeanType())) { requestResponseBodyAdviceBeans.add(bean); if (logger.isInfoEnabled()) { logger.info("Detected RequestBodyAdvice bean in " + bean); } } //如果实现了ResponseBodyAdvice接口 if (ResponseBodyAdvice.class.isAssignableFrom(bean.getBeanType())) { requestResponseBodyAdviceBeans.add(bean); if (logger.isInfoEnabled()) { logger.info("Detected ResponseBodyAdvice bean in " + bean); } } } //添加到requestResponseBodyAdvice集合中 if (!requestResponseBodyAdviceBeans.isEmpty()) { this.requestResponseBodyAdvice.addAll(0, requestResponseBodyAdviceBeans); } }在这个方法里主要是获取了带ControllerAdvice注解的类,并从这些类中查找实现了RequestBodyAdvice或者ResponseBodyAdvice接口的类,添加到requestResponseBodyAdvice集合中,另外获取所有带ModelAttribute注解且没有RequestMapping注解的方法,放到modelAttributeAdviceCache集合中,获取所有带InitBinder注解的方法放到initBinderAdviceCache的集合中。下面我们再看这一段代码: if (this.argumentResolvers == null) { List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers(); this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers); } private HandlerMethodArgumentResolverComposite argumentResolvers;argumentResolvers这个属性是HandlerMethodArgumentResolverComposite 类型的,如果我们在配置RequestMappingHandlerAdapter的时候设置了一系列HandlerMethodArgumentResolver的实现类的话,如下所示, public void setArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) { if (argumentResolvers == null) { this.argumentResolvers = null; } else { this.argumentResolvers = new HandlerMethodArgumentResolverComposite(); this.argumentResolvers.addResolvers(argumentResolvers); } }它会先创建HandlerMethodArgumentResolverComposite对象,然后把配置的HandlerMethodArgumentResolver的实现类添加到HandlerMethodArgumentResolverComposite的对象中。这里我们是没有手工配置RequestMappingHandlerAdapter的,所以,会先调用getDefaultArgumentResolvers方法,获取一系列默认的HandlerMethodArgumentResolver的实现类,代码如下: private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() { List<HandlerMethodArgumentResolver> resolvers = new ArrayList<HandlerMethodArgumentResolver>(); // Annotation-based argument resolution resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false)); resolvers.add(new RequestParamMapMethodArgumentResolver()); resolvers.add(new PathVariableMethodArgumentResolver()); resolvers.add(new PathVariableMapMethodArgumentResolver()); resolvers.add(new MatrixVariableMethodArgumentResolver()); resolvers.add(new MatrixVariableMapMethodArgumentResolver()); resolvers.add(new ServletModelAttributeMethodProcessor(false)); resolvers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice)); resolvers.add(new RequestPartMethodArgumentResolver(getMessageConverters(), this.requestResponseBodyAdvice)); resolvers.add(new RequestHeaderMethodArgumentResolver(getBeanFactory())); resolvers.add(new RequestHeaderMapMethodArgumentResolver()); resolvers.add(new ServletCookieValueMethodArgumentResolver(getBeanFactory())); resolvers.add(new ExpressionValueMethodArgumentResolver(getBeanFactory())); resolvers.add(new SessionAttributeMethodArgumentResolver()); resolvers.add(new RequestAttributeMethodArgumentResolver()); // Type-based argument resolution resolvers.add(new ServletRequestMethodArgumentResolver()); resolvers.add(new ServletResponseMethodArgumentResolver()); resolvers.add(new HttpEntityMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice)); resolvers.add(new RedirectAttributesMethodArgumentResolver()); resolvers.add(new ModelMethodProcessor()); resolvers.add(new MapMethodProcessor()); resolvers.add(new ErrorsMethodArgumentResolver()); resolvers.add(new SessionStatusMethodArgumentResolver()); resolvers.add(new UriComponentsBuilderMethodArgumentResolver()); // Custom arguments if (getCustomArgumentResolvers() != null) { resolvers.addAll(getCustomArgumentResolvers()); } // Catch-all resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true)); resolvers.add(new ServletModelAttributeMethodProcessor(true)); return resolvers; }SpringMVC为我们自动添加了大概26个HandlerMethodArgumentResolver的实现类。这里有一个方法需要我们注意一下:getCustomArgumentResolvers() 方法。这个方法是用来获取自定义的HandlerMethodArgumentResolver的实现类,也就是说如果我们有自己写的HandlerMethodArgumentResolver的方法的话,我们配置的属性是customArgumentResolvers,最好不要配置argumentResolvers这个属性。然后我们注意的一点是我们所添加的所以的HandlerMethodArgumentResolver的实现类,都是添加到了HandlerMethodArgumentResolverComposite这个类中,HandlerMethodArgumentResolverComposite也是HandlerMethodArgumentResolver的一个实现类。还有一点需要注意的是,这里创建了两个RequestParamMethodArgumentResolver的实例,一个传了true参数,一个传了false参数,这是用来解析不同的请求参数的,详情请看这里:SpringMVC之分析请求对应处理器方法参数的解析过程(一)
在上一篇文章中我们分析了SpringMVC对简单对象和@RequestParam注解的解析过程,这一章中我们继续分析其他形式的参数解析过程。 ServletRequestMethodArgumentResolver 下面来看一下我们的第一个请求:http://localhost:8086/allRequestFormat/requestAndResponseRequest?userName=zhangsan 对应的后台处理代码是: @RequestMapping("requestAndResponseRequest") public String requestAndResponseRequest(HttpServletRequest request, HttpServletResponse response) { System.out.println("userName" + request.getParameter("userName")); return "这是一个接收Request和Response的请求"; }我们的请求处理方法中的两个参数是HttpServletRequest和HttpServletResponse类型,那么SpringMVC在调用requestAndResponseRequest这个方法的时候是怎么解析到这两个参数的值的呢?我们先看HttpServletRequest这个参数,这个参数是ServletRequestMethodArgumentResolver这个类来解析的。在上一篇文章中,我们知道通过调用argumentResolvers.supportsParameter这个方法来判断HandlerMethodArgumentResolver的实现类是否支持对应的参数的解析,和resolveArgument方法来实现真正的参数解析。 所以我们先看一下ServletRequestMethodArgumentResolver这个类中的supportsParameter这个方法的内容: public boolean supportsParameter(MethodParameter parameter) { Class<?> paramType = parameter.getParameterType(); return (WebRequest.class.isAssignableFrom(paramType) || ServletRequest.class.isAssignableFrom(paramType) || MultipartRequest.class.isAssignableFrom(paramType) || HttpSession.class.isAssignableFrom(paramType) || Principal.class.isAssignableFrom(paramType) || InputStream.class.isAssignableFrom(paramType) || Reader.class.isAssignableFrom(paramType) || HttpMethod.class == paramType || Locale.class == paramType || TimeZone.class == paramType || "java.time.ZoneId".equals(paramType.getName())); }从上面的代码中我们可以看到如果参数类型为WebRequest类型、ServletRequest类型、MultipartRequest类型、HttpSession类型、Principal类型、InputStream类型、Reader类型或者HttpMethod类型、Locale类型、TimeZone类型、ZoneId类型则使用这个参数解析器进行参数的解析工作。 下面我们在看一下ServletRequestMethodArgumentResolver是怎么进行参数解析工作的: @Override public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { Class<?> paramType = parameter.getParameterType(); //如果是WebRequest的子类 if (WebRequest.class.isAssignableFrom(paramType)) { //不是NativeWebRequest的实现类,则抛出异常 if (!paramType.isInstance(webRequest)) { throw new IllegalStateException( "Current request is not of type [" + paramType.getName() + "]: " + webRequest); } //直接返回传进来的NativeWebRequest return webRequest; } //从传进来的webRequest中获取HttpServletRequest对象 HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class); //如果是ServletRequest的类型或者为MultipartRequest的类型 if (ServletRequest.class.isAssignableFrom(paramType) || MultipartRequest.class.isAssignableFrom(paramType)) { //判断类型是否一致 Object nativeRequest = webRequest.getNativeRequest(paramType); if (nativeRequest == null) { throw new IllegalStateException( "Current request is not of type [" + paramType.getName() + "]: " + request); } return nativeRequest; } else if (HttpSession.class.isAssignableFrom(paramType)) { //从request中得到HttpSession对象 HttpSession session = request.getSession(); if (session != null && !paramType.isInstance(session)) { throw new IllegalStateException( "Current session is not of type [" + paramType.getName() + "]: " + session); } return session; } else if (InputStream.class.isAssignableFrom(paramType)) { //从request中得到InputStream对象 InputStream inputStream = request.getInputStream(); if (inputStream != null && !paramType.isInstance(inputStream)) { throw new IllegalStateException( "Request input stream is not of type [" + paramType.getName() + "]: " + inputStream); } return inputStream; } else if (Reader.class.isAssignableFrom(paramType)) { //从request中得到Reader对象 Reader reader = request.getReader(); if (reader != null && !paramType.isInstance(reader)) { throw new IllegalStateException( "Request body reader is not of type [" + paramType.getName() + "]: " + reader); } return reader; } else if (Principal.class.isAssignableFrom(paramType)) { //从request中得到Reader对象 Principal userPrincipal = request.getUserPrincipal(); if (userPrincipal != null && !paramType.isInstance(userPrincipal)) { throw new IllegalStateException( "Current user principal is not of type [" + paramType.getName() + "]: " + userPrincipal); } return userPrincipal; } else if (HttpMethod.class == paramType) { //从request中得到请求类型的值 return HttpMethod.resolve(request.getMethod()); } else if (Locale.class == paramType) { //从Request上下文中获取国际化对象 return RequestContextUtils.getLocale(request); } else if (TimeZone.class == paramType) { //从Request上下文中获取时区对象 TimeZone timeZone = RequestContextUtils.getTimeZone(request); return (timeZone != null ? timeZone : TimeZone.getDefault()); } else if ("java.time.ZoneId".equals(paramType.getName())) { //JDK1.8时区 return ZoneIdResolver.resolveZoneId(request); } else { // Should never happen... throw new UnsupportedOperationException( "Unknown parameter type [" + paramType.getName() + "] in " + parameter.getMethod()); } }从上面的代码中我们发现最主要的是NativeWebRequest这个对象,我们可以从这个对象中获取到HttpServletRequest,从HttpServletRequest中获取一系列的其他对象的值。我们可以看一下NativeWebRequest这个对象的值是怎么创建的。在RequestMappingHandlerAdapter#invokeHandlerMethod中有这样一段代码: ServletWebRequest webRequest = new ServletWebRequest(request, response);在ServletWebRequest这个对象中持有request和response这两个对象的引用。ServletWebRequest的UML类图关系如下: 所以resolveArgument中的NativeWebRequest其实是ServletWebRequest这个对象。ServletResponseMethodArgumentResolver 上面分析完了HttpServletRequest,我们还有一个参数类型是HttpServletResponse,那么这个HttpServletResponse这个参数是谁解析的呢?是ServletResponseMethodArgumentResolver来解析的。我们看一下它的supportsParameter方法的实现: @Override public boolean supportsParameter(MethodParameter parameter) { Class<?> paramType = parameter.getParameterType(); return (ServletResponse.class.isAssignableFrom(paramType) || OutputStream.class.isAssignableFrom(paramType) || Writer.class.isAssignableFrom(paramType)); }ServletResponseMethodArgumentResolver解析的参数类型是:ServletResponse类型、OutputStream类型和Writer类型。我们再看下一resolveArgument方法中对参数解析的实现: @Override public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { if (mavContainer != null) { mavContainer.setRequestHandled(true); } //从webRequest获取HttpServletResponse对象,我们前面说了webRequest持有HttpServletResponse对象的引用 HttpServletResponse response = webRequest.getNativeResponse(HttpServletResponse.class); Class<?> paramType = parameter.getParameterType(); //ServletResponse类型 if (ServletResponse.class.isAssignableFrom(paramType)) { Object nativeResponse = webRequest.getNativeResponse(paramType); if (nativeResponse == null) { throw new IllegalStateException( "Current response is not of type [" + paramType.getName() + "]: " + response); } return nativeResponse; }//从HttpServletResponse中获取OutputStream对象 else if (OutputStream.class.isAssignableFrom(paramType)) { return response.getOutputStream(); }//从HttpServletResponse中获取Writer对象 else if (Writer.class.isAssignableFrom(paramType)) { return response.getWriter(); } else { // should not happen Method method = parameter.getMethod(); throw new UnsupportedOperationException("Unknown parameter type: " + paramType + " in method: " + method); } }这个参数的解析过程和ServletRequestMethodArgumentResolver中对象参数的解析过程如出一辙。我们对HttpServletRequest和HttpServletResponse参数的解析就先到这里了。
在我们做Web开发的时候,会提交各种数据格式的请求,而我们的后台也会有相应的参数处理方式。SpringMVC就为我们提供了一系列的参数解析器,不管你是要获取Cookie中的值,Header中的值,JSON格式的数据,URI中的值。下面我们分析几个SpringMVC为我们提供的参数解析器。 在SpringMVC中为我们定义了一个参数解析的顶级父类:HandlerMethodArgumentResolver。同时SpringMVC为我们提供了这么多的实现类: 这么多的类,看起来眼花缭乱的。下面选择几个常用的参数解析的类型来分析一下。在这之前先说一下HandlerMethodArgumentResolverComposite这个类,这个类是SpringMVC参数解析器的一个集合。 在前面的文章中我们大致说过SpringMVC请求处理的大致过程,首先我们先进入到RequestMappingHandlerAdapter#invokeHandlerMethod这个方法中,相关源码如下: protected ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception { ServletWebRequest webRequest = new ServletWebRequest(request, response); try { ............. ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod); invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);//添加参数解析器 invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);//添加请求返回的处理器 invocableMethod.setDataBinderFactory(binderFactory); invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer); ............ invocableMethod.invokeAndHandle(webRequest, mavContainer); if (asyncManager.isConcurrentHandlingStarted()) { return null; } return getModelAndView(mavContainer, modelFactory, webRequest); } finally { webRequest.requestCompleted(); } }在上面的代码中我们创建了一个ServletInvocableHandlerMethod对象,在这个对象中设置了参数解析器、返回值处理器、数据校验工厂类等。接着我们进入到invocableMethod.invokeAndHandle这个方法中看一下(省略了其他代码): public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs); .................... .................... }在invokeForRequest这个方法中,主要干了两件事,一是解析请求参数,二是调用Controller中的请求方法。这里我们主要关注的是参数解析的部分: public Object invokeForRequest(NativeWebRequest request, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { //解析请求参数 Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs); Object returnValue = doInvoke(args); return returnValue; }请求解析的方法是getMethodArgumentValues,这个是我们要分析的重点: private Object[] getMethodArgumentValues(NativeWebRequest request, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { //获取所有执行的方法的参数信息 MethodParameter[] parameters = getMethodParameters(); Object[] args = new Object[parameters.length]; for (int i = 0; i < parameters.length; i++) { MethodParameter parameter = parameters[i]; parameter.initParameterNameDiscovery(this.parameterNameDiscoverer); //如果之前有预先设置值的话,则取预先设置好的值 args[i] = resolveProvidedArgument(parameter, providedArgs); if (args[i] != null) { continue; } //获取能解析出方法参数值的参数解析器类 if (this.argumentResolvers.supportsParameter(parameter)) { try { //解析出参数值 args[i] = this.argumentResolvers.resolveArgument( parameter, mavContainer, request, this.dataBinderFactory); continue; } catch (Exception ex) { throw ex; } } //如果没有能解析方法参数的类,抛出异常 if (args[i] == null) { throw new IllegalStateException("Could not resolve method parameter at index " + parameter.getParameterIndex() + " in " + parameter.getMethod().toGenericString() + ": " + getArgumentResolutionErrorMessage("No suitable resolver for", i)); } } return args; }这里需要说的是argumentResolvers这个对象是HandlerMethodArgumentResolverComposite这个类。所有参数的解析都是委托这个类来完成的,这个类会调用真正的请求参数的解析的类: public boolean supportsParameter(MethodParameter parameter) { return (getArgumentResolver(parameter) != null); } private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) { //先看之前有没有解析过这个方法参数,如果解析过,则从缓存中取 HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter); if (result == null) { //循环所有的参数解析类,匹配真正参数解析的类 for (HandlerMethodArgumentResolver methodArgumentResolver : this.argumentResolvers) { if (methodArgumentResolver.supportsParameter(parameter)) { result = methodArgumentResolver; //放到缓存中 this.argumentResolverCache.put(parameter, result); break; } } } return result; } RequestParamMethodArgumentResolver 我们先来看这样的一个请求: http://localhost:8086/allRequestFormat/simpleClassObjectRequest?userName=zhangsan&id=123 后端对于的代码如下: @RequestMapping("simpleClassObjectRequest") public String simpleClassObjectRequest(Long id, String userName) { System.out.println(String.format("id:%d,userName:%s",id,userName)); return "这是一个接受简单类型参数的请求"; }这样的写法相信大家都很熟悉,那么SpringMVC是怎么解析出请求中的参数给InvocableHandlerMethod#doInvoke方法当入参的呢?经过我们debug发现,这里methodArgumentResolver.supportsParameter所匹配到的HandlerMethodArgumentResolver的实现类是RequestParamMethodArgumentResolver。我们进入到RequestParamMethodArgumentResolver中看一下supportsParameter方法: public boolean supportsParameter(MethodParameter parameter) { //方法的参数中是否有RequestParam注解。 if (parameter.hasParameterAnnotation(RequestParam.class)) { //方法的参数是否是Map if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) { String paramName = parameter.getParameterAnnotation(RequestParam.class).name(); return StringUtils.hasText(paramName); } else { return true; } } else { //如果有RequestPart注解,直接返回faslse if (parameter.hasParameterAnnotation(RequestPart.class)) { return false; } parameter = parameter.nestedIfOptional(); //是否是文件上传中的值 if (MultipartResolutionDelegate.isMultipartArgument(parameter)) { return true; }//如果useDefaultResolution为true else if (this.useDefaultResolution) { return BeanUtils.isSimpleProperty(parameter.getNestedParameterType()); } else { return false; } } }在上面的代码中我们可以看到,请求对应的处理方法的中的参数如果带有RequestParam注解,则判断是不是Map类型的参数,如果不是,则直接返回true。如果没有RequestParam注解,则判断如果有RequestPart注解,则直接返回false,接着判断是否是文件上传表单中的参数值,如果不是,则接着判断useDefaultResolution是否为true。这里需要说明一下的是:argumentResolvers中有两个RequestParamMethodArgumentResolver bean,一个useDefaultResolution为false,一个useDefaultResolution为true。当useDefaultResolution为false的bean是用来处理RequestParam注解的,useDefaultResolution为true的bean是用来处理简单类型的bean的。在我们这个例子中,useDefaultResolution的值为true。那么接下来回判断是不是简单类型参数,我们进到BeanUtils.isSimpleProperty这个方法中看一下: public static boolean isSimpleProperty(Class<?> clazz) { Assert.notNull(clazz, "Class must not be null"); return isSimpleValueType(clazz) || (clazz.isArray() && isSimpleValueType(clazz.getComponentType())); } public static boolean isSimpleValueType(Class<?> clazz) { return (ClassUtils.isPrimitiveOrWrapper(clazz) || clazz.isEnum() || CharSequence.class.isAssignableFrom(clazz) || Number.class.isAssignableFrom(clazz) || Date.class.isAssignableFrom(clazz) || URI.class == clazz || URL.class == clazz || Locale.class == clazz || Class.class == clazz); }真正进行类型判断的方法是isSimpleValueType这个方法,如果请求对应处理类的方法的参数为枚举类型、String类型、Long、Integer、Float、Byte、Short、Double、Date、URI、URL、Locale、Class、文件上传对象或者参数是数组,数组类型为上面列出的类型则返回true。即我们的请求对应处理类的方法的参数为:枚举类型、String类型、Long、Integer、Float、Byte、Short、Double、Date、URI、URL、Locale、Class、文件上传对象或者参数是数组,数组类型为上面列出的类型,则请求参数处理类为:RequestParamMethodArgumentResolver。我们先看一下RequestParamMethodArgumentResolver的UML类图关系: 接着我们看一下resolveArgument的这个方法,我们在RequestParamMethodArgumentResolver这个方法中没有找到对应的resolveArgument方法,但是我们在他的父类中找到了resolveArgument这个方法源码如下: @Override public final Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { //获取请求对应处理方法的参数字段值 NamedValueInfo namedValueInfo = getNamedValueInfo(parameter); MethodParameter nestedParameter = parameter.nestedIfOptional(); //解析之后的请求对应处理方法的参数字段值 Object resolvedName = resolveStringValue(namedValueInfo.name); if (resolvedName == null) { throw new IllegalArgumentException( "Specified name must not resolve to null: [" + namedValueInfo.name + "]"); } //解析参数值 Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest); //如果从请求中得到的参数值为null的话 if (arg == null) { //判断是否有默认值 if (namedValueInfo.defaultValue != null) { arg = resolveStringValue(namedValueInfo.defaultValue); }//判断这个字段是否是必填,RequestParam注解默认为必填。 else if (namedValueInfo.required && !nestedParameter.isOptional()) { handleMissingValue(namedValueInfo.name, nestedParameter, webRequest); }//处理null值 arg = handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType()); }//如果为得到的参数值为null,且有默认的值 else if ("".equals(arg) && namedValueInfo.defaultValue != null) { arg = resolveStringValue(namedValueInfo.defaultValue); } //参数的校验 if (binderFactory != null) { //参数校验 } //空实现 handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest); return arg; }在这个方法中,首先获取到参数名字是什么,这里封装为了NamedValueInfo的一个对象,我们可以去getNamedValueInfo这个方法中看一下: private NamedValueInfo getNamedValueInfo(MethodParameter parameter) { NamedValueInfo namedValueInfo = this.namedValueInfoCache.get(parameter);//先从缓存中获取 if (namedValueInfo == null) {//缓存中不存在这个值 namedValueInfo = createNamedValueInfo(parameter);//创建NamedValueInfo对象 namedValueInfo = updateNamedValueInfo(parameter, namedValueInfo);//更新刚才得到的NamedValueInfo对象 this.namedValueInfoCache.put(parameter, namedValueInfo);//放入缓存中 } return namedValueInfo; }我们看一下createNamedValueInfo这个方法,这个方法在RequestParamMethodArgumentResolver中: protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) { RequestParam ann = parameter.getParameterAnnotation(RequestParam.class);//判断是否有RequestParam注解 return (ann != null ? new RequestParamNamedValueInfo(ann) : new RequestParamNamedValueInfo()); }RequestParamNamedValueInfo对象的源码如下: public RequestParamNamedValueInfo() { super("", false, ValueConstants.DEFAULT_NONE); } public RequestParamNamedValueInfo(RequestParam annotation) { super(annotation.name(), annotation.required(), annotation.defaultValue()); }这两个构造函数的区别是,如果有RequestParam注解的话,则取ReuqestParam注解中的值,否则取默认的值,我们看一下updateNamedValueInfo这个方法的源码: private NamedValueInfo updateNamedValueInfo(MethodParameter parameter, NamedValueInfo info) { String name = info.name; //如果上一步创建的NamedValueInfo中的name为空的话, if (info.name.isEmpty()) { //从MethodParameter中解析出参数的名字 name = parameter.getParameterName(); if (name == null) { throw new IllegalArgumentException( "Name for argument type [" + parameter.getNestedParameterType().getName() + "] not available, and parameter name information not found in class file either."); } } //转换默认值 String defaultValue = (ValueConstants.DEFAULT_NONE.equals(info.defaultValue) ? null : info.defaultValue); return new NamedValueInfo(name, info.required, defaultValue); }这个方法的主要作用是获取参数的名字。学过反射的我们都知道通过反射的API只能获取方法的形参的类型,不能获取形参的名称,但是这里很明显我们需要获取到形参的名称,所以这里获取形参的名称不是通过反射的方式获取,而是通过了一个叫ASM的技术来实现的。当我们获取到NamedValueInfo 之后,会对获取到的形成做进一步的处理,这里我们可以先不用关注,直接到resolveName这个方法中看一下: @Override protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception { //获取HttpServletRequest HttpServletRequest servletRequest = request.getNativeRequest(HttpServletRequest.class); //判断是不是文件上传的请求 MultipartHttpServletRequest multipartRequest = WebUtils.getNativeRequest(servletRequest, MultipartHttpServletRequest.class); //先从文件上传的请求中获取上传的文件对象(Part这种东西现在应该很少用了吧,所以这里就直接忽略了) Object mpArg = MultipartResolutionDelegate.resolveMultipartArgument(name, parameter, servletRequest); if (mpArg != MultipartResolutionDelegate.UNRESOLVABLE) { return mpArg; } Object arg = null; //如果是文件上传请求, if (multipartRequest != null) { //则获取文件上传请求中的普通表单项的值 List<MultipartFile> files = multipartRequest.getFiles(name); if (!files.isEmpty()) { //如果只有一个值的话,则返回一个值,否则返回数组 arg = (files.size() == 1 ? files.get(0) : files); } } //说明是普通的请求 if (arg == null) { //则从request.getParameterValues中获取值 String[] paramValues = request.getParameterValues(name); if (paramValues != null) { //如果只有一个值的话,则返回一个值,否则返回数组 arg = (paramValues.length == 1 ? paramValues[0] : paramValues); } } return arg; }这里同时支持了获取文件上传对象、文件上传请求中的表单项参数值的获取,普通请求参数值的获取。对于普通请求参数值的获取是通过request.getParameterValues来获取的。如果我们没有从请求中获取到参数值的话,则先判断是否是有默认值(用RequestParam注解可以设置默认值),接着判断这个参数是否是必要的参数(RequestParam默认为必要参数),如果是必要的参数且这个值为null的话,则处理过程如下: protected void handleMissingValue(String name, MethodParameter parameter, NativeWebRequest request) throws Exception { HttpServletRequest servletRequest = request.getNativeRequest(HttpServletRequest.class); if (MultipartResolutionDelegate.isMultipartArgument(parameter)) { if (!MultipartResolutionDelegate.isMultipartRequest(servletRequest)) { throw new MultipartException("Current request is not a multipart request"); } else { throw new MissingServletRequestPartException(name); } } else { throw new MissingServletRequestParameterException(name, parameter.getNestedParameterType().getSimpleName()); } }如果是文件上传请求,则异常信息为:Current request is not a multipart request。普通请求,则异常信息为:"Required " + this.parameterType + " parameter '" + this.parameterName + "' is not present"。如果值为null的话,则会对null值进行处理, private Object handleNullValue(String name, Object value, Class<?> paramType) { if (value == null) { if (Boolean.TYPE.equals(paramType)) { return Boolean.FALSE; } else if (paramType.isPrimitive()) { //int long 等 throw new IllegalStateException("Optional " + paramType.getSimpleName() + " parameter '" + name + "' is present but cannot be translated into a null value due to being declared as a " + "primitive type. Consider declaring it as object wrapper for the corresponding primitive type."); } } return value; }如果参数类型为int、long等基本类型,则如果请求参数值为null的话,则会抛出异常,异常信息如下:"Optional " + paramType.getSimpleName() + " parameter '" + name +' is present but cannot be translated into a null value due to being declared as a primitive type. Consider declaring it as object wrapper for the corresponding primitive type."。 我们在上一步获取到的参数,会在下一步进行数据校验。 如果我们的请求换成这个: http://localhost:8086/allRequestFormat/requestParamRequest?id=122 后台处理代码如下: @RequestMapping("requestParamRequest") public String requestParamRequest(@RequestParam("id") Long id) { System.out.println("参数ID为:" + id); return "这是一个带RequestParam注解的请求"; }这个处理过程,和我们上面说的处理过程基本是一致的。 这里再多说一些RequestParam这个注解,使用RequestParam注解的好处是,可以指定所要的请求参数的名称,缩短处理过程,可以指定参数默认值。 另外再多说一点:java类文件编译为class文件时,有release和debug模式之分,在命令行中直接使用javac进行编译的时候,默认的是release模式,使用release模式会改变形参中的参数名,如果形成的名称变化的话,我们可能不能正确的获取到请求参数中的值。而IDE都是使用debug模式进行编译的。ant编译的时候,需要在ant的配置文件中指定debug="true"。 如果要修改javac编译类文件的方式的话,需要指定-g参数。即:javac -g 类文件。 我们最后再总结一下:如果你的请求对应处理类中的形参类型为:枚举类型、String类型、Long、Integer、Float、Byte、Short、Double、Date、URI、URL、Locale、Class、文件上传对象或者参数是数组,数组类型为上面列出的类型的话,则会使用RequestParamMethodArgumentResolver进行参数解析。
一个输出ThreadLocal中的值小工具类,代码如下: package com.zkn.utils; import org.springframework.core.NamedThreadLocal; import java.lang.ref.Reference; import java.lang.reflect.Field; import java.util.*; /** * Created by zkn on 2017/10/4. */ public class ThreadLocalUtil { public static void dumpThreadDetails() { try { //获取当前线程对象 Thread thread = Thread.currentThread(); //获取Thread中的threadLocals对象 Field threadLocals = Thread.class.getDeclaredField("threadLocals"); threadLocals.setAccessible(true); //ThreadLocalMap是ThreadLocal中的一个内部类,并且访问权限是default // 这里获取的是ThreadLocal.ThreadLocalMap Object threadLocalMap = threadLocals.get(thread); //这里要这样获取ThreadLocal.ThreadLocalMap Class threadLocalMapClazz = Class.forName("java.lang.ThreadLocal$ThreadLocalMap"); //获取ThreadLocalMap中的Entry对象 Field tableField = threadLocalMapClazz.getDeclaredField("table"); tableField.setAccessible(true); //获取ThreadLocalMap中的Entry Object[] objects = (Object[]) tableField.get(threadLocalMap); //获取ThreadLocalMap中的Entry Class entryClass = Class.forName("java.lang.ThreadLocal$ThreadLocalMap$Entry"); //获取ThreadLocalMap中的Entry中的value字段 Field entryValueField = entryClass.getDeclaredField("value"); entryValueField.setAccessible(true); //Entry继承了WeakReference,WeakReference继承了Reference Field referEnceField = Reference.class.getDeclaredField("referent"); referEnceField.setAccessible(true); Arrays.stream(objects).filter(obj -> obj != null).forEach((obj) -> { try { Object value = entryValueField.get(obj); if (value != null) { if (value instanceof Reference) { Reference ref = (Reference) value; System.out.println(" ref " + ref.getClass().getName() + " ref to " + ref.get()); } else { System.out.println(value); } } } catch (IllegalAccessException e) { e.printStackTrace(); } }); } catch (Exception e) { e.printStackTrace(); } } }我们来测试一下: public static void main(String[] args) { ThreadLocal<Map<String, String>> threadLocal = new ThreadLocal(); Map<String, String> maps = new HashMap<String, String>() {{ put("zhangsan", "lisiwww"); put("zhangsan000000", "lisi7845www"); put("zha0124545ngsan", "lisiw02255ww"); }}; threadLocal.set(maps); ThreadLocal<List<String>> threadLocalList = new ThreadLocal(); List<String> lists = new ArrayList<String>() {{ add("qwwweweqqqqqq2222"); add("qww111weweqqqqqq2222"); add("qwww44444eweqqqqqq2222"); }}; threadLocalList.set(lists); dumpThreadDetails(); } 在Spring中有一个ThreadLocal的扩展类:NamedThreadLocal,Spring中的很多资源都是保存在这里面,如果我们要输出NamedThreadLocal中的值的话,只需要这样改动一下就行了: if (value != null) { ThreadLocal threadLocal = (ThreadLocal) referEnceField.get(obj); if (threadLocal instanceof NamedThreadLocal) { System.out.print("spring threadlocal name: " + threadLocal + " value: "); } if (value instanceof Reference) { Reference ref = (Reference) value; System.out.println(" ref " + ref.getClass().getName() + " ref to " + ref.get()); } else { System.out.println(value); } }
首先我们先定义一个接口: public interface PersonInter { String test(String str); }接着就是我们想的要生成的JDK代理类源码的代码: public class JdkProxySourceClass { public static void writeClassToDisk(String path){ byte[] classFile = ProxyGenerator.generateProxyClass("$proxy4", new Class[]{PersonInter.class}); FileOutputStream fos = null; try { fos = new FileOutputStream(path); fos.write(classFile); fos.flush(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); }finally{ if(fos != null){ try { fos.close(); } catch (IOException e) { e.printStackTrace(); } } } } @Test public void testGenerateProxyClass() { JdkProxySourceClass.writeClassToDisk("D:/$Proxy4.class"); } }重要的就是这一句话:byte[] classFile = ProxyGenerator.generateProxyClass("$proxy4", new Class[]{PersonInter.class}); OK接下来我们用反编译工具看一下生成的代理类源码: import com.zkn.newlearn.gof.proxyhandler.PersonInter; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.lang.reflect.UndeclaredThrowableException; public final class Proxy4 extends Proxy implements PersonInter { private static Method m1; private static Method m2; private static Method m3; private static Method m0; public Proxy4(InvocationHandler paramInvocationHandler) throws { super(paramInvocationHandler); } public final boolean equals(Object paramObject) throws { try { return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue(); } catch (RuntimeException localRuntimeException) { throw localRuntimeException; } catch (Throwable localThrowable) { throw new UndeclaredThrowableException(localThrowable); } } public final String toString() throws { try { return ((String)this.h.invoke(this, m2, null)); } catch (RuntimeException localRuntimeException) { throw localRuntimeException; } catch (Throwable localThrowable) { throw new UndeclaredThrowableException(localThrowable); } } public final String test(String paramString) throws { try { return ((String)this.h.invoke(this, m3, new Object[] { paramString })); } catch (RuntimeException localRuntimeException) { throw localRuntimeException; } catch (Throwable localThrowable) { throw new UndeclaredThrowableException(localThrowable); } } public final int hashCode() throws { try { return ((Integer)this.h.invoke(this, m0, null)).intValue(); } catch (RuntimeException localRuntimeException) { throw localRuntimeException; } catch (Throwable localThrowable) { throw new UndeclaredThrowableException(localThrowable); } } static { try { m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") }); m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]); m3 = Class.forName("com.zkn.newlearn.gof.proxyhandler.PersonInter").getMethod("test", new Class[] { Class.forName("java.lang.String") }); m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]); return; } catch (NoSuchMethodException localNoSuchMethodException) { throw new NoSuchMethodError(localNoSuchMethodException.getMessage()); } catch (ClassNotFoundException localClassNotFoundException) { throw new NoClassDefFoundError(localClassNotFoundException.getMessage()); } } } 从反编译出来的源码中我们可以看到在静态代码块中得到了equals、toString、hashCode和PersonInter接口中test方法的Method对象。当我们调用PersonInter中的test方法的时候: public final String test(String paramString) throws { try { return ((String)this.h.invoke(this, m3, new Object[] { paramString })); } catch (RuntimeException localRuntimeException) { throw localRuntimeException; } catch (Throwable localThrowable) { throw new UndeclaredThrowableException(localThrowable); } } 其实是调用了InvocationHandler中的invoke方法,并传入了之前获取到的对应的Method和参数。在这里也简单的说一下为什么JDK的动态代理只能代理接口不能代理类,请注意看我们所得到的代理类的源码,注意看这一句:public final class Proxy4 extends Proxy implements PersonInter。生成的代理类默认继承了Proxy这个类,而java中又是单继承的,所以这里只能代理接口,不能代理类了。就像枚举类,不能继承别的枚举类一样。
前几天看了领导写的一段代码,在Controller中注入了HttpServletRequest,形式如下所示: @RestController public class AutowiredRequestController { @Autowired private HttpServletRequest request; }当时看到了这一段代码,首先想到的是AutowiredRequestController是一个singleton的bean,HttpServletRequest是一个变化的共享变量,每个请求对象都是不一样的,这样写不会有线程安全问题吗?带着疑问去翻了翻SpringMVC的源码,结论是:不会有线程安全问题!!!不会有线程安全问题!!!!不会有线程安全问题!!!下面我们来分析一下: 在前面的文章中我们简单的分析过SpringMVC的上下文初始化过程(SpringMVC之浅析上下文初始化(一)和 SpringMVC之浅析上下文初始化(二)),SpringMVC环境中的父上下文时:XmlWebApplicationContext。下面我们先看一下XmlWebApplicationContext的UML类图(去掉了一些暂时无关的): 之前也说过会调用AbstractApplicationContext中的refresh方法进行Bean的组装初始化的过程,在refresh()这个方法中会调用:postProcessBeanFactory()这个方法,设置一些需要预先处理的Bean。而XmlWebApplicationContext继承了AbstractRefreshableWebApplicationContext,AbstractRefreshableWebApplicationContext中重写了postProcessBeanFactory这个方法,我们去AbstractRefreshableWebApplicationContext这个类中看一下这个方法的内容: @Override protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) { //注册Bean的生命周期的相关的类(实现了BeanPostProcessor接口) beanFactory.addBeanPostProcessor(new ServletContextAwareProcessor(this.servletContext, this.servletConfig)); //需要忽略依赖检查的接口 beanFactory.ignoreDependencyInterface(ServletContextAware.class); beanFactory.ignoreDependencyInterface(ServletConfigAware.class); //这个是这次要重点分析的类 WebApplicationContextUtils.registerWebApplicationScopes(beanFactory, this.servletContext); //注册一些环境变量相关的类 WebApplicationContextUtils.registerEnvironmentBeans(beanFactory, this.servletContext, this.servletConfig); }WebApplicationContextUtils.registerWebApplicationScopes(beanFactory, this.servletContext);这个方法是我们需要重点分析的类,我进去看一下: public static void registerWebApplicationScopes(ConfigurableListableBeanFactory beanFactory, ServletContext sc) { beanFactory.registerScope(WebApplicationContext.SCOPE_REQUEST, new RequestScope()); beanFactory.registerScope(WebApplicationContext.SCOPE_SESSION, new SessionScope(false)); beanFactory.registerScope(WebApplicationContext.SCOPE_GLOBAL_SESSION, new SessionScope(true)); if (sc != null) { ServletContextScope appScope = new ServletContextScope(sc); beanFactory.registerScope(WebApplicationContext.SCOPE_APPLICATION, appScope); // Register as ServletContext attribute, for ContextCleanupListener to detect it. sc.setAttribute(ServletContextScope.class.getName(), appScope); } beanFactory.registerResolvableDependency(ServletRequest.class, new RequestObjectFactory()); beanFactory.registerResolvableDependency(ServletResponse.class, new ResponseObjectFactory()); beanFactory.registerResolvableDependency(HttpSession.class, new SessionObjectFactory()); beanFactory.registerResolvableDependency(WebRequest.class, new WebRequestObjectFactory()); if (jsfPresent) { FacesDependencyRegistrar.registerFacesDependencies(beanFactory); } }在这个类中注册了Web开发相关的三个Scope:RequestScope、SessionScope和全局的SessionScope以及一个ServletContextScope。接下来了注册了几个实现了ObjectFactory的Bean。RequestObjectFactory、RequestObjectFactory、SessionObjectFactory、WebRequestObjectFactory。这几个类是在Controller中注入Request和Response的关键。我们先记住RequestObjectFactory这个类。这几个类被放到了resolvableDependencies中。resolvableDependencies这个Map中存放的是在Autowire时所要使用到的bean的类型和对应的Object。 下面我们去看一下在Controller中注入Request的一个过程。首先看一下相应的一些调用链: 这个调用链比较长,调用链的执行过程也很复杂,这里先不过多展开的,我们直接进入到关键代码中。先进入到DefaultListableBeanFactory的findAutowireCandidates这个方法中,看一些关键代码: protected Map<String, Object> findAutowireCandidates( String beanName, Class<?> requiredType, DependencyDescriptor descriptor) { String[] candidateNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors( this, requiredType, true, descriptor.isEager()); Map<String, Object> result = new LinkedHashMap<String, Object>(candidateNames.length); for (Class<?> autowiringType : this.resolvableDependencies.keySet()) { if (autowiringType.isAssignableFrom(requiredType)) { Object autowiringValue = this.resolvableDependencies.get(autowiringType); autowiringValue = AutowireUtils.resolveAutowiringValue(autowiringValue, requiredType); if (requiredType.isInstance(autowiringValue)) { result.put(ObjectUtils.identityToString(autowiringValue), autowiringValue); break; } } } //省略其他代码 ........... }在上面的代码中我们看到了resolvableDependencies这个属性。当注入HttpServletRequest的时候,requiredType值是HttpServletRequest.class,我们还记得在resolvableDependencies中放入了ServletRequest.class这个key。所以 if (autowiringType.isAssignableFrom(requiredType)) 这个判断会返回true,接着会取得RequestObjectFactory这个对象。接着会调用AutowireUtils.resolveAutowiringValue这个方法进一步的解析Autowire的值。我们进到这个方法中进行看一下: public static Object resolveAutowiringValue(Object autowiringValue, Class<?> requiredType) { if (autowiringValue instanceof ObjectFactory && !requiredType.isInstance(autowiringValue)) { ObjectFactory<?> factory = (ObjectFactory<?>) autowiringValue; if (autowiringValue instanceof Serializable && requiredType.isInterface()) { autowiringValue = Proxy.newProxyInstance(requiredType.getClassLoader(), new Class<?>[] {requiredType}, new ObjectFactoryDelegatingInvocationHandler(factory)); } else { return factory.getObject(); } } return autowiringValue; }在这个方法中会先判断上一步取得的autowireValue是不是可以实例化为ObjectFactory 对象。上一步取得的autowireValue是RequestObjectFactory。我们看一下RequestObjectFactory这个类的内容: private static class RequestObjectFactory implements ObjectFactory<ServletRequest>, Serializable { @Override public ServletRequest getObject() { return currentRequestAttributes().getRequest(); } @Override public String toString() { return "Current HttpServletRequest"; } }很明显的实现了ObjectFactory这个接口。接着RequestObjectFactory 是不能实例化为HttpServletRequest对象的。所以会进入到循环体中。接着进一步的判断上一步取得的autowireValue的值能不能被序列化,以及requiredType是不是可以接口,从上面的分析可以看出这个条件也是成立的。所以最终生成的autowireValue的值是一个JDK动态代理生成的对象。InvocationHandler的实现类为:ObjectFactoryDelegatingInvocationHandler。所以我们在Controller层中所注入的HttpServletRequest其实是一个JDK动态代理生成的对象。这个是很关键的一个点!!!当我们在Controller中调用的时候: @RequestMapping("testAutowiredRequest") public String testAutowiredRequest() { request.getParameter("userNmae"); return "success"; }会进入到ObjectFactoryDelegatingInvocationHandler的invoke方法中。我们去看一下: @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { String methodName = method.getName(); if (methodName.equals("equals")) { // Only consider equal when proxies are identical. return (proxy == args[0]); } else if (methodName.equals("hashCode")) { // Use hashCode of proxy. return System.identityHashCode(proxy); } else if (methodName.equals("toString")) { return this.objectFactory.toString(); } try { return method.invoke(this.objectFactory.getObject(), args); } catch (InvocationTargetException ex) { throw ex.getTargetException(); } }这里对equals方法、hashcode方法、toString方法进行了特殊处理。其他的方法则是直接执行method.invoke的方法。第一个参数为所调用的对象。是通过调用this.objectFactory.getObject()来获取的。objectFactory,通过我们上面的分析我们知道它是RequestObjectFactory这个对象。所以我们去RequestObjectFactory这个类中看一下objectFactory这个方法: @Override public ServletRequest getObject() { return currentRequestAttributes().getRequest(); }在上面的代码中调用了currentRequestAttributes().getRequest()。我们接着去currentRequestAttributes()这个方法中看一下: private static ServletRequestAttributes currentRequestAttributes() { RequestAttributes requestAttr = RequestContextHolder.currentRequestAttributes(); if (!(requestAttr instanceof ServletRequestAttributes)) { throw new IllegalStateException("Current request is not a servlet request"); } return (ServletRequestAttributes) requestAttr; }看到这里时,你是不是会有一种恍然大悟的感觉呢?((ServletRequestAttributes)RequestContextHolder.currentRequestAttributes()).getRequest();这种方式不也是我们经常在代码中获取HttpServletRequest对象的方式吗?我在进一步的分析一下。看一下RequestContextHolder.currentRequestAttributes()这个方法: public static RequestAttributes currentRequestAttributes() throws IllegalStateException { RequestAttributes attributes = getRequestAttributes(); if (attributes == null) { if (jsfPresent) { attributes = FacesRequestAttributesFactory.getFacesRequestAttributes(); } if (attributes == null) { throw new IllegalStateException(""); } } return attributes; }通过getRequestAttributes()这个方法来获取RequestAttributes对象。我们进入到getRequestAttributes()这个方法中继续看一下: public static RequestAttributes getRequestAttributes() { RequestAttributes attributes = requestAttributesHolder.get(); if (attributes == null) { attributes = inheritableRequestAttributesHolder.get(); } return attributes; }发现是通过requestAttributesHolder或者inheritableRequestAttributesHolder的get()方法,来获取的RequestAttributes对象。那么requestAttributesHolder和inheritableRequestAttributesHolder是什么呢? private static final ThreadLocal<RequestAttributes> requestAttributesHolder = new NamedThreadLocal<RequestAttributes>("Request attributes"); private static final ThreadLocal<RequestAttributes> inheritableRequestAttributesHolder = new NamedInheritableThreadLocal<RequestAttributes>("Request context");两个ThreadLocal的对象!!!到这里总算是一切都真相大白了!!!每次都是从ThreadLocal对象中取的值,那还能不是线程安全的吗?这里在多说一句,什么时候往这两个ThreadLocal中放入值的呢?请接着往下看:org.springframework.web.servlet.FrameworkServlet#processRequest这个方法中有这样的几行代码: RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes(); ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);//创建ServletRequestAttributes initContextHolders(request, localeContext, requestAttributes);//往ThreadLocal中放入值initContextHolders这个方法的代码如下: private void initContextHolders( HttpServletRequest request, LocaleContext localeContext, RequestAttributes requestAttributes) { if (localeContext != null) { LocaleContextHolder.setLocaleContext(localeContext, this.threadContextInheritable); } //把RequestAttributes放入到ThreadLocal中 if (requestAttributes != null) { RequestContextHolder.setRequestAttributes(requestAttributes, this.threadContextInheritable); } } RequestContextHolder.setRequestAttributes这个方法的内容如下: public static void setRequestAttributes(RequestAttributes attributes, boolean inheritable) { if (attributes == null) { resetRequestAttributes(); } else { if (inheritable) { inheritableRequestAttributesHolder.set(attributes); requestAttributesHolder.remove(); } else { requestAttributesHolder.set(attributes); inheritableRequestAttributesHolder.remove(); } } }不需要再多说了。 这里总结一下:Controller层中所注入的HttpServletReuqest的实现类为JDK动态代理生成的一个代理类,从Request中获取值的时候是从ThreadLocal中得到的对象中的值。
我们在做项目开发的时候,生产环境和测试环境的一些配置可能会不一样,有时候一些功能也可能会不一样,所以我们可能会在上线的时候手工修改这些配置信息。但是Spring中为我们提供了Profile这个功能。我们只需要在启动的时候添加一个虚拟机参数,激活自己环境所要用的Profile就可以了。下面举个例子来说明一下: 首先我们先创建一个bean,用来测试是不是从不同的配置文件中取的值。代码如下(省略getter/setter): @ConfigurationProperties("profile.test") @Component public class ProfileDomain implements Serializable { private static final long serialVersionUID = 2092752356451204202L; /** * 用户名 */ private String userName; /** * 密码 */ private String passWord; }接着我们在resources下面建立两个properties文件。 application-dev.properties用来存放的时候开发环境的配置信息,application-prod.properties用来存放的是生产环境的配置信息。内容分别如下: application-dev.properties: profile.test.userName=\u6211\u662f\u5f00\u53d1\u73af\u5883 profile.test.passWord=this is development environmentapplication-prod.properties: profile.test.userName=\u6211\u662f\u751f\u4ea7\u73af\u5883 profile.test.passWord=this is production environment我们写个Controller来测试一下: @RestController public class ProfileController { @Autowired private ProfileDomain profileDomain; @RequestMapping("testProfile") public ProfileDomain testProfile() { return profileDomain; } }我们用命令行启动一下(注意我们这里加了个参数 --spring.profiles.active=dev): java -jar LearnSpringBoot-0.0.1-SNAPSHOT.jar --spring.profiles.active=dev下面我们访问一下:http://localhost:8003/testProfile,输出结果如下: 从输出结果中我们可以看出,输出的内容是application-dev.properties中的配置信息。下面我们用另外一个命令参数启动一下: java -jar LearnSpringBoot-0.0.1-SNAPSHOT.jar --spring.profiles.active=prod我们还是用上面的请求访问一下: http://localhost:8003/testProfile,输出结果如下: 从上面的输出结果中我们可以看到我们在启动SpringBoot的时候所启用的profile,它就会从不同的配置文件中加载配置项。另外还可以增加虚拟机参数:-Dspring.profiles.active=prod的方式来激活不同的profile。也可以在SpringBoot默认的或者指定的配置文件中增加profile的激活项。如:spring.profiles.active=dev。 有时候我们可能还会有这样的需求,生产环境或者开发环境我们所看到的功能是不同的,也需要我们根据配置项来激活不同的功能。 首先我们先定义一个Service的接口: public interface ProfileService { String getProfileDomain(); }如果spring.profiles.active的值是prod的时,它的实现类如下: @Profile(value = "prod") @Service public class ProfileProdServiceImpl implements ProfileService { public ProfileProdServiceImpl() { System.out.println("我是生产环境。。。。。"); } @Override public String getProfileDomain() { StringBuilder sb = new StringBuilder(); sb.append("我在生产环境,").append("我可以吃鸡鸭鱼牛羊肉。。。。"); return sb.toString(); } } 如果spring.profiles.active的值是dev的时,它的实现类如下: @Profile(value = "dev") @Service public class ProfileDevServiceImpl implements ProfileService { public ProfileDevServiceImpl() { System.out.println("我是开发环境。。。。。"); } @Override public String getProfileDomain() { StringBuilder sb = new StringBuilder(); sb.append("我在开发环境,").append("我只能吃加班餐:大米饭。。。。"); return sb.toString(); } }下面我们来测试一下,在刚才的Controller类中注入ProfileService,代码如下: @RestController public class ProfileController { @Autowired private ProfileDomain profileDomain; @Autowired private ProfileService profileService; @RequestMapping("testProfile") public ProfileDomain testProfile() { return profileDomain; } @RequestMapping("testProfile2") public String testProfile2() { return profileService.getProfileDomain(); } }当我们的spring.profiles.active为prod的时候:请求如下:http://localhost:8003/testProfile2,输出如下: 当我们的spring.profiles.active为dev的时候:请求如下:http://localhost:8003/testProfile2,输出如下: 我们在根据环境进行功能切换的类上加上@Profile注解,这样就可以根据所激活的不同的环境,注入不同的实现类。 这里需要注意一下:如果用了@Profile这个注解,在配置项中找不到@Profile中的值的话,在系统启动的时候是会报错的。
说明:本文所用的SpringMVC版本为4.3.4.RELEASE,应用服务器为TomCat8.0.33。 在上一篇文章中我们分析了请求service方法到doDispatch的一部分,今天我们先简单的分析doDispatch这个方法,以后还会有很多围绕着这个方法进行分析的内容。首先我们先看一下这个方法的源码的内容: protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { HttpServletRequest processedRequest = request; HandlerExecutionChain mappedHandler = null; boolean multipartRequestParsed = false; WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); try { ModelAndView mv = null; Exception dispatchException = null; try { processedRequest = checkMultipart(request); multipartRequestParsed = (processedRequest != request); mappedHandler = getHandler(processedRequest); if (mappedHandler == null || mappedHandler.getHandler() == null) { noHandlerFound(processedRequest, response); return; } // Determine handler adapter for the current request. HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); // Process last-modified header, if supported by the handler. String method = request.getMethod(); boolean isGet = "GET".equals(method); if (isGet || "HEAD".equals(method)) { long lastModified = ha.getLastModified(request, mappedHandler.getHandler()); if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) { return; } } if (!mappedHandler.applyPreHandle(processedRequest, response)) { return; } // Actually invoke the handler. mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); if (asyncManager.isConcurrentHandlingStarted()) { return; } applyDefaultViewName(processedRequest, mv); mappedHandler.applyPostHandle(processedRequest, response, mv); } catch (Exception ex) { dispatchException = ex; } catch (Throwable err) { dispatchException = new NestedServletException("Handler dispatch failed", err); } processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); } catch (Exception ex) { triggerAfterCompletion(processedRequest, response, mappedHandler, ex); } catch (Throwable err) { triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", err)); } finally { if (asyncManager.isConcurrentHandlingStarted()) { if (mappedHandler != null) { mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response); } } else { if (multipartRequestParsed) { cleanupMultipart(processedRequest); } } } }下面我们分析一下这些代码的意思,先看这句代码: processedRequest = checkMultipart(request);从这个方法名字我们能看出来它是用来检查这个请求是不是文件上传的请求的。我们具体的看一下它是怎么判断是否是文件上传的。 protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException { if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) { if (WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class) != null) { } else if (request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) instanceof MultipartException) { } else { return this.multipartResolver.resolveMultipart(request); } } return request; } checkMultipart这个方法的源码如上所示。这里先是判断multipartResolver这个类是不是为空,我们之前分析过,multipartResolver是需要我们进行配置的,通常配置如下所示: <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"/>如果没有配置MultipartResolver的话,则认为不是文件上传的请求,如果配置了MultipartResolver的话,调用isMultipart方法验证是不是文件上传的请求,isMultipart方法的内容如下: public boolean isMultipart(HttpServletRequest request) { return (request != null && ServletFileUpload.isMultipartContent(request)); }在这里我们看到了一个类是:ServletFileUpload,这个类就是大名鼎鼎的文件上传工具包:commons-fileupload中的类。SpringMVC对文件上传的处理是借助于commons-fileupload包来实现的。我们去isMultipartContent这个方法中看一下: public static final boolean isMultipartContent( HttpServletRequest request) { if (!POST_METHOD.equalsIgnoreCase(request.getMethod())) { return false; } return FileUploadBase.isMultipartContent(new ServletRequestContext(request)); }这里首先判断一下是不是POST请求,如果不是POST请求直接返回false,接着通过isMultipartContent方法来继续验证: public static final boolean isMultipartContent(RequestContext ctx) { String contentType = ctx.getContentType(); if (contentType == null) { return false; } if (contentType.toLowerCase(Locale.ENGLISH).startsWith(MULTIPART)) { return true; } return false; }这段代码的内容也比较简单,首先获取请求头中Context-Type的值,如果Context-Type等于null,则直接返回false,接着判断Context-Type是不是以multipart/开头。总结下来就是:如果请求是POST请求,并且请求头中的Context-Type是以multipart/开头的就认为是文件上传的请求。我们接着回到DIspatcherServlet中的checkMultipart这个方法中,继续看: protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException { if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) { if (WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class) != null) { } else if (request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) instanceof MultipartException) { } else { return this.multipartResolver.resolveMultipart(request); } } return request; }如果是文件上传请求,则继续判断这个请求是不是已经被转换为MultipartHttpServletRequest类型了。为什么会有这个判断呢?在Spring-Web这个jar中有一个过滤器:org.springframework.web.multipart.support.MultipartFilter,如果在web.xml中配置这个过滤器的话,则会在过滤器中提前判断是不是文件上传的请求,并将请求转换为MultipartHttpServletRequest类型。这个过滤器中默认使用的MultipartResolver为StandardServletMultipartResolver。如果不是MultipartHttpServletRequest类型的话,则判断是不是出现异常了。如果上面这两步返回的都是false,则会执行这句:this.multipartResolver.resolveMultipart(request),将请求转换为MultipartHttpServletRequest类型。我们具体看一下: public MultipartHttpServletRequest resolveMultipart(final HttpServletRequest request) throws MultipartException { Assert.notNull(request, "Request must not be null"); if (this.resolveLazily) { return new DefaultMultipartHttpServletRequest(request) { @Override protected void initializeMultipart() { MultipartParsingResult parsingResult = parseRequest(request); setMultipartFiles(parsingResult.getMultipartFiles()); setMultipartParameters(parsingResult.getMultipartParameters()); setMultipartParameterContentTypes(parsingResult.getMultipartParameterContentTypes()); } }; } else { MultipartParsingResult parsingResult = parseRequest(request); return new DefaultMultipartHttpServletRequest(request, parsingResult.getMultipartFiles(), parsingResult.getMultipartParameters(), parsingResult.getMultipartParameterContentTypes()); } }http://blog.csdn.net/zknxx/article/details/72836388#t5在CommonsMultipartResolver中有一个属性叫resolveLazily,这个属性值代表是不是延迟解析文件上传,默认为false。最终返回的是一个DefaultMultipartHttpServletRequest的类。这里有一个重要的方法是:parseRequest,这个方法干的事是解析文件上传请求。它的底层是commons-fileupload那一套,不同的是Spring在获取FileItem之后,又进行了一下封装,封装为便于Spring框架整合。如果想简单的了解一下怎么解析文件上传的请求的话,可以参考这个简陋版的文件上传:http://blog.csdn.net/zknxx/article/details/60884573 下面我们接着看这一句话: multipartRequestParsed = (processedRequest != request);processedRequest是checkMultipart(request)这个方法返回的值,如果processedRequest和request不相等的话,则认为是文件上传的请求。 我们接着看这一句话: mappedHandler = getHandler(processedRequest);这段代码的意思是获取当前请求对应的处理类,在这个处理链中会包含对应的拦截器的信息。HandlerExecutionChain这个类中包含变和不变量的两部分内容。我们具体的看一下getHandler这个方法的代码: protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { for (HandlerMapping hm : this.handlerMappings) { HandlerExecutionChain handler = hm.getHandler(request); if (handler != null) { return handler; } } return null; }这段代码的内容是循环handlerMappings,然后获取对应的执行链,只要找到一个对应的执行链就返回。关于handlerMappings,我们之前分析过参考这里:http://blog.csdn.net/zknxx/article/details/72836388#t5。SpringMVC默认加载三个请求处理映射类:RequestMappingHandlerMapping、SimpleUrlHandlerMapping、和BeanNameUrlHandlerMapping。这三个类有一个共同的父类:AbstractHandlerMapping。在上面代码中hm.getHandler(request)这个getHandler方法在AbstractHandlerMapping中,它的子类都没有重写这个方法。我们去AbstractHandlerMapping这个类中看一下这个方法: @Override public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { //这里会两个子类重写这个方法:AbstractHandlerMethodMapping和AbstractUrlHandlerMapping Object handler = getHandlerInternal(request); //如果没有找到的话,去默认的处理类 if (handler == null) { handler = getDefaultHandler(); } //如果没有默认的处理类,返回null if (handler == null) { return null; } // Bean name or resolved handler? if (handler instanceof String) { String handlerName = (String) handler; handler = getApplicationContext().getBean(handlerName); } //包装为执行器链 HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request); //是不是cors请求,cors是跨域请求 if (CorsUtils.isCorsRequest(request)) { CorsConfiguration globalConfig = this.corsConfigSource.getCorsConfiguration(request); CorsConfiguration handlerConfig = getCorsConfiguration(handler, request); CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig); executionChain = getCorsHandlerExecutionChain(request, executionChain, config); } return executionChain; }这里主要干了这几件事:获取Handler、创建执行器链,判断是不是cors跨域请求。获取handler的过程比较复杂,这个我们专开一篇文章来说明。下面我们来分析一下getHandlerExecutionChain这个方法,即创建执行器链的内容: protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) { //判断handler是不是执行器链,如果不是创建一个执行器链 HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ? (HandlerExecutionChain) handler : new HandlerExecutionChain(handler)); String lookupPath = this.urlPathHelper.getLookupPathForRequest(request); //包装拦截器 for (HandlerInterceptor interceptor : this.adaptedInterceptors) { if (interceptor instanceof MappedInterceptor) { MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor; if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) { chain.addInterceptor(mappedInterceptor.getInterceptor()); } } else { chain.addInterceptor(interceptor); } } return chain; }这个方法主要是创建执行器链,添加拦截器。拦截器这块,以后会开新的文章详细说明。 HandlerMapping的主要类图如下: 我们回到DIspatcherServlet的doDispatch方法中继续分析。 if (mappedHandler == null || mappedHandler.getHandler() == null) { noHandlerFound(processedRequest, response); return; }如果没有找到对应的处理类的话,这里通常会返回404,如果throwExceptionIfNoHandlerFound属性值为true的情况下会抛出异常。 我们继续往下分析: HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());这句话是获取处理适配器,我们进入到getHandlerAdapter这个方法中看一下: protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException { for (HandlerAdapter ha : this.handlerAdapters) { if (ha.supports(handler)) { return ha; } } throw new ServletException("No adapter for handler [" + handler + "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler"); }关于handlerAdapters看这里:http://blog.csdn.net/zknxx/article/details/72836388#t6。HandlerAdapter的类图如下: SimpleControllerHandlerAdapter是用来适配SimpleUrlHandlerMapping和BeanNameUrlHandlerMapping的映射的,也就是实现Controller接口的Handler。 AbstractHandlerMethodAdapter是用来适配RequestMappingHandlerMapping,也就是我们常用的RequestMapping注解。 HttpRequestHandlerAdapter是用来适配远程调用的。SimpleServletHandlerAdapter是用来适配Servlet实现类的。supports这个方法的实现都比较简单,我们这里就不细分析了。下面我们接着分析DispatcherServlet。 String method = request.getMethod(); boolean isGet = "GET".equals(method); if (isGet || "HEAD".equals(method)) { long lastModified = ha.getLastModified(request, mappedHandler.getHandler()); if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) { return; } }这段代码的意思清晰,如果是GET请求,如果内容没有变化的话,则直接返回。HEAD请求这个很奇怪啊。。。 我们接着分析 if (!mappedHandler.applyPreHandle(processedRequest, response)) { return; }这段代码的内容是调用执行器链中的拦截器,就是循环装配好的执行器链,执行。代码如下boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception { HandlerInterceptor[] interceptors = getInterceptors(); if (!ObjectUtils.isEmpty(interceptors)) { for (int i = 0; i < interceptors.length; i++) { HandlerInterceptor interceptor = interceptors[i]; if (!interceptor.preHandle(request, response, this.handler)) { triggerAfterCompletion(request, response, null); return false; } this.interceptorIndex = i; } } return true; }自定义拦截器的话,可以参考这里:http://blog.csdn.net/zknxx/article/details/72633444#t6 我们继续分析: mv = ha.handle(processedRequest, response, mappedHandler.getHandler());这句话就是执行handle了。关于这个可以先看一下这个:http://blog.csdn.net/zknxx/article/details/68952518 接续分析: applyDefaultViewName(processedRequest, mv); mappedHandler.applyPostHandle(processedRequest, response, mv);这两段代码的意思是:如果返回的ModelAndView不为null,并且没有设置view的话,这设置默认的view。处理拦截器的postHandle。 我们最后再看一下processDispatchResult这个方法: private void processDispatchResult(HttpServletRequest request, HttpServletResponse response, HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception) throws Exception { boolean errorView = false; //如果有异常,则处理异常,返回异常页面 if (exception != null) { if (exception instanceof ModelAndViewDefiningException) { mv = ((ModelAndViewDefiningException) exception).getModelAndView(); } else { Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null); mv = processHandlerException(request, response, handler, exception); errorView = (mv != null); } } //返回的ModelAndView不为null // Did the handler return a view to render? if (mv != null && !mv.wasCleared()) { //解析页面 render(mv, request, response); if (errorView) { WebUtils.clearErrorRequestAttributes(request); } } else { } if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) { // Concurrent handling started during a forward return; } //调用处理拦截器的afterCompletion方法 if (mappedHandler != null) { mappedHandler.triggerAfterCompletion(request, response, null); } }在这个方法里干了这样的几件事:如果出现异常,返回异常页面。如果没有异常,ModelAndView不为null,则正常渲染页面,调用拦截器的afterCompletion方法。 我们对于doDispatch的分析,就先到这里。
这几天在家写了一个简陋版的SpringMVC,先把代码贴出来,有兴趣的同学可以看一下。 首先定义了一个简陋的服务器,其实也就是用了ServerSocket写了一个服务端(更详细的点看这里:创建一个简单的web服务器): public class HttpServer { public static void main(String[] args) { await(); } private static void await() { ServerSocket serverSocket = null; try { ApplicationContext ac = new ApplicationContext(new HttpServletImpl(), "/com/zkn/fullstacktraining/spring/one/spring-config.properties"); ac.init(); boolean shutDown = false; //创建一个服务端 serverSocket = new ServerSocket(8005, 1, InetAddress.getByName("127.0.0.1")); //用线程池处理请求 ExecutorService executorService = new ThreadPoolExecutor(10, 10, 0L, TimeUnit.SECONDS, new LinkedBlockingDeque<>(), new ThreadFactoryBuilder().setNameFormat("XX-task-%d").build()); while (!shutDown) { //接收客户端请求 ProcessSocket processSocket = new ProcessSocket(serverSocket.accept()); executorService.execute(processSocket); } } catch (Exception e) { e.printStackTrace(); System.exit(0); } } }这里用了线程池来处理多个socket连接。 接着我们需要自定义一个Request和Response两个类来处理请求和响应信息。 Request这里存了请求头信息、请求参数信息、请求类型、请求URI等。Request的代码如下: public class Request { /** * 输入流 */ private InputStream inputStream; /** * 头文件信息 */ private Map<String, String> headerMap = new HashMap<>(); /** * 参数信息 */ private Map<String, Object> parameterMap = new HashMap<>(); /** * 是否被解析过 */ private boolean isParser = false; /** * 请求类型 */ private String requestMethod; /** * 请求URI */ private String uri; public Request(InputStream inputStream) { this.inputStream = inputStream; } /** * 获取头文件信息 * * @param key * @return */ public String getHeader(String key) { if (key == null) { return null; } return headerMap.get(key.toLowerCase()); } /** * 获取参数的值 * * @param key * @return */ public String getParameterValue(String key) { Object obj = parameterMap.get(key); if (obj == null) return null; if (obj instanceof List) { if (!((List) obj).isEmpty()) { return (String) ((List) obj).get(0); } return null; } return (String) obj; } /** * 获取多个值 * * @param key * @return */ public List<String> getParameterValues(String key) { Object obj = parameterMap.get(key); if (obj == null) { return null; } if (obj instanceof List) { return (List<String>) obj; } return null; } /** * 获取所有的key * * @return */ public Set<String> getParameterNames() { return parameterMap.keySet(); } public String getRequestMethod() { return requestMethod; } /** * 解析请求 */ public void parseRequest() { if (isParser) { return; } isParser = true; //这里用了BufferedReader 没有考虑性能问题 BufferedReader lineNumberReader = new BufferedReader(new InputStreamReader(inputStream)); try { //获取请求行 请求行格式 Method URI 协议 String str = lineNumberReader.readLine(); if (str != null) { String[] strArray = str.split(" "); requestMethod = strArray[0]; parseUrl(strArray[1]); } String headerStr = null; String[] strArr = null; //解析头信息 while ((headerStr = lineNumberReader.readLine()) != null) { if ("".equals(headerStr)) { break; } strArr = headerStr.split(":"); if (strArr.length == 2) { headerMap.put(strArr[0].toLowerCase(), strArr[1].trim()); } } //如果是POST请求 String contentType = null; if ("POST".equals(requestMethod)) { //文件上传 if ((contentType = headerMap.get("content-type")) != null && headerMap.get("content-type").startsWith("multipart/form-data")) { //解析文件上传 parseUploadFile(lineNumberReader, contentType); } else { //非文件上传 String postParameter = ""; while ((postParameter = lineNumberReader.readLine()) != null) { if ("".equals(postParameter)) { break; } wrapperParameterValue(postParameter); } } } System.out.println(JSON.toJSONString(parameterMap)); } catch (IOException e) { e.printStackTrace(); } System.out.println("执行完了。。。"); } private void parseUploadFile(BufferedReader lineNumberReader, String contentType) throws IOException { String str;//文件上传的分割位 这里只处理单个文件的上传 String boundary = contentType.substring(contentType.indexOf("boundary") + "boundary=".length()); //解析消息体 while ((str = lineNumberReader.readLine()) != null) { //解析结束的标记 do { //读取boundary中的内容 //读取Content-Disposition str = lineNumberReader.readLine(); //说明是文件上传 if (str.indexOf("Content-Disposition:") >= 0 && str.indexOf("filename") > 0) { str = str.substring("Content-Disposition:".length()); String[] strs = str.split(";"); String fileName = strs[strs.length - 1].replace("\"", "").split("=")[1]; System.out.println("fileName = " + fileName); //这一行是Content-Type lineNumberReader.readLine(); //这一行是换行 lineNumberReader.readLine(); //正式去读文件的内容 BufferedWriter bw = null; try { bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("G:\\LearnVideo\\fileLoad" + File.separator + fileName), "UTF-8")); while (true) { str = lineNumberReader.readLine(); if (str.startsWith("--" + boundary)) { break; } bw.write(str); bw.newLine(); } bw.flush(); } catch (Exception e) { } finally { if (bw != null) { bw.close(); } } } if (str.indexOf("Content-Disposition:") >= 0) { str = str.substring("Content-Disposition:".length()); String[] strs = str.split(";"); String name = strs[strs.length - 1].replace("\"", "").split("=")[1]; lineNumberReader.readLine(); StringBuilder stringBuilder = new StringBuilder(); while (true) { str = lineNumberReader.readLine(); if (str.startsWith("--" + boundary)) { break; } stringBuilder.append(str); } parameterMap.put(name, stringBuilder.toString()); } } while (("--" + boundary).equals(str)); //解析结束 if (str.equals("--" + boundary + "--")) { break; } } } /** * 解析URI * * @param s */ private void parseUrl(String s) { if ("/".equals(s)) { uri = "/"; return; } String tempStr = s; /** * 说明可能带参数 */ int flag = 0; if ((flag = tempStr.indexOf("?")) > 0) { uri = tempStr.substring(0, flag); if (tempStr.length() > (flag + 1)) { tempStr = tempStr.substring(flag + 1, tempStr.length()); String[] strArray = tempStr.split("&"); for (String str : strArray) { wrapperParameterValue(str); } } return; } uri = s; } /** * 组装参数值 * * @param str */ private void wrapperParameterValue(String str) { Object value; String[] strArr = str.split("="); if (strArr.length == 2) { value = parameterMap.get(strArr[0]); if (value == null) { parameterMap.put(strArr[0], strArr[1]); } else { if (value instanceof List) { ((List) value).add(strArr[1]); } else { List<String> list = new ArrayList<>(2); list.add((String) value); list.add(strArr[1]); parameterMap.put(strArr[0], strArr[1]); } } } } public String getUri() { return uri; }Response这里只处理了返回简单字符串和返回静态资源文件,Response的代码如下: public class Response { /** * 输出流 */ private OutputStream outputStream; /** * 字符输出流 */ private PrintWriter printWriter; /** * 请求类 */ private Request request; /** * Cookie信息 */ private List<Cookie> cookieList = new ArrayList<>(2); public Response(OutputStream outputStream, Request request) { this.outputStream = outputStream; this.request = request; } public void sendStaticResource(String path) { FileInputStream fis = null; try { if ("/".equals(path)) { path = "/static/html/index.html"; } else { path = request.getUri(); } URL url = this.getClass().getResource(path); if (url != null) { File file = new File(url.getFile()); if (file.exists() && !file.isDirectory() && file.canRead()) { fis = new FileInputStream(file); int flag = 0; byte[] bytes = new byte[1024]; while ((flag = fis.read(bytes)) != -1) { outputStream.write(bytes, 0, flag); } } } else { PrintWriter printWriter = getWriter(); //这里用PrintWriter字符输出流,设置自动刷新 printWriter.write("HTTP/1.1 404 File Not Found \r\n"); printWriter.write("Content-Type: text/html\r\n"); printWriter.write("Content-Length: 23\r\n"); printWriter.write("\r\n"); printWriter.write("<h1>File Not Found</h1>"); printWriter.close(); } } catch (IOException e) { e.printStackTrace(); } finally { if (fis != null) { try { fis.close(); } catch (IOException e) { e.printStackTrace(); } } } } public PrintWriter getWriter() throws IOException { printWriter = new PrintWriter(outputStream, true); return printWriter; } public void sendSuccess() { PrintWriter printWriter = null; try { printWriter = getWriter(); //这里用PrintWriter字符输出流,设置自动刷新 printWriter.write("HTTP/1.1 200 OK \r\n"); printWriter.write("Content-Type: text/html;charset=utf-8\r\n"); printWriter.write("Content-Length: " + "成功了".getBytes().length + "\r\n"); if (cookieList != null && !cookieList.isEmpty()) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < cookieList.size(); i++) { //设置多个Cookie sb.append("Set-Cookie: ").append(cookieList.get(i).getKey()).append("=").append(cookieList.get(i).getValue()).append("\r\n"); } printWriter.write(sb.toString()); } printWriter.write("\r\n"); printWriter.write("成功了"); } catch (IOException e) { e.printStackTrace(); } finally { printWriter.close(); } } public void addCookie(Cookie cookie) { cookieList.add(cookie); } }这里我们模仿Servlet,也自定义一个Servlet接口。代码如下: public interface Servlet { void init(); void service(Request request, Response response) throws Exception; void destory(); }它的一个实现类如下,在这个类中我们根据URI找到对应的请求处理类,并调用响应的请求方法。 public class HttpServletImpl implements Servlet { private StaticResourceProcessor resouce = new StaticResourceProcessor(); @Override public void init() { System.out.println("我被初始化了、、、、、"); } @Override public void service(Request request, Response response) throws Exception { String uri = request.getUri(); if ("/".equals(uri) || (!StringUtils.isEmpty(uri) && uri.startsWith("/static/"))) { //处理静态资源 resouce.process(request, response); } else { RequestMappingInfo mappingInfo = ApplicationContext.mappingMap.get(uri); if (mappingInfo != null) { List<String> parameterList = mappingInfo.getParameter(); int parLen = mappingInfo.getParameter().size() - 1; Class<?>[] clazzs = mappingInfo.getFormalParameter(); List<Object> list = new ArrayList<>(); Object obj = null; if (clazzs != null) { for (int i = 0; i < clazzs.length; i++) { obj = getObject(request, response, parameterList, parLen < i, clazzs[i], i); list.add(obj); } } mappingInfo.getMethod().invoke(mappingInfo.getObj(), list.toArray()); response.sendSuccess(); } } } /** * 组装方法的参数 * * @param request * @param response * @param parameterList * @param b * @param clazz * @param i * @return * @throws InstantiationException * @throws IllegalAccessException */ private Object getObject(Request request, Response response, List<String> parameterList, boolean b, Class<?> clazz, int i) throws InstantiationException, IllegalAccessException { Object obj = null; //如果参数类型为Request if (clazz.isAssignableFrom(Request.class)) { obj = request; } else if (clazz.isAssignableFrom(Response.class)) { //如果参数类型为Response obj = response; } //如果是字节类型(包含基本类型和包装类) else if (clazz == byte.class || clazz == Byte.class) { if (!b) { obj = Byte.parseByte(request.getParameterValue(parameterList.get(i))); } } //如果是short类型(包含基本类型和包装类) else if (clazz == short.class || clazz == Short.class) { if (!b) { obj = Short.parseShort(request.getParameterValue(parameterList.get(i))); } } //如果是char类型(包含基本类型和包装类) else if (clazz == char.class || clazz == Character.class) { } //如果是整型(包含基本类型和包装类) else if (clazz == int.class || clazz == Integer.class) { if (!b) { obj = Integer.parseInt(request.getParameterValue(parameterList.get(i))); } } //如果是float(包含基本类型和包装类) else if (clazz == float.class || clazz == Float.class) { if (!b) { obj = Float.parseFloat(request.getParameterValue(parameterList.get(i))); } } //如果是double(包含基本类型和包装类) else if (clazz == double.class || clazz == Double.class) { if (!b) { obj = Double.parseDouble(request.getParameterValue(parameterList.get(i))); } } //如果是double(包含基本类型和包装类) else if (clazz == long.class || clazz == Long.class) { if (!b) { obj = Long.parseLong(request.getParameterValue(parameterList.get(i))); } } //如果是boolean(包含基本类型和包装类) else if (clazz == boolean.class || clazz == Boolean.class) { if (!b) { obj = Boolean.parseBoolean(request.getParameterValue(parameterList.get(i))); } } //如果是boolean(包含基本类型和包装类) else if (clazz == String.class) { if (!b) { obj = request.getParameterValue(parameterList.get(i)); } } //如果是日期类型,先不做处理 else if (clazz == Date.class) { obj = new Date(); } else { //暂不考虑数组、集合、Map等类型 obj = clazz.newInstance(); Field[] fields = obj.getClass().getDeclaredFields(); wrapperObjectFieldValue(request, obj, fields); } return obj; } /** * 组装属性对象值 * * @param request * @param obj * @param fields * @throws IllegalAccessException */ private void wrapperObjectFieldValue(Request request, Object obj, Field[] fields) throws IllegalAccessException { if (fields != null) { Field field = null; for (int j = 0; j < fields.length; j++) { field = fields[j]; String fieldName = field.getName(); field.setAccessible(true); String value = request.getParameterValue(fieldName); if (value != null && !"".equals(value)) { if (field.getType() == byte.class || field.getType() == Byte.class) { field.set(obj, Byte.parseByte(value)); } //如果是short类型(包含基本类型和包装类) else if (field.getType() == short.class || field.getType() == Short.class) { field.set(obj, Short.parseShort(value)); } //如果是char类型(包含基本类型和包装类) else if (field.getType() == char.class || field.getType() == Character.class) { field.set(obj, value.toCharArray()[0]); } //如果是整型(包含基本类型和包装类) else if (field.getType() == int.class || field.getType() == Integer.class) { field.set(obj, Integer.parseInt((value))); } //如果是float(包含基本类型和包装类) else if (field.getType() == float.class || field.getType() == Float.class) { field.set(obj, Float.parseFloat((value))); } //如果是double(包含基本类型和包装类) else if (field.getType() == double.class || field.getType() == Double.class) { field.set(obj, Double.parseDouble((value))); } //如果是double(包含基本类型和包装类) else if (field.getType() == long.class || field.getType() == Long.class) { field.set(obj, Long.parseLong((value))); } //如果是boolean(包含基本类型和包装类) else if (field.getType() == boolean.class || field.getType() == Boolean.class) { field.set(obj, Boolean.parseBoolean((value))); } //如果是boolean(包含基本类型和包装类) else if (field.getType() == String.class) { field.set(obj, value); } } } } } @Override public void destory() { } }接着我们模仿Spring写几个注解: 模仿Spring的Autowire注解 @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface CustomAutowire { String name() default ""; }模仿Spring的Component注解: @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface CustomComponent { }模仿Controller注解:@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface CustomController { }模仿RequestMapping注解: @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE,ElementType.METHOD}) public @interface CustomRequestMapping { /** * URI * @return */ String value(); /** * 参数信息 * @return */ String[] parameter() default ""; }这里根据请求找到对应的处理类和处理方法的思路是:定义一个Map,key是请求URI,value是自定义的一个类。这个自定义的类(RequestMappingInfo)包含这几个信息:Class信息、Method类、对象、请求参数和方法参数。java类在编译为class的时候有release和debug模式之分,在命令行中直接使用javac进行编译的时候,默认的时候release模式,使用release模式会改变形参中的参数名。而IDE都是使用debug模式进行编译的。ant编译的时候,需要在ant的配置文件中指定debug="true"。 如果要修改javac编译类文件的方式的话,需要指定-g参数。即:javac -g 类文件。通常我们都是用IDE进行项目开发的,所以我们的java类在编译成class的时候参数是不会变的,而SpringMVC在处理参数这个地方用到了ASM技术,来获取参数的信息,并且是在第一次处理请求的时候,来获取参数信息并放到缓存中,以后都从缓存中获取参数信息。RequestMappingInfo代码如下: public class RequestMappingInfo { /** * 类名 */ private Class<?> clazz; /** * 方法名 */ private Method method; /** * 对象 */ private Object obj; /** * 参数 */ private List<String> parameter; /** * 方法的参数 */ private Class<?>[] formalParameter; //省略get set方法 }接着我们定义一个类方便IOC: public class BeanDefinitionInfo implements Serializable { private static final long serialVersionUID = -5988012842492544219L; /** * 类信息 */ private Class<?> clazz; /** * 对象 */ private Object object; /** * 父类 */ private List<Class<?>> superClass; //省略 get set }接着就是我们比较重要的类了ApplicationContext,我们会在这里类中,简单模仿Spring IOC的过程,扫描固定包下固定注解的类。在这里遇到了一个问题,一开始在加载类的时候自己写了一个类加载器,但是一个类加载器在他的生命周期中只会加载这个类一次,所以会出现多次加载同一个类的情况。后来别人推荐了一个小工具类:Reflections,这个问题才比较完美的解决,Reflections是一个很不错的工具类,可以扫描Classpath下面任意的类,并且让反射更容易。如下:扫描带某个注解的类,获取含有方法名为某个字符串的类等等。github地址在这儿:https://github.com/ronmamo/reflections,里面有很多例子。自定义的ApplicationContext的代码如下: public class ApplicationContext { public static final Map<String, RequestMappingInfo> mappingMap = new HashMap<>(); /** * 所有扫描到的类 */ private static final Map<Class<?>, BeanDefinitionInfo> allScanClazz = new HashMap<>(); private static final List<Class<?>> allClass = Lists.newArrayList(); private static Servlet servlet; private CustomInputStreamSource streamSource = null; public ApplicationContext(Servlet servlet, String location) { this.servlet = servlet; streamSource = new CustomClasspathResource(location); } public void init() throws Exception { Properties properties = new Properties(); properties.load(streamSource.getInputStream()); String componentScan = properties.getProperty("component.scan"); wrapperCompontent(componentScan); imitateIOC(); servlet.init(); } private void wrapperCompontent(String componentScan) throws InstantiationException, IllegalAccessException { Reflections reflection = new Reflections(componentScan); //扫描所有有CustomController注解的类 Set<Class<?>> controllerClazz = reflection.getTypesAnnotatedWith(CustomController.class); //扫描所有有CustomComponent注解的类 Set<Class<?>> componentClazz = reflection.getTypesAnnotatedWith(CustomComponent.class); //扫描所有有CustomService注解的类 Set<Class<?>> serviceClazz = reflection.getTypesAnnotatedWith(CustomService.class); for (Iterator<Class<?>> it = controllerClazz.iterator(); it.hasNext(); ) { wrapperController(it.next()); } componentClazz.addAll(serviceClazz); for (Iterator<Class<?>> it = componentClazz.iterator(); it.hasNext(); ) { Class<?> clazz = it.next(); BeanDefinitionInfo beanDefinitionInfo = new BeanDefinitionInfo(); beanDefinitionInfo.setClazz(clazz); wrapperSuperClass(clazz, beanDefinitionInfo); allScanClazz.put(clazz, beanDefinitionInfo); allClass.add(clazz); } } /** * 模仿IOC * * @throws InstantiationException * @throws IllegalAccessException */ private void imitateIOC() throws InstantiationException, IllegalAccessException { Object instance = null; BeanDefinitionInfo beanDefinitionInfo = null; for (Map.Entry<Class<?>, BeanDefinitionInfo> entry : allScanClazz.entrySet()) { Class clazz = entry.getKey(); beanDefinitionInfo = entry.getValue(); instance = beanDefinitionInfo.getObject(); if (instance == null) { instance = clazz.newInstance(); beanDefinitionInfo.setObject(instance); } Field[] fields = clazz.getDeclaredFields(); if (fields != null && fields.length > 0) { for (int i = 0; i < fields.length; i++) { if (!fields[i].isAccessible()) { fields[i].setAccessible(true); } if (AnnotationUtil.isAutowire(fields[i])) { Class tmpClass = fields[i].getType(); if (tmpClass.isInterface()) { BeanDefinitionInfo tmpBean = null; for (int j = 0; j < allClass.size(); j++) { tmpBean = allScanClazz.get(allClass.get(j)); if (tmpClass.isAssignableFrom(tmpBean.getClazz())) { if (tmpBean.getObject() == null) { Object tmp = tmpBean.getClazz().newInstance(); tmpBean.setObject(tmp); } fields[i].set(instance, tmpBean.getObject()); break; } } } else { BeanDefinitionInfo tmpBean = allScanClazz.get(tmpClass); if (tmpBean.getObject() == null) { tmpBean.setObject(tmpBean.getClazz().newInstance()); } fields[i].set(instance, tmpBean.getObject()); } } } } } } /** * 组装Controller * * @param clazz * @throws IllegalAccessException * @throws InstantiationException */ private void wrapperController(Class<?> clazz) throws IllegalAccessException, InstantiationException { Object obj = clazz.newInstance(); BeanDefinitionInfo beanDefinitionInfo = new BeanDefinitionInfo(); beanDefinitionInfo.setClazz(clazz); beanDefinitionInfo.setObject(obj); wrapperSuperClass(clazz, beanDefinitionInfo); allScanClazz.put(clazz, beanDefinitionInfo); String str = ""; CustomRequestMapping customRequestMapping = null; if ((customRequestMapping = isRequestMapping(clazz)) != null) { if (customRequestMapping.value().startsWith("/")) { str = customRequestMapping.value(); } else { str = "/" + customRequestMapping.value(); } } Method[] methodArray = clazz.getMethods(); if (methodArray != null) { RequestMappingInfo requestMappingInfo = null; for (Method method : methodArray) { customRequestMapping = method.getAnnotation(CustomRequestMapping.class); if (customRequestMapping != null) { requestMappingInfo = new RequestMappingInfo(); requestMappingInfo.setClazz(clazz); requestMappingInfo.setMethod(method); requestMappingInfo.setObj(obj); Class<?>[] clazzs = method.getParameterTypes(); String strInner = ""; if (clazzs != null) { requestMappingInfo.setFormalParameter(clazzs); } if (customRequestMapping.value().startsWith("/")) { strInner = customRequestMapping.value(); } else { strInner = "/" + customRequestMapping.value(); } String[] parameter = customRequestMapping.parameter(); if (parameter != null && parameter.length > 0) { requestMappingInfo.setParameter(Arrays.asList(parameter)); } mappingMap.put(str + strInner, requestMappingInfo); } } } } /** * 组装父类 * * @param clazz * @param beanDefinitionInfo */ private void wrapperSuperClass(Class<?> clazz, BeanDefinitionInfo beanDefinitionInfo) { Class<?> tmp = clazz; List<Class<?>> superList = Lists.newArrayList(); while (tmp.getSuperclass() != null && tmp.getSuperclass() != Object.class) { superList.add(clazz.getSuperclass()); tmp = clazz.getSuperclass(); } beanDefinitionInfo.setSuperClass(superList); } public CustomRequestMapping isRequestMapping(Class<?> clazz) { return clazz.getAnnotation(CustomRequestMapping.class); } public static Servlet getServlet() { return servlet; }下面我们写个Controller类来测试一下: @CustomController @CustomRequestMapping(value = "/custom") public class FirstPageController { @CustomAutowire private UserService userService; @CustomRequestMapping(value = "/myFirstPage.do") public void myFirstPage() { System.out.println("我被调用了、、、、"); } /** * 插入操作 * * @param userScope */ @CustomRequestMapping(value = "/inserUser.do") public void inserUser(UserScope userScope, Response response) { Cookie cookie = new Cookie("test", "testcookie"); Cookie cookie2 = new Cookie("test02", "testcookie02"); response.addCookie(cookie); response.addCookie(cookie2); System.out.println("我是插入操作的Controller层、、、、"); userService.insert(userScope); } }我们在浏览器中输入:http://localhost:8005/custom/myFirstPage.do来看一下结果: 控制台输出如下: 返回结果如下: 和我们程序写的结果一样。 接着我们写一个稍微复杂一点的,定义一个javaBean、DAO类、Service类。 UserScope public class UserScope implements Serializable{ private static final long serialVersionUID = -8340887359752275426L; /** * 用户名 */ private String userName; /** * 密码 */ private String passWord; //省略get set }DAO public interface UserDAO { /** * 插入用户信息 * @param user * @return */ Integer insert(UserScope user); } @CustomComponent public class UserDAOImpl implements UserDAO { private Random random = new Random(); @Override public Integer insert(UserScope user) { System.out.println("我是DAO层。。。。进行插入操作......"); System.out.println(JSON.toJSONString(user)); return random.nextInt(); } }Service: public interface UserService { /** * 插入用户信息 * @param userScope * @return */ Integer insert(UserScope userScope); } @CustomService public class UserServiceImpl implements UserService { @CustomAutowire private UserDAO userDAO; /** * 插入用户信息 * * @param userScope * @return */ @Override public Integer insert(UserScope userScope) { System.out.println("我是Service层。。。。进行插入操作......"); return userDAO.insert(userScope); } }从上面的代码中我们可以看到在DAO的实现类上我们用了CustomComponent注解,在Service的实现类上我们用了CustomService的注解。在我们的那个Controller中我们又用了CustomAutowire这个注解。下面我们访问一下看看效果,我们在浏览器中输入:http://localhost:8005/custom/inserUser.do?userName=hangssewe&passWord=2ewewe 控制台输出如下: 浏览器输出为: 我们在Controller中的inserUser这个方法中设置了两个Cookie,我们来看一下Cookie有没有设置成功: 结果和我们预想的是一样的。 接着我们写一个静态资源的处理类: public class StaticResourceProcessor { public void process(Request request, Response response) { response.sendStaticResource(request.getUri()); } }访问一下看看效果如何: 这里遇到的一个问题是在Chrome浏览器中访问的时候会报错,在IE浏览器中访问则正常显示,还没搞清楚为什么会出现这样的问题。 完整的代码请点击这里:https://github.com/zhangconan/fullstacktraining/tree/master/src/main/java/com/zkn/fullstacktraining/spring/one
说明:本文所用的SpringMVC版本为4.3.4.RELEASE,应用服务器为TomCat8.0.33。 前面几篇文章我们简单的分析了一下SpringMVC初始化的过程,接下来的这几篇文章我们从源码的角度分析一下SpringMVC对请求的处理过程。这一篇文章我们来分析请求从service到DispatcherServlet的doDispatch的过程。我们做web开发的同学大都对Servlet的生命周期比较了解,对Servlet规范也有一些了解。我们知道请求在被Servlet处理之前会先被过滤器处理,过滤器处理完之后会调用Servlet的service方法来对相应的请求进行处理响应(关于过滤器可以看这里)。所以我们这里分析的入口是Servlet的service方法。我们在用SpringMVC的时候,通常都会在web.xml中进行这样的配置: <servlet> <servlet-name>spring-mvc</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:learn-spring-mvc.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>spring-mvc</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping>我们了解Servlet的同学都知道这样配置的话,所有的请求(静态资源除外)将由DispatcherServlet进行处理。即我们这里处理请求的Servlet类是DispatcherServlet。下面首先我们来看一下DispatcherServlet的UML类图: 在上面的类图中我们先关注蓝色线的这一部分,即左面的这一部分内容。从上面的类图中我们可以看到DispatcherServlet继承了FrameworkServlet,FrameworkServlet继承了HttpServletBean,HttpServletBean继承了HttpServlet,HttpServlet继承了GenericServlet,GenericServlet则实现了我们最顶级的接口Servlet和ServletConfig。请求的主要时序图如下: 从DispatcherServlet的源码中我们没有找到service(ServletRequest req, ServletResponse res)这个方法,但是我们在DispatcherServlet的父类HttpServlet中找到了这个方法,我们去HttpServlet中看看这个方法的内容: HttpServlet#service(ServletRequest req, ServletResponse res) @Override public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException { HttpServletRequest request; HttpServletResponse response; try { request = (HttpServletRequest) req; response = (HttpServletResponse) res; } catch (ClassCastException e) { throw new ServletException("non-HTTP request or response"); } service(request, response); } }service这个方法的内容很简单,就是将ServletRequest和ServletResponse转换为HttpServletRequest和HttpServletResponse。因为我们是做web开发,通常用的是HTTP协议,所以这里我们需要的时候HttpServletRequest和HttpServletResponse。接下来就是调用service(HttpServletRequest request, HttpServletResponse response),我们在HttpServlet和FrameworkServlet中都找到了这个方法,但是HttpServlet是FrameworkServlet的父类,即FrameworkServlet中重写了service这个方法,所以我们这里取FrameworkServlet中去看看这个方法的内容: FrameworkServlet#service(HttpServletRequest request, HttpServletResponse response) protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { HttpMethod httpMethod = HttpMethod.resolve(request.getMethod()); if (HttpMethod.PATCH == httpMethod || httpMethod == null) { processRequest(request, response); } else { super.service(request, response); } }这个方法的内容也很简单,第一步根据请求的方法类型转换对应的枚举类。我们可以看一下HttpMethod这个枚举类: public enum HttpMethod { GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE; private static final Map<String, HttpMethod> mappings = new HashMap<String, HttpMethod>(8); static { for (HttpMethod httpMethod : values()) { mappings.put(httpMethod.name(), httpMethod); } } public static HttpMethod resolve(String method) { return (method != null ? mappings.get(method) : null); } public boolean matches(String method) { return (this == resolve(method)); } }HttpMethod这个定义了这样的几种枚举类型:GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE;而这些也是RFC标准中几种请求类型。如果请求类型为PATCH或者没有找到相应的请求类型的话,则直接调用processRequest这个方法。但是这种情况我们很少很少会遇到。所以这里会执行super.service这个方法。即调用HttpServlet中的service方法。我们看一下HttpServlet中这个service方法的内容: HttpServlet#service(HttpServletRequest req, HttpServletResponse resp) protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //获取请求类型 String method = req.getMethod(); //如果是get请求 if (method.equals(METHOD_GET)) { //检查是不是开启了页面缓存 通过header头的 Last-Modified/If-Modified-Since //获取Last-Modified的值 long lastModified = getLastModified(req); if (lastModified == -1) { // servlet doesn't support if-modified-since, no reason // to go through further expensive logic //没有开启页面缓存调用doGet方法 doGet(req, resp); } else { long ifModifiedSince; try { //获取If-Modified-Since的值 ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE); } catch (IllegalArgumentException iae) { ifModifiedSince = -1; } if (ifModifiedSince < (lastModified / 1000 * 1000)) { // If the servlet mod time is later, call doGet() // Round down to the nearest second for a proper compare // A ifModifiedSince of -1 will always be less //更新Last-Modified maybeSetLastModified(resp, lastModified); //调用doGet方法 doGet(req, resp); } else { //设置304状态码 在HttpServletResponse中定义了很多常用的状态码 resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED); } } } else if (method.equals(METHOD_HEAD)) { //调用doHead方法 long lastModified = getLastModified(req); maybeSetLastModified(resp, lastModified); doHead(req, resp); } else if (method.equals(METHOD_POST)) { //调用doPost方法 doPost(req, resp); } else if (method.equals(METHOD_PUT)) { //调用doPost方法 doPut(req, resp); } else if (method.equals(METHOD_DELETE)) { //调用doPost方法 doDelete(req, resp); } else if (method.equals(METHOD_OPTIONS)) { //调用doPost方法 doOptions(req,resp); } else if (method.equals(METHOD_TRACE)) { //调用doPost方法 doTrace(req,resp); } else { //服务器不支持的方法 直接返回错误信息 String errMsg = lStrings.getString("http.method_not_implemented"); Object[] errArgs = new Object[1]; errArgs[0] = method; errMsg = MessageFormat.format(errMsg, errArgs); resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg); } }这个方法的主要作用是根据请求类型调用响应的请求方法如果GET类型,调用doGet方法;POST类型,调用doPost方法。这些方法都是在HttpServlet中定义的,平时我们做web开发的时候主要是继承HttpServlet这个类,然后重写它的doPost或者doGet方法。我们的FrameworkServlet这个子类就重写了这些方法中的一部分:doGet、doPost、doPut、doDelete、doOption、doTrace。这里我们只说我们最常用的doGet和doPost这两个方法。通过翻开源码我们发现,这两个方法体的内容是一样的,都是调用了processRequest这个方法。processRequest这个方法是我们接下来要分析的: FrameworkServlet#processRequest(HttpServletRequest request, HttpServletResponse response) protected final void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { long startTime = System.currentTimeMillis(); Throwable failureCause = null; LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext(); //国际化 LocaleContext localeContext = buildLocaleContext(request); RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes(); //构建ServletRequestAttributes对象 ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes); //异步管理 WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor()); //初始化ContextHolders initContextHolders(request, localeContext, requestAttributes); //执行doService try { doService(request, response); } finally { //重新设置ContextHolders resetContextHolders(request, previousLocaleContext, previousAttributes); if (requestAttributes != null) { requestAttributes.requestCompleted(); } //发布请求处理事件 publishRequestHandledEvent(request, response, startTime, failureCause); } }在这个方法里大概做了这样几件事:国际化的设置,创建ServletRequestAttributes对象,初始化上下文holders(即将Request对象放入到线程上下文中),调用doService方法。我们分析主要的几个: 国际化的设置主要是在DispatcherServlet#buildLocaleContext这个方法中完成的,其源码如下: protected LocaleContext buildLocaleContext(final HttpServletRequest request) { if (this.localeResolver instanceof LocaleContextResolver) { return ((LocaleContextResolver) this.localeResolver).resolveLocaleContext(request); } else { return new LocaleContext() { @Override public Locale getLocale() { return localeResolver.resolveLocale(request); } }; } }根据我们之前文章的分析,如果我们没有配置国际化解析器的话,那么它会使用默认的解析器:AcceptHeaderLocaleResolver,即从Header中获取国际化的信息。除了AcceptHeaderLocaleResolver之外,SpringMVC中还提供了这样的几种解析器:CookieLocaleResolver、SessionLocaleResolver、FixedLocaleResolver。分别从cookie、session中去国际化信息和JVM默认的国际化信息(Local.getDefault())。 initContextHolders这个方法主要是将Request请求、ServletRequestAttribute对象和国际化对象放入到上下文中。其源码如下: private void initContextHolders( HttpServletRequest request, LocaleContext localeContext, RequestAttributes requestAttributes) { if (localeContext != null) { LocaleContextHolder.setLocaleContext(localeContext, this.threadContextInheritable);//threadContextInheritable默认为false } if (requestAttributes != null) {//threadContextInheritable默认为false RequestContextHolder.setRequestAttributes(requestAttributes, this.threadContextInheritable); } }RequestContextHolder这个类有什么用呢?有时候我们想在某些类中获取HttpServletRequest对象,比如在AOP拦截的类中,那么我们就可以这样来获取Request的对象了, HttpServletRequest request = (HttpServletRequest) RequestContextHolder.getRequestAttributes().resolveReference(RequestAttributes.REFERENCE_REQUEST); DispatcherServlet#doService 我们接下来分析一下DispatcherServlet中的doService这个方法 @Override protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception { //include这种的 Map<String, Object> attributesSnapshot = null; if (WebUtils.isIncludeRequest(request)) { attributesSnapshot = new HashMap<String, Object>(); Enumeration<?> attrNames = request.getAttributeNames(); while (attrNames.hasMoreElements()) { String attrName = (String) attrNames.nextElement(); if (this.cleanupAfterInclude || attrName.startsWith("org.springframework.web.servlet")) { attributesSnapshot.put(attrName, request.getAttribute(attrName)); } } } //Spring上下文 request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext()); //国际化解析器 request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver); //主题解析器 request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver); //主题 request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource()); //重定向的数据 FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response); if (inputFlashMap != null) { request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap)); } request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap()); request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager); try { //调用doDispatch方法-核心方法 doDispatch(request, response); } finally { if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) { // Restore the original attribute snapshot, in case of an include. if (attributesSnapshot != null) { restoreAttributesAfterInclude(request, attributesSnapshot); } } } }在这个方法中主要做了这几件事:处理include标签的请求,将上下文放到request的属性中,将国际化解析器放到request的属性中,将主题解析器放到request属性中,将主题放到request的属性中,处理重定向的请求数据最后调用doDispatch这个核心的方法对请求进行处理,我们在下一章中详细分析一下doDispatch这个方法。
说明:本文所用的SpringMVC版本为4.3.4.RELEASE,应用服务器为TomCat8.0.33。 在上一篇文章中(点这里查看)我们说了ContextLoaderListener初始化Web上下文的过程,这篇文章中我们说一下DispatcherServlet初始化上下文的过程。我们先来看一下DispatcherServlet相关的UML类图: 从上图中我们可以看到DispatcherServlet也是一个HttpServlet的一个子类,并间接的实现了ApplicationContextAware这个接口。DispatcherServlet既然是一个Servlet的实现类,那么它也是遵守Servlet的生命周期的。也会有实例化、初始化(执行init方法)、接收请求处理请求(执行service方法)、销毁(执行destroy()方法)。所以DispatcherServlet的初始化过程,我们也是从init()这个方法开始(注意:我们这里说的初始化时执行init()方法,和类的初始化不是一回事,要区分开)。在开始之前我们还是要看一下相关的一些堆栈信息。 为什么要把这些堆栈信息截出来呢?因为这些堆栈信息是相当重要的东西。这也是TomCat的体系架构中很重要的一些类和组成部分。从上图中我们可以看到init()这个方法是在StandarWrapper中的initServlet中被调用的(StandarWrapper代表着一个Servlet)。OK,下面进入我们的正题吧。 GenericServlet#init 从上面的UML图中和堆栈信息那张图中我们可以看到Servlet的第一个实现类是GenericServlet,并且最先调用的也是GenericServlet中的init方法,所以我们首先分析的也就是它了。我们先看一下这个方法的源码: @Override public void init(ServletConfig config) throws ServletException { this.config = config; this.init(); }源码中的东西非常简单啊,只有两句话话,一句是给ServletConfig赋值,一句是调用无参的init()方法。这里的ServletConfig的实现类为:StandardWrapperFacade。下面我们进入到无参的init方法中看看这个方法中执行了哪些内容。我们在GenericServlet这个类的init方法中发现代码是这样的: public void init() throws ServletException { // NOOP by default }WHAT?空实现?空实现我们还怎么玩?别着急,我们在HttpServletBean中找到了一个重写的init方法。下面我们进转到HttpServletBean中去看一下。 HttpServletBean#init 我们看一下init这个方法的源码(省略了一些不重要的代码): public final void init() throws ServletException { try { //创建属性编辑器类 (1) PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties); //创建一个编辑属性值的BeanWrapper (2) BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this); //用Resource加载Resource类型的属性(3) ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext()); bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment())); //初始化一些其他的BeanWrapper信息(其实是一个空方法) initBeanWrapper(bw); //为DispatcherServlet中的属性赋值 bw.setPropertyValues(pvs, true); } catch (BeansException ex) { throw ex; } // Let subclasses do whatever initialization they like. //继续执行初始化的动作(4) initServletBean(); }简单来说这个方法中干了这样的两件事,一是为DispatcherServlet中的属性赋值,二是调用initServletBean()方法。这里有几个地方需要说明一下,PropertyValue是一个存放一个对象的属性名字和值的类,即它保存一个bean的单独属性的值信息。PropertyValues是PropertyValue的集合。在(1)处创建了一个PropertyValues的对象,它的具体实现类是ServletConfigPropertyValues,从名字我们也能看出来这是一个获取Servlet配置信息的PropertyValues的属性值的结合,即它存放的是在Servlet中配置的信息。在这里它还做了另外的一件事,即校验一些必须配置的属性信息。在(2)处创建了一个BeanWrapper的实现类,BeanWrapper也是Spring框架中很重要的一个组件类,它可以用来编辑对象的属性值,所以这里创建的是一个编辑DispatcherServlet属性值的BeanWrapper的实现类。(3)处,如果有Resource类型的资源,则用相应的ResourceLoader来进行处理。bw.setPropertyValues这里就是给DispatcherServlet中的属性进行赋值的动作了。说了那么多,到底会给哪些属性赋值呢?又赋值的是哪些属性呢?举几个例子说明一下吧: 我们在web.xml中配置DispatcherServlet的时候,如果更改默认的SpringMVC配置文件的话,一般都会这样配置: <servlet> <servlet-name>spring-miscellaneous</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring-miscellaneous-mvc.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet>注意,我们在<servlet>标签中添加了一个<init-param>的标签,指定了SpringMVC的配置文件的位置,我们在FrameworkServlet中发现有这样的一个属性contextConfigLocation,和我们的初始化参数的名字一样,然后我们翻遍代码也找不到有调用setContextConfigLocation这个方法的地方,那什么时候给这个属性进行赋值的呢?答案很明显了,就是在调用bw.setPropertyValues(pvs, true);的时候了。还有detectAllHandlerMappings等等属性,也是这样进行赋值的。(4)这个方法是一个很重要的方法,主要的初始化就是在这里完成。我们看一下这个方法的源码: FrameworkServlet#initServletBean 去掉一些不重要的代码。 protected final void initServletBean() throws ServletException { try { this.webApplicationContext = initWebApplicationContext(); //空实现 initFrameworkServlet(); } }这个方法里面的内容也是很简单,其实就一句话,因为initFrameworkServlet是一个空实现的方法。最主要的方法是initWebApplicationContext,这个方法是DispatcherServlet初始化最重要的一个方法了。 FrameworkServlet#initWebApplicationContext initWebApplicationContext中的注意源码如下: protected WebApplicationContext initWebApplicationContext() { //根据ServletContext获取根上下文即ContextLoaderListener中创建的XmlWebApplicationContext WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(getServletContext()); WebApplicationContext wac = null; //如果有webApplicationContext注入的话,则使用注入的webApplicationContext if (this.webApplicationContext != null) { wac = this.webApplicationContext; //如果注入的webApplicationContext是ConfigurableWebApplicationContext的子类 if (wac instanceof ConfigurableWebApplicationContext) { ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac; //如果注入的webApplicationContext还没有被激活 if (!cwac.isActive()) { if (cwac.getParent() == null) { cwac.setParent(rootContext); } //配置webApplicationContext中的内容,并调用refresh()方法,进行初始化 configureAndRefreshWebApplicationContext(cwac); } } } //如果wac为null的话,即没有注入webApplicationContext if (wac == null) { //则从ServletContext中查找配置的WebApplicationContext wac = findWebApplicationContext(); } //如果上下文中也没有WebApplicationContext,则创建WebApplicationContext if (wac == null) { wac = createWebApplicationContext(rootContext); } //如果还没有调用refresh()方法的话,则调用onRefresh方法 if (!this.refreshEventReceived) { onRefresh(wac); } //将WebApplicationContext放入到ServletContext中 if (this.publishContext) { String attrName = getServletContextAttributeName(); getServletContext().setAttribute(attrName, wac); } return wac; }在这个方法中主要干了下面几件事: 获取根WebApplicationContext(即在ContextLoaderListener中创建的XmlWebApplicationContext,可以把它当做是一个父容器) 如果在实例化DispatcherServlet的时候,如果有传入WebApplicationContext,则使用传入的WebApplicationContext。 如果2没有,则从ServletContext中查找配置的WebApplicationContext, 如果3也没有找到,则创建WebApplicationContext 调用onRefresh方法,进行一系列的初始化动作 将初始化之后的WebApplicationContext放入到ServletContext中(key是FrameworkServlet.class.getName() + ".CONTEXT."+servletName) 因为我们是在实例化DispatcherServlet的时候,调用的是默认的无参构造函数,所以在实例化的时候没有传入的WebApplicationContext,我们也没有在ServletContext配置WebApplicationContext,所以这里我们直接进入到createWebApplicationContext这个方法中,进行创建WebApplicationContext。 FrameworkServlet#createWebApplicationContext createWebApplicationContext中的主要源码如下: protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) { //获取上下文类 Class<?> contextClass = getContextClass(); //这个类必须是ConfigurableWebApplicationContext的子类 if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) { throw new ApplicationContextException( "Fatal initialization error in servlet with name '" + getServletName() + "': custom WebApplicationContext class [" + contextClass.getName() + "] is not of type ConfigurableWebApplicationContext"); } //实例化上下文类 ConfigurableWebApplicationContext wac = (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass); wac.setEnvironment(getEnvironment()); //设置父ApplicationContext即设置父容器 wac.setParent(parent); //设置contextConfigLocation中的配置文件 wac.setConfigLocation(getContextConfigLocation()); //初始化WebApplicationContext configureAndRefreshWebApplicationContext(wac); return wac; }这个方法中主要干了这几件事: 获取WebApplicationContext上下文的类。 校验是不是ConfigurableWebApplicationContext的子类。 实例化WebApplicationContext 设置父容器 设置SpringMVC的配置文件 进行WebApplicationContext相关的一些其他配置,并调用refresh方法。 我们先来看一下getContextClass这个方法。 public Class<?> getContextClass() { return this.contextClass; }getContextClass这个方法也很简单,就是获取contextClass的值。contextClass这个属性有一个默认的值:XmlWebApplicationContext.class。如果没有在web.xml的<servlet>标签中进行其他值的配置的话,则contextClass就取默认值XmlWebApplicationContext.class。XmlWebApplicationContext是一个实现了ConfigurableWebApplicationContext接口的一个类,下面我们需要分析的一个方法是configureAndRefreshWebApplicationContext FrameworkServlet#configureAndRefreshWebApplicationContext 其主要源码如下: protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) { if (ObjectUtils.identityToString(wac).equals(wac.getId())) { if (this.contextId != null) { wac.setId(this.contextId); } else { wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + ObjectUtils.getDisplayString(getServletContext().getContextPath()) + '/' + getServletName()); } } wac.setServletContext(getServletContext()); wac.setServletConfig(getServletConfig()); wac.setNamespace(getNamespace()); wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener())); ConfigurableEnvironment env = wac.getEnvironment(); if (env instanceof ConfigurableWebEnvironment) { ((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig()); } postProcessWebApplicationContext(wac); applyInitializers(wac); wac.refresh(); }上面这些代码中主要做了这几件事: 设置id值(还没完全明白它的具体实际作用) 设置ServletContext 设置ServletConfig 设置nameSpace 设置一些监听器 初始化一些属性信息 如果有配置ApplicationContextInitializer相关的类,则调用ApplicationContextInitializer的initialize方法进行一些初始化的操作。 调用refresh方法。这个方法就是读取SpringMVC配置文件,解析bean、组装bean等等一系列操作了。 关于wac.refresh()方法的调用,我们这里先不分析的。在Spring的源码分析中再进行分析。接下来我们就回到org.springframework.web.servlet.FrameworkServlet#initWebApplicationContext这个方法的这一段代码中: if (!this.refreshEventReceived) { onRefresh(wac); }onRefresh()这个方法的实现是在DispatcherServlet中的。 DispatcherServlet#onRefresh 其源码内容如下: @Override protected void onRefresh(ApplicationContext context) { initStrategies(context); }从上面的源码中我们发现它只是调用了initStrategies这个方法。关于这个方法的分析,请点击这里(SpringMVC之浅析组件初始化过程)。 OK了,到这里我们关于DispatcherServlet初始化的主干流程的分析就先结束了。接着会做一些枝干流程的分析的工作(即一些Spring的生命周期接口的一些实现类)。 PS:感觉最近CSDN的这个编辑器总是失焦呢、、、