拋棄配置後的Spring

一、前言

Spring 有XML配置和註解兩種版本,我個人非常喜歡使用註解,相當熱衷Spring boot!

對於Spring,核心就是IOC容器,這個容器說白了就是把你放在裏面的對象(Bean)進行統一管理,你不用考慮對象如何創建如何銷燬,從這方面來說,所謂的控制反轉就是獲取對象的方式被反轉了。

既然你都把對象交給人家Spring管理了,那你需要的時候不得給人家要呀。這就是依賴注入(DI)!再想下,我們在傳入一個參數的時候除了在構造方法中就是在setter方法中,換個好聽的名字就是構造注入和設值注入。

至於AOP(面向切面),這玩意我舉個例子說下,比如你寫了個方法用來做一些事情,但這個事情要求登錄用戶才能做,你就可以在這個方法執行前驗證一下,執行後記錄下操作日誌,把前後的這些與業務邏輯無關的代碼抽取出來放一個類裏,這個類就是切面(Aspect),這個被環繞的方法就是切點(Pointcut),你所做的執行前執行後的這些方法統一叫做增強處理(Advice)。

二、配置

推薦使用IDEA快速構建Spring項目!

拋棄配置第一步,快速定義application.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <!--開啓自動掃描-->
    <context:component-scan base-package="cn.zyzpp"/>

</beans>

作用

  1. 默認掃描Spring提供的@Component, @Repository, @Service,@Controller、@RestController、@ControllerAdvice和@Configuration等註解的類。

  2. 默認開啓annotation註解配置,激活@Required,@Autowired, @PostConstruct, @PreDestroy, @Resource, @PersistenceContext, @PersistenceUnit組件類中的註釋。

三、依賴注入

搜索Bean類
Spring提供如下幾個Annotation來標註Spring Bean:

  • @Component: 標註一個普通的Spring Bean類

  • @Controller: 標註一個控制器組件類

  • @Service: 標註一個業務邏輯組件類

  • @Repository: 標註一個DAO組件類

使用@Resource配置依賴

使用@Resource與元素的ref屬性有相同的效果。

@Resource不僅可以修飾setter方法,也可以直接修飾實例變量,如果使用@Resource修飾實例變量將會更加簡單,此時Spring將會直接使用JavaEE規範的Field注入,此時連setter方法都可以不要。

@Resource位於javax.annotation包下,是來自JavaEE規範的一個Annotation。

@Resource有兩個屬性是比較重要的,分是name和type,Spring將@Resource註解的name屬性解析爲bean的名字,而type屬性則解析爲bean的類型。所以如果使用name屬性,則使用byName的自動注入策略,而使用type屬性時則使用byType自動注入策略。如果既不指定name也不指定type屬性,這時將通過反射機制使用byName自動注入策略。

使用@Autowired配置依賴

Spring提供了@Autowired註解來指定自動裝配,@Autowired可以修飾setter方法、普通方法、實例變量和構造器等。當使用@Autowired標註setter方法時,默認採用byType自動裝配策略。

默認情況下@Autowired(required = true),意思是要求依賴對象必須存在。

搭配@Qualifier指定BeanId

在這種策略下,符合自動裝配類型的候選Bean實例常常有多個,這個時候就可能引起異常,爲了實現精確的自動裝配,Spring提供了@Qualifier註解,通過使用@Qualifier,允許根據Bean的id來執行自動裝配。

    @Autowired
    @Qualifier(value = "user")
    User user;

四、定義Bean

@Configuration配置

@Configuration用於定義配置類,可替換xml配置文件,被註解的類內部包含有一個或多個被@Bean註解的方法,這些方法將會被AnnotationConfigApplicationContext或AnnotationConfigWebApplicationContext類進行掃描,並用於構建bean定義,初始化Spring容器。

@Configuration
 public class AppConfig {

     @Autowired 
     Environment env;

     @Bean
     public MyBean myBean() {
         MyBean myBean = new MyBean();
         myBean.setName(env.getProperty("bean.name"));
         return myBean;
     }
 }

@Bean定義

@Bean 與 Component 的區別是用在方法上 ,而不是類上。類似於XML中。默認Bean名稱爲方法名。

如果需要顯式命名,可以使用name屬性(或value屬性)。還要注意,name接受字符串數組,允許對單個bean使用多個名稱(即主bean名稱加上一個或多個別名)。

可以定義Bean的初始化方法與關閉應用程序時調用的方法。

@Bean({"b1", "b2"},initMethod = "",destroyMethod = "") // 有b1,b2 bean,但沒有myBean
     public MyBean myBean() {
         return new MyBean;
     }

@Scope作用域

@Scope可以搭配@Component等或者@Bean註解定義Bean的作用域。默認作用域 Singleton。

示例

@Component
@Scope(value = "singleton")
public class HelloWord {

@Lazy懶加載

@Lazy搭配@Component或@Bean使用,作用是延遲初始化 bean,告訴IOC 容器在它第一次被請求時,而不是在啓動時去創建一個 bean 實例。

@Component
@Lazy
public class HelloWord {

@PostConstruct和@PreDestroy定製生命週期行爲

@PostConstruct和@PreDestroy同樣位於javax.annotation包下,也是來自JavaEE規範的兩個Annotation,Spring直接借鑑了它們,用於定製Spring容器中Bean的生命週期行爲。

它們都用於修飾方法,無須任何屬性。

其中前者修飾的方法時Bean的初始化方法;而後者修飾的方法時Bean銷燬之前的方法。

五:使用AOP

AOP(Aspect Orient Programming)也就是面向切面編程,作爲面向對象編程的一種補充,已經成爲一種比較成熟的編程方式。其實AOP問世的時間並不太長,AOP和OOP互爲補充,面向切面編程將程序運行過程分解成各個切面。

AOP專門用於處理系統中分佈於各個模塊(不同方法)中的交叉關注點的問題,在JavaEE應用中,常常通過AOP來處理一些具有橫切性質的系統級服務,如事務管理、安全檢查、緩存、對象池管理等,AOP已經成爲一種非常常用的解決方案。

配置開啓AOP

  <!--啓動@AspectJ支持-->
  <aop:aspectj-autoproxy/>

  <!--指定自動搜索Bean組件、自動搜索切面類-->
  <context:component-scan base-package="cn.zyzpp">
    <context:include-filter type="annotation" expression="org.aspectj.lang.annotation.Aspect"/>
  </context:component-scan>

@Aspect定義切面

@Aspect是不能被掃描到的,所以需要配合@Component註解。@Aspect標識自己是一個切面,將自己從自動代理中刪除。

@Aspect
public class AspectModule {
}

 

@Pointcut定義切點

注意切點通過一個普通方法來定義,返回類型必須爲void。支持execution表達式,within表達式,例如:

@Pointcut("execution(* cn.zyzpp.demo.*Word.*(..))")
private void businessService() {}  // signature

第一個*表示任意返回類型,以.號進行劃分,接下來是包名,類名,類名後是方法名,方法名跟括號,括號內是參數,兩個點..意思是任意參數。

Advice增強處理

 

執行順序

 

JoinPoint 獲取目標方法

訪問目標方法最簡單的做法是定義增強處理方法時,將第一個參數定義爲JoinPoint類型,當該增強處理方法被調用時,該JoinPoint參數就代表了織入增強處理的連接點。JoinPoint裏包含了如下幾個常用的方法:

  • Object[] getArgs:返回目標方法的參數

  • Signature getSignature:返回目標方法的簽名

  • Object getTarget:返回被織入增強處理的目標對象

  • Object getThis:返回AOP框架爲目標對象生成的代理對象

自定義註解向Advice傳參

@Retention定義自定義註解的生命週期

  1. RetentionPolicy.SOURCE:註解只作用在Java源文件(.java文件) ,不會被編譯爲Class字節碼文件。

  2. RetentionPolicy.CLASS:註解保留到Class文件,在JVM加載Class文件時候被遺棄,爲默認生命週期

  3. RetentionPolicy.RUNTIME:註解在源文件與Class中存在,可被JVM加載,在運行時動態獲取。

@Target定義註解的作用位置

  • ElementType.TYPE //接口、類、枚舉、註解

  • ElementType.FIELD //字段、枚舉的常量

  • ElementType.METHOD //方法

  • ElementType.PARAMETER //方法參數

  • ElementType.CONSTRUCTOR //構造函數

  • ElementType.LOCAL_VARIABLE //局部變量

  • ElementType.ANNOTATION_TYPE //註解

  • ElementType.PACKAGE ///包

@Documented,@Inherited 忽略即可!

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Annota {
    String value();
}

在目標方法使用此註解

    @Annota("Mark")
    public void say(String name){
        ....
    }

完整AOP示例

@Component
@Aspect
public class MyAspect {
    //聲明切點
    @Pointcut("execution(* cn.zyzpp.demo.*Word.*(..))")
    public void pointcut(){}

    //聲明切點
    @Pointcut("within(cn.zyzpp.demo.*)")
    public void bizPointcut(){}

    //前置通知:目標方法執行之前執行以下方法體的內容
    @Before("pointcut()")
    public void before(){
        System.out.println("    before");
    }

    //前置通知:獲取註解,給Advice傳遞參數
    @Before("pointcut() && @annotation(annota)")
    public void beforeWithAnnotaion(Annota annota) {
        System.out.println("BeforeWithAnnotation: " + annota.value());
    }

    //前置通知:獲取參數
    @Before("pointcut()")
    public void beforeArgs(JoinPoint joinPoint){
        for (Object object:joinPoint.getArgs()){
            System.out.println("    before args: "+object.toString());
        }
    }

    //前置通知:獲取對象
    @Before("pointcut() && args(arg)")
    public void beforeWithPar(Object arg){
        System.out.println("    before obj: "+arg.toString());
    }

    //後置通知:目標方法執行之後執行以下方法體的內容,不管是否發生異常。
    @After("pointcut()")
    public void after() {
        System.out.println("    After");
    }

    //返回通知::目標方法正常執行完畢時執行以下代碼
    @AfterReturning(pointcut="bizPointcut()" ,returning="retrunValue")
    public void afterReturning(Object retrunValue){
        System.out.println("AfterReturning: "+retrunValue);
    }

    //異常通知:目標方法發生異常的時候執行以下代碼
    @AfterThrowing(pointcut="pointcut()", throwing="e")
    public void afterThrowing(RuntimeException e) {
        System.out.println(" AfterThrowing: " + e.getMessage());
    }

    //環繞通知:目標方法執行前後分別執行一些代碼,發生異常的時候執行另外一些代碼
    @Around("pointcut()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("  Around: start");
        Object obj = pjp.proceed();
        System.out.println("  Around: end, return: "+obj);
        return obj;
    }

}

六、事物管理

一個數據庫事務是一個被視爲單一的工作單元的操作序列。這些操作應該要麼完整地執行,要麼完全不執行。事務管理是一個重要組成部分,RDBMS 面向企業應用程序,以確保數據完整性和一致性。事務的概念可以描述爲具有以下四個關鍵屬性說成是 ACID:

  • 原子性:事務應該當作一個單獨單元的操作,這意味着整個序列操作要麼是成功,要麼是失敗的。

  • 一致性:這表示數據庫的引用完整性的一致性,表中唯一的主鍵等。

  • 隔離性:可能同時處理很多有相同的數據集的事務,每個事務應該與其他事務隔離,以防止數據損壞。

  • 持久性:一個事務一旦完成全部操作後,這個事務的結果必須是永久性的,不能因系統故障而從數據庫中刪除。

編程式 vs. 聲明式

Spring 支持兩種類型的事務管理:

  • 編程式事務管理:這意味着你在編程的幫助下有管理事務。這給了你極大的靈活性,但卻很難維護。

  • 聲明式事務管理 :這意味着你從業務代碼中分離事務管理。你僅僅使用註釋或 XML 配置來管理事務。

聲明式事務管理比編程式事務管理更可取,儘管它不如編程式事務管理靈活,但它允許你通過代碼控制事務。

基於註解的方式

1.開啓事務

<!-- 初始化 數據源 -->
    <bean id="dataSource"
          class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver" />
        <property name="url" value="jdbc:mysql://localhost:3306/TEST" />
        <property name="username" value="root" />
        <property name="password" value="123456" />
    </bean>
    <!-- 事務管理器配置 -->
    <bean id="transactionManager"
          class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource" />
    </bean>
    <!-- 使用annotation註解定義事務 -->
    <tx:annotation-driven transaction-manager="transactionManager"/>

2.service類@Transactional(name=value)

@Transactional參數