Spring jcl及spring core源碼深度解析
這兩個內(nèi)容源碼雖然不算少,但是內(nèi)容不太重要,在其他的 Module 里應用到了再做具體的學習。
1.spring-jcl
- jcl 的全稱為 Jakarta commons-logging,原是 apache 提供的一個抽象的日志框架,并不提供日志功能,若需要使用具體的日志則需要添加依賴的 jar 包,由于 jcl 的自我拋棄,不再進行維護了。但是框架總歸是要記錄日志的。所以 spring 5.0.x 框架封裝了一個 jcl 框架 spring-jcl。
1.1.日志加載
spring-jcl 對外提供統(tǒng)一的接口,對日志的操作委托給具體的日志框架,5.0.2.RELEASE 版本中支持的日志如下:
private enum LogApi {LOG4J, SLF4J_LAL, SLF4J, JUL}
其中 JUL 為 java.util.logging ,JDK提供的基礎日志功能,默認為 JUL,其他日志功能需要引入對應依賴。
靜態(tài)塊在類進行加載的時候就會嘗試加載上述日志框架。當調(diào)用 LogFactory 工廠的 getLog 靜態(tài)方法時,根據(jù)對應日志框架名稱,創(chuàng)建對應日志類,部分源碼如下:
public abstract class LogFactory { private static LogApi logApi = LogApi.JUL; static { ClassLoader cl = LogFactory.class.getClassLoader(); try { // Try Log4j 2.x API cl.loadClass("org.apache.logging.log4j.spi.ExtendedLogger"); logApi = LogApi.LOG4J; } catch (ClassNotFoundException ex1) { try { // Try SLF4J 1.7 SPI cl.loadClass("org.slf4j.spi.LocationAwareLogger"); logApi = LogApi.SLF4J_LAL; } catch (ClassNotFoundException ex2) { try { // Try SLF4J 1.7 API cl.loadClass("org.slf4j.Logger"); logApi = LogApi.SLF4J; } catch (ClassNotFoundException ex3) { // Keep java.util.logging as default } } } } public static Log getLog(String name) { switch (logApi) { case LOG4J: return Log4jDelegate.createLog(name); case SLF4J_LAL: return Slf4jDelegate.createLocationAwareLog(name); case SLF4J: return Slf4jDelegate.createLog(name); default: // Defensively use lazy-initializing delegate class here as well since the // java.logging module is not present by default on JDK 9. We are requiring // its presence if neither Log4j nor SLF4J is available; however, in the // case of Log4j or SLF4J, we are trying to prevent early initialization // of the JavaUtilLog adapter - e.g. by a JVM in debug mode - when eagerly // trying to parse the bytecode for all the cases of this switch clause. return JavaUtilDelegate.createLog(name); } } }
該源碼比較與 apache 的 jcl 簡化了很多,核心類只有 LogFactory。
2.spring-core
spring核心包,主要包含 Spring 框架基本的核心工具類, Spring 的其他紐件都要用到這個包里的類, Core 模塊是其他紐件的基本核心。
編譯 spring-core 報錯:找不到 DefaultNamingPolicy,Objenesis 類:為了避免第三方 class 的沖突,Spring 把最新的 cglib 和 objenesis 重新打包,并沒有再源碼提供這部分的代碼,在官方文檔里注釋:這種重新打包技術避免了與應用程序級或第三方庫和框架中不同 Objensis 版本之間的依賴關系的任何潛在沖突。
解決辦法:在IDEA中打開右側邊欄的gradle,找到 Tasks --- other 模塊,分別雙擊 cglibRepackJar 和 objenesisRepackJar 將兩部分重新打包即可?;蛘咭部梢允謩釉?spring-core.gradle 配置文件中的 dependcies 配置項末尾添加:
dependencies { cglib("cglib:cglib:${cglibVersion}@jar") objenesis("org.objenesis:objenesis:${objenesisVersion}@jar") jarjar("com.googlecode.jarjar:jarjar:1.3") compile(files(cglibRepackJar)) compile(files(objenesisRepackJar)) compile(project(":spring-jcl")) optional("net.sf.jopt-simple:jopt-simple:5.0.4") optional("org.aspectj:aspectjweaver:${aspectjVersion}") optional("org.jetbrains.kotlin:kotlin-reflect:${kotlinVersion}") optional("org.jetbrains.kotlin:kotlin-stdlib:${kotlinVersion}") optional("io.projectreactor:reactor-core") optional("io.reactivex:rxjava:${rxjavaVersion}") optional("io.reactivex:rxjava-reactive-streams:${rxjavaAdapterVersion}") optional("io.reactivex.rxjava2:rxjava:${rxjava2Version}") optional("io.netty:netty-buffer") testCompile("io.projectreactor:reactor-test") testCompile("javax.xml.bind:jaxb-api:2.3.0") testCompile("org.apache.tomcat.embed:tomcat-embed-core:${tomcatVersion}") testCompile("com.fasterxml.woodstox:woodstox-core:5.0.3") { exclude group: "stax", module: "stax-api" } compile fileTree(dir: 'libs', include : '*.jar') //添加該行 }
重新導入即可。
2.1.目錄結構
asm:一個 Java 字節(jié)碼操控框架。它能夠以二進制形式修改已有類或者動態(tài)生成類。ASM 可以直接產(chǎn)生二進制 class 文件,也可以在類被加載入 Java 虛擬機之前動態(tài)改變類行為。ASM 從類文件中讀入信息后,能夠改變類行為,分析類信息,甚至能夠根據(jù)用戶要求生成新類
cglib:一個功能強大,高性能的代碼生成包。它為沒有實現(xiàn)接口的類提供代理,為 jdk 的動態(tài)代理提供了很好的補充。通??梢允褂?Java 的動態(tài)代理創(chuàng)建代理,但當要代理的類沒有實現(xiàn)接口或者為了更好的性能,cglib 是一個好的選擇
core:核心包
- 根目錄:別名注冊、屬性訪問。
- annotation:注解、元注解、合并的注解等。
- codec:encode 和 decode 輸入流 (主要用于編解碼) 。
- convert:主要是轉換器服務,將一個類型轉換位另外一個類型。
- env:系統(tǒng)環(huán)境 (jdk環(huán)境參數(shù):System.getProperties()、System.getenv())。
- io:訪問資源(主要是文件系統(tǒng),也有字節(jié)流、jboss VFS)的工具
- serializer:java 對象的字節(jié)流序列化和反序列化工具。
- style:用來控制 java 對象輸出為 String 的風格。
- task:封裝了一套同步和異步執(zhí)行任務的 executor (并未使用線程池)。
- type:訪問 class meta 的工具。
lang:注解定義
objenesis:用于實例化一個特定 class 的對象,在類庫中經(jīng)常會有類必須擁有一個默認構造器的限制。Objenesis 通過繞開對象實例構造器來克服這個限制。常見使用場景有:
序列化,遠程調(diào)用和持久化對象需要實例化并存儲為到一個特殊的狀態(tài),而沒有調(diào)用代碼
代理,AOP 庫和 Mock 對象,類可以被子類繼承而子類不用擔心父類的構造器
容器框架,對象可以以非標準的方式被動態(tài)實例化
util:工具類,這個包的工具類可以獨立于 Spring 框架而存在;而 core 工具類主要還是為 Spring 框架所用,與 Spring 結合比較緊密。
2.2.源碼說明
- 這里的部分并不常用,或者說其原理不是很重要,簡單說明。
2.2.1.asm類解讀
*Visitor 抽象類:包含 AnnotationVisitor、ClassVisitor、FieldVisitor、MethodVisitor、ModuleVisitor 五個抽象類:
- AnnotationVisitor:定義在解析注解時會觸發(fā)的事件,如解析到一個基本值類型的注解、Enum 值類型的注解、Array 值類型的注解、注解值類型的注解等。
- ClassVisitor:定義在讀取 Class 字節(jié)碼時會觸發(fā)的事件,如類頭解析完成、注解解析、字段解析、方法解析等。
- FieldVisitor:定義在解析字段時觸發(fā)的事件,如解析到字段上的注解、解析到字段相關的屬性等。
- MethodVisitor:定義在解析方法時觸發(fā)的事件,如方法上的注解、屬性、代碼等。
- ModuleVisitor:定義在訪問 Module 時觸發(fā)的事件,如訪問包等。
*Writer:包含 AnnotationWriter、ClassWriter、FieldWriter、MethodWriter、ModuleWriter 五個類,分別繼承上述對應的 *Visitor 抽象類,用于生成上述對應類型的二進制字節(jié)碼。
- ClassReader:類的讀取解析。
- Attribute:字節(jié)碼中屬性的類抽象。
- ByteVector:字節(jié)碼二進制存儲的容器。
- Opcodes:字節(jié)碼指令的一些常量定義。
- Type*:類型相關的常量定義以及一些基于其上的操作。
2.2.2.core
annotation:注解、元注解、合并的注解等,注解相關的類和操作都在該包下,主要包含兩大部分,一部分是關于 @AliasFor 注解的定義及其相關的使用說明,如AnnotationUtils,AnnotationAttributes 等類都是為了 @AliasFor 注解的使用做出的相關配套設施,另一部分是 @Order 類及其相關配套:
第一部分:@AliasFor 及其部分相關配套
@AliasFor:該注解相關信息必須通過 AnnotationUtils 加載,通常有以下幾種用法:
別名,在注解定義中的屬性上使用,如 @RequestMapping 注解的 path 和 value 屬性,指定其中一個即指定了另一個:
@Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Mapping public @interface RequestMapping { String name() default ""; @AliasFor("path") String[] value() default {}; @AliasFor("value") String[] path() default {}; RequestMethod[] method() default {}; String[] params() default {}; String[] headers() default {}; String[] consumes() default {}; String[] produces() default {}; }
繼承父注解的屬性,不重寫屬性名,子注解的屬性值的讀寫,其實是對父注解的屬性值的讀寫,如@Service、@Controller 等價于 @Component:
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Component //注意要聲明父注解 public @interface Service { /** * The value may indicate a suggestion for a logical component name, * to be turned into a Spring bean in case of an autodetected component. * @return the suggested component name, if any (or empty String otherwise) */ @AliasFor(annotation = Component.class) String value() default ""; } @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Component //注意要聲明父注解 public @interface Controller { /** * The value may indicate a suggestion for a logical component name, * to be turned into a Spring bean in case of an autodetected component. * @return the suggested component name, if any (or empty String otherwise) */ @AliasFor(annotation = Component.class) String value() default ""; }
繼承父注解的屬性,并重寫屬性名,需指定父注解的類型以及具體的屬性,子注解的屬性值的讀寫,其實是對父注解的屬性值的讀寫,若兩個都指明屬性值,要求值必須相同,否則會報錯。
@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD, ElementType.TYPE}) @Documented @Inherited public @interface MyAnnotation { @AliasFor(attribute = "location") String value() default ""; @AliasFor(attribute = "value") String location() default ""; } @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD, ElementType.TYPE}) @Documented @Inherited @MyAnnotation //注意要聲明父注解 public @interface SubMyAnnotation { @AliasFor(attribute = "value", annotation = MyAnnotation.class) String subValue() default ""; @AliasFor(attribute = "location", annotation = MyAnnotation.class) String subLocation() default ""; // subLocation屬性寫成下邊這兩種結果是一樣的 // @AliasFor(attribute = "value", annotation = MyAnnotation.class) // String subLocation() default ""; // @AliasFor(value = "location", annotation = MyAnnotation.class) // String subLocation() default ""; // }
注解的疊加復用,如@SpringBootApplication,在注解定義上配置需要復用的注解,并在指定屬性上聲明復用的注解屬性 (本質(zhì)上還是相當于實現(xiàn)了注解的繼承功能):
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @SpringBootConfiguration @EnableAutoConfiguration @ComponentScan( excludeFilters = {@Filter( type = FilterType.CUSTOM, classes = {TypeExcludeFilter.class} ), @Filter( type = FilterType.CUSTOM, classes = {AutoConfigurationExcludeFilter.class} )} ) public @interface SpringBootApplication { //復用@EnableAutoConfiguration注解的exclude屬性 @AliasFor(annotation = EnableAutoConfiguration.class) Class<?>[] exclude() default {}; //復用@EnableAutoConfiguration注解的excludeName屬性 @AliasFor(annotation = EnableAutoConfiguration.class) String[] excludeName() default {}; //復用@ComponentScan的basePackages屬性 @AliasFor(annotation = ComponentScan.class,attribute = "basePackages") String[] scanBasePackages() default {}; //復用@ComponentScan的basePackageClasses屬性 @AliasFor(annotation = ComponentScan.class,attribute = "basePackageClasses") Class<?>[] scanBasePackageClasses() default {}; }
@AliasFor 注解需要通過 Spring 中提供的工具類 AnnotationUtils 或 AnnotatedElementUtils 來解析才能生效。AnnotatedElementUtils 內(nèi)部還是調(diào)用的 AnnotationUtils。互為別名的屬性值,使用的時候如果均賦值,同時又通過 AnnotationUtils 的 findAnnotation 方法獲取屬性值,那么會拋出異常。Spring 其實是自己實現(xiàn)了 jdk 動態(tài)的攔截器來實現(xiàn)別名功能。
@RequestMapping("/test") public class App{} @Test public void test(){ RequestMapping springAnnotationProxy = AnnotationUtils.findAnnotation(App.class, RequestMapping.class); System.out.println("springAnnotationProxy path:" + springAnnotationProxy.path()); System.out.println("springAnnotationProxy value:" + springAnnotationProxy.value()); RequestMapping jdkAnnotation = App.class.getAnnotation(RequestMapping.class); System.out.println("jdkAnnotation path:" + jdkAnnotation.path()); System.out.println("jdkAnnotation value:" + jdkAnnotation.value()); }
debug 功能可以看出,springAnnotationProxy 的底層類型是 SynthesizedAnnotationInvocationHandler,其 value 和 path 屬性值均為 test,而 jdkAnnotation 的底層類型是 AnnotationInvocationHandler,其只有 value 屬性值為 test,path 屬性為空。
AnnotationAttributes:本質(zhì)上是一個 Map<String, Object> 集合,繼承 LinkedHashMap,用于保存某個注解實例的全部屬性 (屬性名和對應的屬性值) 。
AnnotationAwareOrderComparator:對 OrderComparator 的增強 (繼承) ,可以對 Ordered 對象、使用 @Order 注解的對象進行比較。與OrderComparator 一樣提供了如上兩個靜態(tài)的排序方法。
AnnotationUtils 中包含了很多解析注解的方法:
public static <A extends Annotation> A getAnnotation(Annotation ann, Class<A> annotationType) public static <A extends Annotation> A getAnnotation(AnnotatedElement annotatedElement, Class<A> annotationType) public static <A extends Annotation> A getAnnotation(Method method, Class<A> annotationType) public static Annotation[] getAnnotations(AnnotatedElement annotatedElement) public static Annotation[] getAnnotations(Method method) // 獲取函數(shù)上或注解上的注解 - public static <A extends Annotation> A findAnnotation(AnnotatedElement annotatedElement, Class<A> annotationType) public static <A extends Annotation> A findAnnotation(Method method, @Nullable Class<A> annotationType) public static <A extends Annotation> A findAnnotation(Class<?> clazz, Class<A> annotationType) 遞歸查找父注解 - public static boolean isAnnotationDeclaredLocally(Class<? extends Annotation> annotationType, Class<?> clazz) public static boolean isAnnotationInherited(Class<? extends Annotation> annotationType, Class<?> clazz) public static boolean isAnnotationMetaPresent(Class<? extends Annotation> annotationType, @Nullable Class<? extends Annotation> metaAnnotationType) 是否聲明了/繼承了某個注解 - public static Map<String, Object> getAnnotationAttributes(Annotation annotation) public static Map<String, Object> getAnnotationAttributes(Annotation annotation, boolean classValuesAsString) public static AnnotationAttributes getAnnotationAttributes(@Nullable AnnotatedElement annotatedElement, Annotation annotation) 獲取注解的所有屬性,返回 AnnotationAttributes - public static Object getValue(Annotation annotation) public static Object getValue(@Nullable Annotation annotation, @Nullable String attributeName) public static Object getDefaultValue(Annotation annotation) public static Object getDefaultValue(@Nullable Annotation annotation, @Nullable String attributeName) public static Object getDefaultValue(Class<? extends Annotation> annotationType) public static Object getDefaultValue(@Nullable Class<? extends Annotation> annotationType, @Nullable String attributeName) 獲取注解的值以及默認值
第二部分:@Order 及其部分相關配套
- @Order:@Order 注解定義了類、方法和字段的優(yōu)先級(排序情況),value 是可選的,默認為 Ordered.LOWEST_PRECEDENCE,即最低優(yōu)先級。表示 Ordered 接口中的 order 屬性。
- OrderUtils:主要用于獲取 @Order 注解的相關屬性。
- OrderComparator(根目錄下):Ordered 對象的比較器,對外提供了兩個靜態(tài)的排序方法
public static void sort(List<?> list)、public static void sort(Object[] array)。
codec:編解碼工具,不常用,略
convert:主要用于類型轉換
TypeDescriptor:對java中所有數(shù)據(jù)類型的描述,包括 Collection、Map、自定義 Object、數(shù)組、基本數(shù)據(jù)類型及其包裝類型。它被成對地用于GenericConversionService 中,表示從 src 類型到 target 類型的轉換,會有一個相應的 converter 實現(xiàn)與之對應。為了提高性能,對基本數(shù)據(jù)類型及其包裝類型做了緩存。
DefaultConversionService:對外提供的 API 的主要服務類
env:提供一個代表系統(tǒng)環(huán)境的 StandardEnvironment,包括對 System.getProperties()、System.getenv() 的訪問。在 Spring Web 中StandardServletEnvironment 繼承了 StandardEnvironment,又增加對 Servlet 容器系統(tǒng)參數(shù)的訪問。
io:供外部調(diào)用的入口API主要是 FileSystemResourceLoader、ClassRelativeResourceLoader。spring web、spring context 等模塊也會自定義覆蓋/實現(xiàn)ResourceLoader的子類和子接口。
serializer: java 對象的字節(jié)流序列化和反序列化
style:略
task:實現(xiàn)了兩個適配器,分別是
并提供了一個簡單的異步 TaskExecutor 的實現(xiàn) SimpleAsyncTaskExecutor。它繼承 CustomizableThreadCreator,用來創(chuàng)建線程并給線程命名(有意義的名字),需要注意的是它并沒有使用線程池。ConcurrencyThrottleSupport 在 SimpleAsyncTaskExecutor 中用于控制并發(fā),應用了生產(chǎn)者-消費者模式。
- TaskExecutorAdapter:將 Executor 適配為 TaskExecutor
- ExecutorServiceAdapter,將 TaskExecutor 適配為 Executor/ExecutorService。
type:對外暴露的、可直接使用的API接口為 CachingMetadataReaderFactory?;?ASM,使用了訪問者設計模式,同時使用 cache 來避免了對 class 的重復加載和解析。
根目錄的部分其他類:
- AliasRegistry、SimpleAliasRegistry:別名注冊,保存的是Map from alias to canonical name,解決了別名循環(huán)的問題。
- CollectionFactory:提供了幾個靜態(tài)函數(shù),Collection createCollection(Class collectionType, int initialCapacity),Map createMap(Class mapType, int initialCapacity) 等等。
- Constants:提供了對一個類中 public static final 且名字為大寫的字段的訪問。
- ControlFlow、ControlFlowFactory:判斷當前運行的堆棧中是否包含某個class或某個函數(shù)。
- DecoratingClassLoader:供其他包來繼承的一個抽象類,加載時可以讓使用者有選擇的排除掉某些class和某些package下的class。
- ExceptionDepthComparator:用來比較exception和targetException深度的比較器。對外提供了一個靜態(tài)工具方法:Class<? extends Throwable> findClosestMatch(Collection<Class<? extends Throwable>> exceptionTypes, Throwable targetException)
- GenericCollectionTypeResolver:一個很有用、很強大的工具類,用來獲取Collection中元素的類型、Map中的Key/Value類型。
- GenericTypeResolver:工具類,獲取函數(shù)的參數(shù)類型、返回類型。
- LocalVariableTableParameterNameDiscoverer、ParameterNameDiscoverer:基于ASM,獲取函數(shù)和構造器的參數(shù)名字。
- SpringProperties:加載spring.properties到Properties中,并訪問其中的屬性。
2.2.3.util
- BooleanComparator:對 Boolean 數(shù)據(jù)進行排序(順序和逆序)用到的比較器;
- ComparableComparator:適配 Comparable 的 Comparator。
- CompoundComparator:組合模式。多個 Comparator 組合成一個 Comparator
- InstanceComparator:基于任意類順序比較對象。
- InvertibleComparator:可逆的比較器 (裝飾者設計模式)。
- NullSafeComparator:將一個只能對非 null 對象排序的比較器,裝飾成一個可以對 null 對象排序的比較器 (裝飾者設計模式)。
- ClassUtils:class 工具類。包括裝載 class,訪問 class 名字、包名、函數(shù)、構造器、class類型是否是基本類型、接口等等。
- CollectionUtils:封裝了少數(shù)幾個集合工具的靜態(tài)方法。
- CompositeIterator:多個迭代期組合到一起的迭代期,使用了組合設計模式。
- ConcurrentReferenceHashMap:與 ConcurrentHashMap 不同的是,它的 key 和 value 存的是是軟引用或弱引用 (可在構造器中指定)
- PropertiesPersister、DefaultPropertiesPersister:實現(xiàn)了對 Properties 的加載和存儲。
- FileCopyUtils:在File、byte[]、IO流之間的轉換。
- FileSystemUtils:對文件、文件夾的遞歸刪除和遞歸拷貝。
- LinkedCaseInsensitiveMap:對 key 大小寫不敏感的 LinkedHashMap 實現(xiàn)。
- LinkedMultiValueMap:一個 key 映射多個 value 的 Map 結構。
- MethodInvoker:將方法、object、class、方法參數(shù)封裝進來,以供該方法的調(diào)用。
- NumberUtils:將 String 轉為制定類型的 Number。
- ObjectUtils:實現(xiàn)了 基本數(shù)據(jù)類型及其數(shù)組的 hashcode,基本數(shù)據(jù)類型及其數(shù)組的 toString() 方法等等。
- PropertyPlaceholderHelper:提供了兩個重載函數(shù) replacePlaceholders,用來替換字符串 value 中中的占位符。
- ReflectionUtils:提供了訪問field、method、函數(shù)調(diào)用等工具方法。
- SerializationUtils:java 序列化和反序列化
- ResourceUtils:用于將資源位置解析為文件系統(tǒng)中的文件的實用工具方法。
- StopWatch:對任務執(zhí)行時間的統(tǒng)計,包括總時間、每個任務的執(zhí)行時間。
- StreamUtils:IO 字節(jié)流和字節(jié)數(shù)組之間的拷貝。
- StringUtils:字符串為空、長度、trim 操作、等等。
- SystemPropertyUtils:與 PropertyPlaceholderHelper 不同的是,它使用 System.getProperty() 來替換占位符。
- TypeUtils:boolean isAssignable(Type lhsType, Type rhsType)
2.2.4.cglib&langobjenesis
- 這里主要引用了一些 jar 包以及部分類的聲明,內(nèi)容很少,不再贅述。
以上就是Spring jcl及spring core源碼深度解析的詳細內(nèi)容,更多關于Spring jcl spring core解析的資料請關注腳本之家其它相關文章!
相關文章
Java技巧函數(shù)方法實現(xiàn)二維數(shù)組遍歷
這篇文章主要介紹了Java技巧函數(shù)方法實現(xiàn)二維數(shù)組遍歷,二維數(shù)組遍歷,每個元素判斷下是否為偶數(shù),相關內(nèi)容需要的小伙伴可以參考一下2022-08-08Springboot整合Dubbo教程之項目創(chuàng)建和環(huán)境搭建
本篇文章主要介紹了Springboot整合Dubbo教程之項目創(chuàng)建和環(huán)境搭建,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-12-12elasticsearch索引創(chuàng)建create?index集群matedata更新
這篇文章主要介紹了elasticsearch索引創(chuàng)建create?index及集群matedata更新,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-04-04Java使用itextpdf實現(xiàn)生成PDF并添加圖片,水印和文字
這篇文章主要為大家詳細介紹了Java在使用itextpdf實現(xiàn)生成PDF時如何實現(xiàn)添加圖片,水印和文字等效果,感興趣的小伙伴可以跟隨小編一起學習一下2024-02-02Java如何獲取resources下的文件路徑和創(chuàng)建臨時文件
這篇文章主要介紹了Java如何獲取resources下的文件路徑和創(chuàng)建臨時文件,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-12-12