Spring之@Aspect中通知的5種方式詳解
@Aspect中有5種通知
- @Before:前置通知, 在方法執(zhí)行之前執(zhí)行
- @Aroud:環(huán)繞通知, 圍繞著方法執(zhí)行
- @After:后置通知, 在方法執(zhí)行之后執(zhí)行
- @AfterReturning:返回通知, 在方法返回結(jié)果之后執(zhí)行
- @AfterThrowing:異常通知, 在方法拋出異常之后
這幾種通知用起來(lái)都比較簡(jiǎn)單,都是通過(guò)注解的方式,將這些注解標(biāo)注在@Aspect類(lèi)的方法上,這些方法就會(huì)對(duì)目標(biāo)方法進(jìn)行攔截,下面我們一個(gè)個(gè)來(lái)看一下。
@Before:前置通知
介紹
定義一個(gè)前置通知
@Aspect public class BeforeAspect { @Before("execution(* com.javacode2018.aop.demo10.test1.Service1.*(..))") public void before(JoinPoint joinPoint) { System.out.println("我是前置通知!"); } }
- 類(lèi)上需要使用
@Aspect
標(biāo)注 - 任意方法上使用
@Before
標(biāo)注,將這個(gè)方法作為前置通知,目標(biāo)方法被調(diào)用之前,會(huì)自動(dòng)回調(diào)這個(gè)方法 - 被
@Before
標(biāo)注的方法參數(shù)可以為空,或者為JoinPoint
類(lèi)型,當(dāng)為JoinPoint
類(lèi)型時(shí),必須為第一個(gè)參數(shù) - 被
@Before
標(biāo)注的方法名稱可以隨意命名,符合java規(guī)范就可以,其他通知也類(lèi)似
@Before
中value的值為切入點(diǎn)表達(dá)式,也可以采用引用的方式指定切入點(diǎn),如:
package com.javacode2018.aop.demo10.test1; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; @Aspect public class BeforeAspect { @Pointcut("execution(* com.javacode2018.aop.demo10.test1.Service1.*(..))") public void pc() { } @Before("com.javacode2018.aop.demo10.test1.BeforeAspect.pc()") public void before(JoinPoint joinPoint) { System.out.println("我是前置通知!"); } }
此時(shí),before方法上面的切入引用了pc方法上面的@Pointcut
的值
案例
來(lái)個(gè)普通的service
package com.javacode2018.aop.demo10.test1; public class Service1 { public String say(String name) { return "你好:" + name; } public String work(String name) { return "開(kāi)始工作了:" + name; } }
給上面的類(lèi)定義一個(gè)前置通知,Service1
中的所有方法執(zhí)行執(zhí)行,輸出一段文字我是前置通知!
package com.javacode2018.aop.demo10.test1; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; @Aspect public class BeforeAspect1 { @Pointcut("execution(* com.javacode2018.aop.demo10.test1.Service1.*(..))") public void pc() { } @Before("com.javacode2018.aop.demo10.test1.BeforeAspect1.pc()") public void before(JoinPoint joinPoint) { System.out.println("我是前置通知!"); } }
測(cè)試代碼
package com.javacode2018.aop.demo10; import com.javacode2018.aop.demo10.test1.BeforeAspect1; import com.javacode2018.aop.demo10.test1.Service1; import org.junit.Test; import org.springframework.aop.aspectj.annotation.AspectJProxyFactory; public class AopTest10 { @Test public void test1() { Service1 target = new Service1(); Class<BeforeAspect1> aspectClass = BeforeAspect1.class; AspectJProxyFactory proxyFactory = new AspectJProxyFactory(); proxyFactory.setTarget(target); proxyFactory.addAspect(aspectClass); Service1 proxy = proxyFactory.getProxy(); System.out.println(proxy.say("路人")); System.out.println(proxy.work("路人")); } }
運(yùn)行輸出
我是前置通知!
你好:路人
我是前置通知!
開(kāi)始工作了:路人
對(duì)應(yīng)的通知類(lèi)
@Before通知最后會(huì)被解析為下面這個(gè)通知類(lèi)
org.springframework.aop.aspectj.AspectJMethodBeforeAdvice
通知中獲取被調(diào)方法信息
通知中如果想獲取被調(diào)用方法的信息,分2種情況
- 非環(huán)繞通知,可以將
org.aspectj.lang.JoinPoint
作為通知方法的第1個(gè)參數(shù),通過(guò)這個(gè)參數(shù)獲取被調(diào)用方法的信息 - 如果是環(huán)繞通知,可以將
org.aspectj.lang.ProceedingJoinPoint
作為方法的第1個(gè)參數(shù),通過(guò)這個(gè)參數(shù)獲取被調(diào)用方法的信息
JoinPoint:連接點(diǎn)信息
org.aspectj.lang.JoinPoint
提供訪問(wèn)當(dāng)前被通知方法的目標(biāo)對(duì)象、代理對(duì)象、方法參數(shù)等數(shù)據(jù):
package org.aspectj.lang; import org.aspectj.lang.reflect.SourceLocation; public interface JoinPoint { String toString(); //連接點(diǎn)所在位置的相關(guān)信息 String toShortString(); //連接點(diǎn)所在位置的簡(jiǎn)短相關(guān)信息 String toLongString(); //連接點(diǎn)所在位置的全部相關(guān)信息 Object getThis(); //返回AOP代理對(duì)象 Object getTarget(); //返回目標(biāo)對(duì)象 Object[] getArgs(); //返回被通知方法參數(shù)列表,也就是目前調(diào)用目標(biāo)方法傳入的參數(shù) Signature getSignature(); //返回當(dāng)前連接點(diǎn)簽名,這個(gè)可以用來(lái)獲取目標(biāo)方法的詳細(xì)信息,如方法Method對(duì)象等 SourceLocation getSourceLocation();//返回連接點(diǎn)方法所在類(lèi)文件中的位置 String getKind(); //連接點(diǎn)類(lèi)型 StaticPart getStaticPart(); //返回連接點(diǎn)靜態(tài)部分 }
ProceedingJoinPoint:環(huán)繞通知連接點(diǎn)信息
用于環(huán)繞通知,內(nèi)部主要關(guān)注2個(gè)方法,一個(gè)有參的,一個(gè)無(wú)參的,用來(lái)繼續(xù)執(zhí)行攔截器鏈上的下一個(gè)通知。
package org.aspectj.lang; import org.aspectj.runtime.internal.AroundClosure; public interface ProceedingJoinPoint extends JoinPoint { /** * 繼續(xù)執(zhí)行下一個(gè)通知或者目標(biāo)方法的調(diào)用 */ public Object proceed() throws Throwable; /** * 繼續(xù)執(zhí)行下一個(gè)通知或者目標(biāo)方法的調(diào)用 */ public Object proceed(Object[] args) throws Throwable; }
Signature:連接點(diǎn)簽名信息
注意JoinPoint#getSignature()
這個(gè)方法,用來(lái)獲取連接點(diǎn)的簽名信息,這個(gè)比較重要
Signature getSignature();
通常情況,spring中的aop都是用來(lái)對(duì)方法進(jìn)行攔截,所以通常情況下連接點(diǎn)都是一個(gè)具體的方法,Signature
有個(gè)子接口
org.aspectj.lang.reflect.MethodSignature
JoinPoint#getSignature()
都可以轉(zhuǎn)換轉(zhuǎn)換為MethodSignature
類(lèi)型,然后可以通過(guò)這個(gè)接口提供的一些方法來(lái)獲取被調(diào)用的方法的詳細(xì)信息。
下面對(duì)上面的前置通知的案例改造一下,獲取被調(diào)用方法的詳細(xì)信息,新建一個(gè)Aspect類(lèi):BeforeAspect2
package com.javacode2018.aop.demo10.test2; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.Signature; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.reflect.MethodSignature; import java.lang.reflect.Method; @Aspect public class BeforeAspect2 { @Pointcut("execution(* com.javacode2018.aop.demo10.test1.Service1.*(..))") public void pc() { } @Before("com.javacode2018.aop.demo10.test2.BeforeAspect2.pc()") public void before(JoinPoint joinPoint) { //獲取連接點(diǎn)簽名 Signature signature = joinPoint.getSignature(); //將其轉(zhuǎn)換為方法簽名 MethodSignature methodSignature = (MethodSignature) signature; //通過(guò)方法簽名獲取被調(diào)用的目標(biāo)方法 Method method = methodSignature.getMethod(); //輸出方法信息 System.out.println(method); } }
測(cè)試用例
@Test public void test2() { Service1 target = new Service1(); Class<BeforeAspect2> aspectClass = BeforeAspect2.class; AspectJProxyFactory proxyFactory = new AspectJProxyFactory(); proxyFactory.setTarget(target); proxyFactory.addAspect(aspectClass); Service1 proxy = proxyFactory.getProxy(); System.out.println(proxy.say("路人")); System.out.println(proxy.work("路人")); }
運(yùn)行輸出
public java.lang.String com.javacode2018.aop.demo10.test1.Service1.say(java.lang.String)
你好:路人
public java.lang.String com.javacode2018.aop.demo10.test1.Service1.work(java.lang.String)
開(kāi)始工作了:路人
@Around:環(huán)繞通知
介紹
環(huán)繞通知會(huì)包裹目標(biāo)目標(biāo)方法的執(zhí)行,可以在通知內(nèi)部調(diào)用ProceedingJoinPoint.process
方法繼續(xù)執(zhí)行下一個(gè)攔截器。
用起來(lái)和@Before類(lèi)似,但是有2點(diǎn)不一樣
若需要獲取目標(biāo)方法的信息,需要將ProceedingJoinPoint作為第一個(gè)參數(shù)
通常使用Object類(lèi)型作為方法的返回值,返回值也可以為void
特點(diǎn)
環(huán)繞通知比較特殊,其他4種類(lèi)型的通知都可以用環(huán)繞通知來(lái)實(shí)現(xiàn)。
案例
通過(guò)環(huán)繞通知來(lái)統(tǒng)計(jì)方法的耗時(shí)。
package com.javacode2018.aop.demo10.test3; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.Signature; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.reflect.MethodSignature; import java.lang.reflect.Method; @Aspect public class AroundAspect3 { @Pointcut("execution(* com.javacode2018.aop.demo10.test1.Service1.*(..))") public void pc() { } @Around("com.javacode2018.aop.demo10.test3.AroundAspect3.pc()") public Object around(ProceedingJoinPoint joinPoint) throws Throwable { //獲取連接點(diǎn)簽名 Signature signature = joinPoint.getSignature(); //將其轉(zhuǎn)換為方法簽名 MethodSignature methodSignature = (MethodSignature) signature; //通過(guò)方法簽名獲取被調(diào)用的目標(biāo)方法 Method method = methodSignature.getMethod(); long startTime = System.nanoTime(); //調(diào)用proceed方法,繼續(xù)調(diào)用下一個(gè)通知 Object returnVal = joinPoint.proceed(); long endTime = System.nanoTime(); long costTime = endTime - startTime; //輸出方法信息 System.out.println(String.format("%s,耗時(shí)(納秒):%s", method.toString(), costTime)); //返回方法的返回值 return returnVal; } }
測(cè)試用例
@Test public void test3() { Service1 target = new Service1(); Class<AroundAspect3> aspectClass = AroundAspect3.class; AspectJProxyFactory proxyFactory = new AspectJProxyFactory(); proxyFactory.setTarget(target); proxyFactory.addAspect(aspectClass); Service1 proxy = proxyFactory.getProxy(); System.out.println(proxy.say("路人")); System.out.println(proxy.work("路人")); }
運(yùn)行輸出
public java.lang.String com.javacode2018.aop.demo10.test1.Service1.say(java.lang.String),耗時(shí)(納秒):19000500
你好:路人
public java.lang.String com.javacode2018.aop.demo10.test1.Service1.work(java.lang.String),耗時(shí)(納秒):59600
開(kāi)始工作了:路人
對(duì)應(yīng)的通知類(lèi)
@Around通知最后會(huì)被解析為下面這個(gè)通知類(lèi)
org.springframework.aop.aspectj.AspectJAroundAdvice
@After:后置通知
介紹
后置通知,在方法執(zhí)行之后執(zhí)行,用法和前置通知類(lèi)似。
特點(diǎn)
- 不管目標(biāo)方法是否有異常,后置通知都會(huì)執(zhí)行
- 這種通知無(wú)法獲取方法返回值
- 可以使用
JoinPoint
作為方法的第一個(gè)參數(shù),用來(lái)獲取連接點(diǎn)的信息
案例
在Service1
中任意方法執(zhí)行完畢之后,輸出一行日志。
package com.javacode2018.aop.demo10.test4; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.reflect.MethodSignature; @Aspect public class AfterAspect4 { @Pointcut("execution(* com.javacode2018.aop.demo10.test1.Service1.*(..))") public void pc() { } @After("com.javacode2018.aop.demo10.test4.AfterAspect4.pc()") public void after(JoinPoint joinPoint) throws Throwable { MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature(); System.out.println(String.format("%s,執(zhí)行完畢!", methodSignature.getMethod())); } }
測(cè)試案例
@Test public void test4() { Service1 target = new Service1(); Class<AfterAspect4> aspectClass = AfterAspect4.class; AspectJProxyFactory proxyFactory = new AspectJProxyFactory(); proxyFactory.setTarget(target); proxyFactory.addAspect(aspectClass); Service1 proxy = proxyFactory.getProxy(); System.out.println(proxy.say("路人")); System.out.println(proxy.work("路人")); }
運(yùn)行輸出
public java.lang.String com.javacode2018.aop.demo10.test1.Service1.say(java.lang.String),執(zhí)行完畢!
你好:路人
public java.lang.String com.javacode2018.aop.demo10.test1.Service1.work(java.lang.String),執(zhí)行完畢!
開(kāi)始工作了:路人
對(duì)應(yīng)的通知類(lèi)
@After通知最后會(huì)被解析為下面這個(gè)通知類(lèi)
org.springframework.aop.aspectj.AspectJAfterAdvice
這個(gè)類(lèi)中有invoke
方法,這個(gè)方法內(nèi)部會(huì)調(diào)用被通知的方法,其內(nèi)部采用try..finally
的方式實(shí)現(xiàn)的,所以不管目標(biāo)方法是否有異常,通知一定會(huì)被執(zhí)行。
@Override public Object invoke(MethodInvocation mi) throws Throwable { try { //繼續(xù)執(zhí)行下一個(gè)攔截器 return mi.proceed(); } finally { //內(nèi)部通過(guò)反射調(diào)用被@After標(biāo)注的方法 invokeAdviceMethod(getJoinPointMatch(), null, null); } }
@AfterReturning:返回通知
用法
返回通知,在方法返回結(jié)果之后執(zhí)行。
特點(diǎn)
- 可以獲取到方法的返回值
- 當(dāng)目標(biāo)方法返回異常的時(shí)候,這個(gè)通知不會(huì)被調(diào)用,這點(diǎn)和@After通知是有區(qū)別的
案例
后置通知中打印出方法及返回值信息。
package com.javacode2018.aop.demo10.test5; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.reflect.MethodSignature; @Aspect public class AfterReturningAspect5 { @Pointcut("execution(* com.javacode2018.aop.demo10.test1.Service1.*(..))") public void pc() { } @AfterReturning(value = "com.javacode2018.aop.demo10.test5.AfterReturningAspect5.pc()", returning = "retVal") public void afterReturning(JoinPoint joinPoint, Object retVal) throws Throwable { MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature(); System.out.println(String.format("%s返回值:%s", methodSignature.getMethod(), retVal)); } }
注意@AfterReturning
注解,用到了2個(gè)參數(shù)
- value:用來(lái)指定切入點(diǎn)
- returning:用來(lái)指定返回值對(duì)應(yīng)方法的參數(shù)名稱,返回值對(duì)應(yīng)方法的第二個(gè)參數(shù),名稱為retVal
對(duì)應(yīng)的通知類(lèi)
@AfterReturning通知最后會(huì)被解析為下面這個(gè)通知類(lèi)
org.springframework.aop.aspectj.AspectJAfterReturningAdvice
@AfterThrowing:異常通知
用法
在方法拋出異常之后會(huì)回調(diào)@AfterThrowing
標(biāo)注的方法。
@AfterThrowing標(biāo)注的方法可以指定異常的類(lèi)型,當(dāng)被調(diào)用的方法觸發(fā)該異常及其子類(lèi)型的異常之后,會(huì)觸發(fā)異常方法的回調(diào)。也可以不指定異常類(lèi)型,此時(shí)會(huì)匹配所有異常。
未指定異常類(lèi)型
未指定異常類(lèi)型,可以匹配所有異常類(lèi)型,如下
@AfterThrowing(value = "切入點(diǎn)") public void afterThrowing()
指定異常類(lèi)型
通過(guò)@AfterThrowing
的throwing
指定參數(shù)異常參數(shù)名稱,我們用方法的第二個(gè)參數(shù)用來(lái)接收異常,第二個(gè)參數(shù)名稱為e,下面的代碼,當(dāng)目標(biāo)方法發(fā)生IllegalArgumentException
異常及其子類(lèi)型異常時(shí),下面的方法會(huì)被回調(diào)。
@AfterThrowing(value = "com.javacode2018.aop.demo10.test6.AfterThrowingAspect6.pc()", throwing = "e") public void afterThrowing(JoinPoint joinPoint, IllegalArgumentException e)
特點(diǎn)
不論異常是否被異常通知捕獲,異常還會(huì)繼續(xù)向外拋出。
案例
Service1中加了login方法,用戶名不是路人甲java
時(shí)拋出異常。
package com.javacode2018.aop.demo10.test1; public class Service1 { public String say(String name) { return "你好:" + name; } public String work(String name) { return "開(kāi)始工作了:" + name; } public boolean login(String name) { if (!"路人甲java".equals(name)) { throw new IllegalArgumentException("非法訪問(wèn)!"); } return true; } }
來(lái)個(gè)異常通知
package com.javacode2018.aop.demo10.test6; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.AfterThrowing; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.reflect.MethodSignature; @Aspect public class AfterThrowingAspect6 { @Pointcut("execution(* com.javacode2018.aop.demo10.test1.Service1.*(..))") public void pc() { } @AfterThrowing(value = "com.javacode2018.aop.demo10.test6.AfterThrowingAspect6.pc()", throwing = "e") public void afterThrowing(JoinPoint joinPoint, IllegalArgumentException e) { MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature(); System.out.println(String.format("%s發(fā)生異常,異常信息:%s", methodSignature.getMethod(), e.getMessage())); } }
測(cè)試用例
@Test public void test6() { Service1 target = new Service1(); Class<AfterThrowingAspect6> aspectClass = AfterThrowingAspect6.class; AspectJProxyFactory proxyFactory = new AspectJProxyFactory(); proxyFactory.setTarget(target); proxyFactory.addAspect(aspectClass); Service1 proxy = proxyFactory.getProxy(); proxy.login("路人"); }
運(yùn)行輸出
public boolean com.javacode2018.aop.demo10.test1.Service1.login(java.lang.String)發(fā)生異常,異常信息:非法訪問(wèn)!
java.lang.IllegalArgumentException: 非法訪問(wèn)!
at com.javacode2018.aop.demo10.test1.Service1.login(Service1.java:14)
at com.javacode2018.aop.demo10.test1.Service1$$FastClassBySpringCGLIB$$ea03ccbe.invoke(<generated>)
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:769)
對(duì)應(yīng)的通知類(lèi)
@AfterThrowing通知最后會(huì)被解析為下面這個(gè)通知類(lèi)
org.springframework.aop.aspectj.AspectJAfterThrowingAdvice
來(lái)看一下這個(gè)類(lèi)的invoke
方法,這個(gè)方法是關(guān)鍵
@Override public Object invoke(MethodInvocation mi) throws Throwable { try { //繼續(xù)調(diào)用下一個(gè)攔截器鏈 return mi.proceed(); } catch (Throwable ex) { //判斷ex和需要不糊的異常是否匹配 if (shouldInvokeOnThrowing(ex)) { //通過(guò)反射調(diào)用@AfterThrowing標(biāo)注的方法 invokeAdviceMethod(getJoinPointMatch(), null, ex); } //繼續(xù)向外拋出異常 throw ex; } }
幾種通知對(duì)比
通知類(lèi)型 | 執(zhí)行時(shí)間點(diǎn) | 可獲取返回值 | 目標(biāo)方法異常時(shí)是否會(huì)執(zhí)行 |
---|---|---|---|
@Before | 方法執(zhí)行之前 | 否 | 是 |
@Around | 環(huán)繞方法執(zhí)行 | 是 | 自己控制 |
@After | 方法執(zhí)行后 | 否 | 是 |
@AfterReturning | 方法執(zhí)行后 | 是 | 否 |
@AfterThrowing | 方法發(fā)生異常后 | 否 | 是 |
到此這篇關(guān)于Spring之@Aspect中通知的5種方式詳解的文章就介紹到這了,更多相關(guān)Spring @Aspect通知內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SprinBoot如何集成參數(shù)校驗(yàn)Validator及參數(shù)校驗(yàn)的高階技巧
這篇文章主要介紹了SprinBoot如何集成參數(shù)校驗(yàn)Validator及參數(shù)校驗(yàn)的高階技巧包括自定義校驗(yàn)、分組校驗(yàn),本文分步驟給大家介紹的非常詳細(xì),需要的朋友可以參考下2022-05-05springboot整合JavaCV實(shí)現(xiàn)視頻截取第N幀并保存圖片
這篇文章主要為大家詳細(xì)介紹了springboot如何整合JavaCV實(shí)現(xiàn)視頻截取第N幀并保存為圖片,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起了解一下2023-08-08詳細(xì)聊聊SpringBoot中動(dòng)態(tài)切換數(shù)據(jù)源的方法
在大型分布式項(xiàng)目中,經(jīng)常會(huì)出現(xiàn)多數(shù)據(jù)源的情況,下面這篇文章主要給大家介紹了關(guān)于SpringBoot中動(dòng)態(tài)切換數(shù)據(jù)源的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友可以參考下2021-09-09Java日常練習(xí)題,每天進(jìn)步一點(diǎn)點(diǎn)(2)
下面小編就為大家?guī)?lái)一篇Java基礎(chǔ)的幾道練習(xí)題(分享)。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧,希望可以幫到你2021-07-07Hibernate環(huán)境搭建與配置方法(Hello world配置文件版)
這篇文章主要介紹了Hibernate環(huán)境搭建與配置方法,這里演示Hello world配置文件版的具體實(shí)現(xiàn)步驟與相關(guān)代碼,需要的朋友可以參考下2016-03-03使用Springboot實(shí)現(xiàn)word在線編輯保存
PageOffice目前支持的Web編程語(yǔ)言及架構(gòu)有:Java(JSP、SSH、MVC等),ASP.NET(C#、VB.NET、MVC、Razor等),PHP,ASP,本篇文章就帶你使用Springboot整合PageOffice實(shí)現(xiàn)word在線編輯保存2021-08-08java原裝代碼完成pdf在線預(yù)覽和pdf打印及下載
本文主要介紹了java原裝代碼完成pdf在線預(yù)覽和pdf打印及下載的方法,具有一定的參考價(jià)值,下面跟著小編一起來(lái)看下吧2017-02-02jdk8的datetime時(shí)間函數(shù)使用示例
這篇文章主要介紹了jdk8的datetime時(shí)間函數(shù)使用示例,需要的朋友可以參考下2014-03-03