手寫Spring框架準備工做之Spring核心AOP

Spring AOP 知識點思惟導圖git

file

什麼是 AOP?

AOP(Aspect-oriented Programming) , 名字與 OOP(Object-oriented programming) 僅差一個字母, 其實它是對 OOP 編程的一種補充. AOP 翻譯過來叫面向切面編程, 核心就是這個切面. 切面表示從業務邏輯中分離出來的橫切邏輯, 好比性能監控, 日誌記錄, 權限控制等, 這些功能均可以從核心業務邏輯代碼中抽離出來. 也就是說, 經過 AOP 能夠解決代碼耦合問題, 讓職責更加單一.正則表達式

咱們來經過代碼來理解下概念, 這裏有一個轉帳業務:spring

public interface IAccountService {
    //主業務邏輯: 轉帳
    void transfer();
}
public class AccountServiceImpl implements IAccountService {
    @Override
    public void transfer() {
        System.out.println("調用dao層,完成轉帳主業務.");
    }
}
複製代碼

如今有一個需求是, 在轉帳以前須要驗證用戶身份, 通常狀況下咱們直接就去修改源代碼了:express

public class AccountServiceImpl implements IAccountService {
    @Override
    public void transfer() {
	  System.out.println("對轉帳人身份進行驗證.");

        System.out.println("調用dao層,完成轉帳主業務.");
    }
}
複製代碼

但做爲一個"有經驗"的 Java 開發, 咱們知道 「身份驗證」 這個業務徹底是能夠剝離出來的, 因此使用下代理設計模式的思想:編程

public class AccountProxy implements IAccountService {
    //目標對象
    private IAccountService target;

    public AccountProxy(IAccountService target) {
        this.target = target;
    }

    /**
     * 代理方法,實現對目標方法的功能加強
     */
    @Override
    public void transfer() {
        before();
        target.transfer();
    }

    /**
     * 身份驗證
     */
    private void before() {
        System.out.println("對轉帳人身份進行驗證.");
    }
}

public class Client {
    public static void main(String[] args) {
        //建立目標對象
        IAccountService target = new AccountServiceImpl();
        //建立代理對象
        AccountProxy proxy = new AccountProxy(target);
        proxy.transfer();
    }
}
複製代碼

AOP 的實現原理就是代理設計模式, 上面這個是靜態代理, 但 Spring AOP 是經過動態代理實現的, 因此咱們須要瞭解下動態代理: blog.csdn.net/litianxiang…後端

AOP 並非由 Spring 首創, 在 Spring 以前就有了 AOP 開源框架—AspectJ, 並且 AspectJ 的 AOP 功能要比 Spring 更強大, 因此在 Spring 的後期版本中就集成了 AspectJ. 但咱們仍是有必要了解下 Spring 的 AOP 功能.設計模式

AOP 術語

  • Targe 目標對象
  • Joinpoint 鏈接點, 全部可能被加強的方法都是鏈接點.
  • Pointcut 切入點, 將被加強的方法.
  • Advice 加強, 從主業務邏輯中剝離出來的橫切邏輯.
  • Aspect 切面, 切入點加上加強就是切面.
  • Weaving 織入, 把切面應用到目標對象上的過程.
  • Proxy 代理對象, 被加強過的目標對象.

Advice 常見類型

  • 前置加強 org.springframework.aop.MethodBeforeAdvice, - 在目標方法執行前實施加強.
  • 後置加強 org.springframework.aop.AfterReturningAdvice, 在目標方法執行後實施加強.
  • 環繞加強 org.aopalliance.intercept.MethodInterceptor, 在目標方法執行先後都實施加強.
  • 異常拋出加強 org.springframework.aop.ThrowsAdvice, 在方法拋出異常後實施加強.
  • 引入加強 org.springframework.aop.IntroductionInterceptor, 對類進行加強, 即在目標類中添加一些新的方法和屬性.

Spring AOP 之編程式

咱們用編程式來感覺下 Spring AOP. 下面例子使用的是環繞加強.bash

(1) 業務類架構

public interface IAccountService {
    //主業務邏輯: 轉帳
    void transfer();
}
public class AccountServiceImpl implements IAccountService {
    @Override
    public void transfer() {
        System.out.println("調用dao層,完成轉帳主業務.");
    }
}
複製代碼

(2) 加強mvc

public class AccountAdvice implements MethodInterceptor {
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        before();
        Object result = invocation.proceed();
        after();
        return result;
    }

    private void before() {
        System.out.println("Before");
    }

    private void after(){
        System.out.println("After");
    }
}
複製代碼

(3) 測試

public class Test {
    public static void main(String[] args) {
        //建立代理工廠
        ProxyFactory proxyFactory = new ProxyFactory();
        //配置目標對象
        proxyFactory.setTarget(new AccountServiceImpl());
        //配置加強
        proxyFactory.addAdvice(new AccountAdvice());

        IAccountService proxy = (IAccountService) proxyFactory.getProxy();
        proxy.transfer();
    }
}

結果:
Before
調用dao層,完成轉帳主業務.
After
複製代碼

Spring AOP 之聲明式

Spring AOP 之聲明式就是使用配置文件來聲明各類 bean.

(1) 業務類

public interface IAccountService {
    //主業務邏輯: 轉帳
    void transfer();
}
public class AccountServiceImpl implements IAccountService {
    @Override
    public void transfer() {
        System.out.println("調用dao層,完成轉帳主業務.");
    }
}
複製代碼

(2) 加強

public class AccountAdvice implements MethodInterceptor {
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        before();
        Object result = invocation.proceed();
        after();
        return result;
    }

    private void before() {
        System.out.println("Before");
    }

    private void after(){
        System.out.println("After");
    }
}
複製代碼

(3) 配置文件

<?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"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       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 http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">

    <!--聲明bean-->
    <bean id="accountService" class="org.service.impl.AccountServiceImpl"></bean>
    <bean id="accountAdvice" class="org.aspect.AccountAdvice"></bean>

    <!--配置代理工廠-->
    <bean id="accountProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
        <!--目標接口-->
        <property name="interfaces" value="org.service.IAccountService"/>
        <!--目標對象-->
        <property name="target" ref="accountService"/>
        <!--加強-->
        <property name="interceptorNames" value="accountAdvice"/>
    </bean>

</beans>
複製代碼

(4) 測試

public class Test {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring/spring-service.xml");
        IAccountService proxy = (IAccountService) context.getBean("accountProxy");
        proxy.transfer();
    }
}

結果:
Before
調用dao層,完成轉帳主業務.
After
複製代碼

Spring AOP 之切面

前面的編程式和聲明式都沒有用到切面, 他們對一個類中的全部方法都實施了加強. 若是咱們須要針對類中的某個方法進行加強, 就可使用切面來解決這個問題.

在聲明式的代碼基礎上修改下配置文件, 加入切面:

<?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"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       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 http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">

    <!--聲明bean-->
    <bean id="accountService" class="org.service.impl.AccountServiceImpl"></bean>
    <bean id="accountAdvice" class="org.aspect.AccountAdvice"></bean>

    <!--配置切面-->
    <bean id="accountAspect" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
        <!--加強-->
        <property name="advice" ref="accountAdvice"/>
        <!--切入點-->
        <property name="pattern" value="org.service.impl.AccountServiceImpl.transfer.*"/>
    </bean>


    <!--配置代理-->
    <bean id="accountProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
        <!--目標對象-->
        <property name="target" ref="accountService"/>
        <!--切面-->
        <property name="interceptorNames" value="accountAspect"/>
    </bean>

</beans>
複製代碼

這裏的切面配置的是基於正則表達式的 RegexpMethodPointcutAdvisor, 表示攔截全部以 「transfer」 開頭的方法. 除此以外, Spring AOP 還有如下配置:

  • org.springframework.aop.support.DefaultPointcutAdvisor 匹配繼承了該類的切面.
  • org.springframework.aop.support.NameMatchMethodPointcutAdvisor 根據方法名稱進行匹配.
  • org.springframework.aop.support.StaticMethodMatcherPointcutAdvisor 用於匹配靜態方法.

Spring AOP 之自動代理

讓用戶去配置一個或少數幾個代理, 彷佛還能夠接受, 但隨着項目的擴大, 代理配置會愈來愈多, 這個時候再讓你手動配置, 那整我的都很差了. 不過不用擔憂, Spring AOP 爲咱們提供了自動生成代理的功能.

在聲明式的代碼基礎上修改下配置文件和測試類:

(1) 配置文件

<?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"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       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 http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
    <!--聲明bean-->
    <bean id="accountService" class="org.service.impl.AccountServiceImpl"></bean>
    <bean id="accountAdvice" class="org.aspect.AccountAdvice"></bean>

    <!--配置切面-->
    <bean id="accountAspect" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
        <!--加強-->
        <property name="advice" ref="accountAdvice"/>
        <!--切入點-->
        <property name="pattern" value="org.service.impl.AccountServiceImpl.transfer.*"/>
    </bean>

    <!--配置自動代理: 自動掃描全部切面類, 併爲其生成代理-->
    <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/>
</beans>
複製代碼

(2) 測試

public class Test{
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring/spring-service.xml");
        IAccountService proxy = (IAccountService) context.getBean("accountService");
        proxy.transfer();
    }
}

結果:
Before
調用dao層,完成轉帳主業務.
After
複製代碼

Spring + AspectJ

在企業開發中幾乎不使用 Spring 自身的 AOP 功能, 而是用 AspectJ 代替, Spring 在後期本身集成了 AspectJ 也間接證實了 AspectJ 的強大, 咱們下面來了解下 Spring + AspectJ.

AspectJ 加強類型

  • 前置加強
    註解: @Before, 配置: < aop:before>
  • 後置加強
    註解: @After, 配置: < aop:after>
  • 環繞加強
    註解: @Around, 配置: < aop:around>
  • 異常拋出加強
    註解: @AfterThrowing, 配置: < aop:after-throwing>
  • 引入加強
    註解: @DeclareParents, 配置: < aop:declare-parents> 切入點表達式
execution(* org.service.impl.AccountServiceImpl.*(..))
複製代碼
  • execution()表示攔截方法, 括號中可定義須要匹配的規則.
  • 第一個 「*」 表示方法的返回值是任意的.
  • 第二個 「*」 表示匹配該類中全部的方法.
  • (…) 表示方法的參數是任意的. 能夠看到, AspectJ 的切入點表達式要比 Spring AOP 的正則表達式可讀性更強.

Spring AspectJ 實例—基於配置

(1) 業務類

public interface IAccountService {
    //主業務邏輯: 轉帳
    void transfer();
}
public class AccountServiceImpl implements IAccountService {
    @Override
    public void transfer() {
        System.out.println("調用dao層,完成轉帳主業務.");
    }
}
複製代碼

(2) 加強

public class AccountAdvice{
    //前置加強
    public void myBefore(JoinPoint joinPoint){
        before();
    }

    //後置加強
    public void myAfter(JoinPoint joinPoint) {
        after();
    }

    //環繞加強
    public Object myAround(ProceedingJoinPoint joinPoint) throws Throwable{
        before();
        Object result  = joinPoint.proceed();
        after();
        return result;
    }

    //拋出異常加強
    public void myThrowing(JoinPoint joinPoint, Throwable e) {
        System.out.println("拋出異常加強: " + e.getMessage());
    }


    private void before() {
        System.out.println("Before");
    }

    private void after(){
        System.out.println("After");
    }
}
複製代碼

(3) 配置

<?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"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       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 http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
    <!--聲明bean-->
    <bean id="accountService" class="org.service.impl.AccountServiceImpl"></bean>
    <bean id="accountAdvice" class="org.aspect.AccountAdvice"></bean>

    <!--切面-->
    <aop:config>
        <aop:aspect ref="accountAdvice">
            <!--切入點表達式-->
            <aop:pointcut expression="execution(* org.service.impl.AccountServiceImpl.*(..))" id="myPointCut"/>
            <!--環繞加強-->
            <aop:around method="myAround" pointcut-ref="myPointCut"/>
        </aop:aspect>
    </aop:config>
</beans>
複製代碼

(4) 測試

public class Test{
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring/spring-service.xml");
        IAccountService proxy = (IAccountService) context.getBean("accountService");
        proxy.transfer();
    }
}

結果:
Before
調用dao層,完成轉帳主業務.
After
複製代碼

Spring AspectJ 實例—基於註解

(1) 業務類

public interface IAccountService {
    //主業務邏輯: 轉帳
    void transfer();
}
@Component
public class AccountServiceImpl implements IAccountService {
    @Override
    public void transfer() {
        System.out.println("調用dao層,完成轉帳主業務.");
    }
}
複製代碼

(2) 切面

這裏就再也不叫加強了, 由於有了切入點和加強, 叫切面更好.

@Component
@Aspect
public class AccountAspect{
    //切入點
    @Pointcut("execution(* org.tyshawn.service.impl.AccountServiceImpl.*(..))")
    private void pointCut(){};

    //前置加強
    @Before("pointCut()")
    public void myBefore(JoinPoint joinPoint){
        before();
    }

    //後置加強
    @After("pointCut()")
    public void myAfter(JoinPoint joinPoint) {
        after();
    }

    //環繞加強
    @Around("pointCut()")
    public Object myAround(ProceedingJoinPoint joinPoint) throws Throwable{
        before();
        Object result  = joinPoint.proceed();
        after();
        return result;
    }

    //拋出異常加強
    @AfterThrowing(value = "pointCut()", throwing = "e")
    public void myThrowing(JoinPoint joinPoint, Throwable e) {
        System.out.println("拋出異常加強: " + e.getMessage());
    }

    private void before() {
        System.out.println("Before");
    }

    private void after(){
        System.out.println("After");
    }
}
複製代碼

(3) 配置

<?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"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       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 http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">

    <!--註解掃描-->
    <context:component-scan base-package="org.tyshawn"></context:component-scan>
    <!--自動代理 -->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
複製代碼

(4) 測試

public class Test{
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring/spring-service.xml");
        IAccountService proxy = (IAccountService) context.getBean("accountServiceImpl");
        proxy.transfer();
    }
}
結果:
Before
Before
調用dao層,完成轉帳主業務.
After
After
複製代碼

來源:t.im/kzfr

開源項目推薦

做者的開源項目推薦:

關注公衆號回覆開源項目便可獲取

相關文章
相關標籤/搜索