[TOC]
AOP簡(jiǎn)介
理解
AOP(Aspect-Oriented Programming), 即 面向切面編程,其基本思想是在極少影響原程序的代碼的前提下,在程序中的某些地方,使用某些方式,不可見(jiàn)的(即不在原程序中添加其他代碼)為原程序切入一些額外的功能。
優(yōu)點(diǎn):
- 減少代碼間的耦合性,使功能具有拔插性,保證自己代碼的清潔性。
- 能夠讓你只關(guān)注自己的代碼,不需要關(guān)注切面是如何實(shí)現(xiàn)的。
術(shù)語(yǔ)
通知(advice)
其定義了切點(diǎn)什么時(shí)候去增強(qiáng),是在方法調(diào)用前,還是調(diào)用之后,還是前后都是,還是拋出異常時(shí)。
- Before 某方法調(diào)用之前發(fā)出通知。
- After 某方法完成之后發(fā)出通知,不考慮方法運(yùn)行的結(jié)果。
- After-returning 將通知放置在被通知的方法成功執(zhí)行之后。
- After-throwing 將通知放置在被通知的方法拋出異常之后。
- Around 通知包裹在被通知的方法的周?chē)诜椒ㄕ{(diào)用之前和之后發(fā)出通知
連接點(diǎn)(join point)
可以被作為切點(diǎn)的地方,都可以被認(rèn)為是鏈接點(diǎn)。
切點(diǎn)(point cut)
按照規(guī)則被選中的鏈接點(diǎn),可以被稱(chēng)作為切點(diǎn)。
Aspect(切面)
aspect 由 pointcount 和 advice 組成, 它既包含了橫切邏輯的定義, 也包括了連接點(diǎn)的定義. Spring AOP就是負(fù)責(zé)實(shí)施切面的框架, 它將切面所定義的橫切邏輯織入到切面所指定的連接點(diǎn)中.
目標(biāo)對(duì)象(Target)
織入 advice 后的目標(biāo)對(duì)象. 目標(biāo)對(duì)象也被稱(chēng)為 advised object.
引入(introductions):
- 引入允許你添加一個(gè)新的方法給已經(jīng)存在的類(lèi)。
Spring對(duì)AOP的支持
- Spring建議在Java中書(shū)寫(xiě)AOP
- Spring是在運(yùn)行階段才將切面編織進(jìn)bean中,是使用代理類(lèi)。
- Spring只支持方法級(jí)別的連接點(diǎn)。
AOP應(yīng)用
XML形式的AOP
proxy-target-class="true"指定使用GCLIB代理,如果proxy-target-class="false"或者沒(méi)設(shè)置,則默認(rèn)使用動(dòng)態(tài)代理,但是如果代理類(lèi)沒(méi)有實(shí)現(xiàn)接口,則依然會(huì)使用GCLIB代理。
aop:pointcut指定了切點(diǎn)。
aop:advisor指定了通知時(shí)機(jī),同樣的還有aop:before aop:after。
需要注意的是spiritCommonInterceptor實(shí)現(xiàn)了MethodInterceptor接口
<bean id="spiritCommonInterceptor" class="com.mogujie.stable.spirit.point.methond.CommonInterceptor"/>
<aop:config proxy-target-class="true">
<aop:pointcut id="modulePoint" expression="@target(com.mogujie.stable.spirit.point.annotation.ClassSpirit) and @annotation(com.mogujie.stable.spirit.point.annotation.MethodSpirit)"/>
<aop:advisor advice-ref="spiritCommonInterceptor" pointcut-ref="modulePoint"/>
</aop:config>
public class CommonInterceptor implements MethodInterceptor {
private static final Logger LOG = LoggerFactory.getLogger(CommonInterceptor.class);
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
Method executed = invocation.getMethod();
Class<?> clazz = invocation.getThis().getClass();
ClassSpirit classSpirit = clazz.getAnnotation(ClassSpirit.class);
MethodSpirit methodSpirit = executed.getAnnotation(MethodSpirit.class);
// 不做限流降級(jí)處理
if (executed.getName().equals("toString") || executed.getName().equals("hashCode") || executed.getName().equals("equals") || (null != classSpirit && !classSpirit.trace()) || (null == classSpirit && null != methodSpirit && !methodSpirit.trace()) || ((null != classSpirit && classSpirit.trace()) && (null == methodSpirit || !methodSpirit.trace()))) {
return invocation.proceed();
}
Entry entry = null;
try {
String methodName = MethodUtil.getMethodName(executed);
// 初始化Context
ContextUtil.enter(methodName);
// 初始化Entry
entry = EntryUtil.entry(executed);
// 執(zhí)行方法
Object result = invocation.proceed();
return result;
} catch (Throwable e) {
throw ExceptionUtil.dealProxyException(e);
} finally {
if (entry != null) {
entry.exit();
}
ContextUtil.exit();
}
}
}
Annotation形式的AOP
Spring除了支持Schema方式配置AOP,還支持注解方式:使用@Aspect來(lái)配置。但Spring默認(rèn)不支持@Aspect風(fēng)格的切面聲明,通過(guò)如下配置開(kāi)啟@Aspect支持:
<aop:aspectj-autoproxy/>
package com.sxit;
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;
@Aspect
public class AspectStyle {
@Pointcut("execution(* com.sxit..*.*(..))")
public void init(){
}
@Before(value="init()")
public void before(){
System.out.println("方法執(zhí)行前執(zhí)行.....");
}
@AfterReturning(value="init()")
public void afterReturning(){
System.out.println("方法執(zhí)行完執(zhí)行.....");
}
@AfterThrowing(value="init()")
public void throwss(){
System.out.println("方法異常時(shí)執(zhí)行.....");
}
@After(value="init()")
public void after(){
System.out.println("方法最后執(zhí)行.....");
}
@Around(value="init()")
public Object around(ProceedingJoinPoint pjp){
System.out.println("方法環(huán)繞start.....");
Object o = null;
try {
o = pjp.proceed();
} catch (Throwable e) {
e.printStackTrace();
}
System.out.println("方法環(huán)繞end.....");
return o;
}
}
一個(gè)Annotation與AOP結(jié)合的例子
要實(shí)現(xiàn)一個(gè)aop的功能,關(guān)鍵在于三個(gè)地方。
- 通知(Advice) 定義了
何時(shí)切。比如:before、around等。 - 切點(diǎn)(PointCut) 定義了
何處切。比如:execution(* com.mogujie.houston.openapi.api.impl..*(..)) - 連接點(diǎn)(JoinPoint) 連接點(diǎn)是在應(yīng)用執(zhí)行過(guò)程中能夠插入切面的一個(gè)點(diǎn)。能夠利用它拿到應(yīng)用的方法和參數(shù)等。
aspect
@Aspect
@Component
public class ValidatorAspect implements ApplicationContextAware {
private static Logger logger = LoggerFactory.getLogger(ValidatorAspect.class);
protected static ApplicationContext context;
@Around("execution(* com.mogujie.houston.openapi.api.impl..*(..))")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
try {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
if (method.getDeclaringClass().isInterface()) {
try {
method = joinPoint.getTarget().getClass().getDeclaredMethod(joinPoint.getSignature().getName(),
method.getParameterTypes());
} catch (final SecurityException exception) {
}
}
// check passport
Object[] args = joinPoint.getArgs();
Validator validatorClass = method.getAnnotation(Validator.class);
if (null != validatorClass) {
ValidationHandler validationHandler = validatorClass.handler().newInstance();
HoustonOpenApiResult result = validationHandler.validate(args);
if (!result.isSuccess()) {
return result;
}
}
TokenValidator tokenValidatorClass = method.getAnnotation(TokenValidator.class);
if (tokenValidatorClass != null) {
TokenValidationHandler tokenValidationHandler = tokenValidatorClass.handler().newInstance();
HoustonOpenApiResult result = tokenValidationHandler.check(args, context);
if (!result.isSuccess()) {
return result;
}
}
} catch (Exception e) {
logger.error("ValidatorAspect驗(yàn)證出現(xiàn)異常", e);
return HoustonOpenApiResult.error(OpenApiResultCode.INNER_ERROR, "系統(tǒng)異常 請(qǐng)@Houston答疑, error:" + e.getMessage());
}
try {
return joinPoint.proceed();
} catch (Exception e) {
logger.error("Service服務(wù)出現(xiàn)異常", e);
return HoustonOpenApiResult.error(OpenApiResultCode.INNER_ERROR, "Service出現(xiàn)異常 請(qǐng)@Houston答疑, error:" + e.getMessage());
}
}
public static ApplicationContext getContext() {
return context;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
ValidatorAspect.context = applicationContext;
}
}
@TokenValidator
@Documented
@Target({ElementType.METHOD})//只能在方法上使用
@Retention(RetentionPolicy.RUNTIME)//運(yùn)行時(shí)使用
public @interface TokenValidator {
Class<? extends TokenValidationHandler> handler() default TokenValidationHandler.class;//定義了一個(gè)接口類(lèi)
}
TokenValidationHandler
public interface TokenValidationHandler {
HoustonOpenApiResult check(Object[] args, ApplicationContext applicationContext);
HoustonOpenApiResult getGroupResult(Object[] args, DefaultGroupBiz defaultGroupBiz);
}
一個(gè)Handler的實(shí)現(xiàn)
public abstract class BaseTVHandler implements TokenValidationHandler {
@Override
public HoustonOpenApiResult check(Object[] args, ApplicationContext context) {
if (args.length >= 2) {
Token token = (Token) args[0];
DefaultGroupBiz defaultGroupBiz = context.getBean(DefaultGroupBiz.class);
HoustonOpenApiResult<Group> groupResult = getGroupResult(args, defaultGroupBiz);
if (!groupResult.isSuccess()) {
return groupResult;
}
if (TokenUtil.check(token, groupResult.getData().getKeyName())) {
return HoustonOpenApiResult.success(true);
}
return new HoustonOpenApiResult(OpenApiResultCode.TOKEN_ILLEGAL);
} else {
return new HoustonOpenApiResult(OpenApiResultCode.TOKEN_PARAM_ERROR);
}
}
}
public class ConfigValueTokenValidator {
public static class DetailHandler extends BaseTVHandler {
@Override
public HoustonOpenApiResult getGroupResult(Object[] args, DefaultGroupBiz defaultGroupBiz) {
ConfigValueDetail configValueDetail = (ConfigValueDetail) args[1];
return defaultGroupBiz.queryByConfigId(configValueDetail.getConfigId());
}
}
}
AOP原理
Spring AOP使用了兩種代理機(jī)制:一種是基于JDK的動(dòng)態(tài)代理;另一種是基于CGLib的動(dòng)態(tài)代理。之所以需要兩種代理機(jī)制,很大程度上是因?yàn)镴DK本身只提供接口的代理,而不支持類(lèi)的代理。
JDK動(dòng)態(tài)代理
步驟
- 通過(guò)實(shí)現(xiàn)InvocationHandler接口創(chuàng)建自己的調(diào)用處理器
- 通過(guò)為Proxy類(lèi)指定ClassLoader對(duì)象和一組interface來(lái)創(chuàng)建動(dòng)態(tài)代理類(lèi)
- 通過(guò)反射機(jī)制獲得動(dòng)態(tài)代理類(lèi)的構(gòu)造函數(shù),其唯一參數(shù)類(lèi)型是調(diào)用處理器接口類(lèi)型
- 通過(guò)構(gòu)造函數(shù)創(chuàng)建動(dòng)態(tài)代理類(lèi)實(shí)例,構(gòu)造時(shí)調(diào)用處理器對(duì)象作為參數(shù)被傳入
public class DynamicTest implements InvocationHandler {
private Test target;
private DynamicTest(Test target) {
this.target = target;
}
public static Test newProxyInstance(Test test) {
return (Test) Proxy.newProxyInstance(test.getClass().getClassLoader(), test.getClass().getInterfaces(), new DynamicTest(test));
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return method.invoke(target, args);
}
}
基于CGLIB的動(dòng)態(tài)代理
CGLIB直接生成代理目標(biāo)類(lèi)的子類(lèi),不能對(duì)目標(biāo)類(lèi)中的final方法進(jìn)行代理。
查找A上的所有非final 的public類(lèi)型的方法定義;
將這些方法的定義轉(zhuǎn)換成字節(jié)碼;
將組成的字節(jié)碼轉(zhuǎn)換成相應(yīng)的代理的class對(duì)象;
實(shí)現(xiàn) MethodInterceptor接口,用來(lái)處理 對(duì)代理類(lèi)上所有方法的請(qǐng)求(這個(gè)接口和JDK動(dòng)態(tài)代理InvocationHandler的功能和角色是一樣的)
public class CglibTest implements MethodInterceptor {
private CglibTest() {
}
public static <T extends Test> Test newProxyInstance(Class<T> targetClass) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(targetClass);
enhancer.setCallback(new CglibTest());
return (Test) enhancer.create();
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
return methodProxy.invokeSuper(o, objects);
}
}
ASM(介紹)
ASM 是一個(gè) Java 字節(jié)碼操控框架。它能夠以二進(jìn)制形式修改已有類(lèi)或者動(dòng)態(tài)生成類(lèi)。ASM 可以直接產(chǎn)生二進(jìn)制 class 文件,也可以在類(lèi)被加載入 Java 虛擬機(jī)之前動(dòng)態(tài)改變類(lèi)行為。ASM 從類(lèi)文件中讀入信息后,能夠改變類(lèi)行為,分析類(lèi)信息,甚至能夠根據(jù)用戶要求生成新類(lèi)。
不過(guò)ASM在創(chuàng)建class字節(jié)碼的過(guò)程中,操縱的級(jí)別是底層JVM的匯編指令級(jí)別,這要求ASM使用者要對(duì)class組織結(jié)構(gòu)和JVM匯編指令有一定的了解。
Javassist(介紹)
Javassist是一款字節(jié)碼編輯工具,可以直接編輯和生成Java生成的字節(jié)碼,以達(dá)到對(duì).class文件進(jìn)行動(dòng)態(tài)修改的效果。熟練使用這套工具,可以讓Java編程更接近與動(dòng)態(tài)語(yǔ)言編程。
JDK動(dòng)態(tài)代理與CGLIB性能比較
- 被代理接口
public interface Test {
public int test(int i);
}
- 實(shí)現(xiàn)類(lèi)
public class TestImpl implements Test {
@Override
public int test(int i) {
return i + 1;
}
public void print() {
System.out.println("111111");
}
}
- JDK代理類(lèi)
public class DynamicTest implements InvocationHandler {
private Test target;
private DynamicTest(Test target) {
this.target = target;
}
public static Test newProxyInstance(Test test) {
return (Test) Proxy.newProxyInstance(test.getClass().getClassLoader(), test.getClass().getInterfaces(), new DynamicTest(test));
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return method.invoke(target, args);
}
}
- CGLIB代理類(lèi)
public class CglibTest implements MethodInterceptor {
private CglibTest() {
}
public static <T extends Test> Test newProxyInstance(Class<T> targetClass) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(targetClass);
enhancer.setCallback(new CglibTest());
return (Test) enhancer.create();
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
return methodProxy.invokeSuper(o, objects);
}
}
- 測(cè)試類(lèi)
public class ProxyPerfTester {
public static void main(String[] args) {
//創(chuàng)建測(cè)試對(duì)象;
Test nativeTest = new TestImpl();
Test dynamicProxy = DynamicTest.newProxyInstance(nativeTest);
Test cglibProxy = CglibTest.newProxyInstance(TestImpl.class);
//預(yù)熱一下;
int preRunCount = 100000;
runWithoutMonitor(nativeTest, preRunCount);
runWithoutMonitor(cglibProxy, preRunCount);
runWithoutMonitor(dynamicProxy, preRunCount);
//執(zhí)行測(cè)試;
Map<String, Test> tests = new LinkedHashMap<String, Test>();
tests.put("Native ", nativeTest);
tests.put("Dynamic ", dynamicProxy);
tests.put("Cglib ", cglibProxy);
int repeatCount = 3;
int runCount = 1000000;
runTest(repeatCount, runCount, tests);
runCount = 50000000;
runTest(repeatCount, runCount, tests);
}
private static void runTest(int repeatCount, int runCount, Map<String, Test> tests){
System.out.println(String.format("\n==================== run test : [repeatCount=%s] [runCount=%s] [java.version=%s] ====================", repeatCount, runCount, System.getProperty("java.version")));
for (int i = 0; i < repeatCount; i++) {
System.out.println(String.format("\n--------- test : [%s] ---------", (i+1)));
for (String key : tests.keySet()) {
runWithMonitor(tests.get(key), runCount, key);
}
}
}
private static void runWithoutMonitor(Test test, int runCount) {
for (int i = 0; i < runCount; i++) {
test.test(i);
}
}
private static void runWithMonitor(Test test, int runCount, String tag) {
long start = System.currentTimeMillis();
for (int i = 0; i < runCount; i++) {
test.test(i);
}
long end = System.currentTimeMillis();
System.out.println("["+tag + "] Elapsed Time:" + (end-start) + "ms");
}
}
- 結(jié)果
Create Native Proxy:1ms
Create Dynamic Proxy17ms
Create Cglib Proxy521ms
==================== run test : [repeatCount=3] [runCount=1000000] [java.version=1.7.0_79] ====================
--------- test : [1] ---------
[Native ] Elapsed Time:7ms
[Dynamic ] Elapsed Time:289ms
[Cglib ] Elapsed Time:93ms
--------- test : [2] ---------
[Native ] Elapsed Time:7ms
[Dynamic ] Elapsed Time:12ms
[Cglib ] Elapsed Time:51ms
--------- test : [3] ---------
[Native ] Elapsed Time:6ms
[Dynamic ] Elapsed Time:14ms
[Cglib ] Elapsed Time:45ms
==================== run test : [repeatCount=3] [runCount=50000000] [java.version=1.7.0_79] ====================
--------- test : [1] ---------
[Native ] Elapsed Time:468ms
[Dynamic ] Elapsed Time:1855ms
[Cglib ] Elapsed Time:1577ms
--------- test : [2] ---------
[Native ] Elapsed Time:165ms
[Dynamic ] Elapsed Time:418ms
[Cglib ] Elapsed Time:807ms
--------- test : [3] ---------
[Native ] Elapsed Time:161ms
[Dynamic ] Elapsed Time:484ms
[Cglib ] Elapsed Time:889ms
可見(jiàn)在JDK1.7下:
- 運(yùn)行速度,Native是最快的,JDK動(dòng)態(tài)代理稍次之,CGLIB最慢。
- 創(chuàng)建速度,Native是最快的,JDK動(dòng)態(tài)代理稍次之,CGLIB最慢。