AOP常用的實(shí)現(xiàn)方式有兩種,一種是采用聲明的方式來實(shí)現(xiàn)(基于XML),一種是采用注解的方式來實(shí)現(xiàn)(基于AspectJ)。 首先復(fù)習(xí)下AOP中一些比較重要的概念: Joinpoint(連接點(diǎn)):程序執(zhí)行時(shí)的某個(gè)特定的點(diǎn),在Spring中就是某一個(gè)方法的執(zhí)行 。 一、基于XML配置的Spring AOP 采用聲明的方式實(shí)現(xiàn)(在XML文件中配置),大致步驟為:配置文件中配置pointcut, 在java中用編寫實(shí)際的aspect 類, 針對(duì)對(duì)切入點(diǎn)進(jìn)行相關(guān)的業(yè)務(wù)處理。 業(yè)務(wù)接口: package com.springboottime.time.service; public interface AdviceService { /*查找用戶*/ public String findUser(); /*添加用戶*/ public void addUser(); } 業(yè)務(wù)實(shí)現(xiàn): package com.springboottime.time.service.serviceImpl; import com.springboottime.time.service.AdviceService; import lombok.Data; @Data public class AdviceServiceImpl implements AdviceService { private String name; @Override public String findUser() { System.out.println("***************執(zhí)行業(yè)務(wù)方法findUser,查找的用戶名字為:"+name+"****************"); return name; } @Override public void addUser() { System.out.println("***************執(zhí)行業(yè)務(wù)方法addUser****************"); throw new RuntimeException(); } } 切面類: package com.springboottime.time.aop; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; public class AopAspect { /** * 前置通知:目標(biāo)方法調(diào)用之前執(zhí)行的代碼 * @param jp */ public void doBefore(JoinPoint jp){ System.out.println("===========執(zhí)行前置通知============"); } /** * 后置返回通知:目標(biāo)方法正常結(jié)束后執(zhí)行的代碼 * 返回通知是可以訪問到目標(biāo)方法的返回值的 * @param jp * @param result */ public void doAfterReturning(JoinPoint jp,String result){ System.out.println("===========執(zhí)行后置通知============"); System.out.println("返回值result==================="+result); } /** * 最終通知:目標(biāo)方法調(diào)用之后執(zhí)行的代碼(無論目標(biāo)方法是否出現(xiàn)異常均執(zhí)行) * 因?yàn)榉椒赡軙?huì)出現(xiàn)異常,所以不能返回方法的返回值 * @param jp */ public void doAfter(JoinPoint jp){ System.out.println("===========執(zhí)行最終通知============"); } /** * * 異常通知:目標(biāo)方法拋出異常時(shí)執(zhí)行的代碼 * 在目標(biāo)方法執(zhí)行的時(shí)候,如果拋出異常,立即進(jìn)入此方法 * 可以訪問到異常對(duì)象 * @param jp * @param ex */ public void doAfterThrowing(JoinPoint jp,Exception ex){ System.out.println("===========執(zhí)行異常通知============"); } /** * 環(huán)繞通知:目標(biāo)方法調(diào)用前后執(zhí)行的代碼,可以在方法調(diào)用前后完成自定義的行為。 * 包圍一個(gè)連接點(diǎn)(join point)的通知。它會(huì)在切入點(diǎn)方法執(zhí)行前執(zhí)行同時(shí)方法結(jié)束也會(huì)執(zhí)行對(duì)應(yīng)的部分。 * 主要是調(diào)用proceed()方法來執(zhí)行切入點(diǎn)方法,來作為環(huán)繞通知前后方法的分水嶺。 * * 環(huán)繞通知類似于動(dòng)態(tài)代理的全過程:ProceedingJoinPoint類型的參數(shù)可以決定是否執(zhí)行目標(biāo)方法。 * 而且環(huán)繞通知必須有返回值,返回值即為目標(biāo)方法的返回值 * @param pjp * @return * @throws Throwable */ public Object doAround(ProceedingJoinPoint pjp) throws Throwable{ System.out.println("======執(zhí)行環(huán)繞通知開始========="); // 調(diào)用方法的參數(shù) Object[] args = pjp.getArgs(); // 調(diào)用的方法名 String method = pjp.getSignature().getName(); // 獲取目標(biāo)對(duì)象 Object target = pjp.getTarget(); // 執(zhí)行完方法的返回值 // 調(diào)用proceed()方法,就會(huì)觸發(fā)切入點(diǎn)方法執(zhí)行 Object result=pjp.proceed(); //如果調(diào)用pjp.proceed()執(zhí)行業(yè)務(wù)方法的時(shí)候拋出異常,那么下面的代碼將不會(huì)執(zhí)行 System.out.println("輸出,方法名:" + method + ";目標(biāo)對(duì)象:" + target + ";返回值:" + result); System.out.println("======執(zhí)行環(huán)繞通知結(jié)束========="); return result; } } Spring的AOP配置:spring-aop.xml <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www./schema/beans" xmlns:xsi="http://www./2001/XMLSchema-instance" xmlns:aop="http://www./schema/aop" xsi:schemaLocation="http://www./schema/beans http://www./schema/beans/spring-beans-3.0.xsd http://www./schema/aop http://www./schema/aop/spring-aop-3.0.xsd"> <!-- 聲明一個(gè)業(yè)務(wù)類 --> <bean id="userManager" class="com.springboottime.time.service.serviceImpl.AdviceServiceImpl"> <property name="name" value="lixiaoxi"></property> </bean> <!-- 聲明通知類 --> <bean id="aspectBean" class="com.springboottime.time.aop.AopAspect" /> <aop:config> <aop:aspect ref="aspectBean"> <aop:pointcut id="pointcut" expression="execution(* com.springboottime.time.service.serviceImpl.AdviceServiceImpl..*(..))"/> <aop:before method="doBefore" pointcut-ref="pointcut"/> <aop:after-returning method="doAfterReturning" pointcut-ref="pointcut" returning="result"/> <aop:after method="doAfter" pointcut-ref="pointcut" /> <aop:around method="doAround" pointcut-ref="pointcut"/> <aop:after-throwing method="doAfterThrowing" pointcut-ref="pointcut" throwing="ex"/> </aop:aspect> </aop:config> </beans> springboot啟動(dòng)類設(shè)置:注意要引入xml文件 package com.springboottime.time; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.ImportResource; @ImportResource("classpath:spring-aop.xml") //@ImportResource("classpath:spring-aop-aspectJ.xml") @SpringBootApplication public class TimeApplication { public static void main(String[] args) { SpringApplication.run(TimeApplication.class, args); } } 測(cè)試類: package com.springboottime.time; import com.springboottime.time.service.AdviceService; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; @RunWith(SpringRunner.class) @SpringBootTest public class TimeApplicationTests { @Autowired private AdviceService adviceService; @Test public void contextLoads() { } @Test public void test(){ String user = adviceService.findUser(); System.out.println("<><><><><><><><><><><><><>"); adviceService.addUser(); } } 測(cè)試結(jié)果: ===========執(zhí)行前置通知============ ======執(zhí)行環(huán)繞通知開始========= ***************執(zhí)行業(yè)務(wù)方法findUser,查找的用戶名字為:lixiaoxi**************** 輸出,方法名:findUser;目標(biāo)對(duì)象:AdviceServiceImpl(name=lixiaoxi);返回值:lixiaoxi ======執(zhí)行環(huán)繞通知結(jié)束========= ===========執(zhí)行最終通知============ ===========執(zhí)行后置通知============ 返回值result===================lixiaoxi <><><><><><><><><><><><><> ===========執(zhí)行前置通知============ ======執(zhí)行環(huán)繞通知開始========= ***************執(zhí)行業(yè)務(wù)方法addUser**************** ===========執(zhí)行異常通知============ ===========執(zhí)行最終通知============ java.lang.RuntimeException at com.springboottime.time.service.serviceImpl.AdviceServiceImpl.addUser(AdviceServiceImpl.java:20) 二、使用注解配置AOP 采用注解來做aop, 主要是將寫在spring 配置文件中的連接點(diǎn)寫到注解里面。 業(yè)務(wù)接口和業(yè)務(wù)實(shí)現(xiàn)與上邊一樣,具體切面類如下: package com.springboottime.time.aop; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.*; @Aspect public class AopAspectJ { /** * 必須為final String類型的,注解里要使用的變量只能是靜態(tài)常量類型的 */ public static final String EDP="execution(* com.springboottime.time.service.serviceImpl.AdviceServiceImpl..*(..))"; /** * 切面的前置方法 即方法執(zhí)行前攔截到的方法 * 在目標(biāo)方法執(zhí)行之前的通知 * @param jp */ @Before(EDP) public void doBefore(JoinPoint jp){ System.out.println("=========AopAspectJ執(zhí)行前置通知=========="); } /** * 在方法正常執(zhí)行通過之后執(zhí)行的通知叫做返回通知 * 可以返回到方法的返回值 在注解后加入returning * @param jp * @param result */ @AfterReturning(value=EDP,returning="result") public void doAfterReturning(JoinPoint jp,String result){ System.out.println("===========AopAspectJ執(zhí)行后置通知============"); } /** * 最終通知:目標(biāo)方法調(diào)用之后執(zhí)行的通知(無論目標(biāo)方法是否出現(xiàn)異常均執(zhí)行) * @param jp */ @After(value=EDP) public void doAfter(JoinPoint jp){ System.out.println("===========AopAspectJ執(zhí)行最終通知============"); } /** * 在目標(biāo)方法非正常執(zhí)行完成, 拋出異常的時(shí)候會(huì)走此方法 * @param jp * @param ex */ @AfterThrowing(value=EDP,throwing="ex") public void doAfterThrowing(JoinPoint jp,Exception ex) { System.out.println("===========AopAspectJ執(zhí)行異常通知============"); } /** * 環(huán)繞通知:目標(biāo)方法調(diào)用前后執(zhí)行的通知,可以在方法調(diào)用前后完成自定義的行為。 * @param pjp * @return * @throws Throwable */ @Around(EDP) public Object doAround(ProceedingJoinPoint pjp) throws Throwable{ System.out.println("======AopAspectJ執(zhí)行環(huán)繞通知開始========="); // 調(diào)用方法的參數(shù) Object[] args = pjp.getArgs(); // 調(diào)用的方法名 String method = pjp.getSignature().getName(); // 獲取目標(biāo)對(duì)象 Object target = pjp.getTarget(); // 執(zhí)行完方法的返回值 // 調(diào)用proceed()方法,就會(huì)觸發(fā)切入點(diǎn)方法執(zhí)行 Object result=pjp.proceed(); System.out.println("輸出,方法名:" + method + ";目標(biāo)對(duì)象:" + target + ";返回值:" + result); System.out.println("======AopAspectJ執(zhí)行環(huán)繞通知結(jié)束========="); return result; } } spring的配置:spring-aop-aspectJ.xml <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www./schema/beans" xmlns:xsi="http://www./2001/XMLSchema-instance" xmlns:aop="http://www./schema/aop" xsi:schemaLocation="http://www./schema/beans http://www./schema/beans/spring-beans-3.0.xsd http://www./schema/aop http://www./schema/aop/spring-aop-3.0.xsd"> <!-- 聲明spring對(duì)@AspectJ的支持 --> <aop:aspectj-autoproxy/> <!-- 聲明一個(gè)業(yè)務(wù)類 --> <bean id="userManager" class="com.springboottime.time.service.serviceImpl.AdviceServiceImpl"> <property name="name" value="lixiaoxi"></property> </bean> <!-- 聲明通知類 --> <bean id="aspectBean" class="com.springboottime.time.aop.AopAspectJ" /> </beans> springboot啟動(dòng)類配置: package com.springboottime.time; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.ImportResource; //@ImportResource("classpath:spring-aop.xml") @ImportResource("classpath:spring-aop-aspectJ.xml") @SpringBootApplication public class TimeApplication { public static void main(String[] args) { SpringApplication.run(TimeApplication.class, args); } } 測(cè)試結(jié)果: ======AopAspectJ執(zhí)行環(huán)繞通知開始========= =========AopAspectJ執(zhí)行前置通知========== ***************執(zhí)行業(yè)務(wù)方法findUser,查找的用戶名字為:lixiaoxi**************** 輸出,方法名:findUser;目標(biāo)對(duì)象:AdviceServiceImpl(name=lixiaoxi);返回值:lixiaoxi ======AopAspectJ執(zhí)行環(huán)繞通知結(jié)束========= ===========AopAspectJ執(zhí)行最終通知============ ===========AopAspectJ執(zhí)行后置通知============ <><><><><><><><><><><><><> ======AopAspectJ執(zhí)行環(huán)繞通知開始========= =========AopAspectJ執(zhí)行前置通知========== ***************執(zhí)行業(yè)務(wù)方法addUser**************** ===========AopAspectJ執(zhí)行最終通知============ ===========AopAspectJ執(zhí)行異常通知============ java.lang.RuntimeException at com.springboottime.time.service.serviceImpl.AdviceServiceImpl.addUser(AdviceServiceImpl.java:20) 入門級(jí)例子。 1、猴子偷桃類(普通類): package com.samter.common; /** * 猴子 * @author Administrator * */ public class Monkey { public void stealPeaches(String name){ System.out.println("【猴子】"+name+"正在偷桃..."); } } 2、守護(hù)者類(聲明為Aspect): package com.samter.aspect; import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; /** * 桃園守護(hù)者 * @author Administrator * */ @Aspect public class Guardian { /** * 定義一個(gè)方法,用于聲明切入點(diǎn)表達(dá)式。一般的,該方法中再不需要添加其他的代碼 * 使用@Pointcut 來聲明切入點(diǎn)表達(dá)式 * 后面的其他通知直接使用方法名直接引用方法名即可 */ @Pointcut("execution(* com.samter.common.Monkey.stealPeaches(..))") public void foundMonkey(){} @Before(value="foundMonkey()") public void foundBefore(){ System.out.println("【守護(hù)者】發(fā)現(xiàn)猴子正在進(jìn)入果園..."); } @AfterReturning("foundMonkey() && args(name,..)") public void foundAfter(String name){ System.out.println("【守護(hù)者】抓住了猴子,守護(hù)者審問出了猴子的名字叫“"+name+"”..."); } } 3、XML配置文件: <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www./schema/beans" xmlns:xsi="http://www./2001/XMLSchema-instance" xmlns:aop="http://www./schema/aop" xsi:schemaLocation=" http://www./schema/beans http://www./schema/beans/spring-beans-2.0.xsd http://www./schema/aop http://www./schema/aop/spring-aop-2.0.xsd" > <!-- 定義Aspect --> <bean id="guardian" class="com.samter.aspect.Guardian" /> <!-- 定義Common --> <bean id="monkey" class="com.samter.common.Monkey" /> <!-- 啟動(dòng)AspectJ支持 --> <aop:aspectj-autoproxy /> </beans> 4、測(cè)試類: package com.samter.common; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class Main { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("config.xml"); Monkey monkey = (Monkey) context.getBean("monkey"); try { monkey.stealPeaches("孫大圣的大徒弟"); } catch(Exception e) {} } } 5、控制臺(tái)輸出: 【守護(hù)者】發(fā)現(xiàn)猴子正在進(jìn)入果園... 【猴子】孫大圣的大徒弟正在偷桃... 【守護(hù)者】抓住了猴子,守護(hù)者審問出了猴子的名字叫“孫大圣的大徒弟”... 解說: 1寫了一個(gè)猴子正在偷桃的方法。 A、@Aspect的聲明表示這是一個(gè)切面類。 |
|