Spring基礎筆記

賜我白日夢發表於2019-08-15
  • Spring帶給了我們什麼便利?
  • 註解版本的IOC如何玩?
    • 元件註冊
      • 元件註冊的過程中有哪些過濾規則?
      • 如何控制元件的作用域(單例多例)?
      • 六種註冊元件的方式?
    • 生命週期
      • 什麼是bean的生命週期
      • 在bean的生命週期的各個階段我們可以插手做什麼?
    • 屬性賦值
      • 我們有哪些手段給bean的屬性賦值?
    • 自動裝配
      • 什麼是自動裝配?
      • Spring提供哪些註解可以幫我們實現自動裝配?
      • java規範提供了哪些註解幫助我們實現了自動裝配?
      • Spring提供的@Profile實現適配多環境?
  • 註解版本的AOP如何玩?

Spring帶給了我們什麼?

假如我們是第一次學習Spirng,我們大可不必關心spring帶給我了我們什麼便利,因為spring大概是web階段築基需要學習的第一個主流框架,初學難免會遇見各種各樣的錯誤,所以儘管放心大膽的去學習如何使用就行了.先會用,其它的不用多想

過幾天,用著熟悉瞭如何使用,再考慮spring究竟帶給了我們什麼便利也不遲, 那麼,spring究竟帶給了我們什麼便利呢?

  • IOC(Inverse of Control),把物件的建立權反轉給Spring容器,實現瞭解耦

    我們使用Spring提供給我們的註解,把bean註冊進IOC容器,進而把bean之間的依賴關係全部交給Spring管理,現在想想這絕對是一件超級讚的事情,可能原來的我會想,我自己可以new物件,幹嘛非讓Spring插一腳,是,假如我的專案就是個小demo全文一共三物件,完全顧的上來,那麼spring對我來說就是累贅,但是!慢慢的接觸的工程大了就會發現,離開了Spring,自己去管理那些物件之間的依賴會累死人的,而且SpringIOC的誕生也應正了bean的管理,完全不需要我們關係,我們關心的就是,我們如何向Spring索取依賴的Bean就行了,所以說Spring真的是web愛好者的小春天.

  • AOP(Aspect Oriented Programming),spring的aop

    面向切面程式設計,aop說白了就是程式碼複用,把大量的重複程式碼從我們的業務邏輯中抽取出去,等程式執行的時候再動態的植入抽取出去的程式碼,這是件要多優雅就多優雅的事情!

  • 宣告式事務

    後端天天和資料庫打交道,事務安全這樣的不可能遇不見,我們這一代學習的人還真的是幸運,因為spring提供的宣告式事務,我們不用再去苦苦的去寫程式設計式事務,而且,現在spring4全面支援註解開發,我們甚至連配置檔案都不用寫,加一個註解就引入了spring的事務模組,激動不?

  • 方便整合其他開源框架

    spring最擅長的事情就是整合這使它的生態變的超級龐大


例外給大家推薦一篇很棒的部落格,裡面詳細圖文論述了,spring究竟帶給了我們什麼?

spring實現解耦論述


註解版Spring-IOC怎麼玩?

元件註冊

只使用IOC,匯入Spring-Context就可以

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>4.3.7.RELEASE</version>
</dependency>

這個過程,我們要注意IOC以下幾點

  • 元件註冊的過程中有哪些過濾規則?
  • 如何控制元件的作用域(單例多例)?
  • 六種註冊元件的方式?

元件註冊及其過濾規則我串聯在下面相鄰的兩部分裡,這裡先提一下Spring提供的所有過濾規則型別

在spring啟動,開始掃描包,註冊元件時,如果我們想按照我們的需求往IOC裡面新增元件,就需要指定過濾規則,下面這五種型別的過濾規則,都源於我們在主配置類(相當於配置檔案)上新增的@ComponentScan()包掃描註解

  • 按照註解過濾ANNOTATION
@ComponentScan(// 這個註解替換了原來配置檔案中的包掃描
    value="com.changwu.tryspring",
    useDefaultFilters=false,
    includeFilters = {
      @Filter(type = FilterType.ANNOTATION, classes = Service.class)}
      )
  • 按照給定的型別過濾
@Configuration
@ComponentScan(// 這個註解替換了原來配置檔案中的包掃描
        value="com.changwu.tryspring",
        useDefaultFilters=false,
        includeFilters = {
         //只要是給定的型別的類,就可以載入進IOC,包括它的子類
          @Filter(type = FilterType.ASSIGNABLE_TYPE,classes = BookDao.class)
        }
        )
  • 按照切面 (ASPECTJ) 基本不用

  • 按照正則 REGEX

  • 自定義規則 CUSTOM

@Configuration
@ComponentScan(// 這個註解替換了原來配置檔案中的包掃描
    value="com.changwu.tryspring",
    useDefaultFilters=false,
    includeFilters = {
           // 只要是給定的型別的類,就可以載入進IOC,包括它的子類
            @Filter(type = FilterType.CUSTOM,classes = MyTypeFilter.class)
    }
)

註解版本中,配置類替換了原來的配置檔案

@Configuration 註解標記本類為配置類

那麼我不加@Configuration註解,這個類就不能成為配置類嗎? 裡面通過@Bean註解就新增不進bean來了? 不是的, @Configuration標記會被Spring使用Cglib技術攔截下來,換言之我們得到的bean,不再是我們自己new 的那種小白bean,而是被代理過的Bean

那麼什麼才是百分百的配置類呢? 配置類是我們呼叫如下程式碼所需要的那個類

  AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
        applicationContext.register(配置類.class);

如下程式碼! 原來的包掃描被@ComponentScan 取代!!! 裡面可以配置多個資訊

  • 基礎包資訊value="com.XXX"
  • 排除被哪個註解標記的類excludeFilters = @Filter(type... classes...)
  • 只要被哪個註解標記的類,(配置檔案版本的分兩步!第一步禁用掉預設的過濾器),註解版同樣分兩步useDefaultFilters=false, includeFilters = @Filter(classes= Service.class)

excludeFilters 把bean排除出IOC容器!!! 一般都是按照註解, 後面是 !!註解!!的Class陣列,
另外,過濾器Filter的過濾型別,預設為按照註解過濾!!!

還可以在配置類中往IOC容器中組成物件!!!@Bean,用的時候 @Autowired

預設情況下,無論獲取多少次,都是單例項的!!!

/**
 * 配置類,等同於原來的配置檔案
 *
 * @Author: Changwu
 * @Date: 2019/3/31 16:57
 */
@Configuration
@ComponentScan(// 這個註解替換了原來配置檔案中的包掃描
        value="com.changwu.tryspring",
         excludeFilters = @Filter( type =FilterType.ANNOTATION, classes = Repository.class)
        )
// excludeFilters 把bean排除出IOC容器!!!  一般都是按照註解, 後面是  !!註解!!的Class陣列,

public class MainConfig {
    /**
     * 給容器註冊bean
     * 型別: 為返回值的型別
     * id 預設使用方法名
     * @return
     */
    @Bean(value = "stu")  // value可以自定義名字
    public Student getStu(){
        return  new Student("張三",23);
    }
}

此外在java8中,有如下程式碼,@ComponentSacn的倒數第二個註解是@Repeatble 意味著我們可以在主配置類上寫多個@ComponentScan

 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Repeatable(ComponentScans.class)
public @interface ComponentScan {...}

自定義TypeFilter的過濾規則

自定義註解,要求實現FilterType介面,實現他的match(),該方法裡面的兩個引數都是介面型別的,我們可以從它裡面取出關於IOC所掃描到的所有類的源資訊,滿足我們的預期,返回true,表示允許註冊進IOC

public class MyTypeFilter implements TypeFilter {
/**
 *
 * @param metadataReader  // 獲取到當前的正在掃描的類的資訊
 * @param metadataReaderFactory  // 可以獲取到,其他任何類的資訊
 * @return
 * @throws IOException
 */
@Override
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
    AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();//獲取當前類的註解資訊
    ClassMetadata classMetadata = metadataReader.getClassMetadata();// 獲取當前正在掃描類的 類資訊, 比如實現了什麼介面,有什麼方法
    Resource resource = metadataReader.getResource();// 獲取類的資源資訊, 比如在那個盤,路徑

    String className = classMetadata.getClassName();
    System.out.println("---->"+className);
    // 自定義,如果有@Repository註解,注入進去
    Set<String> annotationTypes = annotationMetadata.getAnnotationTypes();

    for (String annotationType : annotationTypes) {
        System.out.println("++++++"+annotationType);
    }
    if (annotationMetadata.hasAnnotation("org.springframework.stereotype.Service")){
        return true;
    }
 /*   if (className.contains("er")){
    return true;
}*/
    return false;
}
}

@Scope, 設定元件的作用域

容器中的物件預設都是單例項的!!!,我們使用@Scope 註解改變這種狀態

    /*
    String SCOPE_SINGLETON = "singleton";  單例
    String SCOPE_PROTOTYPE = "prototype";  多例
   */
    @Scope(scopeName = "prototype")
    @Bean(value = "stu2")
    public Student getStu2(){
        return new Student("李四",24);
    }

}

觀察在單例項和多例項情況下,bean建立的時機

  • 預設在單例項的情況下,ioc容器建立完,直接載入bean

    但是以後每次獲取都會都是從IOC容器中獲取

懶載入@Lazy! 針對單例項Bean,我們知道,容器啟動的時候,就會建立一個唯一的bean,我們使用懶載入可以做到,在第一次使用的時候載入bean

  • 多例項情況下,只有在獲取類時,IOC容器才會建立bean,!!!

    以後每次獲取Bean,ioc容器都會呼叫那個方法去new Bean


六種給容器註冊元件的方法

著重看一種,按照條件註冊bean @Conditional springboot底層大量的使用!!!

它的實現方式和自定義過濾條件相似
@Conditional : 按照條件註冊bean

// 既可以標在類上,也可以標在方法上
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Conditional {

    /**
     * 所有的條件,都必須是註冊過的!!!.
     */
    Class<? extends Condition>[] value();
}

解釋一下怎麼用:

  • 它是個介面,需要的屬性名為 value, 值是Class陣列 ,可以進一步看到它的泛型是Condition介面 我們自己實現介面,重寫裡面的方法match(),根據方法提供的引數可以獲取到我們想要的任何資訊,返回true表示滿足條件,註冊bean,否則不註冊

其他註冊元件的方法

  • 方式1: 包掃描+註解 (@Cotroller @Service @Repository等等註解的類可以被IOC掃描到,新增進容器)

這種方法有侷限性!!!, 只能是我們自己寫的類, id為首字母小寫的類名

  • 方式2: 在配置類內部 使用: @Bean註解,

更多的使用它註冊第三方bean

  • 方式3: 在配置類頭上使用  @Import(XXX.class,YYY.class)

解放了第二種通過@Bean還的自己new的缺陷!!! 組冊進IOC中的元件的預設id,是元件的全類名

  • 方式4: @Import({Color.class, MyImportSelector.class})

    實現ImportSelector介面: 返回需要匯入的元件的全類名陣列,完成批量匯入

  • 方式5: 手動註冊@Import({Color.class,MyImportSelector.class, MyImportBeanDefinitionRegisrar.class})

實現ImportBeanDefinitionRegistrar介面: 它裡面的registerBeanDefinitions()方法的第二個引數就是BeanDefinitionRegistry, 所有bean註冊進IOC容器都經由他完成,因此我們可以手動註冊bean, 還可以通過第一個引數獲取當前標註@Import註解的類的全部註解資訊,加上第二個引數可以獲取當前IOC容器的全部資訊,動態判斷是否要注入類到IOC

同時,第五種在SpringBoot中得到了大量的使用,實現SpringBoot的自動配置

  • 方式6 : 使用Spring提供的FactoryBean (工廠bean)
  1. 自己實現FactoryBean介面,重寫三個方法
  2. @Bean,把自己實現的工廠bean新增到IOC
  3. 測試自己實現的BeanFactory的型別,是我們指定的泛型的型別的!!!
  4. 想獲取到工廠的話, 需要新增字首&
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyConfig02.class);
Object getFB1 = applicationContext.getBean("&getFB");
Object getFB2 = applicationContext.getBean("getFB");

Bean的生命週期

通過第一階段的學習,我們順利把bean的建立權反轉給了Spring,下面就來看看,IOC是如何控制Bean的生命週期的!

什麼是bean的生命週期?

其實,就是下面的過程

Bean建立------> 初始化------> 銷燬


在IOC管理bean生命週期的過程中,我們可以插手做什麼?

  • 我們可以自定義bean的初始化和銷燬方法,bean在到達相應的生命週期時,ioc會呼叫我們指定的方法,施加在bean上

    原來的配置檔案版本,需要我們寫 init-method 和distroy-method


如何實現?

方法1: 使用@Bean註解完成

  @Bean(initMethod="init",destroyMethod = "destory")

測試類

 @Test
    public void text6(){
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(LifeConfig.class);
        Object car = applicationContext.getBean("car");
        System.out.println(car);
        applicationContext.close();
    }

單例項情況下: 物件建立後並賦值好了後,執行init方法,容器關閉物件銷燬

多例項情況下: 物件建立後並賦值好了後,執行init方法,IOC不會管理bean,需要我們自己去銷燬bean

用處?

在配置資料來源是大量使用,物件建立後需要初始話很多的資料,物件銷燬了,很多資源要釋放

方法2: 讓Bean實現spring提供的兩個介面InitializingBeanDisposableBean,重寫這兩個介面的方法

public interface InitializingBean {
    // Bean初始化後呼叫
    void afterPropertiesSet() throws Exception;
}

public interface DisposableBean {
  // Bean銷燬後呼叫
    void destroy() throws Exception;
}
  • 單例模式下: 在IOC一初始化就進行bean構造,並且執行完了的afterPropertiesSet()初始化,容器關閉,bean執行銷燬DisposableBean()

方法3: 使用JS205規範提供的兩個註解@PostConstruct和@PreDestory完成

  • @PostConstruct :在bean建立完成並且屬性賦值好了後執行本初始化方法
  • @PreDestory: 在容器銷燬bean之前,通知我們進行清理工作

這兩個註解都是標註在方法上的!!!

@Documented
@Retention (RUNTIME)
@Target(METHOD)
public @interface PostConstruct {
}

@Documented
@Retention (RUNTIME)
@Target(METHOD)
public @interface PreDestroy {
}

方法4: 使用Spring提供給我們的元件,介面BeanPostProcessor

這個元件很重要,Spring的底層,尤其是AOP底層大量使用它
裡面有兩個方法

public interface BeanPostProcessor {

   // 該方法,會在我們說的前三種初始化方法呼叫之前, 提前呼叫!!!
   // 返回值,可以是我們新建立好的這個bean,也可以包裝一下bean 再返回     
    /* Apply this BeanPostProcessor to the given new bean instance <i>before</i> any bean
     * initialization callbacks (like InitializingBean's {@code afterPropertiesSet}
     * or a custom init-method). The bean will already be populated with property values.
     * The returned bean instance may be a wrapper around the original.
     */
    Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;
  // 在上面我們所說的三種初始化方法呼叫之後,立刻呼叫!!!
    Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;
}

實現自己的BeanPostprocessor

@Component
public class MyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
    System.out.println("postProcessBeforeInitialization執行了!!!"+"    beanname=="+beanName+"  bean=="+bean);
    return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
    System.out.println("postProcessAfterInitialization執行了!!!"+"    beanname=="+beanName+"  bean=="+bean);
    return bean;
}
}

測試結果如下圖

Spring基礎筆記

劃重點!!! --- BeanPostprocessor意味bean的後置處理器,前面也說了,它是spring提供給我們的一個輔助型別的元件,我們可以自定義BeanPostProcessor,在所有滿足加入IOC容器的bean初始化方法前後執行BeanPostProcessor介面裡的postProcessAfterInitialization()postProcessBeforeInitialization()方法,完成對bean的增強,AOP的底層使用的 後置處理器 是spring自動給我們載入進來的,他的這種特性,為AOP,動態植入程式碼的實現,提供的前提

介面BeanPostProcessor執行原理

  • 遍歷得到容器中所有的BeanPostProcessor,挨個執行beforeInitialization,一旦返回值為null,說明ioc在沒有這個物件,直接跳出for迴圈,不會執行BeanPostProcessor.postProcessBeforeInitialization()方法進行處理
populateBean(beanName,mdb,instanceWrapper);給bean的屬性賦值

initlizeBean // 初始化
{
    applyBeanPostProcessorBeforeInitialization()  // 前置處理
    invokeInitMethods(beanName,wrappedBean,mdb); // 執行初始化方法
    applyBeanPostProcessorAfterInitialization() ; // 後置處理
}

Spring基礎筆記

通過這個繼承圖,可以看到,BeanPostProcessor作為後置處理器的頂級介面存在,程式執行打上斷點,也能看到我們自定義的MyBeanPostProcessor,另外需要我們關心的一個實現是 InstantiationAwareBeanPosProcessor這個子介面,AOP的實現,就應用到了它(第二篇部落格會記錄)


使用配置檔案給bean賦值

<bean id="person" class="com.changwu.bean" scope="prototype">
    <property name="age" value="18"/>
    <property name="name" value="zhangsan"/>
</bean>

使用註解的方法,給bean賦值

@Value

1. 基本數值  
@Value("changwu")
private String name;

2. #{}  SPEL表示式
@Value(value ="#{2*2}")
private int  age;

3. ${}  取出配置檔案中的值
@Value(value ="${student.age}")
private int  age;

其中取出配置檔案中的值,要在主配置類上新增@PropertySource(value = "classpath:/bean.properties")指明配置檔案的路徑

@Value+@PropertySource

前者使用${} 取出環境變數中的屬性(程式執行後配置檔案會載入進環境變數),後者給前者提供定位

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Repeatable(PropertySources.class)  // 可以寫多個
public @interface PropertySource {

String name() default "";

//支援同時載入多個配置檔案
String[] value();

// 忽略檔案找不到
boolean ignoreResourceNotFound() default false;

//設定編碼
String encoding() default "";

/**
 * Specify a custom {@link PropertySourceFactory}, if any.
 * <p>By default, a default factory for standard resource files will be used.
 * @since 4.3
 * @see org.springframework.core.io.support.DefaultPropertySourceFactory
 * @see org.springframework.core.io.support.ResourcePropertySource
 */
Class<? extends PropertySourceFactory> factory() default PropertySourceFactory.class;
}

自動裝配?

  • 什麼是自動裝配?

自動裝配就是,Spring利用依賴注入DI,完成對IOC容器中的各個元件依賴關係的賦值

@Autowired & @Qualifier & @Primary

@Autowired是Spring自己的註解

那些Bean,都在IOC中,通過@Autowired註解可以完成自動裝配

  • 預設按照型別(XXX.class)去IOC中找到對應的元件
  • 如果存在多個bean,就按照id找( @Autowired標記的引用名)
  • 使用@Autowired,預設如果IOC中不存在該bean,就會報錯

  • 通過設定@Autowired(required=false) 設定元件不必需存在於,IOC
  • @Autowired+@Qualifier 明確指定裝配的bean的id

@Qualifier("bookDao2")
@Autowired
private BookDao bookDao;

再強調一遍,如果是包掃描的話,Bean在IOC的id是類名首字母小寫,@Qualifier("XXX")不能亂寫,要麼是類名首字母小寫,要麼是我們通過@Bean("XXX")指定的id

  • @Primary 標記在我們手動新增進去的bean上,強制,首選注入!!!

但是,如果同時存在@Primary@Qualifier 依然會裝配我們明確指定的Bean

// 構造器, 方法,引數,屬性,全能!!!
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {

    /**
     * Declares whether the annotated dependency is required.
     * <p>Defaults to {@code true}.
     */
    boolean required() default true;
}
  1. @Autowired標註在方法上,方法的引數,預設從容器中獲取
  2. @Autowired標註在構造器上,構造器需要的引數,預設從容器中獲取
  3. @Bean標註的方法,方法的引數,預設從容器中獲取,在引數位置的@Autowired可以省略不寫

@Resources(JSR205) & @Inject(JSR330)

java規範註解

@Resources

  • 作用: 預設按照元件名稱裝配
  • 缺點: 不能和@Qulifier@Primary一起使用

@Inject

  • 還麻煩! 需要我們匯入依賴
<dependency>
    <groupId>javax.inject</groupId>
    <artifactId>javax.inject</artifactId>
    <version>1</version>
</dependency>

@Profile

Spring為我們提供的可以根據當前的環境,動態的啟用和切換一系列元件的功能

比如在開發環境下,我們使用A資料庫, 測試環境使用B資料庫, 生產環境使用C資料庫

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
@Documented
@Conditional(ProfileCondition.class)
public @interface Profile {

    /**
     * The set of profiles for which the annotated component should be registered.
     */
    String[] value();

}

支援寫到方法上!滿足@profile指定的環境下,方法的中元件的注入才會生效

支援寫到類上!滿足@profile指定的環境下,整個類的中元件的注入才會生效

  1. 準備工作,註冊三個資料來源
@Configuration
@PropertySource("classpath:/dbProperlies.properties")
public class MainConfiguration {

    @Value("${db.user}")
    private String user;

    @Value("${db.driverClass}")
    private String driverClass;


    @Profile("text")
    @Bean("TextDBSource")
    public DataSource dataSourceText(@Value("${db.password}") String pwd) {
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setUser(user);
        dataSource.setPassword(pwd);
        dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/text");
        try {
            dataSource.setDriverClass(driverClass);
        } catch (PropertyVetoException e) {
            e.printStackTrace();
        }
        return dataSource;
    }

    @Profile("dev")
    @Bean("DevDBSource")
    public DataSource dataSourceDev(@Value("${db.password}") String pwd) {
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setUser(user);
        dataSource.setPassword(pwd);
        dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/person");
        try {
            dataSource.setDriverClass(driverClass);
        } catch (PropertyVetoException e) {
            e.printStackTrace();
        }
        return dataSource;
    }

    @Profile("pro")
    @Bean("ProductDBSource")
    public DataSource dataSourceProduct(@Value("${db.password}") String pwd) {
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setUser(user);
        dataSource.setPassword(pwd);
        dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/ssh1");
        try {
            dataSource.setDriverClass(driverClass);
        } catch (PropertyVetoException e) {
            e.printStackTrace();
        }
        return dataSource;
    }
}

2 . 結合@Profile註解,在元件上做一個標識,只有滿足@Profile標識條件的才會被註冊進IOC

  • 加了環境標識的bean,只有那個環境被啟用,才會註冊到容器中,預設是@Profile("default")
  • 沒有環境標識的bean,任何條件下都會載入進容器

如何改變環境?

  1. 使用程式碼的方法
   @Test
    public void text13(){
        // 建立上下文
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
        // 設定需要啟用的環境
        applicationContext.getEnvironment().setActiveProfiles("dev");    // 開發環境下注冊元件
        // 載入主配置類
        applicationContext.register(MainConfiguration.class);
        // 啟動重新整理容器
        applicationContext.refresh();
        String[] names = applicationContext.getBeanDefinitionNames();
        for (String name : names) {
            System.out.println(name);
        }
        applicationContext.close();
    }

註解版AOP怎麼玩?

AOP面向切面程式設計,指的是在程式執行期間,動態的將某段程式碼,切入到指定方法的指定位置額執行的程式設計方式 

匯入AOP模組

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>4.3.7.RELEASE</version>
</dependency>

建立業務邏輯類

  • 我們要做的就是在動態的在div執行前,執行後,出現異常時,正常結束,異常結束等不同情況下列印日誌
public class MathAop {
    public int div(int a,int b){
        return  a-b;
    }
}

定義一個日誌切面類

切面類上的方法會動態的感知到,切入點div的狀態,在其指定的狀態上,做出不同的反應而無序更改仍和div的程式碼

五種通知方法,對應五種不同的註解,具體的細節,在下面的程式碼中有註釋

@Component
@Aspect
public class AopLog {

抽取切入表示式

  1. 註解@Pointcut
  2. 表示式可以使用* 通配
    • execution(pulbic com.changwu.tryspring.aop.MathAop.*(int,int))
    • execution(* com.changwu.tryspring.aop.MathAop.div(..))
  3. 本類使用 : 直接用方法名--> pointCut()
  4. 其他類使用: 使用帶包名的全路徑--> com.changwu.tryspring.aop.AopLog.pointCut()
    @Pointcut("execution(* com.changwu.tryspring.aop.MathAop.div(..))")
    public void pointCut(){}

前置通知

  1. 註解@Before("切入點表示式")
  2. 呼叫時機: 在目標方法執行前執行
  3. 可選引數: JoinPoint,裡面封裝著切入點方法的全部資訊,比如方法名,引數等等
    @Before("com.changwu.tryspring.aop.AopLog.pointCut()")
    public void before(JoinPoint joinPoint){
        Object[] args = joinPoint.getArgs();  // 獲取引數
        String name = joinPoint.getSignature().getName();  //  獲取方法名
        System.out.println("前置通知....引數:"+ Arrays.asList(args)+"  方法名: "+name);
    }

後置通知

  1. 註解@After("切入點表示式")
  2. 呼叫時機:無論方法正常結束還是異常結束,都呼叫
    @After("execution(* com.changwu.tryspring.aop.MathAop.div(..))")
    public void after(){
        System.out.println("後置通知執行了!!!");
    }

返回通知

  1. 註解@AfterReturning(value="切入點表示式",returning="XXX")
  2. 呼叫時機: 方法正常返回執行
  3. 注意點: 函式的入參,引數名和註解中的XXX要相同,裡面封裝著,函式的返回值結果
    @AfterReturning(value = "com.changwu.tryspring.aop.AopLog.pointCut()",returning = "result")
    public void returning(Object result){
        System.out.println("切入點正常執行...執行結果..{"+result+"}");
    }

異常通知

  1. 註解@AfterThrowing(value="切入點表示式",throwing="XXX")
  2. 呼叫時機: 切入點出現異常
  3. 注意點:函式的入參,引數名和註解中的XXX要相同,裡面封裝著,函式的返回值結果
  4. XXX裡面封裝著方法的異常資訊
    @AfterThrowing(value = "com.changwu.tryspring.aop.AopLog.pointCut()",throwing = "expection")
    public void afterThrowing(Exception expection){
        System.out.println("切入點出現異常,異常結果..{"+expection+"}..");
    }

環繞通知

  1. 註解@Around("切入點表示式")
  2. 呼叫時機: 切入點執行前後都會執行
  3. 引數:ProceedingJoinPoint裡面封裝了關於切入點的所有資訊
  4. proceedingJoinPoint.proceed();返回值: 為切入點的返回值, 必須返回
  5. 環繞通知裡面的所有異常全部丟擲去,,一旦我們try起來了,異常通知就獲取不到異常,進而返回通知就認為方法是正常結束的,結果為NULL
    @Around("com.changwu.tryspring.aop.AopLog.pointCut()")
    public Object arounding(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {

        System.out.println("環繞通知開始:  方法名:  "+
                proceedingJoinPoint.getSignature().getName() +
                "將要被執行   引數為:  "+Arrays.asList(proceedingJoinPoint.getArgs()));
        Object result = proceedingJoinPoint.proceed();// 代理呼叫方法,如過呼叫方法add丟擲異常,就不會執行後面的程式碼(所以要丟擲去!)
        // 呼叫方法之後執行
        System.out.println("環繞通知,呼叫方法之後執行,獲取的結果是:"+result);
        return result;

    }

}

注意點: 業務邏輯類,切面類都要新增進IOC

注意點: 切面類都要新增註解@Aspects

補充: JointPoint 可以獲取的切入點的資訊 而且,必須在引數的第一位


好繼續準備,回顧xml版本的spring開發方式需要我們新增如下的配置

<aop:aspectj-autoproxy></aop:aspectj-autopeoxy>

基於註解,開啟切面--->@EnableAspectjAutoProxy

@Configuration
@EnableAspectJAutoProxy
@ComponentScan("com.changwu.tryspring.aop")
public class MainAopConfig {
}

測試:

@Test
public void text14(){
    AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainAopConfig.class);
    // 根據型別獲取bean
    MathAop mathAop = applicationContext.getBean(MathAop.class);
    mathAop.div(2,1);
    applicationContext.close();
}

相關文章