aop原理是spring幫我們封裝了動態代理,然后我們只管寫具體的業務,我們將公共業務也寫到具體的一個類中並實現spring為我們提供的對應要連接切入哪個位置的接口,然后再xml中配置它們的關系即可。
優點:和動態代理一樣,具體實現只管具體實現使的代碼更加純粹,公共業務只需實現自己對應的接口,然后編碼即可,有了動態代理的好處,又沒有手寫動態代理那么復雜,這就是aop(被分裝后的動態代理)。
一:實現spring接口的方式
1.UserService接口:
package mr.li.service; public interface UserService { void add(); void remove(); }
2.UserServiceImpl實現類:
package mr.li.service.impl; import mr.li.service.UserService; public class UserServiceImpl implements UserService{ @Override public void add() { System.out.println("添加一條數據"); } @Override public void remove() { System.out.println("刪除一條數據"); } }
3.前置通知:Log日志打印
package mr.li.log; import java.lang.reflect.Method; import org.springframework.aop.MethodBeforeAdvice; /** * 前置切面:日志打印 * @author yanLong.Li * @date 2019年3月16日 下午10:33:22 */ public class Log implements MethodBeforeAdvice{ @Override public void before(Method method, Object[] arg1, Object target) throws Throwable { System.out.println("aop前置:"+target.getClass().getName()+"類的"+ method + "方法執行了~~"); } }
4后置通知:AfterLog 日志打印
package mr.li.log; import java.lang.reflect.Method; import org.springframework.aop.AfterReturningAdvice; /** * 后置切面:日志打印 * @author yanLong.Li * @date 2019年3月16日 下午10:33:44 */ public class AfterLog implements AfterReturningAdvice{ @Override public void afterReturning(Object result, Method method, Object[] arg2, Object target) throws Throwable { System.out.println("aop后置:"+target.getClass().getName()+"類的"+ method + "方法執行了,返回結果為"+result); } }
5.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.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <bean id="userService" class="mr.li.service.impl.UserServiceImpl"/> <bean id = "log" class="mr.li.log.Log"/> <bean id = "afterLog" class="mr.li.log.AfterLog"/> <aop:config> <!-- 配置被切入的哪個類使用 execution表達式第一個:“*”表示返回值 第二個*表示所有的 ..標識所有的參數 --> <aop:pointcut expression="execution(* mr.li.service.impl.UserServiceImpl.*(..))" id="aoop"/> <!-- 設置要切入的類:--> <aop:advisor advice-ref="log" pointcut-ref="aoop"/> <aop:advisor advice-ref="afterLog" pointcut-ref="aoop"/> </aop:config> </beans>
6.測試類
package mr.li.client; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import mr.li.service.UserService; public class Client { public static void main(String[] args) { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml"); UserService userService = (UserService) applicationContext.getBean("userService"); userService.remove(); userService.add(); } }
打印結果:
aop前置:mr.li.service.impl.UserServiceImpl類的public abstract void mr.li.service.UserService.remove()方法執行了~~
刪除一條數據
aop后置:mr.li.service.impl.UserServiceImpl類的public abstract void mr.li.service.UserService.remove()方法執行了,返回結果為null
aop前置:mr.li.service.impl.UserServiceImpl類的public abstract void mr.li.service.UserService.add()方法執行了~~
添加一條數據
aop后置:mr.li.service.impl.UserServiceImpl類的public abstract void mr.li.service.UserService.add()方法執行了,返回結果為null
二:自定義aop,切面不在需要實現接口,自己寫即可,只是調整下配置
1.UserService:和上面一樣
2.UserServiceImpl:和上面一樣
3.Log切面
package mr.li.log; /** * 前置切面:日志打印 * @author yanLong.Li * @date 2019年3月16日 下午10:33:22 */ public class Log { public void before() { System.out.println("前置通知"); } public void after() { System.out.println("后置通知"); } }
4.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.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <bean id="userService" class="mr.li.service.impl.UserServiceImpl"/> <bean id = "log" class="mr.li.log.Log"/> <aop:config> <aop:aspect ref="log"> <!-- 配置被切入的哪個類使用 execution表達式 *表示所有的 ..標識所有的參數 --> <aop:pointcut expression="execution(* mr.li.service.impl.UserServiceImpl.*(..))" id="pointcut"/> <!-- 配置前置通知 --> <aop:before method="before" pointcut-ref="pointcut"/> <!-- 配置后置通知 --> <aop:after method="after" pointcut-ref="pointcut"/> </aop:aspect> </aop:config> </beans>
5.測試
package mr.li.client; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import mr.li.service.UserService; public class Client { public static void main(String[] args) { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml"); UserService userService = (UserService) applicationContext.getBean("userService"); userService.remove(); userService.add(); } }
打印結果:
前置通知
刪除一條數據
后置通知
前置通知
添加一條數據
后置通知
三:注解的方式:注意下環繞通知,參數說明,以及使用(proceedingJoinpoint在下面有額外解釋)
1.UserService:和最上面一樣
2.UserServiceImpl:和最上面一樣
3.切面:Log
package mr.li.log; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; /** * 切面:日志打印 * @author yanLong.Li * @date 2019年3月16日 下午10:33:22 */ @Aspect public class Log { /** * 前置通知:execution表示切入點 */ @Before("execution(* mr.li.service.impl.UserServiceImpl.*(..))") public void before() { System.out.println("注解:前置通知"); } /** * 后置通知 */ @After("execution(* mr.li.service.impl.UserServiceImpl.*(..))") public void after() { System.out.println("注解:后置通知"); } /** * 環繞通知 * @param jp 此參數是spring給我們的,它里面可以干很多事,包括繼續執行被切面的方法,拿到方法簽名,他會在被切面的方法之前運作 * @throws Throwable */ @Around("execution(* mr.li.service.impl.UserServiceImpl.*(..))") public Object aroud(ProceedingJoinPoint jp) throws Throwable { System.out.println("環繞前"); System.out.println("方法簽名:"+jp.getSignature()); //執行此方法 Object result = jp.proceed(); System.out.println("環繞后"); return result; } }
4.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.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <bean id="userService" class="mr.li.service.impl.UserServiceImpl"/> <bean id = "log" class="mr.li.log.Log"/> <!-- 此配置會自動去找aop配置 --> <aop:aspectj-autoproxy/> </beans>
5.測試:
package mr.li.client; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import mr.li.service.UserService; public class Client { public static void main(String[] args) { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml"); UserService userService = (UserService) applicationContext.getBean("userService"); userService.remove(); userService.add(); } }
打印結果:
注解:前置通知
刪除一條數據
環繞后
注解:后置通知
環繞前
方法簽名:void mr.li.service.UserService.add()
注解:前置通知
添加一條數據
環繞后
注解:后置通知
額外對ProceedingJoinPoint參數的描述
package com.qty.arena.util; import java.util.Set; import java.util.concurrent.ConcurrentSkipListSet; 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; import com.qty.arena.helper.MessagePushHelper; import com.qty.database.dto.arena.match.SyncAll; /** * 在被@NotDuplicate注解所標注的方法上,過濾請求者重復請求的消息 * @author yanLong.Li * @date 2019年2月11日 上午11:11:01 */ @Aspect @Component public class NotDuplicateAop { // private static final Set<String> KEY = new ConcurrentSkipListSet<>(); private static final Set<Long> KEY = new ConcurrentSkipListSet<>(); @Pointcut("@annotation(com.qty.arena.util.NotDuplicate)") public void duplicate() {} /** * 對方法攔截后進行參數驗證 * @param pjp * @return * @throws Throwable */ @Around("duplicate()") public Object duplicate(ProceedingJoinPoint pjp) throws Throwable{ Object[] objs = pjp.getArgs(); Long dbId = null; if(objs[0] instanceof Long) { dbId = (Long)objs[0]; } if(dbId == null) { throw new NumberFormatException("在解析請求的dbId時發生異常,數字不能解析,值:"+objs[0]); } boolean success = KEY.add(dbId); if(!success){ MessagePushHelper.getInstance().pushSingleMessage(dbId, SyncAll.valueOf("頻繁操作,請稍后再試")); return null; } try { //方法執行前 return pjp.proceed(); } finally { //方法執行后 KEY.remove(dbId); } }
