mybatis中mapper代理的生成過程全面分析
mybatis中mapper代理的生成過程
構(gòu)建代理類工廠
從入口點開始一步一步看,首先SqlSessionFactoryBuilder類中build()方法加載配置文件
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
// ...省略
}
}
將配置文件讀取為XMLConfigBuilder對象,并調(diào)用parse()方法來解析文件,進到parse()中
public Configuration parse() {
// ...省略
parsed = true;
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
可以看到具體的解析過程是在parseConfiguration方法中進行的。
private void parseConfiguration(XNode root) {
try {
// ...省略
//解析mapper
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}這里重點看一下最后解析mapper的方法mapperElement(root.evalNode("mappers")),進到方法里,
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
// package 形式加載 ,加載package下的所有class文件
String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);
} else {
String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
if (resource != null && url == null && mapperClass == null) {
// 通過Mapper.xml 加載
ErrorContext.instance().resource(resource);
InputStream inputStream = Resources.getResourceAsStream(resource);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url != null && mapperClass == null) {
// 通過Mapper.xml 加載
ErrorContext.instance().resource(url);
InputStream inputStream = Resources.getUrlAsStream(url);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url == null && mapperClass != null) {
// 通過單個class文件加載
Class<?> mapperInterface = Resources.classForName(mapperClass);
configuration.addMapper(mapperInterface);
} else {
throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
}
}
}
}
}整個mapperElement()方法就是加載mapper的過程了,可以看到加載mapper
有兩種形式:通過class文件和通過xml文件。
構(gòu)建mapper代理的過程也就是從這開始的,那就一步一步分析。
看一下通過XML文件加載的過程,mybatis將mapper相關(guān)的配置讀取為一個XMLMapperBuilder對象,并通過parse()方法進行解析,進到這個方法中
public void parse() {
if (!configuration.isResourceLoaded(resource)) {
// 加載xml文件
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
// 加載mapper class文件
bindMapperForNamespace();
}
// ...省略
}
parse()方法做了主要做了兩件事,加載xml文件和加載class文件。
看一下加載xml的過程
private void configurationElement(XNode context) {
try {
// 獲取xml文件的namespace
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.equals("")) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
// 保存獲取xml文件的namespace
builderAssistant.setCurrentNamespace(namespace);
// ...省略
} catch (Exception e) {
throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
}
}本文是分析mapper代理的生成過程,所以加載xml的具體細節(jié)就不詳細分析了,這里注意的是讀取xml文件中namespace標(biāo)簽的值,并將值設(shè)置到builderAssistant對象中
現(xiàn)在回過頭來看一下加載class文件的過程。進到bindMapperForNamespace()方法中去
private void bindMapperForNamespace() {
// 獲取xml文件中設(shè)置的namespace值
String namespace = builderAssistant.getCurrentNamespace();
if (namespace != null) {
Class<?> boundType = null;
try {
// 加載類
boundType = Resources.classForName(namespace);
} catch (ClassNotFoundException e) {
//ignore, bound type is not required
}
if (boundType != null) {
if (!configuration.hasMapper(boundType)) {
// Spring may not know the real resource name so we set a flag
// to prevent loading again this resource from the mapper interface
// look at MapperAnnotationBuilder#loadXmlResource
configuration.addLoadedResource("namespace:" + namespace);
// 添加到configuration中
configuration.addMapper(boundType);
}
}
}
}bindMapperForNamespace()通過xml文件中設(shè)置的namespace值加載對應(yīng)的mapper接口,最后通過configuration.addMapper()添加到configuration中。
還記不記得剛才提到的加載mapper有兩種形式:通過class文件和通過xml文件。通過class文件的方式直接調(diào)用configuration.addMapper()將mapper接口加載到了configuration 中了。
Configuration是mybatis的全局配置類,所有的mybatis相關(guān)的信息都保存在Configuration中。
繼續(xù)進到Configuration的addMapper方法中
public <T> void addMapper(Class<T> type) {
mapperRegistry.addMapper(type);
}Configuration把對應(yīng)的mapper接口添加到mapperRegistry中,再進到mapperRegistry.addMapper()方法中
public <T> void addMapper(Class<T> type) {
if (type.isInterface()) {
// ...省略
try {
knownMappers.put(type, new MapperProxyFactory<T>(type));
// ...省略
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}該方法首先判斷是否是接口,如果是接口則將mapper接口添加到knownMappers中。
看一下knownMappers的定義
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();
knownMappers是一個HashMap,它保存的是所有的mapper接口和對應(yīng)的mapper代理工廠。
到現(xiàn)在為止,mapper已經(jīng)加載完了,但是并沒有生成mapper的代理對象,只是生成了對應(yīng)的代理工廠。
生成并使用代理對象
mybatis并沒有在加載mapper接口的時候生成代理對象,而是在調(diào)用的時候生成的。
首先從入口開始
sqlSession.getMapper(XXX.class)
sqlSession默認(rèn)是DefaultSqlSession。進到DefaultSqlSession的getMapper()方法中
@Override
public <T> T getMapper(Class<T> type) {
return configuration.<T>getMapper(type, this);
}繼續(xù)到Configuration的getMapper中
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}繼續(xù)到mapperRegistry.getMapper()中
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
// ...省略
}
try {
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}從knownMappers中獲取到對應(yīng)mapper接口的代理工廠類MapperProxyFactory,然后通過MapperProxyFactory獲取真正的代理對象。進到MapperProxyFactory的newInstance()方法中
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}首先生成了MapperProxy類,再通過Proxy生成真正的代理類。
看一下MapperProxy類
public class MapperProxy<T> implements InvocationHandler, Serializable {
// ...省略
}MapperProxy實現(xiàn)了InvocationHandler接口,mapper接口的具體處理邏輯也就是在這類中處理。
到此為止,代理對象才真正的生成。
與Spring集成時mapper代理的生成過程
mybatis與Spring集成時需要用到mybatis-spring的jar。
Spring注冊mapper代理類
既然是與Spring集成,那么就要配置一下,將mybatis交給Spring管理。
spring的xml文件配置
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="driverClassName"/>
<property name="url" value="url"/>
<property name="username" value="username"/>
<property name="password" value="password"/>
</bean>
<!--sqlSessionFactory-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<!--綁定mybatis配置文件-->
<property name="configLocation" value="classpath:mybatis-config.xml"/>
<!--注冊Mapper.xm映射器-->
<property name="mapperLocations" value="classpath:cn/ycl/mapper/*.xml"/>
</bean>
<!--注冊所有mapper-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!--basePackage 屬性是映射器接口文件的包路徑。-->
<!--你可以使用分號或逗號 作為分隔符設(shè)置多于一個的包路徑-->
<property name="basePackage" value="cn/ycl/mapper"/>
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
</bean>將mybatis交給Spring只需要配置3個bean就可以了
1、 數(shù)據(jù)庫相關(guān)的dataSource
2、 mybatis的sqlSessionFactory
3、 將mapper委托給Spring的工具類MapperScannerConfigurer生成mapper代理的過程主要在MapperScannerConfigurer里,看一下MapperScannerConfigurer的定義
public class MapperScannerConfigurer
implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {
// ...省略
}
關(guān)鍵點在MapperScannerConfigurer 實現(xiàn)了BeanDefinitionRegistryPostProcessor,BeanDefinitionRegistryPostProcessor是Spring留的擴展點,可以往Spring中注冊自定義的bean。
MapperScannerConfigurer中實現(xiàn)了BeanDefinitionRegistryPostProcessor的postProcessBeanDefinitionRegistry()方法,mapper的注冊就是在該方法中注冊的
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
// ...省略
// 實例化ClassPathMapperScanner,并對scanner相關(guān)屬性進行配置
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
scanner.setAddToConfig(this.addToConfig);
scanner.setAnnotationClass(this.annotationClass);
scanner.setMarkerInterface(this.markerInterface);
scanner.setSqlSessionFactory(this.sqlSessionFactory);
scanner.setSqlSessionTemplate(this.sqlSessionTemplate);
scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);
scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);
scanner.setResourceLoader(this.applicationContext);
scanner.setBeanNameGenerator(this.nameGenerator);
scanner.setMapperFactoryBeanClass(this.mapperFactoryBeanClass);
if (StringUtils.hasText(lazyInitialization)) {
scanner.setLazyInitialization(Boolean.valueOf(lazyInitialization));
}
if (StringUtils.hasText(defaultScope)) {
scanner.setDefaultScope(defaultScope);
}
// 注冊掃描規(guī)則
scanner.registerFilters();
// 掃描并注冊所有的mapper
scanner.scan(
StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}postProcessBeanDefinitionRegistry()的主要邏輯是定義一個ClassPathMapperScanner對象,然后調(diào)用registerFilters()注冊掃描規(guī)則,最后調(diào)用scan()方法。
在xml中定義MapperScannerConfigurerbean時可以設(shè)置一個annotationClass屬性,值是一個注解類,調(diào)用registerFilters()時,registerFilters()會添加一個只掃描設(shè)置有annotationClass注解的類,這里沒有設(shè)置,會掃描所有的接口。SpringBoot集成mybatis時會用到這個字段
看一下ClassPathMapperScanner類的定義
public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner {
// ...省略
}
ClassPathMapperScanner繼承了ClassPathBeanDefinitionScanner,ClassPathBeanDefinitionScanner是Spring中定義的,是一個從指定包內(nèi)掃描所有bean定義的Spring工具。
看一下ClassPathMapperScanner的scan()方法
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
if (beanDefinitions.isEmpty()) {
// ...省略
} else {
processBeanDefinitions(beanDefinitions);
}
return beanDefinitions;
}
通過super.doScan(basePackages)已經(jīng)掃描到了所有的mapper,繼續(xù)processBeanDefinitions()方法
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
AbstractBeanDefinition definition;
BeanDefinitionRegistry registry = getRegistry();
// 遍歷掃描到的所有bean
for (BeanDefinitionHolder holder : beanDefinitions) {
definition = (AbstractBeanDefinition) holder.getBeanDefinition();
boolean scopedProxy = false;
if (ScopedProxyFactoryBean.class.getName().equals(definition.getBeanClassName())) {
definition = (AbstractBeanDefinition) Optional
.ofNullable(((RootBeanDefinition) definition).getDecoratedDefinition())
.map(BeanDefinitionHolder::getBeanDefinition).orElseThrow(() -> new IllegalStateException(
"The target bean definition of scoped proxy bean not found. Root bean definition[" + holder + "]"));
scopedProxy = true;
}
String beanClassName = definition.getBeanClassName();
LOGGER.debug(() -> "Creating MapperFactoryBean with name '" + holder.getBeanName() + "' and '" + beanClassName
+ "' mapperInterface");
// 增加一個構(gòu)造方法,接口類型作為構(gòu)造函數(shù)的入?yún)?
definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName);
// 將bean的類型轉(zhuǎn)換成mapperFactoryBean
definition.setBeanClass(this.mapperFactoryBeanClass);
// 增加addToConfig屬性
definition.getPropertyValues().add("addToConfig", this.addToConfig);
definition.setAttribute(FACTORY_BEAN_OBJECT_TYPE, beanClassName);
boolean explicitFactoryUsed = false;
// 增加sqlSessionFactory屬性
if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {
definition.getPropertyValues().add("sqlSessionFactory",
new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
explicitFactoryUsed = true;
} else if (this.sqlSessionFactory != null) {
definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
explicitFactoryUsed = true;
}
// 增加sqlSessionTemplate屬性
if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {
if (explicitFactoryUsed) {
LOGGER.warn(
() -> "Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
}
definition.getPropertyValues().add("sqlSessionTemplate",
new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
explicitFactoryUsed = true;
} else if (this.sqlSessionTemplate != null) {
if (explicitFactoryUsed) {
LOGGER.warn(
() -> "Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
}
definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
explicitFactoryUsed = true;
}
if (!explicitFactoryUsed) {
LOGGER.debug(() -> "Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
}
definition.setLazyInit(lazyInitialization);
if (scopedProxy) {
continue;
}
if (ConfigurableBeanFactory.SCOPE_SINGLETON.equals(definition.getScope()) && defaultScope != null) {
definition.setScope(defaultScope);
}
if (!definition.isSingleton()) {
BeanDefinitionHolder proxyHolder = ScopedProxyUtils.createScopedProxy(holder, registry, true);
if (registry.containsBeanDefinition(proxyHolder.getBeanName())) {
registry.removeBeanDefinition(proxyHolder.getBeanName());
}
registry.registerBeanDefinition(proxyHolder.getBeanName(), proxyHolder.getBeanDefinition());
}
}
}這個方法比較長,但是并不復(fù)雜,主要邏輯為將掃描的bean的類型修改成MapperFactoryBean類型,并增加一個將接口類型作為入?yún)⒌臉?gòu)造函數(shù),也就是說Spring獲取mapper時都是通過FactoryBean生成的。最后通過調(diào)用egistry.registerBeanDefinition() 方法注冊到Spring中。
看一下mybatis提供的MapperFactoryBean的定義
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
}MapperFactoryBean實現(xiàn)了FactoryBean,FactoryBean是一個Spring提供的一個能生產(chǎn)對象的工廠Bean
MapperFactoryBean同時繼承了SqlSessionDaoSupport,SqlSessionDaoSupport繼承了DaoSupport,DaoSupport實現(xiàn)了InitializingBean。InitializingBean的作用是在Spring初始化bean對象時會首先調(diào)用InitializingBean的afterPropertiesSet()方法。
DaoSupport的afterPropertiesSet()中調(diào)用了checkDaoConfig()方法。
public final void afterPropertiesSet() throws IllegalArgumentException, BeanInitializationException {
this.checkDaoConfig();
try {
this.initDao();
} catch (Exception var2) {
throw new BeanInitializationException("Initialization of DAO failed", var2);
}
}
具體checkDaoConfig()方法的實現(xiàn)邏輯在MapperFactoryBean 中
protected void checkDaoConfig() {
super.checkDaoConfig();
notNull(this.mapperInterface, "Property 'mapperInterface' is required");
Configuration configuration = getSqlSession().getConfiguration();
if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
try {
configuration.addMapper(this.mapperInterface);
} catch (Exception e) {
// ..省略
} finally {
ErrorContext.instance().reset();
}
}
}
OK,到這又回到mybatis了。在前面中說了configuration.addMapper()方法只是生成了對應(yīng)的代理工廠。
以上整個過程,即把mapper注冊為Spring的bean,又將mapper設(shè)置到mybatis中的configuration中,所以,在使用時既可以使用Spring自動注入那一套,又可以使用mybatis中通過sqlSession來獲取mapper的代理對象
Spring生成代理對象
Spring中所有的mapper對應(yīng)的bean是mapper對應(yīng)的MapperFactoryBean,那么在獲取mapper bean時是通過MapperFactoryBean的getObject()方法生成的
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
MapperFactoryBean先獲取到sqlsession,再通過getMapper()獲取到的代理對象。到這里就回到了mybatis生成代理對象的過程了。
與SpringBoot集成時mapper代理的生成過程
mybatis與Spring集成時需要用到mybatis-spring-boot-starter的jar,mybatis-spring-boot-starter依賴mybatis-spring-boot-autoconfigure這個jar,而mybatis-spring-boot-autoconfigure這個jar又依賴mybatis-spring這個jar,所以最終其實還是mybatis集成Spring那一套
根據(jù)SpringBoot自動加載的原理直接看mybatis-spring-boot-autoconfigurejar下META-INF/spring.factories文件
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.mybatis.spring.boot.autoconfigure.MybatisLanguageDriverAutoConfiguration,\ org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration
SpringBoot會自動加載MybatisAutoConfiguration這個類,直接看這個類,MybatisAutoConfiguration定義了mybtis所需的各個bean。
//生成SqlSessionFactory
@Bean
@ConditionalOnMissingBean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
// ...省略
}
//生成SqlSessionTemplate
@Bean
@ConditionalOnMissingBean
public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
// ...省略
}
//掃描mapper
@Configuration
@Import({MybatisAutoConfiguration.AutoConfiguredMapperScannerRegistrar.class})
@ConditionalOnMissingBean({MapperFactoryBean.class, MapperScannerConfigurer.class})
public static class MapperScannerRegistrarNotFoundConfiguration implements InitializingBean {
public MapperScannerRegistrarNotFoundConfiguration() {
}
public void afterPropertiesSet() {
MybatisAutoConfiguration.logger.debug("Not found configuration for registering mapper bean using @MapperScan, MapperFactoryBean and MapperScannerConfigurer.");
}
}
//掃描mapper
public static class AutoConfiguredMapperScannerRegistrar implements BeanFactoryAware, ImportBeanDefinitionRegistrar {
private BeanFactory beanFactory;
public AutoConfiguredMapperScannerRegistrar() {
}
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
if (!AutoConfigurationPackages.has(this.beanFactory)) {
MybatisAutoConfiguration.logger.debug("Could not determine auto-configuration package, automatic mapper scanning disabled.");
} else {
MybatisAutoConfiguration.logger.debug("Searching for mappers annotated with @Mapper");
List<String> packages = AutoConfigurationPackages.get(this.beanFactory);
if (MybatisAutoConfiguration.logger.isDebugEnabled()) {
packages.forEach((pkg) -> {
MybatisAutoConfiguration.logger.debug("Using auto-configuration base package '{}'", pkg);
});
}
//生成MapperScannerConfigurer
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
builder.addPropertyValue("processPropertyPlaceHolders", true);
// 注冊掃描規(guī)則
builder.addPropertyValue("annotationClass", Mapper.class);
builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(packages));
BeanWrapper beanWrapper = new BeanWrapperImpl(MapperScannerConfigurer.class);
Stream.of(beanWrapper.getPropertyDescriptors()).filter((x) -> {
return x.getName().equals("lazyInitialization");
}).findAny().ifPresent((x) -> {
builder.addPropertyValue("lazyInitialization", "${mybatis.lazy-initialization:false}");
});
registry.registerBeanDefinition(MapperScannerConfigurer.class.getName(), builder.getBeanDefinition());
}
}
public void setBeanFactory(BeanFactory beanFactory) {
this.beanFactory = beanFactory;
}
}
以上就是mybatis中mapper代理的生成過程全面分析的詳細內(nèi)容,更多關(guān)于mybatis mapper代理生成的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
SpringMVC 參數(shù)綁定相關(guān)知識總結(jié)
這篇文章主要介紹了SpringMVC 參數(shù)綁定相關(guān)知識總結(jié),幫助大家更好的理解和學(xué)習(xí)使用SpringMVC,感興趣的朋友可以了解下2021-03-03
java8 LocalDate LocalDateTime等時間類用法實例分析
這篇文章主要介紹了java8 LocalDate LocalDateTime等時間類用法,結(jié)合具體實例形式分析了LocalDate、LocalTime、LocalDateTime等日期時間相關(guān)類的功能與具體使用技巧,需要的朋友可以參考下2017-04-04
Java中System.setProperty()用法與實際應(yīng)用場景
System.setProperty是Java中用于設(shè)置系統(tǒng)屬性的方法,它允許我們在運行時為Java虛擬機(JVM)或應(yīng)用程序設(shè)置一些全局的系統(tǒng)屬性,下面這篇文章主要給大家介紹了關(guān)于Java中System.setProperty()用法與實際應(yīng)用場景的相關(guān)資料,需要的朋友可以參考下2024-04-04
Java實現(xiàn)的數(shù)字簽名算法RSA完整示例
這篇文章主要介紹了Java實現(xiàn)的數(shù)字簽名算法RSA,結(jié)合完整實例形式詳細分析了RSA算法的相關(guān)概念、原理、實現(xiàn)方法及操作技巧,需要的朋友可以參考下2019-09-09
MyBatis 實現(xiàn)數(shù)據(jù)的批量新增和刪除的操作
這篇文章主要介紹了MyBatis 實現(xiàn)數(shù)據(jù)的批量新增和刪除的操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2021-02-02
詳解Spring關(guān)于@Resource注入為null解決辦法
這篇文章主要介紹了詳解Spring關(guān)于@Resource注入為null解決辦法,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-05-05

