zoukankan      html  css  js  c++  java
  • springboot2.1.3+spring-session2.1.4分库处理

    使用spring session框架来统一管理session,该框架支持jdbc、redis存储,使用非常简单,可以去官网查看文档一步步接入即可,
    官网文档如下:https://docs.spring.io/spring-session/docs/current/reference/html5/,

    不过,我使用的场景官网没有提供方法给予解决,最后,本人只能重写了它的部分源码,来实现分库管理session,好了,上代码。

    pom.xml

    <dependency>
        <groupId>org.springframework.session</groupId>
        <artifactId>spring-session-core</artifactId>
        <version>2.1.4.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.session</groupId>
        <artifactId>spring-session-jdbc</artifactId>
        <version>2.1.4.RELEASE</version>
    </dependency>
    <!-- 
    <dependency>
        <groupId>org.springframework.session</groupId>
        <artifactId>spring-session-redis</artifactId>
        <version>2.1.4.RELEASE</version>
    </dependency>
    -->
    <dependency>
        <groupId>com.zaxxer</groupId>
        <artifactId>HikariCP</artifactId>
        <version>3.2.0</version>
    </dependency>

    application.properties

    # spring-session setting, timeout: 2 years
    spring.session.timeout.setting=63072000
    
    # sharding number, CONFIG_SHARDING_NUM 环境变量名
    mydb.server.sharding.num=${CONFIG_SHARDING_NUM:4}
    
    # global setting dbs parameter
    mysql.global.pools.MinimumIdle=1
    mysql.global.pools.MaximumPoolSize=20
    mysql.global.pools.IdleTimeout=600000
    mysql.global.pools.MaxLifetime=1800000
    
    # 这里配置分库信息,这里只是demo,本人配置了4个主库,至于分库分表不在本博客中体现。
    # master0
    sharding.jdbc.datasource.mainshard0.type=com.zaxxer.hikari.HikariDataSource
    sharding.jdbc.datasource.mainshard0.driver-class-name=org.mariadb.jdbc.Driver
    sharding.jdbc.datasource.mainshard0.jdbc-url=jdbc:mysql://127.0.0.1:3306/demo?useUnicode=true&characterEncoding-utf8&allowMutiQueries=true
    sharding.jdbc.datasource.mainshard0.username=xxxx
    sharding.jdbc.datasource.mainshard0.password=xxxxxx
    
    # master1
    sharding.jdbc.datasource.mainshard1.type=com.zaxxer.hikari.HikariDataSource
    sharding.jdbc.datasource.mainshard1.driver-class-name=org.mariadb.jdbc.Driver
    sharding.jdbc.datasource.mainshard1.jdbc-url=jdbc:mysql://127.0.0.1:3307/demo?useUnicode=true&characterEncoding-utf8&allowMutiQueries=true
    sharding.jdbc.datasource.mainshard1.username=xxxx
    sharding.jdbc.datasource.mainshard1.password=xxxxxx
    
    # master2
    sharding.jdbc.datasource.mainshard2.type=com.zaxxer.hikari.HikariDataSource
    sharding.jdbc.datasource.mainshard2.driver-class-name=org.mariadb.jdbc.Driver
    sharding.jdbc.datasource.mainshard2.jdbc-url=jdbc:mysql://127.0.0.1:3308/demo?useUnicode=true&characterEncoding-utf8&allowMutiQueries=true
    sharding.jdbc.datasource.mainshard2.username=xxxx
    sharding.jdbc.datasource.mainshard2.password=xxxxxx
    
    # master3
    sharding.jdbc.datasource.mainshard3.type=com.zaxxer.hikari.HikariDataSource
    sharding.jdbc.datasource.mainshard3.driver-class-name=org.mariadb.jdbc.Driver
    sharding.jdbc.datasource.mainshard3.jdbc-url=jdbc:mysql://127.0.0.1:3309/demo?useUnicode=true&characterEncoding-utf8&allowMutiQueries=true
    sharding.jdbc.datasource.mainshard3.username=xxxx
    sharding.jdbc.datasource.mainshard3.password=xxxxxx

    重写第一个类(源码文件是JdbcHttpSessionConfiguration.java,可以去官网下载),本人重写如下:

    package com.szl.demo.spring.session.common.datasource.sessionConfig;
    
    import java.util.HashMap;
    import java.util.Map;
    import javax.sql.DataSource;
    import org.springframework.beans.factory.BeanClassLoaderAware;
    import org.springframework.beans.factory.ObjectProvider;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Qualifier;
    import org.springframework.context.EmbeddedValueResolverAware;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.ImportAware;
    import org.springframework.core.annotation.AnnotationAttributes;
    import org.springframework.core.convert.ConversionService;
    import org.springframework.core.convert.support.GenericConversionService;
    import org.springframework.core.serializer.support.DeserializingConverter;
    import org.springframework.core.serializer.support.SerializingConverter;
    import org.springframework.core.type.AnnotationMetadata;
    import org.springframework.jdbc.core.JdbcTemplate;
    import org.springframework.jdbc.support.JdbcUtils;
    import org.springframework.jdbc.support.MetaDataAccessException;
    import org.springframework.jdbc.support.lob.DefaultLobHandler;
    import org.springframework.jdbc.support.lob.LobHandler;
    import org.springframework.scheduling.annotation.EnableScheduling;
    import org.springframework.scheduling.annotation.SchedulingConfigurer;
    import org.springframework.scheduling.config.ScheduledTaskRegistrar;
    import org.springframework.session.MapSession;
    import org.springframework.session.config.annotation.web.http.SpringHttpSessionConfiguration;
    import org.springframework.session.jdbc.JdbcOperationsSessionRepository;
    import org.springframework.session.jdbc.config.annotation.SpringSessionDataSource;
    import org.springframework.session.web.http.SessionRepositoryFilter;
    import org.springframework.transaction.PlatformTransactionManager;
    import org.springframework.util.StringUtils;
    import org.springframework.util.StringValueResolver;
    import com.zaxxer.hikari.HikariConfig;
    import com.zaxxer.hikari.HikariDataSource;
    
    /**
     *  @author Jimmy Shan
     *  @date 2019-06-25
     *  @desc 重写jdbc session配置类
     */
    @Configuration
    @EnableScheduling
    public class CustomizedJdbcHttpSessionConfiguration extends SpringHttpSessionConfiguration
            implements BeanClassLoaderAware, EmbeddedValueResolverAware, ImportAware,
            SchedulingConfigurer {
    
      static final String DEFAULT_CLEANUP_CRON = "0 0 0/1 * * *";
      private String tableName = JdbcOperationsSessionRepository.DEFAULT_TABLE_NAME;
      private String cleanupCron = DEFAULT_CLEANUP_CRON;
      private LobHandler lobHandler;
      private ConversionService springSessionConversionService;
      private ConversionService conversionService;
      private ClassLoader classLoader;
      private StringValueResolver embeddedValueResolver;
      
      //--------------Modify by Jimmy Shan, the date is 2019-06-25 start------//
      @Value("${mydb.server.sharding.num}")
      private String shardingNum;
      @Autowired
      private Environment env;
      private Map<Integer, JdbcTemplate> myJdbcTemplateMap = new HashMap<>();
      private Map<Integer, PlatformTransactionManager> myDataSourceTransactionMap = new HashMap<>();
      private DataSource dataSource;
      // 时间设置,间隔时间为30秒
      private Integer maxInactiveIntervalInSeconds;
      //--------------Modify by Jimmy Shan, the date is 2019-06-25 end-------//
        
        
        /**
         * @desc 创建数据源,手动创建,为了后面的分库
         */
        private DataSource convertDataSource(int num) {
            HikariConfig hkConfig = new HikariConfig();
            hkConfig.setDriverClassName("org.mariadb,jdbc.Driver");
            hkConfig.setMinimumIdle(env.getProperty("mysql.global.pools.MinimumIdle") == null ? 5 : env.getProperty("mysql.global.pools.MinimumIdle")));
            hkConfig.setMaximumPoolSize(env.getProperty("mysql.global.pools.MaximumPoolSize") == null ? 20 : env.getProperty("mysql.global.pools.MaximumPoolSize")));
            hkConfig.setIdleTimeout(env.getProperty("mysql.global.pools.IdleTimeout") == null ? 600000 : Integer.parseInt(env.getProperty("mysql.global.pools.IdleTimeout")));
            hkConfig.setMaxLifetime(env.getProperty("mysql.global.pools.MaxLifetime") == null ? 1800000 : Integer.parseInt(env.getProperty("mysql.global.pools.MaxLifetime")));
            hkConfig.setJdbcUrl(env.getProperty("sharding.jdbc.datasource.mainshard" + num + ".jdbc-url"));
            hkConfig.setUsername(env.getProperty("sharding.jdbc.datasource.mainshard" + num + ".username"));
            hkConfig.setPassword(env.getProperty("sharding.jdbc.datasource.mainshard" + num + ".password"));
            HikariDataSource ds = new HikariDataSource(hkConfig);
            return ds;
        }
        
      /**
       * @desc 重写了部分逻辑
       */
        @Bean
        public CustomizedJdbcOperationsSessionRepository sessionRepository() {
          for (int i = 0; i < Integer.parseInt(shardingNum); i++) {
              DataSource ds = convertDataSource(i);
              myJdbcTemplateMap.put(i, new JdbcTemplate(ds));
              myDataSourceTransactionMap.put(i, new DataSourceTransactionManager(ds));
          }
          
          this.dataSource = myJdbcTemplateMap.get(0).getDataSource();
            CustomizedJdbcOperationsSessionRepository sessionRepository = 
                new JdbcOperationsSessionRepository(myJdbcTemplateMap, myDataSourceTransactionMap);
            if (StringUtils.hasText(this.tableName)) {
                sessionRepository.setTableName(this.tableName);
            }
            
            this.setMaxInactiveIntervalInSeconds(Integer.parseInt(env.getProperty("spring.session.timeout.setting")));
            sessionRepository.setDefaultMaxInactiveInterval(this.maxInactiveIntervalInSeconds);
            if (this.lobHandler != null) {
                 sessionRepository.setLobHandler(this.lobHandler);
            }    else if (requiresTemporaryLob(this.dataSource)) {
                 DefaultLobHandler lobHandler = new DefaultLobHandler();
                 lobHandler.setCreateTemporaryLob(true);
                 sessionRepository.setLobHandler(lobHandler);
            }
            if (this.springSessionConversionService != null) {
                 sessionRepository.setConversionService(this.springSessionConversionService);
            }    else if (this.conversionService != null) {
                 sessionRepository.setConversionService(this.conversionService);
            }    else {
                 sessionRepository.setConversionService(createConversionServiceWithBeanClassLoader());
            }
            return sessionRepository;
        }
    
        private static boolean requiresTemporaryLob(DataSource dataSource) {
            try {
                String productName = JdbcUtils.extractDatabaseMetaData(dataSource,
                        "getDatabaseProductName");
                return "Oracle".equalsIgnoreCase(JdbcUtils.commonDatabaseName(productName));
            }
            catch (MetaDataAccessException ex) {
                return false;
            }
        }
    
        public void setMaxInactiveIntervalInSeconds(Integer maxInactiveIntervalInSeconds) {
            this.maxInactiveIntervalInSeconds = maxInactiveIntervalInSeconds;
        }
    
        public void setTableName(String tableName) {
            this.tableName = tableName;
        }
    
        public void setCleanupCron(String cleanupCron) {
            this.cleanupCron = cleanupCron;
        }
    
        @Autowired(required = false)
        @Qualifier("springSessionLobHandler")
        public void setLobHandler(LobHandler lobHandler) {
            this.lobHandler = lobHandler;
        }
    
        @Autowired(required = false)
        @Qualifier("springSessionConversionService")
        public void setSpringSessionConversionService(ConversionService conversionService) {
            this.springSessionConversionService = conversionService;
        }
    
        @Autowired(required = false)
        @Qualifier("conversionService")
        public void setConversionService(ConversionService conversionService) {
            this.conversionService = conversionService;
        }
    
        @Override
        public void setBeanClassLoader(ClassLoader classLoader) {
            this.classLoader = classLoader;
        }
    
        @Override
        public void setEmbeddedValueResolver(StringValueResolver resolver) {
            this.embeddedValueResolver = resolver;
        }
    
        @Override
        public void setImportMetadata(AnnotationMetadata importMetadata) {
            Map<String, Object> attributeMap = importMetadata
                    .getAnnotationAttributes(EnableJdbcHttpSession.class.getName());
            AnnotationAttributes attributes = AnnotationAttributes.fromMap(attributeMap);
            this.maxInactiveIntervalInSeconds = attributes
                    .getNumber("maxInactiveIntervalInSeconds");
            String tableNameValue = attributes.getString("tableName");
            if (StringUtils.hasText(tableNameValue)) {
                this.tableName = this.embeddedValueResolver
                        .resolveStringValue(tableNameValue);
            }
            String cleanupCron = attributes.getString("cleanupCron");
            if (StringUtils.hasText(cleanupCron)) {
                this.cleanupCron = cleanupCron;
            }
        }
    
        @Override
        public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
            taskRegistrar.addCronTask(() -> sessionRepository().cleanUpExpiredSessions(),
                    this.cleanupCron);
        }
    
        private GenericConversionService createConversionServiceWithBeanClassLoader() {
            GenericConversionService conversionService = new GenericConversionService();
            conversionService.addConverter(Object.class, byte[].class,
                    new SerializingConverter());
            conversionService.addConverter(byte[].class, Object.class,
                    new DeserializingConverter(this.classLoader));
            return conversionService;
        }
    }

    重写第二个类(源码文件是JdbcOperationsSessionRepository.java,可以去官网下载),本人重写如下:

    package com.szl.demo.spring.session.common.datasource.sessionConfig;
    
    import java.sql.PreparedStatement;
    import java.sql.ResultSet;
    import java.sql.SQLException;
    import java.time.Duration;
    import java.time.Instant;
    import java.util.ArrayList;
    import java.util.Collections;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    import java.util.Set;
    import java.util.UUID;
    import java.util.function.Supplier;
    import java.util.stream.Collectors;
    import org.apache.commons.logging.Log;
    import org.apache.commons.logging.LogFactory;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.core.convert.ConversionService;
    import org.springframework.core.convert.TypeDescriptor;
    import org.springframework.core.convert.support.GenericConversionService;
    import org.springframework.core.serializer.support.DeserializingConverter;
    import org.springframework.core.serializer.support.SerializingConverter;
    import org.springframework.dao.DataAccessException;
    import org.springframework.expression.Expression;
    import org.springframework.expression.spel.standard.SpelExpressionParser;
    import org.springframework.jdbc.core.BatchPreparedStatementSetter;
    import org.springframework.jdbc.core.JdbcOperations;
    import org.springframework.jdbc.core.JdbcTemplate;
    import org.springframework.jdbc.core.ResultSetExtractor;
    import org.springframework.jdbc.support.lob.DefaultLobHandler;
    import org.springframework.jdbc.support.lob.LobHandler;
    import org.springframework.session.FindByIndexNameSessionRepository;
    import org.springframework.session.MapSession;
    import org.springframework.session.Session;
    import org.springframework.session.jdbc.JdbcOperationsSessionRepository;
    import org.springframework.transaction.PlatformTransactionManager;
    import org.springframework.transaction.TransactionDefinition;
    import org.springframework.transaction.TransactionException;
    import org.springframework.transaction.TransactionStatus;
    import org.springframework.transaction.support.TransactionCallback;
    import org.springframework.transaction.support.TransactionCallbackWithoutResult;
    import org.springframework.transaction.support.TransactionOperations;
    import org.springframework.transaction.support.TransactionTemplate;
    import org.springframework.util.Assert;
    import org.springframework.util.StringUtils;
    
    /**
     * @author Jimmy Shan
     * @date 2019-06-25
     * @desc 重写jdbc session类
     */
    public class CustomizedJdbcOperationsSessionRepository implements
            FindByIndexNameSessionRepository<JdbcOperationsSessionRepository.JdbcSession> {
            
        public static final String DEFAULT_TABLE_NAME = "SPRING_SESSION";
        private static final String SPRING_SECURITY_CONTEXT = "SPRING_SECURITY_CONTEXT";
        private static final String CREATE_SESSION_QUERY =
                "INSERT INTO %TABLE_NAME%(PRIMARY_ID, SESSION_ID, CREATION_TIME, LAST_ACCESS_TIME, MAX_INACTIVE_INTERVAL, EXPIRY_TIME, PRINCIPAL_NAME) " +
                        "VALUES (?, ?, ?, ?, ?, ?, ?)";
        private static final String CREATE_SESSION_ATTRIBUTE_QUERY =
                "INSERT INTO %TABLE_NAME%_ATTRIBUTES(SESSION_PRIMARY_ID, ATTRIBUTE_NAME, ATTRIBUTE_BYTES) " +
                        "SELECT PRIMARY_ID, ?, ? " +
                        "FROM %TABLE_NAME% " +
                        "WHERE SESSION_ID = ?";
        private static final String GET_SESSION_QUERY =
                "SELECT S.PRIMARY_ID, S.SESSION_ID, S.CREATION_TIME, S.LAST_ACCESS_TIME, S.MAX_INACTIVE_INTERVAL, SA.ATTRIBUTE_NAME, SA.ATTRIBUTE_BYTES " +
                        "FROM %TABLE_NAME% S " +
                        "LEFT OUTER JOIN %TABLE_NAME%_ATTRIBUTES SA ON S.PRIMARY_ID = SA.SESSION_PRIMARY_ID " +
                        "WHERE S.SESSION_ID = ?";
        private static final String UPDATE_SESSION_QUERY =
                "UPDATE %TABLE_NAME% SET SESSION_ID = ?, LAST_ACCESS_TIME = ?, MAX_INACTIVE_INTERVAL = ?, EXPIRY_TIME = ?, PRINCIPAL_NAME = ? " +
                        "WHERE PRIMARY_ID = ?";
        private static final String UPDATE_SESSION_ATTRIBUTE_QUERY =
                "UPDATE %TABLE_NAME%_ATTRIBUTES SET ATTRIBUTE_BYTES = ? " +
                        "WHERE SESSION_PRIMARY_ID = ? " +
                        "AND ATTRIBUTE_NAME = ?";
        private static final String DELETE_SESSION_ATTRIBUTE_QUERY =
                "DELETE FROM %TABLE_NAME%_ATTRIBUTES " +
                        "WHERE SESSION_PRIMARY_ID = ? " +
                        "AND ATTRIBUTE_NAME = ?";
        private static final String DELETE_SESSION_QUERY =
                "DELETE FROM %TABLE_NAME% " +
                        "WHERE SESSION_ID = ?";
        private static final String LIST_SESSIONS_BY_PRINCIPAL_NAME_QUERY =
                "SELECT S.PRIMARY_ID, S.SESSION_ID, S.CREATION_TIME, S.LAST_ACCESS_TIME, S.MAX_INACTIVE_INTERVAL, SA.ATTRIBUTE_NAME, SA.ATTRIBUTE_BYTES " +
                        "FROM %TABLE_NAME% S " +
                        "LEFT OUTER JOIN %TABLE_NAME%_ATTRIBUTES SA ON S.PRIMARY_ID = SA.SESSION_PRIMARY_ID " +
                        "WHERE S.PRINCIPAL_NAME = ?";
        private static final String DELETE_SESSIONS_BY_EXPIRY_TIME_QUERY =
                "DELETE FROM %TABLE_NAME% " +
                        "WHERE EXPIRY_TIME < ?";
    
        private static final Log logger = LogFactory.getLog(JdbcOperationsSessionRepository.class);
        private static final PrincipalNameResolver PRINCIPAL_NAME_RESOLVER = new PrincipalNameResolver();
        private final ResultSetExtractor<List<JdbcSession>> extractor = new SessionResultSetExtractor();
        private String tableName = DEFAULT_TABLE_NAME;
        private String createSessionQuery;
        private String createSessionAttributeQuery;
        private String getSessionQuery;
        private String updateSessionQuery;
        private String updateSessionAttributeQuery;
        private String deleteSessionAttributeQuery;
        private String deleteSessionQuery;
        private String listSessionsByPrincipalNameQuery;
        private String deleteSessionsByExpiryTimeQuery;
        private Integer defaultMaxInactiveInterval;
        private ConversionService conversionService;
      private LobHandler lobHandler = new DefaultLobHandler();
        
      //--------------Modify by Jimmy Shan, the date is 2019-06-25 start--------------//
      private Map<Integer, JdbcOperations> jdbcOperationMaps = new Hash<>();
      private Map<Integer, TransactionOperations> transOperationMaps = new Hash<>();
      @Value("{mydb.server.sharding.num}")
      private String shardingNum;
      //--------------Modify by Jimmy Shan, the date is 2019-06-25 end----------------//
        
      /**
       * @desc 重写这个方法
       */
        public CustomizedJdbcOperationsSessionRepository(Map<Integer, JdbcTemplate> myJdbcTemplateMap,
                Map<Integer, PlatformTransactionManager> myDataSourceTransactionMap) {
                if (myJdbcTemplateMap == null || myJdbcTemplateMap.isEmpty()) {
                    Assert.notNull(myJdbcTemplateMap, "myJdbcTemplateMap must not be null");
                }
                if (myDataSourceTransactionMap == null || myDataSourceTransactionMap.isEmpty()) {
                    Assert.notNull(myDataSourceTransactionMap, "myDataSourceTransactionMap must not be null");
                }
                jdbcOperationMaps.putAll(myJdbcTemplateMap);
                this.conversionService = createDefaultConversionService();
                prepareQueries();
                Set<Integer> setKey = myDataSourceTransactionMap.keySet();
                for (Iterator ir = setKey.iterator(); ir.hasNext(); ) {
                    Integer key = (Integer) ir.next();
                    TransactionOperations transOperations = createTransactionTemplate(myDataSourceTransactionMap.get(key));
                    transOperationMaps.put(key, transOperations);
                }        
        }
      
        public void setTableName(String tableName) {
            Assert.hasText(tableName, "Table name must not be empty");
            this.tableName = tableName.trim();
            prepareQueries();
        }
      
        public void setCreateSessionQuery(String createSessionQuery) {
            Assert.hasText(createSessionQuery, "Query must not be empty");
            this.createSessionQuery = createSessionQuery;
        }
      
        public void setCreateSessionAttributeQuery(String createSessionAttributeQuery) {
            Assert.hasText(createSessionAttributeQuery, "Query must not be empty");
            this.createSessionAttributeQuery = createSessionAttributeQuery;
        }
      
        public void setGetSessionQuery(String getSessionQuery) {
            Assert.hasText(getSessionQuery, "Query must not be empty");
            this.getSessionQuery = getSessionQuery;
        }
      
        public void setUpdateSessionQuery(String updateSessionQuery) {
            Assert.hasText(updateSessionQuery, "Query must not be empty");
            this.updateSessionQuery = updateSessionQuery;
        }
      
        public void setUpdateSessionAttributeQuery(String updateSessionAttributeQuery) {
            Assert.hasText(updateSessionAttributeQuery, "Query must not be empty");
            this.updateSessionAttributeQuery = updateSessionAttributeQuery;
        }
      
        public void setDeleteSessionAttributeQuery(String deleteSessionAttributeQuery) {
            Assert.hasText(deleteSessionAttributeQuery, "Query must not be empty");
            this.deleteSessionAttributeQuery = deleteSessionAttributeQuery;
        }
      
        public void setDeleteSessionQuery(String deleteSessionQuery) {
            Assert.hasText(deleteSessionQuery, "Query must not be empty");
            this.deleteSessionQuery = deleteSessionQuery;
        }
      
        public void setListSessionsByPrincipalNameQuery(String listSessionsByPrincipalNameQuery) {
            Assert.hasText(listSessionsByPrincipalNameQuery, "Query must not be empty");
            this.listSessionsByPrincipalNameQuery = listSessionsByPrincipalNameQuery;
        }
      
        public void setDeleteSessionsByExpiryTimeQuery(String deleteSessionsByExpiryTimeQuery) {
            Assert.hasText(deleteSessionsByExpiryTimeQuery, "Query must not be empty");
            this.deleteSessionsByExpiryTimeQuery = deleteSessionsByExpiryTimeQuery;
        }
      
        public void setDefaultMaxInactiveInterval(Integer defaultMaxInactiveInterval) {
            this.defaultMaxInactiveInterval = defaultMaxInactiveInterval;
        }
    
        public void setLobHandler(LobHandler lobHandler) {
            Assert.notNull(lobHandler, "LobHandler must not be null");
            this.lobHandler = lobHandler;
        }
      
        public void setConversionService(ConversionService conversionService) {
            Assert.notNull(conversionService, "conversionService must not be null");
            this.conversionService = conversionService;
        }
    
        @Override
        public JdbcSession createSession() {
            JdbcSession session = new JdbcSession();
            if (this.defaultMaxInactiveInterval != null) {
                session.setMaxInactiveInterval(Duration.ofSeconds(this.defaultMaxInactiveInterval));
            }
            return session;
        }
      
      
      /**
       * @desc Modify by Jimmy Shan, the date is 2019-06-25
       *      Implementing Routing Function
       */
        @Override
        public void save(final JdbcSession session) {
          String sessionId = session.getId();
          int hashCode = Math.abs(sessionId.hashCode());
          int mod = hashCode % Integer.parseInt(shardingNum);
            if (session.isNew()) {
                this.transOperationMaps.get(mod).execute(new TransactionCallbackWithoutResult() {
                            @Override
                            protected void doInTransactionWithoutResult(TransactionStatus status) {
                                CustomizedJdbcOperationsSessionRepository.this.jdbcOperationMaps.get(mod).update(
                                        CustomizedJdbcOperationsSessionRepository.this.createSessionQuery,
                                        (ps) -> {
                                            ps.setString(1, session.primaryKey);
                                            ps.setString(2, session.getId());
                                            ps.setLong(3, session.getCreationTime().toEpochMilli());
                                            ps.setLong(4, session.getLastAccessedTime().toEpochMilli());
                                            ps.setInt(5, (int) session.getMaxInactiveInterval().getSeconds());
                                            ps.setLong(6, session.getExpiryTime().toEpochMilli());
                                            ps.setString(7, session.getPrincipalName());
                                        });
                                Set<String> attributeNames = session.getAttributeNames();
                                if (!attributeNames.isEmpty()) {
                                      insertSessionAttributes(session, new ArrayList<>(attributeNames));
                                }
                    }
                });
            }    else {
                this.transOperationMaps.get(mod).execute(new TransactionCallbackWithoutResult() {
                            @Override
                            protected void doInTransactionWithoutResult(TransactionStatus status) {
                                if (session.isChanged()) {
                                        CustomizedJdbcOperationsSessionRepository.this.jdbcOperationMaps.get(mod).update(
                                                CustomizedJdbcOperationsSessionRepository.this.updateSessionQuery,
                                                (ps) -> {
                                                    ps.setString(1, session.getId());
                                                    ps.setLong(2, session.getLastAccessedTime().toEpochMilli());
                                                    ps.setInt(3, (int) session.getMaxInactiveInterval().getSeconds());
                                                    ps.setLong(4, session.getExpiryTime().toEpochMilli());
                                                    ps.setString(5, session.getPrincipalName());
                                                    ps.setString(6, session.primaryKey);
                                                });
                                }
                                List<String> addedAttributeNames = session.delta.entrySet().stream()
                                        .filter((entry) -> entry.getValue() == DeltaValue.ADDED)
                                        .map(Map.Entry::getKey)
                                        .collect(Collectors.toList());
                                if (!addedAttributeNames.isEmpty()) {
                                    insertSessionAttributes(session, addedAttributeNames);
                                }
                                List<String> updatedAttributeNames = session.delta.entrySet().stream()
                                        .filter((entry) -> entry.getValue() == DeltaValue.UPDATED)
                                        .map(Map.Entry::getKey)
                                        .collect(Collectors.toList());
                                if (!updatedAttributeNames.isEmpty()) {
                                    updateSessionAttributes(session, updatedAttributeNames);
                                }
                                List<String> removedAttributeNames = session.delta.entrySet().stream()
                                        .filter((entry) -> entry.getValue() == DeltaValue.REMOVED)
                                        .map(Map.Entry::getKey)
                                        .collect(Collectors.toList());
                                if (!removedAttributeNames.isEmpty()) {
                                      deleteSessionAttributes(session, removedAttributeNames);
                                }
                            }
                });
            }
            session.clearChangeFlags();
        }
      
      /**
       * @desc Modify by Jimmy Shan, the date is 2019-06-25
       *      Implementing Routing Function
       */
        @Override
        public JdbcSession findById(final String id) {
          String sessionId = id;
          int hashCode = Math.abs(sessionId.hashCode());
          int mod = hashCode % Integer.parseInt(shardingNum);
            final JdbcSession session = this.transOperationMaps.get(mod).execute((status) -> {
                List<JdbcSession> sessions = CustomizedJdbcOperationsSessionRepository
                                     .this.jdbcOperationMaps.get(mod).query(
                                                                         CustomizedJdbcOperationsSessionRepository.this.getSessionQuery,
                        (ps) -> ps.setString(1, id),
                        CustomizedJdbcOperationsSessionRepository.this.extractor
                );
                if (sessions.isEmpty()) {
                        return null;
                }
                return sessions.get(0);
            });
            
            if (session != null) {
                if (session.isExpired()) {
                        deleteById(id);
                }    else {
                        return session;
                }
            }
            return null;
        }
        
        /**
       * @desc Modify by Jimmy Shan, the date is 2019-06-25
       *      Implementing Routing Function
       */
        @Override
        public void deleteById(final String id) {
          String sessionId = id;
          int hashCode = Math.abs(sessionId.hashCode());
          int mod = hashCode % Integer.parseInt(shardingNum);
            this.transOperationMaps.get(mod).execute(new TransactionCallbackWithoutResult() {
                    @Override
                    protected void doInTransactionWithoutResult(TransactionStatus status) {
                            CustomizedJdbcOperationsSessionRepository.this.jdbcOperationMaps.get(mod).update(
                                    CustomizedJdbcOperationsSessionRepository.this.deleteSessionQuery, id);
                    }
            });
        }
        
        /**
       * @desc Modify by Jimmy Shan, the date is 2019-06-25
       *      Implementing Routing Function
       */
        @Override
        public Map<String, JdbcSession> findByIndexNameAndIndexValue(String indexName, final String indexValue) {
            if (!PRINCIPAL_NAME_INDEX_NAME.equals(indexName)) {
                    return Collections.emptyMap();
            }
            List<JdbcSession> sessions = new ArrayList<>();
            Set<Integer> setKey = transOperationMaps.keySet();
            for (Iterator ir = setKey.iterator(); ir.hasNext(); ) {
                    Integer key = (Integer) ir.next();
                    TransactionOperations transOperation = (TransactionOperations) transOperationMaps.get(key);
                    List<JdbcSession> tempSession = transOperation.execute(status) -> 
                                    CustomizedJdbcOperationsSessionRepository.this.jdbcOperationMaps.get(key).query(
                                            CustomizedJdbcOperationsSessionRepository.this.listSessionsByPrincipalNameQuery,
                                            (ps) -> ps.setString(1, indexValue),
                                            CustomizedJdbcOperationsSessionRepository.this.extractor));
                    if (tempSession != null && !tempSession.isEmpty()) {
                            sessions.addAll(tempSession);
                    }    
            }
            Map<String, JdbcSession> sessionMap = new HashMap<>(sessions.size());
            for (JdbcSession session : sessions) {
                    sessionMap.put(session.getId(), session);
            }
    
            return sessionMap;
        }
        
        /**
       * @desc Modify by Jimmy Shan, the date is 2019-06-25
       *      Implementing Routing Function
       */
        private void insertSessionAttributes(JdbcSession session, List<String> attributeNames) {
            Assert.notEmpty(attributeNames, "attributeNames must not be null or empty");
          String sessionId = session.getId();
          int hashCode = Math.abs(sessionId.hashCode());
          int mod = hashCode % Integer.parseInt(shardingNum);
            if (attributeNames.size() > 1) {
                this.jdbcOperationMaps.get(mod).batchUpdate(this.createSessionAttributeQuery, new BatchPreparedStatementSetter() {
                            @Override
                            public void setValues(PreparedStatement ps, int i) throws SQLException {
                                    String attributeName = attributeNames.get(i);
                                    ps.setString(1, attributeName);
                                    setObjectAsBlob(ps, 2, session.getAttribute(attributeName));
                                    ps.setString(3, session.getId());
                            }
                            @Override
                            public int getBatchSize() {
                                    return attributeNames.size();
                            }
    
                });
            }    else {
                    this.jdbcOperationMaps.get(mod).update(this.createSessionAttributeQuery, (ps) -> {
                        String attributeName = attributeNames.get(0);
                        ps.setString(1, attributeName);
                        setObjectAsBlob(ps, 2, session.getAttribute(attributeName));
                        ps.setString(3, session.getId());
                    });
            }
        }
        
        /**
       * @desc Modify by Jimmy Shan, the date is 2019-06-25
       *      Implementing Routing Function
       */
        private void updateSessionAttributes(JdbcSession session, List<String> attributeNames) {
            Assert.notEmpty(attributeNames, "attributeNames must not be null or empty");
          String sessionId = session.getId();
          int hashCode = Math.abs(sessionId.hashCode());
          int mod = hashCode % Integer.parseInt(shardingNum);
            if (attributeNames.size() > 1) {
                    this.jdbcOperationMaps.get(mod).batchUpdate(this.updateSessionAttributeQuery, new BatchPreparedStatementSetter() {
                                @Override
                                public void setValues(PreparedStatement ps, int i) throws SQLException {
                                    String attributeName = attributeNames.get(i);
                                    setObjectAsBlob(ps, 1, session.getAttribute(attributeName));
                                    ps.setString(2, session.primaryKey);
                                    ps.setString(3, attributeName);
                                }
                                @Override
                                public int getBatchSize() {
                                    return attributeNames.size();
                                }
                    });
            }    else {
                    this.jdbcOperationMaps.get(mod).update(this.updateSessionAttributeQuery, (ps) -> {
                        String attributeName = attributeNames.get(0);
                        setObjectAsBlob(ps, 1, session.getAttribute(attributeName));
                        ps.setString(2, session.primaryKey);
                        ps.setString(3, attributeName);
                    });
            }
        }
        
        /**
       * @desc Modify by Jimmy Shan, the date is 2019-06-25
       *      Implementing Routing Function
       */
        private void deleteSessionAttributes(JdbcSession session, List<String> attributeNames) {
            Assert.notEmpty(attributeNames, "attributeNames must not be null or empty");
          String sessionId = session.getId();
          int hashCode = Math.abs(sessionId.hashCode());
          int mod = hashCode % Integer.parseInt(shardingNum);
            if (attributeNames.size() > 1) {
                    this.jdbcOperationMaps.get(mod).batchUpdate(this.deleteSessionAttributeQuery, new BatchPreparedStatementSetter() {
                                @Override
                                public void setValues(PreparedStatement ps, int i) throws SQLException {
                                    String attributeName = attributeNames.get(i);
                                    ps.setString(1, session.primaryKey);
                                    ps.setString(2, attributeName);
                                }
                                @Override
                                public int getBatchSize() {
                                    return attributeNames.size();
                                }
                    });
            } else {
                    this.jdbcOperationMaps.get(mod).update(this.deleteSessionAttributeQuery, (ps) -> {
                        String attributeName = attributeNames.get(0);
                        ps.setString(1, session.primaryKey);
                        ps.setString(2, attributeName);
                    });
            }
        }
        
        /**
       * @desc Modify by Jimmy Shan, the date is 2019-06-25
       *      Implementing Routing Function
       */
        public void cleanUpExpiredSessions() {
            Set<Integer> setKey = transOperationMaps.keySet();
            for (Iterator ir = setKey.iterator(); ir.hasNext(); ) {
                    Integer key = (Integer) ir.next();
                    TransactionOperations transOperation = (TransactionOperations) transOperationMaps.get(key);
                    Integer deletedCount = transOperation.execute((status) ->
                            CustomizedJdbcOperationsSessionRepository.this.jdbcOperationMaps.get(key).update(
                                    CustomizedJdbcOperationsSessionRepository.this.deleteSessionsByExpiryTimeQuery,
                                            System.currentTimeMillis()));
                    if (logger.isDebugEnabled()) {
                            logger.debug("Cleaned up " + deletedCount + " expired sessions");
                    }
            }
        }
    
        private static TransactionTemplate createTransactionTemplate(
                PlatformTransactionManager transactionManager) {
                TransactionTemplate transactionTemplate = new TransactionTemplate(
                        transactionManager);
                transactionTemplate.setPropagationBehavior(
                        TransactionDefinition.PROPAGATION_REQUIRES_NEW);
                transactionTemplate.afterPropertiesSet();
                return transactionTemplate;
        }
    
        private static GenericConversionService createDefaultConversionService() {
                GenericConversionService converter = new GenericConversionService();
                converter.addConverter(Object.class, byte[].class,
                        new SerializingConverter());
                converter.addConverter(byte[].class, Object.class,
                        new DeserializingConverter());
                return converter;
        }
    
        private String getQuery(String base) {
                return StringUtils.replace(base, "%TABLE_NAME%", this.tableName);
        }
    
        private void prepareQueries() {
                this.createSessionQuery = getQuery(CREATE_SESSION_QUERY);
                this.createSessionAttributeQuery = getQuery(CREATE_SESSION_ATTRIBUTE_QUERY);
                this.getSessionQuery = getQuery(GET_SESSION_QUERY);
                this.updateSessionQuery = getQuery(UPDATE_SESSION_QUERY);
                this.updateSessionAttributeQuery = getQuery(UPDATE_SESSION_ATTRIBUTE_QUERY);
                this.deleteSessionAttributeQuery = getQuery(DELETE_SESSION_ATTRIBUTE_QUERY);
                this.deleteSessionQuery = getQuery(DELETE_SESSION_QUERY);
                this.listSessionsByPrincipalNameQuery =
                        getQuery(LIST_SESSIONS_BY_PRINCIPAL_NAME_QUERY);
                this.deleteSessionsByExpiryTimeQuery =
                        getQuery(DELETE_SESSIONS_BY_EXPIRY_TIME_QUERY);
        }
    
        private void setObjectAsBlob(PreparedStatement ps, int paramIndex, Object object)
                throws SQLException {
                byte[] bytes = (byte[]) this.conversionService.convert(object,
                        TypeDescriptor.valueOf(Object.class),
                        TypeDescriptor.valueOf(byte[].class));
                this.lobHandler.getLobCreator().setBlobAsBytes(ps, paramIndex, bytes);
        }
    
        private Object getBlobAsObject(ResultSet rs, String columnName) throws SQLException {
                byte[] bytes = this.lobHandler.getBlobAsBytes(rs, columnName);
                return this.conversionService.convert(bytes, TypeDescriptor.valueOf(byte[].class),
                        TypeDescriptor.valueOf(Object.class));
        }
    
        private enum DeltaValue {
                ADDED, UPDATED, REMOVED
        }
    
        private static <T> Supplier<T> value(T value) {
                return (value != null) ? () -> value : null;
        }
    
        private static <T> Supplier<T> lazily(Supplier<T> supplier) {
                Supplier<T> lazySupplier = new Supplier<T>() {
                    private T value;
                    @Override
                    public T get() {
                        if (this.value == null) {
                            this.value = supplier.get();
                        }
                        return this.value;
                    }
                };
                
                return (supplier != null) ? lazySupplier : null;
        }
        
        
        final class JdbcSession implements Session {
    
            private final Session delegate;
    
            private final String primaryKey;
    
            private boolean isNew;
    
            private boolean changed;
    
            private Map<String, DeltaValue> delta = new HashMap<>();
    
            JdbcSession() {
                this.delegate = new MapSession();
                this.isNew = true;
                this.primaryKey = UUID.randomUUID().toString();
            }
    
            JdbcSession(String primaryKey, Session delegate) {
                Assert.notNull(primaryKey, "primaryKey cannot be null");
                Assert.notNull(delegate, "Session cannot be null");
                this.primaryKey = primaryKey;
                this.delegate = delegate;
            }
    
            boolean isNew() {
                return this.isNew;
            }
    
            boolean isChanged() {
                return this.changed;
            }
    
            Map<String, DeltaValue> getDelta() {
                return this.delta;
            }
    
            void clearChangeFlags() {
                this.isNew = false;
                this.changed = false;
                this.delta.clear();
            }
    
            String getPrincipalName() {
                return PRINCIPAL_NAME_RESOLVER.resolvePrincipal(this);
            }
    
            Instant getExpiryTime() {
                return getLastAccessedTime().plus(getMaxInactiveInterval());
            }
    
            @Override
            public String getId() {
                return this.delegate.getId();
            }
    
            @Override
            public String changeSessionId() {
                this.changed = true;
                return this.delegate.changeSessionId();
            }
    
            @Override
            public <T> T getAttribute(String attributeName) {
                Supplier<T> supplier = this.delegate.getAttribute(attributeName);
                return (supplier != null) ? supplier.get() : null;
            }
    
            @Override
            public Set<String> getAttributeNames() {
                return this.delegate.getAttributeNames();
            }
    
            @Override
            public void setAttribute(String attributeName, Object attributeValue) {
                boolean attributeExists = (this.delegate.getAttribute(attributeName) != null);
                boolean attributeRemoved = (attributeValue == null);
                if (!attributeExists && attributeRemoved) {
                    return;
                }
                if (attributeExists) {
                    if (attributeRemoved) {
                        this.delta.merge(attributeName, DeltaValue.REMOVED, (oldDeltaValue,
                                deltaValue) -> (oldDeltaValue == DeltaValue.ADDED) ? null
                                        : deltaValue);
                    }
                    else {
                        this.delta.merge(attributeName, DeltaValue.UPDATED,
                                (oldDeltaValue,
                                        deltaValue) -> (oldDeltaValue == DeltaValue.ADDED)
                                                ? oldDeltaValue
                                                : deltaValue);
                    }
                }
                else {
                    this.delta.merge(attributeName, DeltaValue.ADDED,
                            (oldDeltaValue, deltaValue) -> (oldDeltaValue == DeltaValue.ADDED)
                                    ? oldDeltaValue
                                    : DeltaValue.UPDATED);
                }
                this.delegate.setAttribute(attributeName, value(attributeValue));
                if (PRINCIPAL_NAME_INDEX_NAME.equals(attributeName) ||
                        SPRING_SECURITY_CONTEXT.equals(attributeName)) {
                    this.changed = true;
                }
            }
    
            @Override
            public void removeAttribute(String attributeName) {
                setAttribute(attributeName, null);
            }
    
            @Override
            public Instant getCreationTime() {
                return this.delegate.getCreationTime();
            }
    
            @Override
            public void setLastAccessedTime(Instant lastAccessedTime) {
                this.delegate.setLastAccessedTime(lastAccessedTime);
                this.changed = true;
            }
    
            @Override
            public Instant getLastAccessedTime() {
                return this.delegate.getLastAccessedTime();
            }
    
            @Override
            public void setMaxInactiveInterval(Duration interval) {
                this.delegate.setMaxInactiveInterval(interval);
                this.changed = true;
            }
    
            @Override
            public Duration getMaxInactiveInterval() {
                return this.delegate.getMaxInactiveInterval();
            }
    
            @Override
            public boolean isExpired() {
                return this.delegate.isExpired();
            }
    
        }
    
        static class PrincipalNameResolver {
    
            private SpelExpressionParser parser = new SpelExpressionParser();
    
            public String resolvePrincipal(Session session) {
                String principalName = session.getAttribute(PRINCIPAL_NAME_INDEX_NAME);
                if (principalName != null) {
                    return principalName;
                }
                Object authentication = session.getAttribute(SPRING_SECURITY_CONTEXT);
                if (authentication != null) {
                    Expression expression = this.parser
                            .parseExpression("authentication?.name");
                    return expression.getValue(authentication, String.class);
                }
                return null;
            }
    
        }
    
        private class SessionResultSetExtractor implements ResultSetExtractor<List<JdbcSession>> {
    
            @Override
            public List<JdbcSession> extractData(ResultSet rs) throws SQLException, DataAccessException {
                List<JdbcSession> sessions = new ArrayList<>();
                while (rs.next()) {
                    String id = rs.getString("SESSION_ID");
                    JdbcSession session;
                    if (sessions.size() > 0 && getLast(sessions).getId().equals(id)) {
                        session = getLast(sessions);
                    }
                    else {
                        MapSession delegate = new MapSession(id);
                        String primaryKey = rs.getString("PRIMARY_ID");
                        delegate.setCreationTime(Instant.ofEpochMilli(rs.getLong("CREATION_TIME")));
                        delegate.setLastAccessedTime(Instant.ofEpochMilli(rs.getLong("LAST_ACCESS_TIME")));
                        delegate.setMaxInactiveInterval(Duration.ofSeconds(rs.getInt("MAX_INACTIVE_INTERVAL")));
                        session = new JdbcSession(primaryKey, delegate);
                    }
                    String attributeName = rs.getString("ATTRIBUTE_NAME");
                    if (attributeName != null) {
                        Object attributeValue = getBlobAsObject(rs, "ATTRIBUTE_BYTES");
                        session.delegate.setAttribute(attributeName, lazily(() -> attributeValue));
                    }
                    sessions.add(session);
                }
                return sessions;
            }
    
            private JdbcSession getLast(List<JdbcSession> sessions) {
                return sessions.get(sessions.size() - 1);
            }
    
        }
    
    }

    以上工作都完成后,让我们来看看如何使用。

    代码如下:

    package com.szl.demo.spring.session.controller;
    
    import java.io.PrintWriter;
    import java.util.UUID;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import javax.servlet.http.HttpSession;
    import org.springframework.stereotype.Controller;
    import org.springframework.ui.ModelMap;
    import org.springframework.web.bind.annotation.RequestMapping;
    
    @Controller
    public class TestSessionController {
            
            @RequestMapping("/testGetSession")
            public void testGetSession(HttpServletRequest request, HttpServletResponse response, ModelMap model) {
                    PrintWriter out = null;
                    try {
                            out = response.getWriter();
                            
                            HttpSession session = request.getSession();
                            String content = (String) session.getAttribute("userId");
                            System.out.println("session content is : " + content);
                            
                            out.println("session content is : " + content);
                            out.flush();
                            out.close();
                    } catch (Exception e) {
                            e.printStackTrace();
                    }
            }
            
            @RequestMapping("/testSetSession")
            public void testSetSession(HttpServletRequest request, HttpServletResponse response, ModelMap model) {
                    PrintWriter out = null;
                    try {
                            out = response.getWriter();
                            UUID uuid = UUID.randomUUID();
                            String uid = uuid.toString().replaceAll("-", "");
                            
                            HttpSession session = request.getSession();
                            String content = (String) session.getAttribute("userId");
                            System.out.println("old session content is : " + content);
                            
                            
                            // 设置session内容
                            session.setAttribute("userId", uid);
                            out.println("new session content is : " + uid);
                            out.flush();
                            out.close();
                    } catch (Exception e) {
                            e.printStackTrace();
                    }
            }
    
    }

     是不是很简单,和平时使用session方式一样。

    下面是建表脚本(mysql5.6)

    CREATE TABLE SPRING_SESSION (
            PRIMARY_ID CHAR(36) NOT NULL,
            SESSION_ID CHAR(36) NOT NULL,
            CREATION_TIME BIGINT NOT NULL,
            LAST_ACCESS_TIME BIGINT NOT NULL,
            MAX_INACTIVE_INTERVAL INT NOT NULL,
            EXPIRY_TIME BIGINT NOT NULL,
            PRINCIPAL_NAME VARCHAR(100),
            CONSTRAINT SPRING_SESSION_PK PRIMARY KEY (PRIMARY_ID)
    ) ENGINE=InnoDB ROW_FORMAT=DYNAMIC;
    
    CREATE UNIQUE INDEX SPRING_SESSION_IX1 ON SPRING_SESSION (SESSION_ID);
    CREATE INDEX SPRING_SESSION_IX2 ON SPRING_SESSION (EXPIRY_TIME);
    CREATE INDEX SPRING_SESSION_IX3 ON SPRING_SESSION (PRINCIPAL_NAME);
    
    CREATE TABLE SPRING_SESSION_ATTRIBUTES (
            SESSION_PRIMARY_ID CHAR(36) NOT NULL,
            ATTRIBUTE_NAME VARCHAR(200) NOT NULL,
            ATTRIBUTE_BYTES BLOB NOT NULL,
            CONSTRAINT SPRING_SESSION_ATTRIBUTES_PK PRIMARY KEY (SESSION_PRIMARY_ID, ATTRIBUTE_NAME),
            CONSTRAINT SPRING_SESSION_ATTRIBUTES_PK FOREIGN KEY (SESSION_PRIMARY_ID) REFERENCES SPRING_SESSION(PRIMARY_ID) ON DELETE CASCADE
    ) ENGINE=InnoDB ROW_FORMAT=DYNAMIC;

    好了,备忘录记录到此,还是那句,
    仅供有需要的朋友参考,也欢迎转载,但请注明原著,谢谢。

  • 相关阅读:
    寒假Day37:设计模式(封装+继承+多态等)
    INF ClassInstall32 Section详解
    VS2008编译的程序运行提示“由于应用程序配置不正确,应用程序未能启动”
    INF Models Section
    INF DDInstall.Services Section
    INF ClassInstall32.Services Section详解
    INF DDInstall Section
    INF SourceDisksNames Section 和 SourceDisksFiles Section详解
    sys文件查看DbgPrint函数打印的信息
    IRP(I/O Request Package)详解
  • 原文地址:https://www.cnblogs.com/jimmyshan-study/p/11087223.html
Copyright © 2011-2022 走看看