今天我們繼續看看AOP相關的知識,前面說到了Javassit,Spring AOP,通過該篇,讓你對AOP有更完整的認識。
AOP
再看AOP,這是一種面向切面編程思想,相比面向對象編程,可以說是站在更改維度關注對象,我們知道,對象包含由屬性和行為。 基于AOP,我們可以把一段代碼插入到對象中形成新的對象,這是織入的過程,目的是將公共的內容寫入到業務代碼中,通過配置或簡單的編碼完成整個過程。 這樣一來不用修改原有的業務代碼,同時又能自由完成目標代碼的增強,按照代碼的設計思想,確實是降低業務與功能的耦合。
大部分框架都是為我們提供切面織入目標過程的封裝。
實現
通過該圖可以看到AOP相關的實現主要包括ASM、Cglib、JDK Proxy、AspectJ、Javassit,這些實現主要都是對字節碼直接操作,只不過對目標對象的增強可以發生在編譯時、編譯后或運行時。
關于AOP我們說的比較多的就是代理,這屬于設計模式的一種,但是AOP真正做的不僅僅是對目標的代理,更多的是修改,像我們常用的代理工具Cglib、JDK Proxy,都是基于面向對象的特性,生成新的 目標對象,通過繼承與代理模式來實現最終的增強效果。
在Java中,大部分情況下都是對方法的增強,比如Spring AOP,這樣可以解決幾乎所有的業務問題;當然切點不局限于類方法,還可以包括字段、方法、構造函數、靜態初始值等,比如AspectJ,只不過需要特定的 編譯器來實現。
下面我們看下剩下的幾項實現AOP的技術,前面說到,Spring AOP主要基于Cglib、JDK Proxy,在運行時實現目標對象的代理。但是Spring中卻引入了aspectj相關的依賴,但沒有用到AspectJ編譯器
JDK Proxy
JDK動態代理,主要是基于目標接口,通過ByteArrayOutputStream直接構建字節數組,最終生成代理接口的實現類,基于InvocationHandler實現代碼的擴展與增強,通過反射來調用目標代碼的調用。
- 目標接口
public interface HelloService {
String hello(String name);
}
- 目標實現類
@Slf4j
public class HelloServiceImpl implements HelloService{
@Override
public String hello(String name) {
log.info("+++ 執行方法: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); // 執行被代理方法
log.info(" >> > afterReturning : {}", result);
return result;
} catch (Throwable e) {
log.info(" >> > afterThrowing : {}", e.getMessage());
throw e;
} finally {
log.info(" >> > after");
}
}
}
}
- 執行測試
public class JdkProxyTests {
@Test
public void testJdkProxy(){
HelloService helloService = JdkProxyFactory.create(HelloService.class, new JdkProxyFactory.LogInvocationHandler(new HelloServiceImpl()));
helloService.hello("JDK Proxy");
}
}
Cglib
Cglib基于目標類來實現代理,已目標類為參考基于ASM直接操作字節碼,構造目標對象的子類行,基于MethodInterceptor接口實現目標代碼的增強,通過父類調用來執行原目標代碼,因此在執行效率上會高于JDK動態代理。
- 添加依賴
< dependency >
< groupId >cglib< /groupId >
< artifactId >cglib< /artifactId >
< version >3.3.0< /version >
< /dependency >
- 目標類
@Slf4j
public class HiService {
public String hi(String name){
log.info("+++ 執行方法: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 目標對象
* @param method 目標方法
* @param args 參數
* @param methodProxy 代理方法,注意執行方式 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); // 執行被代理方法
log.info(" >> > afterReturning : {}", result);
return result;
} catch (Throwable e) {
log.info(" >> > afterThrowing : {}", e.getMessage());
throw e;
} finally {
log.info(" >> > after");
}
}
}
}
- 執行測試
public class CglibTests {
/**
*
*/
@Test
public void testCglib(){
HiService hiService = CglibFactory.create(HiService.class, new CglibFactory.LogMethodInterceptor());
hiService.hi("Cglib");
}
}
AspectJ
AspectJ是一個功能強大的面向切面編程框架,是對Java面向對象的擴展,支持編譯時、編譯后、加載時為目標對象(不僅僅是類方法)織入代理。
切面織入時機:
- 編譯期織入(compiler-time weaving):在類進行編譯的時候就將相應的代碼織入到元類文件的.class文件中
- 編譯后織入(post-compiler weaving):在類編譯后,再將相關的代碼織入到.class文件中
- 加載時織入(load-time weaving):在JVM加載.class 文件的時候將代碼織入
我們可以通過AspectJ編譯器或者maven插件aspectj-maven-plugin來實現。
AspectJ編譯器
aspectj
- 安裝
java -jar aspectj-1.9.6.jar 配置環境變量PATH與系統變量CLASSPATH
- 使用
通過下面的命令可實現編譯時織入的效果:
# ajc [Options] [file... | @file... | -argfile file...]
ajc -1.8 -sourceroots .srcmainjava -cp %CLASS_PATH% -outjar main.jar
通過ajc編譯后并打包成main.jar,即是編譯時實現了目標對象的代理,通過反編譯工具可以查看到編譯后的目標對象已經被修改。
AspectJ使用
編譯時織入(Compile-Time Weaving)
- 編譯時織入
目標對象:
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)
- 針對編譯好的文件,比如jar中的class文件
編寫測試的目標對象,并打包成jar文件
public class PCWObject {
public void run() {
System.out.println("-- Post-Compile Weaving --");
}
}
- 引入上面的目標jar
< 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 >
- 編譯
mvn clean compile
- 執行測試
public class AspectJPCWTests {
@Test
public void call(){
PCWObject pcwObject = new PCWObject();
pcwObject.run();
}
}
運行時織入(Load-Time Weaving)
- 配置VM參數
-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 >
- 配置aop.xml
/src/main/resources/META-INF/aop.xml
< aspectj >
< aspects >
< !-- 以@Aspect形式編寫切面(aj需要對應編譯器編譯)-- >
< aspect name="com.sucl.blog.aspectj.aspect.LogAspect"/ >
< /aspects >
< /aspectj >
- 啟動測試
public class AspectJLTWTests {
@Test
public void call(){
LTWObject LTWObject = new LTWObject();
LTWObject.run();
}
}
結束語
不管是javassit,還是jdk proxy或者cglib來實現AOP,都是通過對字節碼的修改,只不過對字節碼操作方式不一樣。通過上面的例子我們可以認識到各種AOP框架的使用方式。在究其原理時, 能夠能夠知道這些工具到底為我們做了什么。
-
接口
+關注
關注
33文章
8575瀏覽量
151015 -
編程
+關注
關注
88文章
3614瀏覽量
93686 -
代碼
+關注
關注
30文章
4779瀏覽量
68521 -
AOP
+關注
關注
0文章
40瀏覽量
11098 -
JDK
+關注
關注
0文章
81瀏覽量
16592
發布評論請先 登錄
相關推薦
評論