哈嘍,大家好,我是了不起。
今天我們繼續(xù)看看AOP相關(guān)的知識(shí),前面說到了Javassit,Spring AOP,通過該篇,讓你對(duì)AOP有更完整的認(rèn)識(shí)。
AOP
再看AOP,這是一種面向切面編程思想,相比面向?qū)ο缶幊?,可以說是站在更改維度關(guān)注對(duì)象,我們知道,對(duì)象包含由屬性和行為。 基于AOP,我們可以把一段代碼插入到對(duì)象中形成新的對(duì)象,這是織入的過程,目的是將公共的內(nèi)容寫入到業(yè)務(wù)代碼中,通過配置或簡單的編碼完成整個(gè)過程。 這樣一來不用修改原有的業(yè)務(wù)代碼,同時(shí)又能自由完成目標(biāo)代碼的增強(qiáng),按照代碼的設(shè)計(jì)思想,確實(shí)是降低業(yè)務(wù)與功能的耦合。
大部分框架都是為我們提供切面織入目標(biāo)過程的封裝。
實(shí)現(xiàn)

通過該圖可以看到AOP相關(guān)的實(shí)現(xiàn)主要包括ASM、Cglib、JDK Proxy、AspectJ、Javassit,這些實(shí)現(xiàn)主要都是對(duì)字節(jié)碼直接操作,只不過對(duì)目標(biāo)對(duì)象的增強(qiáng)可以發(fā)生在編譯時(shí)、編譯后或運(yùn)行時(shí)。
關(guān)于AOP我們說的比較多的就是代理,這屬于設(shè)計(jì)模式的一種,但是AOP真正做的不僅僅是對(duì)目標(biāo)的代理,更多的是修改,像我們常用的代理工具Cglib、JDK Proxy,都是基于面向?qū)ο蟮奶匦?,生成新?目標(biāo)對(duì)象,通過繼承與代理模式來實(shí)現(xiàn)最終的增強(qiáng)效果。
在Java中,大部分情況下都是對(duì)方法的增強(qiáng),比如Spring AOP,這樣可以解決幾乎所有的業(yè)務(wù)問題;當(dāng)然切點(diǎn)不局限于類方法,還可以包括字段、方法、構(gòu)造函數(shù)、靜態(tài)初始值等,比如AspectJ,只不過需要特定的 編譯器來實(shí)現(xiàn)。
下面我們看下剩下的幾項(xiàng)實(shí)現(xiàn)AOP的技術(shù),前面說到,Spring AOP主要基于Cglib、JDK Proxy,在運(yùn)行時(shí)實(shí)現(xiàn)目標(biāo)對(duì)象的代理。但是Spring中卻引入了aspectj相關(guān)的依賴,但沒有用到AspectJ編譯器
JDK Proxy
JDK動(dòng)態(tài)代理,主要是基于目標(biāo)接口,通過ByteArrayOutputStream直接構(gòu)建字節(jié)數(shù)組,最終生成代理接口的實(shí)現(xiàn)類,基于InvocationHandler實(shí)現(xiàn)代碼的擴(kuò)展與增強(qiáng),通過反射來調(diào)用目標(biāo)代碼的調(diào)用。
- 目標(biāo)接口
public interface HelloService {
String hello(String name);
}
- 目標(biāo)實(shí)現(xiàn)類
@Slf4j
public class HelloServiceImpl implements HelloService{
@Override
public String hello(String name) {
log.info("+++ 執(zhí)行方法:hello");
return String.format("hello, %s", name);
}
}
- 代理工廠
public class JdkProxyFactory {
public static <T> T create(Class<T> targetClass, InvocationHandler invocationHandler){
return (T) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class[]{targetClass}, invocationHandler);
}
@Slf4j
public static class LogInvocationHandler implements InvocationHandler{
private Object target;
public LogInvocationHandler(Object target) {
this.target = target;
}
/**
*
* @param proxy
* @param method
* @param args
*
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
log.info(">>> before");
Object result = method.invoke(target, args); // 執(zhí)行被代理方法
log.info(">>> afterReturning : {}", result);
return result;
} catch (Throwable e) {
log.info(">>> afterThrowing : {}", e.getMessage());
throw e;
} finally {
log.info(">>> after");
}
}
}
}
- 執(zhí)行測試
public class JdkProxyTests {
@Test
public void testJdkProxy(){
HelloService helloService = JdkProxyFactory.create(HelloService.class, new JdkProxyFactory.LogInvocationHandler(new HelloServiceImpl()));
helloService.hello("JDK Proxy");
}
}
Cglib
Cglib基于目標(biāo)類來實(shí)現(xiàn)代理,已目標(biāo)類為參考基于ASM直接操作字節(jié)碼,構(gòu)造目標(biāo)對(duì)象的子類行,基于MethodInterceptor接口實(shí)現(xiàn)目標(biāo)代碼的增強(qiáng),通過父類調(diào)用來執(zhí)行原目標(biāo)代碼,因此在執(zhí)行效率上會(huì)高于JDK動(dòng)態(tài)代理。
- 添加依賴
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
- 目標(biāo)類
@Slf4j
public class HiService {
public String hi(String name){
log.info("+++ 執(zhí)行方法:hi");
return String.format("hi, %s", name);
}
}
- 代理工廠
public class CglibFactory{
/**
*
* @param targetClass
* @param methodInterceptor
* @return
* @param <T>
*/
public static <T> T create(Class<T> targetClass, MethodInterceptor methodInterceptor){
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(targetClass);
enhancer.setCallback(methodInterceptor);
return (T) enhancer.create();
}
@Slf4j
public static class LogMethodInterceptor implements MethodInterceptor {
/**
*
* @param target 目標(biāo)對(duì)象
* @param method 目標(biāo)方法
* @param args 參數(shù)
* @param methodProxy 代理方法,注意執(zhí)行方式 methodProxy.invokeSuper
* @return
* @throws Throwable
*/
@Override
public Object intercept(Object target, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
try {
log.info(">>> before");
Object result = methodProxy.invokeSuper(target, args); // 執(zhí)行被代理方法
log.info(">>> afterReturning : {}", result);
return result;
} catch (Throwable e) {
log.info(">>> afterThrowing : {}", e.getMessage());
throw e;
} finally {
log.info(">>> after");
}
}
}
}
- 執(zhí)行測試
public class CglibTests {
/**
*
*/
@Test
public void testCglib(){
HiService hiService = CglibFactory.create(HiService.class, new CglibFactory.LogMethodInterceptor());
hiService.hi("Cglib");
}
}
AspectJ
AspectJ是一個(gè)功能強(qiáng)大的面向切面編程框架,是對(duì)Java面向?qū)ο蟮臄U(kuò)展,支持編譯時(shí)、編譯后、加載時(shí)為目標(biāo)對(duì)象(不僅僅是類方法)織入代理。
切面織入時(shí)機(jī):
- 編譯期織入(compiler-time weaving):在類進(jìn)行編譯的時(shí)候就將相應(yīng)的代碼織入到元類文件的.class文件中
- 編譯后織入(post-compiler weaving):在類編譯后,再將相關(guān)的代碼織入到.class文件中
- 加載時(shí)織入(load-time weaving):在JVM加載.class 文件的時(shí)候?qū)⒋a織入
我們可以通過AspectJ編譯器或者maven插件aspectj-maven-plugin來實(shí)現(xiàn)。
AspectJ編譯器
aspectj
java -jar aspectj-1.9.6.jar 配置環(huán)境變量PATH與系統(tǒng)變量CLASSPATH
通過下面的命令可實(shí)現(xiàn)編譯時(shí)織入的效果:
# ajc [Options] [file... | @file... | -argfile file...]
ajc -1.8 -sourceroots .\src\main\java\ -cp %CLASS_PATH% -outjar main.jar
通過ajc編譯后并打包成main.jar,即是編譯時(shí)實(shí)現(xiàn)了目標(biāo)對(duì)象的代理,通過反編譯工具可以查看到編譯后的目標(biāo)對(duì)象已經(jīng)被修改。
AspectJ使用
編譯時(shí)織入(Compile-Time Weaving)
目標(biāo)對(duì)象:
public class CTWObject {
public void run() {
System.out.println("-- Compile-Time Weaving --");
}
}
Aspect:
public aspect CTWAspect {
pointcut pc():
execution(* com.sucl.blog.aspectj.target.CTWObject.*());
before(): pc(){
System.out.println(" >> before CTW << ");
}
void around(): pc(){
System.out.println(" >> around before CTW << ");
proceed();
System.out.println(" >> around before CTW << ");
}
after(): pc(){
System.out.println(" >> after CTW << ");
}
}
- 配置maven插件 aspectj-maven-plugin
<!-- 編譯期織入 -->
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>aspectj-maven-plugin</artifactId>
<version>1.14.0</version>
<configuration>
<complianceLevel>1.8</complianceLevel>
<source>1.8</source>
<target>1.8</target>
<showWeaveInfo>true</showWeaveInfo>
<verbose>true</verbose>
<Xlint>ignore</Xlint>
<encoding>UTF-8</encoding>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>test-compile</goal>
</goals>
</execution>
</executions>
</plugin>
public class AspectJCTWTests {
@Test
public void call() {
CTWObject CTWObject = new CTWObject();
CTWObject.run();
}
}
編譯后織入(Post-Compile Weaving)
- 針對(duì)編譯好的文件,比如jar中的class文件
編寫測試的目標(biāo)對(duì)象,并打包成jar文件
public class PCWObject {
public void run() {
System.out.println("-- Post-Compile Weaving --");
}
}
<dependency>
<groupId>com.sucls.blog</groupId>
<artifactId>PCW-target</artifactId>
<version>${project.version}</version>
</dependency>
- 配置maven插件 aspectj-maven-plugin
<!-- 編譯后織入 -->
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>aspectj-maven-plugin</artifactId>
<!-- <version>1.14.0</version>-->
<version>1.11</version>
<configuration>
<complianceLevel>1.8</complianceLevel>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
<weaveDependencies>
<weaveDependency>
<groupId>com.sucls.blog</groupId>
<artifactId>PCW-target</artifactId>
</weaveDependency>
</weaveDependencies>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>test-compile</goal>
</goals>
</execution>
</executions>
</plugin>
public class AspectJPCWTests {
@Test
public void call(){
PCWObject pcwObject = new PCWObject();
pcwObject.run();
}
}
運(yùn)行時(shí)織入(Load-Time Weaving)
-javaagent:${project.basedir}/lib/aspectjweaver-1.9.7.jar
或者配置maven-surefire-plugin插件
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.10</version>
<configuration>
<argLine>
-javaagent:${project.basedir}/lib/aspectjweaver-1.9.7.jar
</argLine>
<useSystemClassLoader>true</useSystemClassLoader>
<forkMode>always</forkMode>
</configuration>
</plugin>
/src/main/resources/META-INF/aop.xml
<aspectj>
<aspects>
<!-- 以@Aspect形式編寫切面(aj需要對(duì)應(yīng)編譯器編譯)-->
<aspect name="com.sucl.blog.aspectj.aspect.LogAspect"/>
</aspects>
</aspectj>
public class AspectJLTWTests {
@Test
public void call(){
LTWObject LTWObject = new LTWObject();
LTWObject.run();
}
}
結(jié)束語
不管是javassit,還是jdk proxy或者cglib來實(shí)現(xiàn)AOP,都是通過對(duì)字節(jié)碼的修改,只不過對(duì)字節(jié)碼操作方式不一樣。通過上面的例子我們可以認(rèn)識(shí)到各種AOP框架的使用方式。在究其原理時(shí), 能夠能夠知道這些工具到底為我們做了什么。