天天看點

深入淺出springboot2.x(7)spring AOP開發

AOP開發詳解

這裡我們采用@Aspect注解方式開發。aop隻能對方法進行攔截,是以首先要确定攔截什麼方法,讓它織入約定的流程中。

确定連接配接點

aop程式設計需要确定連接配接點(在spring中就是什麼類的什麼方法)。我們定義一個UserService接口,裡面有一個printUser方法。

實體類User

public class User {
    private String id;
    private String name;
    private String note;
    ****   getter and setter *****
}
           

接口

public interface UserService {
    public void printUser(User user);
}
           

實作類

@Service
public class UserServiceImpl implements UserService {
    @Override
    public void printUser(User user) {
        if(user == null){
            throw new RuntimeException();
        }
        System.out.println(user.getId());
        System.out.println(user.getName());
        System.out.println(user.getNote());
    }
}
           

這是一個普通的接口和實作類,下面我們以printUser方法為連接配接點,進行aop程式設計。

開發切面

@Aspect
public class MyAspect {
  
    @Before("execution(* springbootall.springboot.spring.springaop2.UserServiceImpl.printUser(..))")
    public void before(){
        System.out.println("before...");
    }
    @After("execution(* springbootall.springboot.spring.springaop2.UserServiceImpl.printUser(..))")
    public void after(){
        System.out.println("after...");
    }
    @AfterReturning("execution(* springbootall.springboot.spring.springaop2.UserServiceImpl.printUser(..))")
    public void afterRetunring(){
        System.out.println("afterRetunring...");
    }
    @AfterThrowing("execution(* springbootall.springboot.spring.springaop2.UserServiceImpl.printUser(..))")
    public void afterThrowing(){
        System.out.println("afterThrowing....");
    }
}
           

spring以@Aspect作為切面聲明,當以@Aspect注解定義時,spring就會知道這是一個切面,我們就可以通過各類注解來定義各類的通知了。代碼中的@Before、@After、@AfterReturning和@AfterThrowing等注解,是定義流程的,然後由springaop織入約定的流程中。

切面定義

在上面開發切面的時候@Before、@After、@AfterReturning和@AfterThrowing等注解中會定義一個正則式,這個正則式的作用就是定義什麼時候啟用aop,也就是spring會通過這個正則式去比對對應的連接配接點(方法)是否啟用切面程式設計,但是上面代碼中每一個注解都重複寫了同一個正則式,這顯得就比較備援了。可以改成下面這樣

@Aspect
public class MyAspect {
    @Pointcut("execution(* springbootall.springboot.spring.springaop2.UserServiceImpl.printUser(..))")
    private void print(){}
    @Before("print()")
    public void before(){
        System.out.println("before...");
    }
    @After("print()")
    public void after(){
        System.out.println("after...");
    }
    @AfterReturning("print()")
    public void afterRetunring(){
        System.out.println("afterRetunring...");
    }
    @AfterThrowing("print()")
    public void afterThrowing(){
        System.out.println("afterThrowing....");
    }
}
           

代碼中使用@Pointcut來定義切點,它标注在print方法上,在後面的通知注解中使用方法名稱來定義。關于正則式

execution(* springbootall.springboot.spring.springaop2.UserServiceImpl.printUser(..))

  • execution表示在執行的時候攔截裡面比對的方法;
  • *表示傳回任意類型的方法;
  • springbootall.springboot.spring.springaop2.UserServiceImpl指定目标對象的全限定名稱;
  • printUser指定目标對象的方法;
  • (…)表示任意參數進行比對。

    對于這個正則式,還可以使用Aspect訓示器。

    |項目類型| 描述 |

    |–|--|

    | arg() | 限定連接配接點方法參數 |

項目類型 描述
arg() 限定連接配接點方法參數
@arg() 通過連接配接點方法參數上的注解進行限定
execution() 用于比對連接配接點的執行方法
this() 限制連接配接點比對aop代理bean引用為指定的類型
target 目标對象
@target 限制目标對象的配置了指定的注解
within 限制連接配接點比對指定的類型
@within 限制連接配接點帶有比對注解類型
@annotation 限定帶有指定注解的連接配接點

測試aop

@Controller
@RequestMapping("/user")
public class UserController {
    @Autowired
    UserService userService;
    @RequestMapping("/print")
    @ResponseBody
    public User print(String id,String name,String note){
        User user = new User();
        user.setId(id);
        user.setName(name);
        user.setNote(note);
        userService.printUser(user);
        return user;
    }
}
           

通過@Autowired注入UserService 服務接口,然後使用它進行使用者資訊列印這裡的UserService 實作類滿足了切點的定義,是以spring aop會将其織入對應的流程中。然後配置啟動檔案使其運作

@SpringBootApplication
@ComponentScan
public class ChapterApplication {
    //定義切面
    @Bean(name="myAspect")
    public MyAspect initMyAspect(){
        return new MyAspect();
    }
    //啟動程式
    public static void main(String[] args) {
        SpringApplication.run(ChapterApplication.class,args);
        System.out.println("啟動成功");
    }
}
           

服務啟動完成後,打開浏覽器位址欄中輸入請求位址:http://localhost:8080/user/print?id=1&name=36&note=3333

輸出結果:

深入淺出springboot2.x(7)spring AOP開發

UserService 對象實際上是一個JDK動态代理對象,它代理了目标對象UserServiceImpl,通過這些spring會将我們定義的内容織入aop的流程中。

環繞通知

環繞通知是所有通知中最為強大的通知,強大也意味着難以控制。使用它的場景是在你需要大幅度修改原有目标對象的服務邏輯時,否則都盡量使用其他通知。環繞通知是一個取代原有目标對象方法的通知,當然它也提供了回調原有目标對象的能力。

我們在MyAspect中加入環繞通知

@Around("print()")
    public void around(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("around before....");
        pjp.proceed();
        System.out.println("around after....");
    }
           

啟動服務後測試結果:

深入淺出springboot2.x(7)spring AOP開發

繼續閱讀