AOP基本概念
面向切面編程 [官網(wǎng)介紹]: https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/core.html#aop
白話(huà)文:增強(qiáng)/加強(qiáng)一系列的類(lèi)/方法
定義幾個(gè)簡(jiǎn)單的接口
public interface ServiceForKTV { void kty(); }
public interface ServiceForSPA { void spa(); }
具體實(shí)現(xiàn)邏輯可以自己寫(xiě)
定義代理邏輯
public interface Advice { //定義一個(gè)方法 //用戶(hù)提供增加邏輯 /** * * @param target * @param method * @param args * @return */ public Object invoke(Object target, Method method,Object[] args) throws Exception; }
public class TimeCsAdvice implements Advice { @Override public Object invoke(Object target, Method method, Object[] args) throws Exception { long startTime = System.currentTimeMillis(); Object ret = method.invoke(target,args); long endTime = System.currentTimeMillis(); System.out.println("類(lèi)名【"+target.getClass().getName()+"】"); System.out.println("方法名【"+method.getName()+"】"); System.out.println("耗時(shí)【"+(endTime - startTime)+"】"); return null; } }
這里是統(tǒng)計(jì)接口耗時(shí)和打印一些參數(shù)
到這里的話(huà),基本上用戶(hù)的在邏輯都寫(xiě)完了,接下來(lái)就要完成一些框架需要干的事了
定義切入點(diǎn)和切入方法
@Data public class Pointcut { private String classPattern; private String methodPattern; }
java是能識(shí)別正則表達(dá)式的,這里我們用正則撒汉。
定義一個(gè)類(lèi)來(lái)封裝切點(diǎn)和Advice
public class Aspect { private Advice advice; private Pointcut pointcut; }
定義AOP核心處理類(lèi)InvocationHandler
public class AopInvocationHandler implements InvocationHandler { private Aspect aspect; private Object bean; public AopInvocationHandler(Aspect aspect, Object bean) { this.aspect = aspect; this.bean = bean; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (bean.getClass().getName().matches(aspect.getPointcut().getMethodPattern())){ return aspect.getAdvice().invoke(bean,method,args); } return method.invoke(bean,args); } }
增強(qiáng)需要被代理的類(lèi)
構(gòu)造簡(jiǎn)易的Spring上下文對(duì)象
public interface IApplicationContext { Object getBean(String beanName) throws Exception; void registerBeanDefinition(String beanName,Class<?> beanClass) throws Exception; void setAspect(Aspect aspect); }
public class MyApplicationContext implements IApplicationContext { private Map<String,Class<?>> beanMap = new ConcurrentHashMap<>(); private Aspect aspect; @Override public Object getBean(String beanName) throws Exception { Object bean = createInstance(beanName); //返回一個(gè)增加的方法 bean = proxyEnhance(bean); return bean; } private Object proxyEnhance(Object bean) { if (aspect != null && (bean.getClass().getName()).matches(aspect.getPointcut().getClassPattern()) ){ return Proxy.newProxyInstance(bean.getClass().getClassLoader(), bean.getClass().getInterfaces(), new AopInvocationHandler(aspect,bean)); } return bean; } private Object createInstance(String beanName) throws Exception { return beanMap.get(beanName).newInstance(); } @Override public void registerBeanDefinition(String beanName, Class<?> beanClass) throws Exception{ this.beanMap.put(beanName,beanClass); } @Override public void setAspect(Aspect aspect) { this.aspect = aspect; } }
解釋&說(shuō)明
beanMap 模擬Spring容器,存放實(shí)例化的Bean對(duì)象
getBean(String beanName) 獲取Bean實(shí)例涕滋。如果對(duì)象都被增強(qiáng)睬辐,則返回增強(qiáng)過(guò)后的類(lèi),在實(shí)例化出來(lái)的時(shí)候,已經(jīng)是被增強(qiáng)過(guò)的溯饵,可以參考Spring 提供的源碼侵俗。
proxyEnhance(Object bean) 簡(jiǎn)易的增強(qiáng)邏輯,通過(guò)正則匹配丰刊,是否需要被增強(qiáng)隘谣。這里有使用到動(dòng)態(tài)代理。java給我們提供了兩種便捷的API啄巧,分別是JDK提供的動(dòng)態(tài)代理和CGLIB提供的寻歧。
registerBeanDefinition(String beanName, Class<?> beanClass) 簡(jiǎn)易的加入邏輯,將Bean放入到Spring容器中棵帽∠ㄇ螅可以通過(guò)XML或是注解的形式將Bean放入到Spring中渣玲。這里簡(jiǎn)化了IOC處理的流程逗概,具體IOC相關(guān)的知識(shí),可以參考
- createInstance(String beanName) Bean 的實(shí)例化
到這里的話(huà)忘衍,已經(jīng)是大功告成逾苫。
功成身退
public class AopMain { public static void main(String[] args) throws Exception{ Advice advice = new TimeCsAdvice(); Pointcut pointcut = new Pointcut(); pointcut.setClassPattern("com\\.lynn\\.pay\\.imooc\\.demo\\.aop\\.service\\..*"); pointcut.setMethodPattern(".*impl"); Aspect aspect = new Aspect(); aspect.setAdvice(advice); aspect.setPointcut(pointcut); //創(chuàng)建容器 IApplicationContext applicationContext = new MyApplicationContext(); applicationContext.setAspect(aspect); applicationContext.registerBeanDefinition("ktv", KTVimpl.class); applicationContext.registerBeanDefinition("spa", SPAimpl.class); ServiceForSPA spa = (ServiceForSPA)applicationContext.getBean("spa"); ServiceForKTV ktv = (ServiceForKTV)applicationContext.getBean("ktv"); spa.spa(); ktv.kty(); } }
運(yùn)行結(jié)果如下圖,只對(duì)KTV服務(wù)進(jìn)行了匹配處理枚钓。
image.png
總結(jié)
Sping已經(jīng)為我們封裝好了AOP铅搓,我們只需要定義好以下幾個(gè)點(diǎn)就可以使用了
- Pointcut(切入點(diǎn))
- Advice (通知)
- invoke(增強(qiáng)邏輯)
當(dāng)然,在實(shí)際生產(chǎn)中也有許多作用搀捷,比如參數(shù)驗(yàn)簽星掰,TOKEN校驗(yàn),統(tǒng)一的脫敏處理嫩舟,重復(fù)請(qǐng)求處理等氢烘。方便我們用的同時(shí),不會(huì)入侵到原有代碼家厌,非常高效播玖。
這里是參考網(wǎng)易云免費(fèi)直播課Tony老師的課學(xué)習(xí)的。