Spring3 整合MyBatis3 配置多数据源动态选择SqlSessionFactory详细教程

网友投稿 1480 2023-05-21


Spring3 整合MyBatis3 配置多数据源动态选择SqlSessionFactory详细教程

一、摘要

这篇文章将介绍Spring整合Mybatis 如何完成SqlSessionFactory的动态切换的。并且会简单的介绍下MyBatis整合Spring中的官方的相关代码。

Spring整合MyBatis切换SqlSessionFactory有两种方法

第一、 继承SqlSessionDaoSupport,重写获取SqlSessionFactory的方法。

第二、继承SqlSessionTemplate 重写getSqlSessionFactory、getConfiguration和SqlSessionInterceptor这个拦截器。其中最为关键还是继承SqlSessionTemplate 并重写里面的方法。

而Spring整合MyBatis也有两种方式,一种是配置MapperFactoryBean,另一种则是利用MapperScannerConfigurer进行扫描接口或包完成对象的自动创建。相对来说后者更方便些。

MapperFactoryBean继承了SqlSessionDaoSupport也就是动态切换SqlSessionFactory的第一种方法,我们需要重写和实现SqlSessionDaoSupport方法,或者是继承MapperFactoryBean来重写覆盖相关方法。如果利用MapperScannerConfigurer的配置整合来切换SqlSessionFactory,那么我们就需要继承SqlSessionTemplate,重写上面提到的方法。在整合的配置中很多地方都是可以注入SqlSessionTemplate代替SqlSessionFactory的注入的。因为SqlSessionTemplate的创建也是需要注入SqlSessionFactory的。

二、实现代码

1、继承SqlSessionTemplate 重写getSqlSessionFactory、getConfiguration和SqlSessionInterceptor

package com.hoo.framework.mybatis.support;

import static java.lang.reflect.Proxy.newProxyInstance;

import static org.apache.ibatis.reflection.ExceptionUtil.unwrapThrowable;

import static org.mybatis.spring.SqlSessionUtils.closeSqlSession;

import static org.mybatis.spring.SqlSessionUtils.getSqlSession;

import static org.mybatis.spring.SqlSessionUtils.isSqlSessionTransactional;

import java.lang.reflect.InvocationHandler;

import java.lang.reflect.Method;

import java.sql.Connection;

import java.util.List;

import java.util.Map;

import org.apache.ibatis.exceptions.PersistenceException;

impohttp://rt org.apache.ibatis.executor.BatchResult;

import org.apache.ibatis.session.Configuration;

import org.apache.ibatis.session.ExecutorType;

import org.apache.ibatis.session.ResultHandler;

import org.apache.ibatis.session.RowBounds;

import org.apache.ibatis.session.SqlSession;

import org.apache.ibatis.session.SqlSessionFactory;

import org.mybatis.spring.MyBatisExceptionTranslator;

import org.mybatis.spring.SqlSessionTemplate;

import org.springframework.dao.support.PersistenceExceptionTranslator;

import org.springframework.util.Assert;

/**

* function: 继承SqlSessionTemplate 重写相关方法

* @author hoojo

* @createDate 2013-10-18 下午03:07:46

* @file CustomSqlSessionTemplate.java

* @package com.hoo.framework.mybatis.support

* @project SHMB

* @blog http://blog.csdn.net/IBM_hoojo

* @email hoojo_@126.com

* @version 1.0

*/

public class CustomSqlSessionTemplate extends SqlSessionTemplate {

private final SqlSessionFactory sqlSessionFactory;

private final ExecutorType executorType;

private final SqlSession sqlSessionProxy;

private final PersistenceExceptionTranslator exceptionTranslator;

private Map targetSqlSessionFactorys;

private SqlSessionFactory defaultTargetSqlSessionFactory;

public void setTargetSqlSessionFactorys(Map targetSqlSessionFactorys) {

this.targetSqlSessionFactorys = targetSqlSessionFactorys;

}

public void setDefaultTargetSqlSessionFactory(SqlSessionFactory defaultTargetSqlSessionFactory) {

this.defaultTargetSqlSessionFactory = defaultTargetSqlSessionFactory;

}

public CustomSqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {

this(sqlSessionFactory, sqlSessionFactory.getConfiguration().getDefaultExecutorType());

}

public CustomSqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType) {

this(sqlSessionFactory, executorType, new MyBatisExceptionTranslator(sqlSessionFactory.getConfiguration()

.getEnvironment().getDataSource(), true));

}

public CustomSqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,

PersistenceExceptionTranslator exceptionTranslator) {

super(sqlSessionFactory, executorType, exceptionTranslator);

this.sqlSessionFactory = sqlSessionFactory;

this.executorType = executorType;

this.exceptionTranslator = exceptionTranslator;

this.sqlSessionProxy = (SqlSession) newProxyInstance(

SqlSessionFactory.class.getClassLoader(),

new Class[] { SqlSession.class },

new SqlSessionInterceptor());

this.defaultTargetSqlSessionFactory = sqlSessionFactory;

}

@Override

public SqlSessionFactory getSqlSessionFactory() {

SqlSessionFactory targetSqlSessionFactory = targetSqlSessionFactorys.get(CustomerContextHolder.getContextType());

if (targetSqlSessionFactory != null) {

return targetSqlSessionFactory;

} else if (defaultTargetSqlSessionFactory != null) {

return defaultTargetSqlSessionFactory;

} else {

Assert.notNull(targetSqlSessionFactorys, "Property 'targetSqlSessionFactorys' or 'defaultTargetSqlSessionFactory' are required");

Assert.notNull(defaultTargetSqlSessionFactory, "Property 'defaultTargetSqlSessionFactory' or 'targetSqlSessionFactorys' are required");

}

return this.sqlSessionFactory;

}

@Override

public Configuration getConfiguration() {

return this.getSqlSessionFactory().getConfiguration();

}

public ExecutorType getExecutorType() {

return this.executorType;

}

public PersistenceExceptionTranslator getPersistenceExceptionTranslator() {

return this.exceptionTranslator;

}

/**

* {@inheritDoc}

*/

public T selectOne(String statement) {

return this.sqlSessionProxy. selectOne(statement);

}

/**

* {@inheritDoc}

*/

public T selectOne(String statement, Object parameter) {

return this.sqlSessionProxy. selectOne(statement, parameter);

}

/**

* {@inheritDoc}

*/

public Map selectMap(String statement, String mapKey) {

return this.sqlSessionProxy. selectMap(statement, mapKey);

}

/**

* {@inheritDoc}

*/

public Map selectMap(String statement, Object parameter, String mapKey) {

return this.sqlSessionProxy. selectMap(statement, parameter, mapKey);

}

/**

* {@inheritDoc}

*/

public Map selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds) {

return this.sqlSessionProxy. selectMap(statement, parameter, mapKey, rowBounds);

}

/**

* {@inheritDoc}

*/

public List sehttp://lectList(String statement) {

return this.sqlSessionProxy. selectList(statement);

}

/**

* {@inheritDoc}

*/

public List selectList(String statement, Object parameter) {

return this.sqlSessionProxy. selectList(statement, parameter);

}

/**

* {@inheritDoc}

*/

public List selectList(String statement, Object parameter, RowBounds rowBounds) {

return this.sqlSessionProxy. selectList(statement, parameter, rowBounds);

}

/**

* {@inheritDoc}

*/

public void select(String statement, ResultHandler handler) {

this.sqlSessionProxy.select(statement, handler);

}

/**

* {@inheritDoc}

*/

public void select(String statement, Object parameter, ResultHandler handler) {

this.sqlSessionProxy.select(statement, parameter, handler);

}

/**

* {@inheritDoc}

*/

public void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {

this.sqlSessionProxy.select(statement, parameter, rowBounds, handler);

}

/**

* {@inheritDoc}

*/

public int insert(String statement) {

return this.sqlSessionProxy.insert(statement);

}

/**

* {@inheritDoc}

*/

public int insert(String statement, Object parameter) {

return this.sqlSessionProxy.insert(statement, parameter);

}

/**

* {@inheritDoc}

*/

public int update(String statement) {

return this.sqlSessionProxy.update(statement);

}

/**

* {@inheritDoc}

*/

public int update(String statement, Object parameter) {

return this.sqlSessionProxy.update(statement, parameter);

}

/**

* {@inheritDoc}

*/

public int delete(String statement) {

return this.sqlSessionProxy.delete(statement);

}

/**

* {@inheritDoc}

*/

public int delete(String statement, Object parameter) {

return this.sqlSessionProxy.delete(statement, parameter);

}

/**

* {@inheritDoc}

*/

public T getMapper(Class type) {

return getConfiguration().getMapper(type, this);

}

/**

* {@inheritDoc}

*/

public void commit() {

throw new UnsupportedOperationException("Manual commit is not allowed over a Spring managed SqlSession");

}

/**

* {@inheritDoc}

*/

public void commit(boolean force) {

throw new UnsupportedOperationException("Manual commit is not allowed over a Spring managed SqlSession");

}

/**

* {@inheritDoc}

*/

public void rollback() {

throw new UnsupportedOperationException("Manual rollback is not allowed over a Spring managed SqlSession");

}

/**

* {@inheritDoc}

*/

public void rollback(boolean force) {

throw new UnsupportedOperationException("Manual rollback is not allowed over a Spring managed SqlSession");

}

/**

* {@inheritDoc}

*/

public void close() {

throw new UnsupportedOperationException("Manual close is not allowed over a Spring managed SqlSession");

}

/**

* {@inheritDoc}

*/

public void clearCache() {

this.sqlSessionProxy.clearCache();

}

/**

* {@inheritDoc}

*/

public Connection getConnection() {

return this.sqlSessionProxy.getConnection();

}

/**

* {@inheritDoc}

* @since 1.0.2

*/

public List flushStatements() {

return this.sqlSessionProxy.flushStatements();

}

/**

* Proxy needed to route MyBatis method calls to the proper SqlSession got from Spring's Transaction Manager It also

* unwraps exceptions thrown by {@code Method#invoke(Object, Object...)} to pass a {@code PersistenceException} to

* the {@code PersistenceExceptionTranslator}.

*/

private class SqlSessionInterceptor implements InvocationHandler {

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

final SqlSession sqlSession = getSqlSession(

CustomSqlSessionTemplate.this.getSqlSessionFactory(),

CustomSqlSessionTemplate.this.executorType,

CustomSqlSessionTemplate.this.exceptionTranslator);

try {

Object result = method.invoke(sqlSession, args);

if (!isSqlSessionTransactional(sqlSession, CustomSqlSessionTemplate.this.getSqlSessionFactory())) {

// force commit even on non-dirty sessions because some databases require

// a commit/rollback before calling close()

sqlSession.commit(true);

}

return result;

} catch (Throwable t) {

Throwable unwrapped = unwrapThrowable(t);

if (CustomSqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {

Throwable translated = CustomSqlSessionTemplate.this.exceptionTranslator

.translateExceptionIfPossible((PersistenceException) unwrapped);

if (translated != null) {

unwrapped = translated;

}

}

throw unwrapped;

} finally {

closeSqlSession(sqlSession, CustomSqlSessionTemplate.this.getSqlSessionFactory());

}

}

}

}

重写后的getSqlSessionFactory方法会从我们配置的SqlSessionFactory集合targetSqlSessionFactorys或默认的defaultTargetSqlSessionFactory中获取Session对象。而改写的SqlSessionInterceptor 是这个MyBatis整合Spring的关键,所有的SqlSessionFactory对象的session都将在这里完成创建、提交、关闭等操作。所以我们改写这里的代码,在这里获取getSqlSessionFactory的时候,从多个SqlSessionFactory中获取我们设置的那个即可。

上面添加了targetSqlSessionFactorys、defaultTargetSqlSessionFactory两个属性来配置多个SqlSessionFactory对象和默认的SqlSessionFactory对象。

CustomerContextHolder 设置SqlSessionFactory的类型

package com.hoo.framework.mybatis.support;

/**

* function: 多数据源

* @author hoojo

* @createDate 2013-9-27 上午11:36:57

* @file CustomerContextHolder.java

* @package com.hoo.framework.spring.support

* @project SHMB

* @blog http://blog.csdn.net/IBM_hoojo

* @email hoojo_@126.com

* @version 1.0

*/

public abstract class CustomerContextHolder {

public final static String SESSION_FACTORY_mysql = "mysql";

public final static String SESSION_FACTORY_ORACLE = "oracle";

private static final ThreadLocal contextHolder = new ThreadLocal();

public static void setContextType(String contextType) {

contextHolder.set(contextType);

}

public static String getContextType() {

return contextHolder.get();

}

public static void clearContextType() {

contextHolder.remove();

}

}

2、配置相关的文件applicationContext-session-factory.xml

xmlns:aop="http://springframework.org/schema/aop"

xmlns:tx="http://springframework.org/schema/tx"

xmlns:xsi="http://w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://springframework.org/schema/beans

http://springframework.org/schema/beans/spring-beans-3.2.xsd

http://springframework.org/schema/aop

http://springframework.org/schema/aop/spring-aop-3.2.xsd

http://springframework.org/schema/tx

http://springframework.org/schema/tx/spring-tx-3.2.xsd ">

classpath:com/hoo/framework/mybatis/mybatis-common.xml

classpath:com/hoo/**/resultmap/*-resultmap.xml

classpath:com/hoo/**/mapper/*-mapper.xml

classpath:com/hoo/**/mapper/**/*-mapper.xml

classpath:com/hoo/framework/mybatis/mybatis-common.xml

classpath:com/hoo/**/resultmap/*-mysql-resultmap.xml

classpath:com/hoo/**/mapper/*-mysql-mapper.xml

classpath:com/hoo/**/mapper/**/*-mysql-mapper.xml

classpath:com/hoo/**/mapper/**/multiple-datasource-mapper.xml

xmlns:aop="http://springframework.org/schema/aop"

xmlns:tx="http://springframework.org/schema/tx"

xmlns:xsi="http://w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://springframework.org/schema/beans

http://springframework.org/schema/beans/spring-beans-3.2.xsd

http://springframework.org/schema/aop

http://springframework.org/schema/aop/spring-aop-3.2.xsd

http://springframework.org/schema/tx

http://springframework.org/schema/tx/spring-tx-3.2.xsd ">

classpath:com/hoo/framework/mybatis/mybatis-common.xml

classpath:com/hoo/**/resultmap/*-resultmap.xml

classpath:com/hoo/**/mapper/*-mapper.xml

classpath:com/hoo/**/mapper/**/*-mapper.xml

classpath:com/hoo/framework/mybatis/mybatis-common.xml

classpath:com/hoo/**/resultmap/*-mysql-resultmap.xml

classpath:com/hoo/**/mapper/*-mysql-mapper.xml

classpath:com/hoo/**/mapper/**/*-mysql-mapper.xml

classpath:com/hoo/**/mapper/**/multiple-datasource-mapper.xml

上面的配置关键是在MapperScannerConfigurer中注入sqlSessionTemplate,这个要注意。当我们配置了多个SqlSessionFactoryBean的时候,就需要为MapperScannerConfigurer指定一个sqlSessionFactoryBeanName或是sqlSessionTemplateBeanName。一般情况下注入了sqlSessionTemplateBeanName对象,那sqlSessionFactory也就有值了。如果单独的注入了sqlSessionFactory那么程序会创建一个sqlSessionTemplate对象。我们可以看看代码SqlSessionFactoryDaoSupport对象的代码。如果你不喜欢使用扫描的方式,也可以注入sqlSessionTemplate或继承sqlSessionTemplate完成数据库操作。

public abstract class SqlSessionDaoSupport extends DaoSupport {

private SqlSession sqlSession;

private boolean externalSqlSession;

public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {

if (!this.externalSqlSession) {

this.sqlSession = new SqlSessionTemplate(sqlSessionFactory);

}

}

public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) {

this.sqlSession = sqlSessionTemplate;

this.externalSqlSession = true;

}

......

这段代码很明显,如果注入了sqlSessionTemplate上面的注入也就不会执行了。如果没有注入sqlSessionTemplate,那么会自动new一个sqlSessionTemplate对象。

3、编写相关测试接口和实现的mapper.xml

package com.hoo.server.datasource.mapper;

import java.util.List;

import java.util.Map;

import com.hoo.framework.mybatis.SqlMapper;

/**

* function: MyBatis 多数据源 测试查询接口

* @author hoojo

* @createDate 2013-10-10 下午04:18:08

* @file MultipleDataSourceMapper.java

* @package com.hoo.server.datasource.mapper

* @project SHMB

* @blog http://blog.csdn.net/IBM_hoojo

* @email hoojo_@126.com

* @version 1.0

*/

public interface MultipleDataSourceMapper extends SqlMapper {

public List> execute4MySQL() throws Exception;

public List> execute4Oracle() throws Exception;

}

multiple-datasource-mapper.xml

<![CDATA[

SELECT

*

FROM

deviceInfo_tab t where rownum < 10

]]>

<![CDATA[

SELECT

*

FROM

city limit 2

]]>

上面分别查询oracle和mysql两个数据库中的table

4、测试代码

@Autowired

@Qualifier("multipleDataSourceMapper")

private MultipleDataSourceMapper mapper;

@Test

public void testMapper() {

CustomerContextHolder.setContextType(CustomerContextHolder.SESSION_FACTORY_MYSQL);

try {

trace(mapper.execute4MySQL());

} catch (Exception e1) {

e1.printStackTrace();

}

CustomerContextHolder.setContextType(CustomerContextHolder.SESSION_FACTORY_ORACLE);

try {

trace(mapper.execute4Oracle());

} catch (Exception e) {

e.printStackTrace();

}

}

运行后发现能够顺利查询出数据。

如果你是重写SqlSessionDaoSupport,那么方法如下

package com.hoo.framework.mybatis.support;

import java.util.Map;

import org.apache.ibatis.session.SqlSession;

import org.apache.ibatis.session.SqlSessionFactory;

import org.mybatis.spring.SqlSessionUtils;

import org.mybatis.spring.support.SqlSessionDaoSupport;

import org.springframework.beans.BeansException;

import org.springframework.context.ApplicationContext;

import org.springframework.context.ApplicationContextAware;

/**

* function: MyBatis 动态SqlSessionFactory

* @author hoojo

* @createDate 2013-10-14 下午02:32:19

* @file DynamicSqlSessionDaoSupport.java

* @package com.hoo.framework.mybatis.support

* @project SHMB

* @blog http://blog.csdn.net/IBM_hoojo

* @email hoojo_@126.com

* @version 1.0

*/

public class DynamicSqlSessionDaoSupport extends SqlSessionDaoSupport implements ApplicationContextAware {

private ApplicationContext applicationContext;

private Map targetSqlSessionFactorys;

private SqlSessionFactory defaultTargetSqlSessionFactory;

private SqlSession sqlSession;

@Override

public final SqlSession getSqlSession() {

SqlSessionFactory targetSqlSessionFactory = targetSqlSessionFactorys.get(CustomerContextHolder.getContextType());

if (targetSqlSessionFactory != null) {

setSqlSessionFactory(targetSqlSessionFactory);

} else if (defaultTargetSqlSessionFactory != null) {

setSqlSessionFactory(defaultTargetSqlSessionFactory);

targetSqlSessionFactory = defaultTargetSqlSessionFactory;

} else {

targetSqlSessionFactory = (SqlSessionFactory) applicationContext.getBean(CustomerContextHolder.getContextType());

setSqlSessionFactory(targetSqlSessionFactory);

}

this.sqlSession = SqlSessionUtils.getSqlSession(targetSqlSessionFactory);

return this.sqlSession;

}

@Override

protected void checkDaoConfig() {

//Assert.notNull(getSqlSession(), "Property 'sqlSessionFactory' or 'sqlSessionTemplate' are required");

}

public void setTargetSqlSessionFactorys(Map targetSqlSessionFactorys) {

this.targetSqlSessionFactorys = targetSqlSessionFactorys;

}

public void setDefaultTargetSqlSessionFactory(SqlSessionFactory defaultTargetSqlSessionFactory) {

this.defaultTargetSqlSessionFactory = defaultTargetSqlSessionFactory;

}

@Override

public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {

this.applicationContext = applicationContext;

}

}

主要重写getSqlSession方法,上面获取SqlSessionFactory的方法。

重写好了后就可以配置这个对象,配置代码如下

//每一个DAO由继承SqlSessionDaoSupport全部改为DynamicSqlSessionDaoSupport

public class UserMapperDaoImpl extends DynamicSqlSessionDaoSupport implements UserDao {

public int addUser(User user) {

return this.getSqlSession().insert("com.hoo.user.dao.UserDao.addUser", user);

}

}

在上面的配置文件中加入配置

就这样也可以利用DynamicSqlSessionDaoSupport来完成动态切换sqlSessionFactory对象,只需用在注入userMapperDao调用方法的时候设置下CustomerContextHolder的contextType即可。

三、总结

为了实现这个功能看了mybatis-spring-1.2.0.jar这个包的部分源代码,代码内容不是很多。所以看了下主要的代码,下面做些简单的介绍。

MapperScannerConfigurer这个类就是我们要扫描的Mapper接口的类,也就是basePackage中继承markerInterface配置的接口。可以看看ClassPathBeanDefinitionScanner、ClassPathMapperScanner中的doScan这个方法。它会扫描basePackage这个包下所有接口,在ClassPathScanningCandidateComponentProvider中有这个方法findCandidateComponents,它会找到所有的BeanDefinition。

最重要的一点是ClassPathMapperScanner中的doScan这个方法它会给这些接口创建一个MapperFactoryBean。并且会检查sqlSessionFactory和sqlSessionTemplate对象的注入情况。

image 所以我们配置扫描的方式也就相当于我们在配置文件中给每一个Mapper配置一个MapperFactoryBean一样。而这个MapperFactoryBean又继承SqlSessionDaoSupport。所以当初我想重写MapperScannerConfigurer中的postProcessBeanDefinitionRegistry方法,然后重写方法中的ClassPathMapperScanner中的doScan方法,将definition.setBeanClass(MapperFactoryBean.class);改成自己定义的MapperFactoryBean。最后以失败告终,因为这里是Spring装载扫描对象的时候都已经为这些对象创建好了代理、设置好了mapperInterface和注入需要的类。所以在调用相关操作数据库的API方法的时候,设置对应的SqlSessionFactory也是无效的。

辗转反侧我看到了SqlSessionTemplate这个类,它的功能相当于SqlSessionDaoSupport的实现类MapperFactoryBean。最为关键的是SqlSessionTemplate有一个拦截器SqlSessionInterceptor,它复制所有SqlSession的创建、提交、关闭,而且是在每个方法之前。这点在上面也提到过了!所以我们只需要在SqlSessionInterceptor方法中获取SqlSessionFactory的时候,在这之前调用下CustomerContextHolder.setContextType方法即可完成数据库的SqlSessionFactory的切换。而在MapperScannerConfigurer提供了注入SqlSessionFactory和sqlSessionTemplate的方法,如果注入了SqlSessionFactory系统将会new一个sqlSessionTemplate,而注入了sqlSessionTemplate就不会创建其他对象(见下面代码)。所以我们配置一个sqlSessionTemplate并注入到MapperScannerConfigurer中,程序将会使用这个sqlSessionTemplate。本文最后的实现方式就是这样完成的。

public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {

if (!this.externalSqlSession) {

this.sqlSession = new SqlSessionTemplate(sqlSessionFactory);

}

}

public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) {

this.sqlSession = sqlSessionTemplate;

this.externalSqlSession = true;

}

以上所述是给大家介绍的Spring3 整合MyBatis3 配置多数据源动态选择SqlSessionFactory详细教程,希望对大家有所帮助,如果大家有任何疑问请给我留言,会及时回复大家的。在此也非常感谢大家对我们网站的支持!


版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:详解Java 自动装箱与拆箱的实现原理
下一篇:详解Angular中的自定义服务Service、Provider以及Factory
相关文章

 发表评论

暂时没有评论,来抢沙发吧~