java架構之路-(spring原始碼篇)由淺入深-spring實戰詳細使用
今天我更新了一篇jvm垃圾回收的演算法和垃圾回收器的內部邏輯,但是看的人不多啊......貌似大家還是比較喜歡看原始碼吧,畢竟實戰要比理論用的多。
這篇文章不會詳細的深入底層原始碼,只是基於註解和配置來說說我們的spring的使用,別小看基礎,保證有你沒用過的註解和配置,走起。
我們先來建立一個maven專案,引入spring檔案,不愛弄的在文章最下面有程式碼地址可以去下載。先看,後面自己下載程式碼自己去嘗試。先給你們吧,邊嘗試邊看吧。碼雲地址:https://gitee.com/dwyui/springboke.git
一、IOC容器註冊元件的方式
1.基礎XML注入Bean
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="car" class="com.springIOC.bean.CarBean"></bean><!--id是唯一表示,class是全路徑 --> </beans>
package com.springIOC; import org.springframework.context.support.ClassPathXmlApplicationContext; public class MainTest { public static void main(String[] args) { ClassPathXmlApplicationContext cac = new ClassPathXmlApplicationContext("config.xml"); Object car = cac.getBean("car"); } }
是不是超級簡單的,我們由淺入深一點點來。
2.基於註解的方式來配置
package com.springIOC2.config; import com.springIOC.bean.CarBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class MainConfig { @Bean public CarBean car(){//注意方法名 return new CarBean(); } }
package com.springIOC2; import com.springIOC2.config.MainConfig; import org.springframework.context.annotation.AnnotationConfigApplicationContext; public class MainTest { public static void main(String[] args) { AnnotationConfigApplicationContext aca = new AnnotationConfigApplicationContext(MainConfig.class); Object car = aca.getBean("car"); } }
我們通過方法名就可以直接得到我們的物件了,預設就是按照方法來裝配。也可以通過@Bean(value="newName") 來指定裝配的名字。
3.按照包掃描的方式裝配(重點),使用@ComponentScan(basePackages={"包的全路徑"})
package com.springIOC3.config; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; @Configuration @ComponentScan(basePackages = {"com.springIOC3"}) public class MainConfig { }
package com.springIOC3; import com.springIOC3.config.MainConfig; import org.springframework.context.annotation.AnnotationConfigApplicationContext; public class MainTest { /** * 基礎@CompentScan,包掃描方式來配置 * @param args */ public static void main(String[] args) { AnnotationConfigApplicationContext aca = new AnnotationConfigApplicationContext(MainConfig.class); String[] beanDefinitionNames = aca.getBeanDefinitionNames(); for (String beanDefinitionName : beanDefinitionNames) { System.out.println("beanDefinitionName = " + beanDefinitionName); } } }
這裡在來說幾個引數,excludeFilters排除某一些物件,語法如下
excludeFilters = {
@ComponentScan.Filter(type = FilterType.ANNOTATION,value = {Controller.class}), //排除所有Controller的註解類,多個value可以用逗號分隔
@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE,value = {RepositoryBean.class}) //排除RepositoryBean類物件
}
FilterType有五種,分別是ANNOTATION(註解類),ASSIGNABLE_TYPE(類名),ASPECTJ(不常用,文件說AspectJ型別模式表示式匹配),REGEX(正則表示式匹配),CUSTOM(自定義),常用的三種我標記了紅色。下面看一下具體寫法
package com.springIOC3b.config; import com.springIOC3b.repository.RepositoryBean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.FilterType; import org.springframework.stereotype.Controller; @Configuration @ComponentScan(basePackages = {"com.springIOC3b"},excludeFilters = { @ComponentScan.Filter(type = FilterType.ANNOTATION,value = {Controller.class}), @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE,value = {RepositoryBean.class}) }) /** * 1:排除用法 excludeFilters(排除@Controller註解的,和RepositoryBean類) */ public class MainConfig { }
剛才我們說到了自定義過濾,我們來看一下怎麼寫自定義的過濾,實現我們TypeFilter介面,重寫我們的match即可,只關注返回的true。下面是一個事例
package com.springIOC3c.config; import org.springframework.core.io.Resource; import org.springframework.core.type.AnnotationMetadata; import org.springframework.core.type.ClassMetadata; import org.springframework.core.type.classreading.MetadataReader; import org.springframework.core.type.classreading.MetadataReaderFactory; import org.springframework.core.type.filter.TypeFilter; import java.io.IOException; public class CustomFilterType implements TypeFilter { @Override public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException { //獲取當前類的註解源資訊 AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata(); //獲取當前類的class的源資訊 ClassMetadata classMetadata = metadataReader.getClassMetadata(); //獲取當前類的資源資訊 Resource resource = metadataReader.getResource(); if (classMetadata.getClassName().contains("RepositoryBean")) { //這裡注意,他是一個包含的匹配規則,即使我寫成下面註釋掉那樣也可以的 return true; } // if (classMetadata.getClassName().contains("ryBean")) { // return true; // } return false; } }
與包含相反的還有一個,只允許引入什麼,也就是我們的includeFilters,需要注意需要把useDefaultFilters屬性設定為false(true表示掃描全部的)。語法和excludeFilters完全一致
package com.springIOC3d.config; import com.springIOC3d.service.ServiceBean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.FilterType; import org.springframework.stereotype.Controller; @Configuration @ComponentScan(basePackages = {"com.springIOC3d"},includeFilters = { @ComponentScan.Filter(type = FilterType.ANNOTATION,value = {Controller.class}), @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE,value = ServiceBean.class) },useDefaultFilters = false) /** * 只許引入*** includeFilters(只許引入@Controller註解和ServiceBean類),useDefaultFilters設定為false,關閉全包掃描 */ public class MainConfig { }
4.回過頭來,我們看一下Bean的作用域。
@Lazy懶載入,使用才例項化,看下程式碼,我們在Bean里加入構造方法,更方便得出什麼時候例項化的。
package com.springIOC4.config; import com.springIOC4.bean.CarBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Lazy; @Configuration public class MainConfig { @Bean @Lazy public CarBean car(){ return new CarBean(); } }
指定@Scpoe可以有四種作用域
a) singleton 單例項的(預設),單例的生命週期有spring容器來控制,非懶載入時在spring例項化以後就產生了物件,容器銷燬則物件銷燬
package com.springIOC4b.config; import com.springIOC4b.bean.CarBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Scope; @Configuration public class MainConfig { @Bean @Scope(value = "singleton") public CarBean car(){ return new CarBean(); } }
package com.springIOC4b; import com.springIOC4b.bean.CarBean; import com.springIOC4b.config.MainConfig; import org.springframework.context.annotation.AnnotationConfigApplicationContext; public class MainTest { /** * @Scope(value = "singleton")單例,預設也是@Scope(value = "singleton") * @param args */ public static void main(String[] args) { AnnotationConfigApplicationContext aca = new AnnotationConfigApplicationContext(MainConfig.class); CarBean car = (CarBean)aca.getBean("car"); CarBean car2 = (CarBean)aca.getBean("car"); System.out.println(car == car2); // # true } }
輸出結果為true,說明我們的物件是單例的,單例的物件,生命週期由spring來管理的。
b) prototype 多例項的
package com.springIOC4c.config; import com.springIOC4c.bean.CarBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Scope; @Configuration public class MainConfig { @Bean @Scope(value = "prototype") public CarBean car(){ return new CarBean(); } }
package com.springIOC4c; import com.springIOC4c.bean.CarBean; import com.springIOC4c.config.MainConfig; import org.springframework.context.annotation.AnnotationConfigApplicationContext; public class MainTest { /** * @Scope(value = "prototype")多例 * @param args */ public static void main(String[] args) { AnnotationConfigApplicationContext aca = new AnnotationConfigApplicationContext(MainConfig.class); CarBean car = (CarBean)aca.getBean("car"); CarBean car2 = (CarBean)aca.getBean("car"); System.out.println(car == car2); // # false } }
多例的不受ioc容器來管理,銷燬時是由GC來清理的,還有request同一次請求和session同一個會話級別的,這裡就不一一演示了。
5.@Configuration註解,來判斷是否注入Bean的。
package com.springIOC5.config; import com.springIOC5.bean.CarBean; import com.springIOC5.bean.UserBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; @Configuration public class MainConfig { @Bean(value = "user") public UserBean userBean() { return new UserBean(); } @Bean @Conditional(value = IOCConditional.class) public CarBean carBean() { return new CarBean(); } }
package com.springIOC5.config; import org.springframework.context.annotation.Condition; import org.springframework.context.annotation.ConditionContext; import org.springframework.core.type.AnnotatedTypeMetadata; public class IOCConditional implements Condition { @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { if (context.getBeanFactory().containsBean("user")) { //這裡必須和Bean的名稱完全一致。 return true; } return false; } }
上面的程式碼什麼意思呢?就是我們是否需要注入carBean,如果包含user這個物件,就注入我們的carBean,不包含就不注入,這裡有個意思的事,Configuration配置裡類的注入是有順序的,我們必須把我們作為判斷條件的Bean放在上面,否則Conditional會識別你沒有那個判斷條件的Bean。
6.@Import引入方式注入Bean
package com.springIOC6.config; import com.springIOC6.bean.CarBean; import com.springIOC6.bean.UserBean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; @Configuration @Import({CarBean.class, UserBean.class}) public class MainConfig { }
直接在註解內寫入我們的要注入的類即可,也可以使用介面的方式來實現,我們來看換一下。
package com.springIOC6b.config; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; @Configuration @Import({ImportSelector.class}) public class MainConfig { }
package com.springIOC6b.config; import org.springframework.core.type.AnnotationMetadata; public class ImportSelector implements org.springframework.context.annotation.ImportSelector { @Override public String[] selectImports(AnnotationMetadata importingClassMetadata) { return new String[]{"com.springIOC6b.bean.CarBean","com.springIOC6b.bean.UserBean"}; } }
實現ImportSelector類,然後返回類名全路徑即可。自動裝配就是基於@Import實現的。
實現ImportBeanDefinitionRegistrar,重寫registerBeanDefinitions方法,也是可以的。
package com.springIOC6c.config; import com.springIOC6c.bean.CarBean; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; import org.springframework.core.type.AnnotationMetadata; public class ImportSelectorRegister implements ImportBeanDefinitionRegistrar { @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(CarBean.class); registry.registerBeanDefinition("CarBean",rootBeanDefinition); } }
7.通過FactoryBean注入
package com.springIOC7.config; import com.springIOC7.bean.UserBean; import org.springframework.beans.factory.FactoryBean; public class IOCFactoryBean implements FactoryBean<UserBean> { @Override public UserBean getObject() throws Exception {//指定物件 return new UserBean(); } @Override public Class<?> getObjectType() {//指定型別 return UserBean.class; } @Override public boolean isSingleton() {//指定是否為單例 return true; } }
package com.springIOC7.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class MainConfig { @Bean public IOCFactoryBean iocFactoryBean(){ return new IOCFactoryBean(); } }
package com.springIOC7; import com.springIOC7.config.MainConfig; import org.springframework.context.annotation.AnnotationConfigApplicationContext; public class MainTest { /** * FactoryBean注入 * @param args */ public static void main(String[] args) { AnnotationConfigApplicationContext aca = new AnnotationConfigApplicationContext(MainConfig.class); Object carBean = aca.getBean("iocFactoryBean");//取出userBean System.out.println(carBean); Object iocFactoryBean = aca.getBean("&iocFactoryBean");//取得FactoryBean System.out.println(iocFactoryBean); } }
說到這所有往IOC容器中新增元件的方式就全部說完了,簡單總結一下:
@Bean注入,可以指定四種作用域,單例,多例(生命週期不受IOC容器管理),一次請求和一次會話,也可以設定懶載入,
@ComponentScan指定包掃描的方式來注入,配合@Controller,@Repository,@Service,@Component註解來使用。
@Import方式注入,兩種實現類ImportSelector和ImportBeanDefinitionRegistrar兩種方式。
@FactoryBean,工程Bean的方式也可以注入。注意不帶&是取得最終物件,帶&是取得真實Bean。三個方法,一個指定物件,一個指定型別,一個指定是否為單例。
二、Bean的生命週期---初始化方法和銷燬方法
針對單例項bean的話,容器啟動的時候,bean的物件就建立了,而且容器銷燬的時候,也會呼叫Bean的銷燬方法。
針對多例項bean的話,容器啟動的時候,bean是不會被建立的而是在獲取bean的時候被建立,而且bean的銷燬不受IOC容器的管理。
1.我們先來看個最簡單的方法,用initMethod和destroyMethod來指定我們的初始化方法和銷燬方法
package com.springlifeCycle1a.config; import com.springlifeCycle1a.bean.CarBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class MainConfig { @Bean(initMethod = "init",destroyMethod = "destroy") public CarBean car() { return new CarBean(); } }
我們在指定了我們的init初始方法,銷燬方法為destroy方法。呼叫順序是,Car的構造方法,Car的init方法,Car的destroy方法,也可以自己嘗試使用@Lazy註解。碼雲程式碼裡有可以自己去嘗試。
2.通過 InitializingBean和DisposableBean 的兩個介面實現bean的初始化以及銷燬方法
package com.springlifeCycle2a.bean; import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.InitializingBean; public class CarBean implements InitializingBean, DisposableBean { public CarBean() { System.out.println("我是Car"); } public void afterPropertiesSet() { System.out.println("我是初始化init"); } public void destroy() { System.out.println("我是銷燬destroy"); } }
3.通過JSR250規範 提供的註解@PostConstruct 和@ProDestory標註的方法
package com.springlifeCycle3.bean; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; public class CarBean { public CarBean() { System.out.println("我是Car"); } @PostConstruct public void init() { System.out.println("我是初始化init--PostConstruct"); } @PreDestroy public void destory() { System.out.println("我是銷燬destroy--PreDestroy"); } }
4.通過Spring的BeanPostProcessor的 bean的後置處理器會攔截所有bean建立過程 (這個方法後面講原始碼的時候會去講內部實現,自己覺得有必要看這個的原始碼)
package com.springlifeCycle4.bean; import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.stereotype.Component; @Component public class LifeBeanPostProcessor implements BeanPostProcessor { @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { System.out.println("初始化方法" + beanName); return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { System.out.println("銷燬方法" + beanName); return bean; } }
這裡也總結一下,我們來指定容器內物件的初始化方法和銷燬方法的方式一共有四種
1.用@Bean的initMethod 和destroyMethod 來給予初始化方法和銷燬方法。
2.通過 InitializingBean和DisposableBean 的二個介面實現bean的初始化以及銷燬方法。
3.通過JSR250規範 提供的註解@PostConstruct 和@ProDestory標註的方法。
4.通過Spring的BeanPostProcessor的 bean的後置處理器會攔截所有bean建立過程。
三、給屬性賦值
這裡的東西不多,我就儘快說一下啦,賦值的方式有三種我們來看一下。
package com.springValue.config; import com.springValue.bean.CarBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.PropertySource; @Configuration @PropertySource(value = "classpath:carBean.properties",encoding = "utf-8") //指定外部檔案的位置 public class MainConfig { @Bean public CarBean carBean() { return new CarBean(); } }
import org.springframework.beans.factory.annotation.Value; public class CarBean { @Value("寶馬")//通過普通的方式 private String name; @Value("#{5-2}")//spel方式來賦值 private int carNum; @Value("${carBean.realName}")//通過讀取外部配置檔案的值 private String realName; }//自己記得加get set方法。
這裡值得一提的就是匯入檔案最好設定一下encoding = "utf-8",不然漢字會亂碼。
四、自動裝配:
主動裝配平時是我們最熟悉的,用的也是最多的,我們來複習一下。
1.@Autowired 自動裝配首先時按照型別進行裝配,若在IOC容器中發現了多個相同型別的元件,那麼就按照 屬性名稱來進行裝配
package com.springxAutowired1.config; import com.springxAutowired1.dao.AutowiredDao; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; @Configuration @ComponentScan(value = "com.springxAutowired1.service") public class MainConfig { @Bean public AutowiredDao autowiredDao1(){ return new AutowiredDao(1); } @Bean public AutowiredDao autowiredDao2(){ return new AutowiredDao(2); } }
package com.springxAutowired1.service; import com.springxAutowired1.dao.AutowiredDao; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service public class ServiceBean { @Autowired private AutowiredDao autowiredDao2; public ServiceBean() { System.out.println("我是serviceBean"); } @Override public String toString() { return "ServiceBean{" + "AutowiredDao=" + autowiredDao2 + '}'; } }
在這裡我們設定了兩個AutowiredDao的物件,一個標識為1,一個標識為2,ServiceBean預設是按照名字來裝配的。
2.我們也可以通過@Qualifier來指定裝配名字。
package com.springxAutowired1b.service; import com.springxAutowired1b.dao.AutowiredDao; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Service; @Service public class ServiceBean { @Autowired @Qualifier(value = "autowiredDao1") private AutowiredDao autowiredDao2; public ServiceBean() { System.out.println("我是serviceBean"); } @Override public String toString() { return "ServiceBean{" + "AutowiredDao=" + autowiredDao2 + '}'; } }
3.我們使用Qualifier是如果名字寫錯了,可能裝配錯誤會報錯,這時我們可以使用required = false來阻止異常的丟擲
package com.springxAutowired3.service; import com.springxAutowired3.dao.AutowiredDao; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Service; @Service public class ServiceBean { @Qualifier(value = "autowiredDaoError") @Autowired(required = false) private AutowiredDao autowiredDao2; public ServiceBean() { System.out.println("我是serviceBean"); } @Override public String toString() { return "ServiceBean{" + "AutowiredDao=" + autowiredDao2 + '}'; } }
注意:這裡我並沒有說@Resource註解,這個註解其實不是spring裡的,是JSR250規範的,但是不支援@Primary 和@Qualifier的支援
五、環境切換:
我們有時候需要通過不同的環境來切換我們的配置,我們通過@Profile註解,來根據環境來啟用標識不同的Bean,
@Profile標識在類上,那麼只有當前環境匹配,整個配置類才會生效
@Profile標識在Bean上 ,那麼只有當前環境的Bean才會被啟用
package com.springxAutowired4.config; import com.springxAutowired4.bean.Environment; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Profile; @Configuration public class MainConfig { @Bean @Profile(value = "test") public Environment environment_test() { return new Environment("test"); } @Bean @Profile(value = "dev") public Environment environment_dev() { return new Environment("dev"); } @Bean @Profile(value = "pro") public Environment environment_pro() { return new Environment("pro"); } }
啟用切換環境的方法
方法一:通過執行時jvm引數來切換 -Dspring.profiles.active=test,dev,prod多個引數表中間使用英文的逗號來分隔
方法二:通過程式碼的方式來啟用
package com.springxAutowired4; import com.springxAutowired4.config.MainConfig; import org.springframework.context.annotation.AnnotationConfigApplicationContext; public class MainTest { /** * 環境切換 * @param args */ public static void main(String[] args) { AnnotationConfigApplicationContext aca = new AnnotationConfigApplicationContext(); aca.getEnvironment().setActiveProfiles("test","dev");//方便演示,我寫了兩個,一般都是一個的. aca.register(MainConfig.class); aca.refresh(); for (String beanDefinitionName : aca.getBeanDefinitionNames()) { System.out.println(beanDefinitionName); } } }
如果兩個都寫了,按照程式碼中的來實現,引數不再起作用
今天就說到這裡, 大概就這麼多吧。後期我也會基於這篇部落格來詳細的扒一下原始碼,中間會穿插一些spring的常見面試題。
程式碼地址:https://gitee.com/dwyui/springboke.git