1-Spring 簡單使用

LZC發表於2020-03-28

簡單使用

第一步:建立一個maven專案

第二步:引入Spring依賴

<properties>
    <spring.version>5.1.1.RELEASE</spring.version>
</properties>
<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>${spring.version}</version>
    </dependency>
</dependencies>

第三步:新建一個配置類

// 告訴Spring這是一個配置類
@Configuration
public class MainConfig {
    // 給容器中註冊一個Bean, id 預設使用方法名. 也可以自定義指定id, 透過@Bean("person")
    @Bean
    public Person person() {
        return new Person();
    }
}

第四步:建立一個測試類

public class Test {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(MainConfig.class);
        // 從IOC容器中獲取id為person的物件
        Person person = (Person) context.getBean("person");
        System.out.println(person);
    }
}

常用註解

@ComponentScan

啟用元件掃描,預設會掃描與配置類相同的包。可以透過設定basePackages屬性來指定掃描路徑,如@ComponentScan(basePackages = {"package1","package2"})。掃描的時候查詢package1package2包下標註了@Controller@Service@Repository@Component的類(預設情況下,這些類注入到IOC容器的id為首字母小寫的類名),這些類都會被注入到IOC容器中。

// 告訴Spring這是一個配置類
@Configuration
// 包掃描, 只要標註了@Controller @Service @Repository @Component的類, 這些類都會被注入到IOC容器中
@ComponentScan(basePackages = {"com.example.spring.sourcecode"})
public class MainConfig {
    // 給容器中註冊一個Bean, id 預設使用方法名. 也可以自定義指定id, 透過@Bean("person")
    @Bean
    public Person person() {
        return new Person();
    }
}

@Autowired

自動注入例項。Spring 容器中匹配的時候 Bean 數目必須有且僅有一個。當找不到一個匹配的 Bean 時,Spring 容器將丟擲 BeanCreationException 異常,並指出必須至少擁有一個匹配的 Bean。

@Autowired 預設是按照byType進行注入的,如果發現找到多個bean,則又按照byName方式比對,如果還有多個,則報出異常。@Autowired可以手動指定按照byName方式注入,使用@Qualifier標籤指定bean的名稱。

@Primary

如果容器中注入了多個型別一樣的Bean,自動注入時會優先注入被@Primary註解標註的Bean

@Import

它可以將多個分散的@Configuration注入到Spring容器中。

用法一

@Configuration
@Import({MainConfig1.class,MainConfig2.class})
public class MainConfig {

}

用法二

Import結合ImportSelector介面

// MyImportSelect.class
public class MyImportSelect implements ImportSelector {
    /**
     * @param annotationMetadata 當前標註 @Import註解的所有註解資訊
     * @return 返回值就是匯入到容器中的全類名
     */
    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        return new String[]{Red.class.getName(), Orange.class.getName()};
    }
}

// MainConfig.class
@Configuration
@Import({MyImportSelect.class})
public class MainConfig {

}

用法三

結合ImportBeanDefinitionRegistrar介面

// MyImportBeanDefinitionRegistrar.class
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
    /**
     * @param annotationMetadata
     * @param beanDefinitionRegistry 透過這個引數可以操作IOC容器
     */
    @Override
    public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) {
        BeanDefinitionBuilder yellow = BeanDefinitionBuilder.rootBeanDefinition(Yellow.class);
        // 往容器中註冊物件
        beanDefinitionRegistry.registerBeanDefinition("yellow", yellow.getBeanDefinition());

        BeanDefinitionBuilder green = BeanDefinitionBuilder.rootBeanDefinition(Green.class);
        // 往容器中註冊物件
        beanDefinitionRegistry.registerBeanDefinition("green", green.getBeanDefinition());
    }
}

// MainConfig.class
@Configuration
@Import({MyImportSelect.class, MyImportBeanDefinitionRegistrar.class})
public class MainConfig {

}

@Conditional

按照一定的條件給容器註冊bean。

假設現在有一個名為WindowsBean的類,我們希望只有當前作業系統是Windows的時候才會將其注入到IOC容器中。

@Configuration
@ComponentScan(basePackages = {"com.example.spring.sourcecode"})
public class MainConfig {
    @Bean
    @Conditional(WindowsCondition.class)
    public WindowsBean windowsBean() {
        return new WindowsBean();
    }
}

可以看到,@Conditional中給定了一個Class,它指明瞭條件。

public class WindowsCondition implements Condition {
    public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
        Environment environment = conditionContext.getEnvironment();
        String osName = environment.getProperty("os.name");
        return osName.contains("Windows");
    }
}

matches方法有一個ConditionContext引數,ConditionContext是一個介面

public interface ConditionContext {
    BeanDefinitionRegistry getRegistry();
    @Nullable
    ConfigurableListableBeanFactory getBeanFactory();
    Environment getEnvironment();
    ResourceLoader getResourceLoader();
    @Nullable
    ClassLoader getClassLoader();
}

透過ConditionContext介面我們可以做到如下幾點

  1. 藉助getRegistry()返回的BeanDefinitionRegistry檢查bean定義
  2. 藉助getBeanFactory()返回的ConfigurableListableBeanFactory檢查bean是否存在,甚至可以檢視bean的屬性
  3. 藉助getEnvironment()返回的Environment檢查環境變數是否存在以及它的值是什麼
  4. 藉助getClassLoader()返回的ClassLoader載入並檢查類是否存在

@Value

使用@Value進行賦值

1.基本數值

2.可以寫SpEL, #{}

3.可以寫${},取出配置檔案中的值(在執行環境中)

public class Person {
    // @Value("張三")
    @Value("${app.name:lzc}") // 如果app.name不存在,預設值為lzc
    private String username;
    // @Value("10")
    @Value("${app.age:16}")
    private Integer age;
}

有兩種方式將app.name和app.age注入到執行環境中

第一種:程式執行時指定執行時環境變數的值

-Dapp.name=zhencheng -Dapp.age=12

第二種:引入配置檔案

新建一個config.properties配置檔案,內容如下

app.name=lizhencheng
app.age=18

在配置類上使用@PropertySource({"config.properties"})註解來引入配置檔案

如果第一種方法和第二種方法都使用了,那麼第一種方法的值會將第二種方法的值覆蓋掉。

Aware 介面

Spring 容器在初始化主動檢測當前 bean 是否實現了 Aware 介面,如果實現了則回撥其 set 方法將相應的引數設定給該 bean ,這個時候該 bean 就從 Spring 容器中取得相應的資源。

org.springframework.beans.factory.Aware 介面,定義如下:

public interface Aware {

}

Aware 介面為 Spring 容器的核心介面,是一個具有標識作用的超級介面,實現了該介面的 bean 是具有被 Spring 容器通知的能力,通知的方式是採用回撥的方式。

Aware 介面是一個空介面,實際的方法簽名由各個子介面來確定,且該介面通常只會有一個接收單引數的 set 方法,該 set 方法的命名方式為 set + 去掉介面名中的 Aware 字尾,即 XxxAware 介面,則方法定義為 setXxx(),例如 BeanNameAware(setBeanName),BeanFactoryAware(setBeanFactory)。

Spring 提供了一系列的 Aware 介面,如 BeanClassLoaderAware、BeanFactoryAware、BeanNameAware。

下面以BeanFactoryAware做一個簡單的演示:

@Component
public class MyApplicationAware implements BeanFactoryAware {
    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        System.out.println("呼叫了 ApplicationContextAware 的 setBeanFactory 方法");
    }
}

透過這種方式 我們就可以將ApplicationContext注入到MyApplicationAware中。

BeanPostProcessor 介面

在 Bean 完成例項化後,如果我們需要對其進行一些配置、增加一些自己的處理邏輯,就可以使用 BeanPostProcessor。

定義一個類,該類實現 BeanPostProcessor 介面,程式碼如下

@Component
public class BeanPostProcessorTest implements BeanPostProcessor {
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("Bean [" + beanName + "] 開始初始化");
        // 這裡一定要返回 bean,不能返回 null
        return bean;
    }
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("Bean [" + beanName + "] 完成初始化");
        return bean;
    }
}

測試方法

public class Test {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(MainConfig.class);
    }
}

控制檯列印

Bean [mainConfig] 開始初始化
Bean [mainConfig] 完成初始化
呼叫了 ApplicationContextAware 的 setApplicationContext 方法
Bean [myApplicationAware] 開始初始化
Bean [myApplicationAware] 完成初始化

BeanPostProcessor 可以理解為是 Spring 的一個工廠鉤子(其實 Spring 提供一系列的鉤子,如 Aware 、InitializingBean、DisposableBean),它是 Spring 提供的物件例項化階段強有力的擴充套件點,允許 Spring 在例項化 bean 階段對其進行定製化修改,比較常見的使用場景是處理標記介面實現類或者為當前物件提供代理實現(例如 AOP)。

InitializingBean和DisposableBean介面

Spring 的 org.springframework.beans.factory.InitializingBean 介面,為 bean 提供了定義初始化方法的方式,它僅包含了一個方法:#afterPropertiesSet() 。程式碼如下:

public interface InitializingBean {
    void afterPropertiesSet() throws Exception;
}

org.springframework.beans.factory.DisposableBean介面提供了物件的自定義銷燬工作,它僅僅包含了一個方法:#destroy()。程式碼如下

public interface DisposableBean {
    void destroy() throws Exception;
}

示例

// InitializingBeanTest.class
public class InitializingBeanTest implements InitializingBean, DisposableBean {
    private String name;
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("InitializingBeanTest initializing...");
        this.name = "張三";
    }
    @Override
    public void destroy() throws Exception {
        System.out.println("InitializingBeanTest destroy...");
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}

// 配置bean
@Bean
InitializingBeanTest initializingBeanTest() {
    InitializingBeanTest test = new InitializingBeanTest();
    test.setName("李四");
    return test;
}

// 測試類
public class Test {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(MainConfig.class);
        InitializingBeanTest initializingBeanTest = (InitializingBeanTest)context.getBean("initializingBeanTest");
        System.out.println(initializingBeanTest.getName());
        ((AnnotationConfigApplicationContext) context).close();
    }
}

控制檯列印

InitializingBeanTest initializing...
張三
InitializingBeanTest destroy...

在這個示例中,改變了 InitializingBeanTest 示例的 name 屬性,也就是說 在 #afterPropertiesSet() 方法中,我們是可以改變 bean 的屬性的,這相當於 Spring 容器又給我們提供了一種可以改變 bean 例項物件的方法。

BeanFactoryPostProcessor介面

BeanFactoryPostProcessor 的機制,就相當於給了我們在 Bean 例項化之前最後一次修改 BeanDefinition 的機會,我們可以利用這個機會對 BeanDefinition 來進行一些額外的操作,比如更改某些 bean 的一些屬性,給某些 Bean 增加一些其他的資訊等等操作。

org.springframework.beans.factory.config.BeanFactoryPostProcessor 介面,定義如下:

@FunctionalInterface
public interface BeanFactoryPostProcessor {
    /**
     * Modify the application context's internal bean factory after its standard
     * initialization. All bean definitions will have been loaded, but no beans
     * will have been instantiated yet. This allows for overriding or adding
     * properties even to eager-initializing beans.
     * @param beanFactory the bean factory used by the application context
     * @throws org.springframework.beans.BeansException in case of errors
     */
    void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException;
}

BeanFactoryPostProcessor 介面僅有一個 #postProcessBeanFactory(...) 方法,該方法接收一個 ConfigurableListableBeanFactory 型別的 beanFactory 引數。

#postProcessBeanFactory(...) 方法,工作於 BeanDefinition 載入完成之後,Bean 例項化之前,其主要作用是對載入 BeanDefinition 進行修改。有一點需要需要注意的是在 #postProcessBeanFactory(...) 方法中,千萬不能進行 Bean 的例項化工作,因為這樣會導致 Bean 過早例項化,會產生嚴重後果,我們始終需要注意的是 BeanFactoryPostProcessor 是與 BeanDefinition 打交道的,如果想要與 Bean 打交道,請使用 BeanPostProcessor

與 BeanPostProcessor 一樣,BeanFactoryPostProcessor 同樣支援排序,一個容器可以同時擁有多個 BeanFactoryPostProcessor ,這個時候如果我們比較在乎他們的順序的話,可以實現 Ordered 介面。

如果要自定義 BeanFactoryPostProcessor ,直接實現該介面即可。

示例如下所示

// 配置Person資訊
@Bean
public Person person() {
    Person person = new Person();
    person.setUsername("張三");
    person.setAge(10);
    return person;
}

@Component
public class BeanFactoryPostProcessor_1 implements BeanFactoryPostProcessor,Ordered {
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        System.out.println("呼叫 BeanFactoryPostProcessor_1 ...");
        // 獲取指定的 BeanDefinition
        BeanDefinition bd = beanFactory.getBeanDefinition("person");
        MutablePropertyValues pvs = bd.getPropertyValues();
        pvs.addPropertyValue("username","lizhencheng");
    }
    @Override
    public int getOrder() {
        return 1;
    }
}


@Component
public class BeanFactoryPostProcessor_2 implements BeanFactoryPostProcessor,Ordered {
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        System.out.println("呼叫 BeanFactoryPostProcessor_2 ...");
        // 獲取指定的 BeanDefinition
        BeanDefinition bd = beanFactory.getBeanDefinition("person");
        MutablePropertyValues pvs = bd.getPropertyValues();
        pvs.addPropertyValue("age","18");
    }
    @Override
    public int getOrder() {
        return 1;
    }
}

提供了兩個自定義的 BeanFactoryPostProcessor ,都繼承 BeanFactoryPostProcessor 和 Ordered,其中 BeanFactoryPostProcessor_1 改變 username 的值,BeanFactoryPostProcessor_2 該變 age 的值。Ordered 分別為 1 和 2 。

測試類

public class Test {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(MainConfig.class);
        Person person = (Person)context.getBean("person");
        System.out.println(person.toString());
    }
}

控制檯輸出

呼叫 BeanFactoryPostProcessor_1 ...
呼叫 BeanFactoryPostProcessor_2 ...
Person{username='lizhencheng', age=18}

看到執行結果,就能理解執行流程了

ApplicationListener介面

用來監聽事件的發生,Spring提供瞭如下事件型別

ApplicationContextEvent
ContextClosedEvent
ContextRefreshedEvent
ContextStartedEvent
ContextStoppedEvent
PayloadApplicationEvent

示例

@Component
public class MyApplicationListener implements ApplicationListener {
    @Override
    public void onApplicationEvent(ApplicationEvent event) {
        System.out.println("MyApplicationListener..." + event);
    }
}

Bean的生命週期

  1. Spring對bean進行例項化
  2. Spring將值和bean的引用注入到bean對應的屬性中
  3. 如果bean實現了BeanNameAware介面,Spring將bean的ID傳遞給setBeanName()方法
  4. 如果bean實現了BeanFactoryAware介面,Spring將呼叫setBeanFactory(BeanFactory beanFactory)方法,將BeanFactory例項傳入
  5. 如果bean實現了BeanClassLoaderAware介面,Spring將呼叫setBeanClassLoader(ClassLoader classLoader)方法,傳入ClassLoader例項
  6. 如果bean實現了BeanPostProcessor介面,Spring將會呼叫它的postProcessBeforeInitialization(Object bean, String beanName)方法
  7. 如果bean實現了InitializingBean介面,Spring將呼叫它的afterPropertiesSet()方法
  8. 如果bean實現了BeanPostProcessor介面,Spring將會呼叫它的postProcessAfterInitialization(Object bean, String beanName)方法
  9. 此時,bean已經準備就緒,可以被應用程式使用了,它們將一直駐留在應用上下文,直到該應用上下文被銷燬
  10. 如果bean實現了DisposableBean介面,Spring將會呼叫它的destory()方法

面向切面

依賴如下

<properties>
    <spring.version>5.1.1.RELEASE</spring.version>
</properties>
<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>${spring.version}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aop</artifactId>
        <version>${spring.version}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aspects</artifactId>
        <version>${spring.version}</version>
    </dependency>
</dependencies>

新建一個類

@Component
public class MathCalculator {
    public int div(int i, int j) {
        System.out.println("MathCalculator.div: i = " + i + ", j = " + j);
        return i/j;
    }
}

新建一個切面類

@Aspect // 告訴Spring這是一個切面類
@Component
public class LogAspects {

    // 切入點表示式
    @Pointcut("execution(* com.example.spring.sourcecode.aop.MathCalculator.*(..))")
    public void pointcut(){
    }

    // 目標方法執行之前執行
    @Before("pointcut()")
    public void logStart(JoinPoint joinPoint) {
        List<Object> args = Arrays.asList(joinPoint.getArgs());
        System.out.println("【@Before】" + joinPoint.getSignature().getName() + ", args = " + args);
}
    // 目標方法執行結束後執行(無論方法正常結束還是異常結束都會呼叫)
    @After("pointcut()")
    public void logEnd(JoinPoint joinPoint) {
        List<Object> args = Arrays.asList(joinPoint.getArgs());
        System.out.println("【@After】" + joinPoint.getSignature().getName() + ", args = " + args);
    }
    // 目標方法正常返回後執行
    @AfterReturning(value = "pointcut()", returning = "result") // returning 設定返回值使用result來接收
    public void logReturn(JoinPoint joinPoint, Object result) { // 這裡的 JoinPoint 一定要在第一個參
        System.out.println("【@AfterReturning】" + joinPoint.getSignature().getName() + ", return = " + result);
    }
    // 目標方法異常後執行
    @AfterThrowing(value = "pointcut()", throwing = "exception") // throwing 設定異常資訊使用exception來接收
    public void logException(JoinPoint joinPoint, Exception exception) {
        System.out.println("【@AfterThrowing】" + joinPoint.getSignature().getName() + ", Exception = " + exception.getMessage());
    }

    // 這個方法的想過和前面方法小效果是一樣的
//    @Around("pointcut()")
//    public Object logAround(ProceedingJoinPoint jp) {
//        Object result = null;
//        try {
//            System.out.println("【Before】" + jp.getSignature().getName());
//            result = jp.proceed();
//            System.out.println("【After】" + jp.getSignature().getName());
//            System.out.println("【Return】" + result);
//        } catch (Throwable throwable) {
//            System.out.println("【Exception】" + jp.getSignature().getName());
//        }
//        return result;
//    }
}

配置類

@Configuration
@ComponentScan(basePackages = {"com.example.spring.sourcecode"})
@EnableAspectJAutoProxy // 開啟基於註解的AOP模式
public class MainConfigOfConfig {

}

測試類

public class AOPTest {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(MainConfigOfConfig.class);
        MathCalculator mathCalculator = context.getBean(MathCalculator.class);
        System.out.println(mathCalculator.div(10,2));
    }
}

事物

依賴

<properties>
        <spring.version>5.1.1.RELEASE</spring.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>c3p0</groupId>
            <artifactId>c3p0</artifactId>
            <version>0.9.1.2</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.47</version>
        </dependency>
    </dependencies>

配置類

/**
 * Created by lzc
 * 2020/3/3 22:24
 * 1. @EnableTransactionManagement // 開啟事物管理器
 * 2. 配置事物管理器
 * 3. 給方法上加上@Transactional註解, 表示當前方法是一個事物方法
 */
@EnableTransactionManagement // 開啟事物管理器
@Configuration
@ComponentScan(basePackages = {"com.example.spring.sourcecode.tx"})
public class TxConfig {

    @Bean
    public DataSource dataSource() throws PropertyVetoException {
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8&useSSL=false");
        dataSource.setUser("root");
        dataSource.setPassword("root");
        dataSource.setDriverClass("com.mysql.jdbc.Driver");
        return dataSource;
    }

    @Bean
    public JdbcTemplate jdbcTemplate(DataSource dataSource) {
        JdbcTemplate jdbcTemplate = new JdbcTemplate();
        jdbcTemplate.setDataSource(dataSource);
        return jdbcTemplate;
    }

    // 註冊事物管理器
    @Bean
    public PlatformTransactionManager platformTransactionManager(DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }
}

UserDao和UserService

@Repository
public class UserDao {
    @Autowired
    private JdbcTemplate jdbcTemplate;
    @Transactional // 表示當前方法是一個事物方法
    public void insertUser() {
        String sql = "INSERT INTO user(username,age)VALUES(?,?)";
        String username = UUID.randomUUID().toString().substring(0,5);
        jdbcTemplate.update(sql,username,18);
        // 測試事物
        int a = 10/0;
    }
}

@Service
public class UserService {
    @Autowired
    private UserDao userDao;

    public void insertUser() {
        userDao.insertUser();
    }
}

測試類

public class Test {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(TxConfig.class);
        UserService userService = context.getBean(UserService.class);
        userService.insertUser();
    }
}
本作品採用《CC 協議》,轉載必須註明作者和本文連結