spring 必知

spring 爲什麼可以一統江湖

答案很簡單,所有的對象都要在容器裏存活。可見spring的重要程度,並不亞於jvm。懂了spring的原理後!我們可以很優雅地去集成我們的代碼。
Inversion of Contro 簡稱IOC 是面向對象編程中的一種設計原則,可以用來減低計算機代碼之間的耦合度。也就是面向接口編程的思想。
簡單的說就是使用配置的方式,修改程序代碼的實現。能靈活的切換代碼,不用修改邏輯代碼。其實就是解決硬編碼創建對象的問題。

學習的路線設定

  • 瞭解有多少種方式添加對象到容器中

這個很重要,在工作中經常出現類找不到的異常,熟悉這個的話,問題很容易找到。

  • 再從spring對一個類的處理的源碼開始解析spring的原理

Spring配置有兩種,詳細法就可以參考一下Spring的使用教程。

xml配置與註解方式配置。其他本質是一樣的。

  • xml
  • 配置類上加Configuration

掃描加載bean的規則有,這些不是很重要.

  • @bean
	<bean id="person" class="com.enjoy.cap1.Person">
		<property name="name" value="wolf"></property>
		<property name="age" value="19"></property>
	</bean>
或在Configuration類中使用,現在的spring boot都是用@Configuration方式的 

@Configuration
@Import(value = { Dog.class })
public class Cap6MainConfig {}


  • @@Component與@ComponentScan(value = "bgy.bean")

一般用這種,在類上加@Component就可以ComponentScan加載到容器中

    @Component
	 public class Dog{}
  • @@Import(bgy.beanout.Fly.class) 單獨加入
@Configuration
@Import(value = { Dog.class })
public class Cap6MainConfig {}
  • 實現ImportSelector
public class MyImportSelector implements ImportSelector{
	@Override
	public String[] selectImports(AnnotationMetadata importingClassMetadata){
		//返回全類名的bean
		return new String[]{"com.enjoy.cap6.bean.Fish","com.enjoy.cap6.bean.Tiger"};
	}
}

//把MyImportSelector當成bean導入
@Import(value = {ImportSelector.class})

  • 使用FactoryBean
public class JamesFactoryBean implements FactoryBean<Monkey>{

	@Override
	public Monkey getObject() throws Exception {
		// TODO Auto-generated method stub
		return new Monkey();
	}

	@Override
	public Class<?> getObjectType() {
		// TODO Auto-generated method stub
		return Monkey.class;
	}
	
	@Override
	public boolean isSingleton() {
		return true;
	}
}
  • 使用HdkImportBeanDefinitionRegistrar
public class HdkImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {

	/*
	*AnnotationMetadata:當前類的註解信息
	*BeanDefinitionRegistry:BeanDefinition註冊類
	*    把所有需要添加到容器中的bean加入;
	*    @Scope
	*/
	@Override
	public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
		boolean bean1 = registry.containsBeanDefinition("com.enjoy.cap6.bean.Dog");
		boolean bean2 = registry.containsBeanDefinition("com.enjoy.cap6.bean.Cat");
		//如果Dog和Cat同時存在於我們IOC容器中,那麼創建Pig類, 加入到容器
		//對於我們要註冊的bean, 給bean進行封裝,
		if(bean1 && bean2){
			RootBeanDefinition beanDefinition = new RootBeanDefinition(Pig.class);
			registry.registerBeanDefinition("pig", beanDefinition);
		}
	}

}
  • BeanDefinitionRegistryPostProcessor
public class JamesBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor{

	@Override
	public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
		System.out.println("JamesBeanDefinitionProcessor..postProcessBeanFactory(),Bean的數量"+beanFactory.getBeanDefinitionCount());
	}

	@Override
	public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
		System.out.println("amesBeanDefinition.postProcessBeanDefinitionRegistry()...bean的數量"+registry.getBeanDefinitionCount());
		//RootBeanDefinition rbd = new RootBeanDefinition(Moon.class);
		AbstractBeanDefinition rbd = BeanDefinitionBuilder.rootBeanDefinition(Moon.class).getBeanDefinition();//註冊一個beandefinition
		registry.registerBeanDefinition("hello", rbd);
	}

}

Condition 條件加載,在spring boot中使用比較廣泛,用來過濾加載到IOC容器中的bean

public class WinCondition implements Condition{
	
	/*
	*ConditionContext: 判斷條件可以使用的上下文(環境)
	*AnnotatedTypeMetadata: 註解的信息
	*
	*/
	@Override
	public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
		// TODO 是否爲WINDOW系統
		//能獲取到IOC容器正在使用的beanFactory
		ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
		//獲取當前環境變量(包括我們操作系統是WIN還是LINUX??)
		Environment environment = context.getEnvironment();
		String os_name = environment.getProperty("os.name");
		if(os_name.contains("Windows")){
			return true;
		}
		return false;
	}

}

小結:

實現的spring包爲:spring-context。上下文容器類都是繼承了AbstractApplicationContext 類的。重點:開發時可以按需要自定義上下文。

spring 源碼解析

從加載容器開始

      ApplicationContext applicationContext=new AnnotationConfigApplicationContext(myconfig.class);
       // ApplicationContext applicationContext=new ClassPathXmlApplicationContext("beans.xml");

上下文抽象類,此設計用的是模板方法設計模式, 在refresh裏 固定好加載時的方法調用順序。

 public void refresh() throws BeansException, IllegalStateException {
        Object var1 = this.startupShutdownMonitor;
        synchronized(this.startupShutdownMonitor) {
            this.prepareRefresh();
            ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();//加載 bean的配置定義
            this.prepareBeanFactory(beanFactory);

            try {
                this.postProcessBeanFactory(beanFactory);
                this.invokeBeanFactoryPostProcessors(beanFactory);//如果有配置類則添加配置類處理器,並將優先級排爲最高
                this.registerBeanPostProcessors(beanFactory);//註冊其他的處理器,並設置好處理順序。
                this.initMessageSource();
                this.initApplicationEventMulticaster();
                this.onRefresh();
                this.registerListeners();
                this.finishBeanFactoryInitialization(beanFactory);//註冊所有注入進來的bean實例到容器中
                this.finishRefresh();
            } catch (BeansException var9) {
                if(this.logger.isWarnEnabled()) {
                    this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var9);
                }

                this.destroyBeans();
                this.cancelRefresh(var9);
                throw var9;
            } finally {
                this.resetCommonCaches();
            }

        }
    }

BeanPostProcessors

後置處理器接口,實現了這個接口類可以對所有的類初始化方法進行攔截處理,分別爲:postProcessBeforeInitialization,postProcessAfterInitialization
AutowiredAnnotationBeanPostProcessor @autowired就是使用這個處理器去處理的,很多Bean處理的階段,都是使用BeanPostProcessors去處理的,可以在所有的bean創建後第一時間處理bean的一系列動作

@Component
////每個對象創建時都會執行
public class MyBeanPostProcessor implements BeanPostProcessor{
	@Override
	public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
		//返回一個的對象(傳過來的對象)
		//在初始化方法調用之前進行後置處理工作,
		//什麼時候調用它: 對象的構造方法之後,init-method=init之前調用
		System.out.println("postProcessBeforeInitialization...."+beanName+"..."+bean);
		return bean;
	}

	@Override
	public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
                //init-method=init之後調用
		System.out.println("postProcessAfterInitialization...."+beanName+"..."+bean);
		return bean;
	}
}

ApplicationContextAware

使用實現了這個接口的bean都可以獲取到容器原上下文。下面講到的spring mvc中就用到這個方法。

public interface ApplicationContextAware extends Aware {
    void setApplicationContext(ApplicationContext var1) throws BeansException;
}

bean中

   public void setApplicationContext(ApplicationContext applicationContext) {
        if(this.webApplicationContext == null && applicationContext instanceof WebApplicationContext) {
            this.webApplicationContext = (WebApplicationContext)applicationContext;
            this.webApplicationContextInjected = true;
        }

    }

spring 應用組件擴展

spring mvc 原理

先用一句話總結:通過url請求路徑,反射調用容器中的bean方法。

首先從問題開始

  • 請求路徑是如何定位到一個servlet的?

這個問題開始學servlet時就會的。在web.xml配置一下就行了!其實就是一個map,key=請求路徑  value=servlet。
就可以快速找到瀏覽器上傳過來的請求路徑。這裏重要不是講servlet,感興趣的可以看看tomcat的源碼。http如果到servlet的。

  • 請求如何找到MVC中的@RequestMapping("view”)方法呢?

  • 在mvc的web.xml找到servlet的配置。

  • 再看看DispatcherServlet是個什麼玩意

tomcat的所有servlet請求都會經過HttpServlet.service,MVC中的DispatcherServlet類就是一個實現了HttpServlet接口中的一個類型。DispatcherServlet.service中的方法的觸> 發將從tomcat中發起。DispatcherServlet中將會獲取請求的上下文。

容器類將加載@Controller的類型的實例加載到容器中。@Controller其實就是一個@Component

dispatcherServlet將@Controller的bean與RequestMapping的value做映射HandlerMappings

  • initStrategies 初始化相關信息

initHandlerMappings

將所有的RequestMapping的value與controller加到handlerMappings中。後面通過放射調用標註了RequestMapping的方法。
private List handlerMappings;

doService()

  • 看看調用堆

    protected ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        AnnotationMethodHandlerAdapter.ServletHandlerMethodResolver methodResolver = this.getMethodResolver(handler);
        Method handlerMethod = methodResolver.resolveHandlerMethod(request);//獲取到controller中的方法
        AnnotationMethodHandlerAdapter.ServletHandlerMethodInvoker methodInvoker = new AnnotationMethodHandlerAdapter.ServletHandlerMethodInvoker(methodResolver);
        ServletWebRequest webRequest = new ServletWebRequest(request, response);
        ExtendedModelMap implicitModel = new BindingAwareModelMap();
        Object result = methodInvoker.invokeHandlerMethod(handlerMethod, handler, webRequest, implicitModel);
        ModelAndView mav = methodInvoker.getModelAndView(handlerMethod, handler.getClass(), result, implicitModel, webRequest);
        methodInvoker.updateModelAttributes(handler, mav != null?mav.getModel():null, implicitModel, webRequest);
        return mav;
    }

1 找到處理類controller

2 找到處理方法進行反射

3 調用到requestmapping的方法

設計模式發現

可適配類型

下面用實現controller接口的方式實現請求處理類。這種方式 使用的是SimpleControllerHandlerAdapter這種適配器,servlet的話就會使用SimpleServletHandlerAdapter

@Component
@RequestMapping("index")
public class TestController implements org.springframework.web.servlet.mvc.Controller {
    
    @Override
    public ModelAndView handleRequest(javax.servlet.http.HttpServletRequest httpServletRequest, javax.servlet.http.HttpServletResponse httpServletResponse) throws Exception {
        return new ModelAndView("index.jsp");
    }
}

總結

1加載:將requestmapping與controller類初始化到一個handlerMappings
2處理請求:通過request在handlerMappings中找到相應的controller與requestmapping的方法,反射調用方法。

spring aop

在Spring中,AOP是一個比較重要的組件,主要用於日誌處理,如:記錄操作日誌,記錄異常日誌等。

原理也很簡單,主要關注所有方法的調用前,中,後,還有異常。很明顯適用代理模式。項目中的bean這麼多,當然不能爲每個bean寫個代理實現類,靜態代理肯定不行。所以用jdk的動態代理或CGLIB的動態代理來創建這些代理對象是唯一的選擇。在spring中使用後置處理器,爲每個bean創建代理對象,所以當程序調用bean時已經不是以前那個bean了!已經是帶有增強的代理實例來的了。

通知方法

  • 前置通知: logStart(),在目標方法(div)運行之前運行 (@Before)
  • 後置通知:logEnd(), 在目標方法(div)運行結束之後運行,無論正常或異常結束 (@After)
  • 返回通知:logReturn, 在目標方法(div)正常返回之後運行 (@AfterReturning)
  • 異常通知:logException, 在目標方法(div)出現異常後運行(@AfterThrowing)
  • 環繞通知:@Around以上沒寫,動態代理, 手動執行目標方法運行joinPoint.procced(),最底層通知,

原理

  • 通過@Pointcut("execution(public int com.enjoy.cap10.aop.Calculator.*(..))") 配置找到要代理攔截的方法。

  • DefaultAdvisorChainFactory.getInterceptorsAndDynamicInterceptionAdvice 生成攔截鏈加到list遍歷調用。

public List<Object> getInterceptorsAndDynamicInterceptionAdvice(Advised config, Method method, @Nullable Class<?> targetClass) {
        List<Object> interceptorList = new ArrayList(config.getAdvisors().length);
        Class<?> actualClass = targetClass != null?targetClass:method.getDeclaringClass();
        boolean hasIntroductions = hasMatchingIntroductions(config, actualClass);
        AdvisorAdapterRegistry registry = GlobalAdvisorAdapterRegistry.getInstance();
        Advisor[] var8 = config.getAdvisors();
        int var9 = var8.length;

        for(int var10 = 0; var10 < var9; ++var10) {
            Advisor advisor = var8[var10];
            MethodInterceptor[] interceptors;
            if(advisor instanceof PointcutAdvisor) {
                PointcutAdvisor pointcutAdvisor = (PointcutAdvisor)advisor;
                if(config.isPreFiltered() || pointcutAdvisor.getPointcut().getClassFilter().matches(actualClass)) {
                    interceptors = registry.getInterceptors(advisor);
                    MethodMatcher mm = pointcutAdvisor.getPointcut().getMethodMatcher();
                    if(MethodMatchers.matches(mm, method, actualClass, hasIntroductions)) {
                        if(mm.isRuntime()) {
                            MethodInterceptor[] var15 = interceptors;
                            int var16 = interceptors.length;

                            for(int var17 = 0; var17 < var16; ++var17) {
                                MethodInterceptor interceptor = var15[var17];
                                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)) {
                    interceptors = registry.getInterceptors(advisor);
                    interceptorList.addAll(Arrays.asList(interceptors));
                }
            } else {
                Interceptor[] interceptors = registry.getInterceptors(advisor);
                interceptorList.addAll(Arrays.asList(interceptors));
            }
        }

        return interceptorList;
    }
  • 雖然是順序結構,但是調用執行順序卻是從下面開始的,類似遞歸算法那樣。下往上執行。

  • 執行攔截器鏈ReflectiveMethodInvocation.proceed()

   public Object proceed() throws Throwable {
        if(this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
            return this.invokeJoinpoint();
        } else {
            Object interceptorOrInterceptionAdvice = this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
            if(interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
                InterceptorAndDynamicMethodMatcher dm = (InterceptorAndDynamicMethodMatcher)interceptorOrInterceptionAdvice;
                return dm.methodMatcher.matches(this.method, this.targetClass, this.arguments)?dm.interceptor.invoke(this):this.proceed();
            } else {
                return ((MethodInterceptor)interceptorOrInterceptionAdvice).invoke(this);
            }
        }
    }

總結

spring 是管理bean的一個框架,在面向對象編程中,一個程序是由一個或多個對象組成的,spring就是用來管理這些對象的
,spring把這些對象抽象成bean,bean的生命週期分三個階段:創建,初始化,銷燬.
創建:spring把bean的定義描述從xml或class讀出來,創建好放到容器中。
spring爲bean抽象了兩個方法:初始化,銷燬。
初始化:可以按一定的邏輯去動態設置bean的初始化屬性狀態,或者執行其他的方法。
銷燬:在銷燬前執行其他的方法。
在開發過程中如果需要按一定的邏輯去處理bean的話,使用配置的方法比較麻煩,每個bean都是實現初始化方法。
spring 提供了一種後置處理器接口BeanPostProcessor,實現了這個接口就可以攔截初始化方法來統一處理。
如:BeanValidationPostProcessor驗證bean,或ServletContextAwareProcessor servlet中的上下文檔context的初始化。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章