首先,這篇文章不會詳細的深入底層源碼,只是基於註解和配置來說說我們的 spring 的使用,別小看基礎,保證有你沒用過的註解和配置,走起。
我們先來建立一個 maven 項目,引入 spring 文件,不愛弄的在文章最下面有代碼地址可以去下載。先看,後面自己下載代碼自己去嘗試。先給你們吧,邊嘗試邊看吧。
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>
</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 {
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}),
@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE,value = {RepositoryBean.class})
}
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})
}
)
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();
ClassMetadata classMetadata = metadataReader.getClassMetadata();
Resource resource = metadataReader.getResource();
if (classMetadata.getClassName().contains("RepositoryBean")) {
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)
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 {
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,說明我們的對象是單例的,單例的對象,生命週期由 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 {
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);
}
}
多例的不受 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")) {
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 {
public static void main(String[] args) {
AnnotationConfigApplicationContext aca = new AnnotationConfigApplicationContext(MainConfig.class);
Object carBean = aca.getBean("iocFactoryBean");
System.out.println(carBean);
Object iocFactoryBean = aca.getBean("&iocFactoryBean");
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 的銷燬不受 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;
}
}
這裏也總結一下,我們來指定容器內對象的初始化方法和銷燬方法的方式一共有四種
- 用 @Bean 的 initMethod 和 destroyMethod 來給予初始化方法和銷燬方法。
- 通過 InitializingBean 和 DisposableBean 的二個接口實現 bean 的初始化以及銷燬方法。
- 通過 JSR250 規範 提供的註解 @PostConstruct 和 @ProDestory 標註的方法。
- 通過 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}")
private int carNum;
@Value("${carBean.realName}")
private String realName;
}
這裏值得一提的就是導入文件最好設置一下 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);
}
}
}
如果兩個都寫了,按照代碼中的來實現,參數不再起作用
“不積跬步,無以至千里”,希望未來的你能:有夢爲馬 隨處可棲!加油,少年!
關注公衆號:「Java 知己」,每天更新Java知識哦,期待你的到來!
- 發送「Group」,與 10 萬程序員一起進步。
- 發送「面試」,領取BATJ面試資料、面試視頻攻略。
- 發送「玩轉算法」,領取《玩轉算法》系列視頻教程。
- 千萬不要發送「1024」…