- Spring帶給了我們什麼便利?
- 註解版本的IOC如何玩?
- 元件註冊
- 元件註冊的過程中有哪些過濾規則?
- 如何控制元件的作用域(單例多例)?
- 六種註冊元件的方式?
- 生命週期
- 什麼是bean的生命週期
- 在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-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)
- 自己實現FactoryBean介面,重寫三個方法
- @Bean,把自己實現的工廠bean新增到IOC
- 測試自己實現的BeanFactory的型別,是我們指定的泛型的型別的!!!
- 想獲取到工廠的話, 需要新增字首&
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提供的兩個介面InitializingBean
和DisposableBean
,重寫這兩個介面的方法
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;
}
}
測試結果如下圖
劃重點!!! ---
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() ; // 後置處理
}
通過這個繼承圖,可以看到,
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;
}
@Autowired
標註在方法上,方法的引數,預設從容器中獲取@Autowired
標註在構造器上,構造器需要的引數,預設從容器中獲取@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
指定的環境下,整個類的中元件的注入才會生效
- 準備工作,註冊三個資料來源
@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,任何條件下都會載入進容器
如何改變環境?
- 使用程式碼的方法
@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 {
抽取切入表示式
- 註解
@Pointcut
- 表示式可以使用* 通配
- execution(pulbic com.changwu.tryspring.aop.MathAop.*(int,int))
- execution(* com.changwu.tryspring.aop.MathAop.div(..))
- 本類使用 : 直接用方法名--> pointCut()
- 其他類使用: 使用帶包名的全路徑--> com.changwu.tryspring.aop.AopLog.pointCut()
@Pointcut("execution(* com.changwu.tryspring.aop.MathAop.div(..))")
public void pointCut(){}
前置通知
- 註解
@Before("切入點表示式")
- 呼叫時機: 在目標方法執行前執行
- 可選引數: 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);
}
後置通知
- 註解
@After("切入點表示式")
- 呼叫時機:無論方法正常結束還是異常結束,都呼叫
@After("execution(* com.changwu.tryspring.aop.MathAop.div(..))")
public void after(){
System.out.println("後置通知執行了!!!");
}
返回通知
- 註解
@AfterReturning(value="切入點表示式",returning="XXX")
- 呼叫時機: 方法正常返回執行
- 注意點: 函式的入參,引數名和註解中的XXX要相同,裡面封裝著,函式的返回值結果
@AfterReturning(value = "com.changwu.tryspring.aop.AopLog.pointCut()",returning = "result")
public void returning(Object result){
System.out.println("切入點正常執行...執行結果..{"+result+"}");
}
異常通知
- 註解
@AfterThrowing(value="切入點表示式",throwing="XXX")
- 呼叫時機: 切入點出現異常
- 注意點:函式的入參,引數名和註解中的XXX要相同,裡面封裝著,函式的返回值結果
- XXX裡面封裝著方法的異常資訊
@AfterThrowing(value = "com.changwu.tryspring.aop.AopLog.pointCut()",throwing = "expection")
public void afterThrowing(Exception expection){
System.out.println("切入點出現異常,異常結果..{"+expection+"}..");
}
環繞通知
- 註解
@Around("切入點表示式")
- 呼叫時機: 切入點執行前後都會執行
- 引數:
ProceedingJoinPoint
裡面封裝了關於切入點的所有資訊 - proceedingJoinPoint.proceed();返回值: 為切入點的返回值, 必須返回
- 環繞通知裡面的所有異常全部丟擲去,,一旦我們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();
}