《Java Spring框架》Spring切面(AOP)配置詳解


1.  Spring 基本概念

AOP(Aspect Oriented Programming)稱為面向切面編程,在程序開發中主要用來解決一些系統層面上的問題,比如日志,事務,權限等待,Struts2的攔截器設計就是基於AOP的思想,是個比較經典的例子。

在不改變原有的邏輯的基礎上,增加一些額外的功能。代理也是這個功能,讀寫分離也能用aop來做。

2. 原理圖:

 

 我們希望業務開發人員只關心中間部分,不再需要關系開啟和關閉數據庫連接的情況,同時也避免了代碼重復和可能出現的問題。

3. 代碼

public interface UserService {
    //
    void save();

    String getStr();

    void setStr(String str);
}
public class UserServiceImpl implements UserService {
    private String str = "0";

    public String getStr() {
        return str;
    }

    public void setStr(String str) {
        this.str = str;
    }

    @Override
    public void save() {
        System.out.println("--------- save --------------");
    }
}
import org.aspectj.lang.ProceedingJoinPoint;

/**
 * 自定義通知類
 */
public class MyAdvice {

    //before 前置通知 在目標方法前調用
    public void before() {
        System.out.println("before");
    }

    //after 最終通知(后置通知)在目標方法后調用,無論是否出現異常都會執行 finally
    public void after() {
        System.out.println("after");
    }

    //afterReturning 成功通知(后置通知)在目標方法執行后,並且執行成功,如果方法出現異常則不調用
    public void afterReturning() {
        System.out.println("afterReturning");
    }

    //afterThrowing 異常通知(后置通知)在目標方法執行出現異常的時候才會調用
    public void afterThrowing() {
        System.out.println("afterThrowing");
    }

    //around 環繞通知 需要我們手動調用目標方法,並且可以設置通知
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("around before");
        Object proceed = pjp.proceed();
        System.out.println("around after");
        return proceed;
    }
}
import com.bing.aop.service.UserService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import javax.annotation.Resource;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext_aop.xml")
public class AopTest {

    @Resource(name="userService")
    UserService us;
    @Test
    public void Test2() {
        us.save();
    }
}

配置文件:文件名(applicationContext_aop.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:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">
    <!-- 目標對象 -->
    <bean name="userService" class="com.bing.aop.service.UserServiceImpl"></bean>

    <!-- 通知對象 -->
    <bean name="myAdvice" class="com.bing.aop.MyAdvice"></bean>

    <aop:config>
        <!-- 切入點  expression 切入點表達式 可以配置要增強的方法
                            public void com.bing.aop.service.UserServiceImpl.save()
                            * com.bing.aop.service.*ServiceImpl.*(..)
                           id 就是唯一標識
        -->
        <aop:pointcut expression="execution(* com.bing.aop.service.*ServiceImpl.*(..))" id="servicePc"/>

        <!-- 切面 通知+切入點 -->
        <aop:aspect ref="myAdvice">
            <!-- 通知類型 -->
            <aop:before method="before" pointcut-ref="servicePc"/>
            <!-- 最終通知 后置通知 -->
            <aop:after method="after" pointcut-ref="servicePc"/>
            <!-- 成功通知 后置通知 -->
            <aop:after-returning method="afterReturning" pointcut-ref="servicePc"/>
            <!-- 異常通知 后置通知 -->
            <aop:after-throwing method="afterThrowing" pointcut-ref="servicePc"/>
            <!--  環繞通知-->
            <aop:around method="around" pointcut-ref="servicePc"/>
        </aop:aspect>
    </aop:config>
</beans>

運行結果:

到這里其實發現這個AOP沒有什么實際用處,前后中間執行的東西都沒有關系,並不能體現真正的AOP思想。

我們將上述代碼修改一下:修改環繞通知的方法。

import com.bing.aop.service.UserService;
import com.bing.aop.service.UserServiceImpl;
import org.aspectj.lang.ProceedingJoinPoint;

/**
 * 自定義通知類
 */
public class MyAdvice {

    //before 前置通知 在目標方法前調用
    public void before() {
        System.out.println("before");
    }

    //after 最終通知(后置通知)在目標方法后調用,無論是否出現異常都會執行 finally
    public void after() {
        System.out.println("after");
    }

    //afterReturning 成功通知(后置通知)在目標方法執行后,並且執行成功,如果方法出現異常則不調用
    public void afterReturning() {
        System.out.println("afterReturning");
    }

    //afterThrowing 異常通知(后置通知)在目標方法執行出現異常的時候才會調用
    public void afterThrowing() {
        System.out.println("afterThrowing");
    }

    //around 環繞通知 需要我們手動調用目標方法,並且可以設置通知
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        UserService userService = (UserServiceImpl)pjp.getTarget();
        Object proceed = null;
        if("1".equals(userService.getStr()) || "setStr".equals(pjp.getSignature().getName())){
            proceed = pjp.proceed();
        }
        return proceed;
    }
}
import com.bing.aop.service.UserService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import javax.annotation.Resource;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext_aop.xml")
public class AopTest {

    @Resource(name="userService")
    UserService us;
    @Test
    public void Test2() {
        System.out.println("設置前:------------Str == 0");
        us.save();
        us.setStr("1");
        System.out.println("設置前:------------Str == 1");
        us.save();
    }
}

然后將配置文件的其他通知都去掉:

<?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:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">
    <!-- 目標對象 -->
    <bean name="userService" class="com.bing.aop.service.UserServiceImpl"></bean>

    <!-- 通知對象 -->
    <bean name="myAdvice" class="com.bing.aop.MyAdvice"></bean>

    <aop:config>
        <!-- 切入點  expression 切入點表達式 可以配置要增強的方法
                            public void com.bing.aop.service.UserServiceImpl.save()
                            * com.bing.aop.service.*ServiceImpl.*(..)
                           id 就是唯一標識
        -->
        <aop:pointcut expression="execution(* com.bing.aop.service.*ServiceImpl.*(..))" id="servicePc"/>

        <!-- 切面 通知+切入點 -->
        <aop:aspect ref="myAdvice">
            <!--  環繞通知-->
            <aop:around method="around" pointcut-ref="servicePc"/>
        </aop:aspect>
    </aop:config>
</beans>

運行效果:

把實體類和MyAdvice 結合起來,可以更加方便的寫你想要處理的邏輯。 

XML 用的越來越少了,現在大部分都使用注解了,下面是使用注解的Demo

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@ComponentScan("com.hundsun.cop.aop")
@Configuration
@EnableAspectJAutoProxy
public class AppConfig {

}
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class MyAdvice {

    //設置切點
    @Pointcut("execution(public * com.hundsun.cop.aop.UserService.*(..))")
    public void execution(){

    }

    // 執行前
    @Before("MyAdvice.execution()")
    public void before(){

    }

    // 執行后
    @After("MyAdvice.execution()")
    public void after() {
        System.out.println("after");
    }

    //afterReturning 成功通知(后置通知)在目標方法執行后,並且執行成功,如果方法出現異常則不調用
    @AfterReturning("MyAdvice.execution()")
    public void afterReturning() {
        System.out.println("afterReturning");
    }

    //afterThrowing 異常通知(后置通知)在目標方法執行出現異常的時候才會調用
    @AfterThrowing("MyAdvice.execution()")
    public void afterThrowing() {
        System.out.println("afterThrowing");
    }

    //around 環繞通知 需要我們手動調用目標方法,並且可以設置通知
    @Around("MyAdvice.execution()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("around before");
        Object proceed = pjp.proceed();
        System.out.println("around after");
        return proceed;
    }
}
public interface UserService {
    void save();

    String getStr();

    void setStr(String str);
}
@Component
public class UserServiceImpl implements UserService {
    private String str = "0";

    public String getStr() {
        return str;
    }

    public void setStr(String str) {
        this.str = str;
    }

    @Override
    public void save() {
        System.out.println("--------- save --------------");
    }
}
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Test {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
        UserService userService = annotationConfigApplicationContext.getBean(UserService.class);
        userService.save();
    }
}

運行結果

通過注解來表示切點的方式:

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

@Slf4j
@Aspect
@Component
public class ExecTimeAdvice {

    //設置切點(設置成注解類,后面需要的地方加上注解即可)
    @Pointcut("@annotation(com.hundsun.o45.repeat.advice.ExecTimeMonitor)")  
    public void execution(){

    }

    @Around("ExecTimeAdvice.execution()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("AOP測試");
        return null;
    }
}

注解:

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(value = {ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ExecTimeMonitor {
}

不喜歡誇誇奇談,實戰才是真理。代碼是說明原理的最可靠的方式。

總結來源: http://www.sikiedu.com/ 網站學習。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM