zoukankan      html  css  js  c++  java
  • (35)Spring Boot集成Redis实现缓存机制【从零开始学Spring Boot】

    【本文章是否对你有用以及是否有好的建议,请留言】

           本文章牵涉到的技术点比较多:Spring Data JPARedisSpring MVC,Spirng Cache,所以在看这篇文章的时候,需要对以上这些技术点有一定的了解或者也可以先看看这篇文章,针对文章中实际的技术点在进一步了解(注意,您需要自己下载Redis Server到您的本地,所以确保您本地的Redis可用,这里还使用了MySql数据库,当然你也可以内存数据库进行测试)。这篇文章会提供对应的Eclipse代码示例,具体大体的分如下几个步骤:

    1)新建Java Maven Project;

    2)在pom.xml中添加相应的依赖包;

    3)编写Spring Boot启动类;

    4)配置application.properties;

    5)编写RedisCacheConfig配置类;

    6)编写DemoInfo测试实体类;

    7)编写DemoInfoRepository持久化类;

    8)编写DemoInfoService类;

    9)编写DemoInfoController类;

    10)测试代码是否正常运行了

    11)自定义缓存key;

     

     


    1)新建Java Maven Project;

           这个步骤就不细说,新建一个spring-boot-redis Java maven project;

     

    2)在pom.xml中添加相应的依赖包;

    Maven中添加相应的依赖包,主要有:spring boot 父节点依赖;spring boot web支持;缓存服务spring-context-support;添加redis支持;JPA操作数据库;mysql 数据库驱动,具体pom.xml文件如下:

    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

      xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

      <modelVersion>4.0.0</modelVersion>

     

      <groupId>com.kfit</groupId>

      <artifactId>spring-boot-redis</artifactId>

      <version>0.0.1-SNAPSHOT</version>

      <packaging>jar</packaging>

     

      <name>spring-boot-redis</name>

      <url>http://maven.apache.org</url>

     

      <properties>

        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

        <!-- 配置JDK编译版本. -->

        <java.version>1.8</java.version>

      </properties>

     

      <!-- spring boot 父节点依赖,

           引入这个之后相关的引入就不需要添加version配置,

            spring boot会自动选择最合适的版本进行添加。

        -->

        <parent>

           <groupId>org.springframework.boot</groupId>

           <artifactId>spring-boot-starter-parent</artifactId>

           <version>1.3.3.RELEASE</version>

        </parent>

     

     

      <dependencies>

           

            <dependency>

               <groupId>junit</groupId>

               <artifactId>junit</artifactId>

               <scope>test</scope>

           </dependency>

     

           <!-- spring boot web支持:mvc,aop... -->

           <dependency>

               <groupId>org.springframework.boot</groupId>

               <artifactId>spring-boot-starter-web</artifactId>

           </dependency>

          

           <!--

               包含支持UI模版(VelocityFreeMarkerJasperReports),

               邮件服务,

               脚本服务(JRuby)

               缓存CacheEHCache),

               任务计划Schedulinguartz)。

            -->

           <dependency>

              <groupId>org.springframework</groupId>

              <artifactId>spring-context-support</artifactId>

            </dependency>

     

           <!-- 添加redis支持-->

           <dependency>

                <groupId>org.springframework.boot</groupId>

                <artifactId>spring-boot-starter-redis</artifactId>

            </dependency>

          

           <!-- JPA操作数据库. -->

           <dependency>

              <groupId>org.springframework.boot</groupId>

              <artifactId>spring-boot-starter-data-jpa</artifactId>

            </dependency>

          

           <!-- mysql 数据库驱动. -->

           <dependency>

              <groupId>mysql</groupId>

              <artifactId>mysql-connector-java</artifactId>

            </dependency>

           

            <!-- 单元测试. -->

           <dependency>

                <groupId>org.springframework.boot</groupId>

                <artifactId>spring-boot-starter-test</artifactId>

                <scope>test</scope>

            </dependency>

          

      </dependencies>

    </project>

    上面是完整的pom.xml文件,每个里面都进行了简单的注释。

     

    3)编写Spring Boot启动类(com.kfit.App);

    package com.kfit;

    import org.springframework.boot.SpringApplication;

    import org.springframework.boot.autoconfigure.SpringBootApplication;

    /**

     * Spring Boot启动类;

     *

     * @author Angel(QQ:412887952)

     * @version v.0.1

     */

     

    @SpringBootApplication

    public class App {

           /**

            * -javaagent:.libspringloaded-1.2.4.RELEASE.jar -noverify

            * @param args

            */

           public static void main(String[] args) {

                  SpringApplication.run(App.class, args);

           }

    }

     

    4)配置application.properties;

    这里主要是配置两个资源,第一就是数据库基本信息;第二就是redis配置;第三就是JPA的配置;

    Src/main/resouces/application.properties

    ########################################################

    ###datasource  配置MySQL数据源;

    ########################################################

    spring.datasource.url = jdbc:mysql://localhost:3306/test

    spring.datasource.username = root

    spring.datasource.password = root

    spring.datasource.driverClassName = com.mysql.jdbc.Driver

    spring.datasource.max-active=20

    spring.datasource.max-idle=8

    spring.datasource.min-idle=8

    spring.datasource.initial-size=10

     

     

     

    ########################################################

    ###REDIS (RedisProperties) redis基本配置;

    ########################################################

    # database name

    spring.redis.database=0

    # server host1

    spring.redis.host=127.0.0.1  

    # server password

    #spring.redis.password=

    #connection port

    spring.redis.port=6379

    # pool settings ...

    spring.redis.pool.max-idle=8

    spring.redis.pool.min-idle=0

    spring.redis.pool.max-active=8

    spring.redis.pool.max-wait=-1

    # name of Redis server

    #spring.redis.sentinel.master=

    # comma-separated list of host:port pairs

    #spring.redis.sentinel.nodes=


    ########################################################
    ### Java Persistence Api 自动进行建表
    ########################################################
    # Specify the DBMS
    spring.jpa.database = MYSQL
    # Show or not log for each sql query
    spring.jpa.show-sql = true
    # Hibernate ddl auto (create, create-drop, update)
    spring.jpa.hibernate.ddl-auto = update
    # Naming strategy
    spring.jpa.hibernate.naming-strategy = org.hibernate.cfg.ImprovedNamingStrategy
    # stripped before adding them to the entity manager)
    spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5Dialect

     

     

    5)编写RedisCacheConfig配置类;

           缓存主要有几个要实现的类:其一就是CacheManager缓存管理器;其二就是具体操作实现类;其三就是CacheManager工厂类(这个可以使用配置文件配置的进行注入,也可以通过编码的方式进行实现);其四就是缓存key生产策略(当然Spring自带生成策略,但是在Redis客户端进行查看的话是系列化的key,对于我们肉眼来说就是感觉是乱码了,这里我们先使用自带的缓存策略)。

    com.kfit.config/RedisCacheConfig

    package com.kfit.config;

     

    import org.springframework.cache.CacheManager;

    import org.springframework.cache.annotation.CachingConfigurerSupport;

    import org.springframework.cache.annotation.EnableCaching;

    import org.springframework.context.annotation.Bean;

    import org.springframework.context.annotation.Configuration;

    import org.springframework.data.redis.cache.RedisCacheManager;

    import org.springframework.data.redis.connection.RedisConnectionFactory;

    import org.springframework.data.redis.core.RedisTemplate;

     

    /**

     * redis 缓存配置;

     *

     * 注意:RedisCacheConfig这里也可以不用继承CachingConfigurerSupport,也就是直接一个普通的Class就好了;

     *

     * 这里主要我们之后要重新实现 key的生成策略,只要这里修改KeyGenerator,其它位置不用修改就生效了。

     *

     * 普通使用普通类的方式的话,那么在使用@Cacheable的时候还需要指定KeyGenerator的名称;这样编码的时候比较麻烦。

     *

     * @author Angel(QQ:412887952)

     * @version v.0.1

     */

    @Configuration

    @EnableCaching//启用缓存,这个注解很重要;

    publicclass RedisCacheConfig extends CachingConfigurerSupport {

       

       

        /**

         * 缓存管理器.

         * @param redisTemplate

         * @return

         */

        @Bean

        public CacheManager cacheManager(RedisTemplate<?,?> redisTemplate) {

           CacheManager cacheManager = new RedisCacheManager(redisTemplate);

           returncacheManager;

        }

     

       

        /**

         * redis模板操作类,类似于jdbcTemplate的一个类;

         *

         * 虽然CacheManager也能获取到Cache对象,但是操作起来没有那么灵活;

         *

         * 这里在扩展下:RedisTemplate这个类不见得很好操作,我们可以在进行扩展一个我们

         *

         * 自己的缓存类,比如:RedisStorage;

         *

         * @param factory : 通过Spring进行注入,参数在application.properties进行配置;

         * @return

         */

        @Bean

        public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory factory) {

           RedisTemplate<String, String> redisTemplate = new RedisTemplate<String, String>();

           redisTemplate.setConnectionFactory(factory);

          

           //key序列化方式;(不然会出现乱码;,但是如果方法上有Long等非String类型的话,会报类型转换错误;

           //所以在没有自己定义key生成策略的时候,以下这个代码建议不要这么写,可以不配置或者自己实现ObjectRedisSerializer

           //或者JdkSerializationRedisSerializer序列化方式;

    //     RedisSerializer<String> redisSerializer = new StringRedisSerializer();//Long类型不可以会出现异常信息;

    //     redisTemplate.setKeySerializer(redisSerializer);

    //     redisTemplate.setHashKeySerializer(redisSerializer);

          

           returnredisTemplate;

        }

     

    }

    在以上代码有很详细的注释,在这里还是在简单的提下:

    RedisCacheConfig这里也可以不用继承CachingConfigurerSupport,也就是直接一个普通的Class就好了;这里主要我们之后要重新实现 key的生成策略,只要这里修改KeyGenerator,其它位置不用修改就生效了。普通使用普通类的方式的话,那么在使用@Cacheable的时候还需要指定KeyGenerator的名称;这样编码的时候比较麻烦。

     

     

    6)编写DemoInfo测试实体类;

           编写一个测试实体类:com.kfit.bean.DemoInfo

    package com.kfit.bean;

     

    import java.io.Serializable;

    import javax.persistence.Entity;

    import javax.persistence.GeneratedValue;

    import javax.persistence.Id;

    /**

     * 测试实体类,这个随便;

     * @author Angel(QQ:412887952)

     * @version v.0.1

     */

    @Entity

    publicclass DemoInfo  implements Serializable{

        privatestaticfinallongserialVersionUID = 1L;

        @Id@GeneratedValue

        privatelongid;

        private String name;

        private String pwd;

        publiclong getId() {

           returnid;

        }

        publicvoid setId(longid) {

           this.id = id;

        }

        public String getName() {

            returnname;

        }

        publicvoid setName(String name) {

           this.name = name;

        }

        public String getPwd() {

           returnpwd;

        }

        publicvoid setPwd(String pwd) {

           this.pwd = pwd;

        }

       

        @Override

        public String toString() {

           return"DemoInfo [id=" + id + ", name=" + name + ", pwd=" + pwd + "]";

        }

    }

     

    7)编写DemoInfoRepository持久化类;

           DemoInfoRepository使用Spirng Data JPA实现:

    com.kfit.repository.DemoInfoRepository

    package com.kfit.repository;

     

    import org.springframework.data.repository.CrudRepository;

     

    import com.kfit.bean.DemoInfo;

     

    /**

     * DemoInfo持久化类

     * @author Angel(QQ:412887952)

     * @version v.0.1

     */

    publicinterface DemoInfoRepository extends CrudRepository<DemoInfo,Long> {

     

    }

     

    8)编写DemoInfoService类;

           编写DemoInfoService,这里有两个技术方面,第一就是使用Spring @Cacheable注解方式和RedisTemplate对象进行操作,具体代码如下:

    com.kfit.service.DemoInfoService:

    package com.kfit.service;

     

    import com.kfit.bean.DemoInfo;

     

    /**

     * demoInfo 服务接口

     * @author Angel(QQ:412887952)

     * @version v.0.1

     */

    publicinterface DemoInfoService {

       

        public DemoInfo findById(longid);

       

        publicvoid deleteFromCache(longid);

     

        void test();

    }

     

     

    com.kfit.service.impl.DemoInfoServiceImpl:

    package com.kfit.service.impl;

     

    import javax.annotation.Resource;

     

    import org.springframework.cache.annotation.CacheEvict;

    import org.springframework.cache.annotation.Cacheable;

    import org.springframework.data.redis.core.RedisTemplate;

    import org.springframework.data.redis.core.ValueOperations;

    import org.springframework.stereotype.Service;

     

    import com.kfit.bean.DemoInfo;

    import com.kfit.repository.DemoInfoRepository;

    import com.kfit.service.DemoInfoService;

     

    /**

     *

     *DemoInfo数据处理类

     *

     * @author Angel(QQ:412887952)

     * @version v.0.1

     */

    @Service

    publicclass DemoInfoServiceImpl implements DemoInfoService {

       

        @Resource

        private DemoInfoRepository demoInfoRepository;

       

        @Resource

        private RedisTemplate<String,String> redisTemplate;

       

        @Override

        publicvoid test(){

           ValueOperations<String,String> valueOperations = redisTemplate.opsForValue();

           valueOperations.set("mykey4", "random1="+Math.random());

           System.out.println(valueOperations.get("mykey4"));

        }

       

        //keyGenerator="myKeyGenerator"

        @Cacheable(value="demoInfo") //缓存,这里没有指定key.

        @Override

        public DemoInfo findById(longid) {

           System.err.println("DemoInfoServiceImpl.findById()=========从数据库中进行获取的....id="+id);

           returndemoInfoRepository.findOne(id);

        }

       

        @CacheEvict(value="demoInfo")

        @Override

        publicvoid deleteFromCache(longid) {

           System.out.println("DemoInfoServiceImpl.delete().从缓存中删除.");

        }

       

    }

     

     

    9)编写DemoInfoController类;

    package com.kfit.controller;

     

    import org.springframework.beans.factory.annotation.Autowired;

    import org.springframework.stereotype.Controller;

    import org.springframework.web.bind.annotation.RequestMapping;

    import org.springframework.web.bind.annotation.ResponseBody;

     

    import com.kfit.bean.DemoInfo;

    import com.kfit.service.DemoInfoService;

     

    /**

     * 测试类.

     * @author Angel(QQ:412887952)

     * @version v.0.1

     */

    @Controller

    publicclass DemoInfoController {

       

        @Autowired

         DemoInfoService demoInfoService;

         

       

        @RequestMapping("/test")

        public@ResponseBody String test(){

            DemoInfo loaded = demoInfoService.findById(1);

    System.out.println("loaded="+loaded);

    DemoInfo cached = demoInfoService.findById(1);

            System.out.println("cached="+cached);

            loaded = demoInfoService.findById(2);

            System.out.println("loaded2="+loaded);

            return"ok";

        }

       

       

        @RequestMapping("/delete")

        public@ResponseBody String delete(longid){

            demoInfoService.deleteFromCache(id);

            return"ok";

        }

       

        @RequestMapping("/test1")

        public@ResponseBody String test1(){

            demoInfoService.test();

            System.out.println("DemoInfoController.test1()");

            return"ok";

        }

       

    }

     

    10)测试代码是否正常运行了

     

    启动应用程序,访问地址:http://127.0.0.1:8080/test

    查看控制台可以查看:

    DemoInfoServiceImpl.findById()=========从数据库中进行获取的....id=1

    loaded=DemoInfo [id=1, name=张三, pwd=123456]

    cached=DemoInfo [id=1, name=张三, pwd=123456]

    DemoInfoServiceImpl.findById()=========从数据库中进行获取的....id=2

    loaded2=DemoInfo [id=2, name=张三, pwd=123456]

    如果你看到以上的打印信息的话,那么说明缓存成功了。

     

    访问地址:http://127.0.0.1:8080/test1

    random1=0.9985031320746356

    DemoInfoController.test1()

     

    二次访问:http://127.0.0.1:8080/test

    loaded=DemoInfo [id=1, name=张三, pwd=123456]

    cached=DemoInfo [id=1, name=张三, pwd=123456]

    loaded2=DemoInfo [id=2, name=张三, pwd=123456]

    这时候所有的数据都是执行缓存的。

     

    这时候执行删除动作:http://127.0.0.1:8080/delete?id=1

    然后在访问:http://127.0.0.1:8080/test

    DemoInfoServiceImpl.findById()=========从数据库中进行获取的....id=1

    loaded=DemoInfo [id=1, name=张三, pwd=123456]

    cached=DemoInfo [id=1, name=张三, pwd=123456]

    loaded2=DemoInfo [id=2, name=张三, pwd=123456]

     

    11)自定义缓存key;

    com.kfit.config.RedisCacheConfig类中重写CachingConfigurerSupport中的keyGenerator ,具体实现代码如下:

     

    /**

         * 自定义key.

         * 此方法将会根据类名+方法名+所有参数的值生成唯一的一个key,即使@Cacheable中的value属性一样,key也会不一样。

         */

        @Override

        public KeyGenerator keyGenerator() {

           System.out.println("RedisCacheConfig.keyGenerator()");

           returnnew KeyGenerator() {

               @Override

               public Object generate(Object o, Method method, Object... objects) {

                  // This will generate a unique key of the class name, the method name

                  //and all method parameters appended.

                  StringBuilder sb = new StringBuilder();

                  sb.append(o.getClass().getName());

                  sb.append(method.getName());

                  for (Object obj : objects) {

                      sb.append(obj.toString());

                  }

                  System.out.println("keyGenerator=" + sb.toString());

                  returnsb.toString();

               }

           };

        }

     

    这时候在redis的客户端查看key的话还是序列化的肉眼看到就是乱码了,那么我改变key的序列方式,这个很简单,redis底层已经有具体的实现类了,我们只需要配置下:

    //key序列化方式;(不然会出现乱码;,但是如果方法上有Long等非String类型的话,会报类型转换错误;

    //所以在没有自己定义key生成策略的时候,以下这个代码建议不要这么写,可以不配置或者自己实现ObjectRedisSerializer

    //或者JdkSerializationRedisSerializer序列化方式;

           RedisSerializer<String> redisSerializer = new StringRedisSerializer();//Long类型不可以会出现异常信息;

           redisTemplate.setKeySerializer(redisSerializer);

           redisTemplate.setHashKeySerializer(redisSerializer);

    综上以上分析:RedisCacheConfig类的方法调整为:

    package com.kfit.config;

     

    import java.lang.reflect.Method;

     

    import org.springframework.cache.CacheManager;

    import org.springframework.cache.annotation.CachingConfigurerSupport;

    import org.springframework.cache.annotation.EnableCaching;

    import org.springframework.cache.interceptor.KeyGenerator;

    import org.springframework.context.annotation.Bean;

    import org.springframework.context.annotation.Configuration;

    import org.springframework.data.redis.cache.RedisCacheManager;

    import org.springframework.data.redis.connection.RedisConnectionFactory;

    import org.springframework.data.redis.core.RedisTemplate;

    import org.springframework.data.redis.serializer.RedisSerializer;

    import org.springframework.data.redis.serializer.StringRedisSerializer;

     

    /**

     * redis 缓存配置;

     *

     * 注意:RedisCacheConfig这里也可以不用继承CachingConfigurerSupport,也就是直接一个普通的Class就好了;

     *

     * 这里主要我们之后要重新实现 key的生成策略,只要这里修改KeyGenerator,其它位置不用修改就生效了。

     *

     * 普通使用普通类的方式的话,那么在使用@Cacheable的时候还需要指定KeyGenerator的名称;这样编码的时候比较麻烦。

     *

     * @author Angel(QQ:412887952)

     * @version v.0.1

     */

    @Configuration

    @EnableCaching//启用缓存,这个注解很重要;

    publicclass RedisCacheConfig extends CachingConfigurerSupport {

       

        /**

         * 缓存管理器.

         * @param redisTemplate

         * @return

         */

        @Bean

        public CacheManager cacheManager(RedisTemplate<?,?> redisTemplate) {

           CacheManager cacheManager = new RedisCacheManager(redisTemplate);

           returncacheManager;

        }

     

       

        /**

         * RedisTemplate缓存操作类,类似于jdbcTemplate的一个类;

         *

         * 虽然CacheManager也能获取到Cache对象,但是操作起来没有那么灵活;

         *

         * 这里在扩展下:RedisTemplate这个类不见得很好操作,我们可以在进行扩展一个我们

         *

         * 自己的缓存类,比如:RedisStorage;

         *

         * @param factory : 通过Spring进行注入,参数在application.properties进行配置;

         * @return

         */

        @Bean

        public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory factory) {

           RedisTemplate<String, String> redisTemplate = new RedisTemplate<String, String>();

           redisTemplate.setConnectionFactory(factory);

          

           //key序列化方式;(不然会出现乱码;,但是如果方法上有Long等非String类型的话,会报类型转换错误;

           //所以在没有自己定义key生成策略的时候,以下这个代码建议不要这么写,可以不配置或者自己实现ObjectRedisSerializer

           //或者JdkSerializationRedisSerializer序列化方式;

           RedisSerializer<String> redisSerializer = new StringRedisSerializer();//Long类型不可以会出现异常信息;

           redisTemplate.setKeySerializer(redisSerializer);

           redisTemplate.setHashKeySerializer(redisSerializer);

          

           returnredisTemplate;

        }

       

        /**

         * 自定义key.

         * 此方法将会根据类名+方法名+所有参数的值生成唯一的一个key,即使@Cacheable中的value属性一样,key也会不一样。

         */

        @Override

        public KeyGenerator keyGenerator() {

           System.out.println("RedisCacheConfig.keyGenerator()");

           returnnew KeyGenerator() {

               @Override

               public Object generate(Object o, Method method, Object... objects) {

                  // This will generate a unique key of the class name, the method name

                  //and all method parameters appended.

                  StringBuilder sb = new StringBuilder();

                  sb.append(o.getClass().getName());

                  sb.append(method.getName());

                  for (Object obj : objects) {

                      sb.append(obj.toString());

                  }

                  System.out.println("keyGenerator=" + sb.toString());

                  returnsb.toString();

               }

           };

        }

     

    }

     

    这时候在访问地址:http://127.0.0.1:8080/test

    这时候看到的Key就是:com.kfit.service.impl.DemoInfoServiceImplfindById1

    在控制台打印信息是:

    1keyGenerator=com.kfit.service.impl.DemoInfoServiceImplfindById1

    2DemoInfoServiceImpl.findById()=========从数据库中进行获取的....id=1

    3keyGenerator=com.kfit.service.impl.DemoInfoServiceImplfindById1

    4loaded=DemoInfo [id=1, name=张三, pwd=123456]

    5keyGenerator=com.kfit.service.impl.DemoInfoServiceImplfindById1

    6cached=DemoInfo [id=1, name=张三, pwd=123456]

    7keyGenerator=com.kfit.service.impl.DemoInfoServiceImplfindById2

    8keyGenerator=com.kfit.service.impl.DemoInfoServiceImplfindById2

    10DemoInfoServiceImpl.findById()=========从数据库中进行获取的....id=2

    11loaded2=DemoInfo [id=2, name=张三, pwd=123456]

     

    其中@Cacheable,@CacheEvict下节进行简单的介绍,这节的东西实在是太多了,到这里就打住吧,剩下的就需要靠你们自己进行扩展了。

     

    Spring Boot 系列博客】

    0)前言【从零开始学Spring Boot :

    http://412887952-qq-com.iteye.com/blog/2291496

     

    1spring boot起步之Hello World【从零开始学Spring Boot:

    http://412887952-qq-com.iteye.com/blog/2291500

     

    2Spring Boot返回json数据【从零开始学Spring Boot

    http://412887952-qq-com.iteye.com/blog/2291508

     

    (15)Spring Boot使用Druid和监控配置【从零开始学Spring Boot】

    http://412887952-qq-com.iteye.com/blog/2292362

     

    16Spring Boot使用Druid(编程注入)【从零开始学Spring Boot

    http://412887952-qq-com.iteye.com/blogs/2292376

     

    17Spring Boot普通类调用bean【从零开始学Spring Boot】:

    http://412887952-qq-com.iteye.com/blog/2292388

     ......

    (35)Spring Boot集成Redis实现缓存机制【从零开始学Spring Boot】

    http://412887952-qq-com.iteye.com/blog/2294942

     

     

    更多查看博客:http://412887952-qq-com.iteye.com/blog

     

     

     

     

  • 相关阅读:
    第十四周学习进度
    二阶段冲刺(七)
    二阶段冲刺(六)
    二阶段冲刺(五)
    二阶段冲刺(四)
    二阶段冲刺(三)
    二阶段冲刺(二)
    二阶段冲刺(一)
    第十三周学习进度
    linux初级学习笔记二:linux操作系统及常用命令,文件的创建与删除和命名规则,命令行展开以及linux中部分目录的作用!(视频序号:02_3)
  • 原文地址:https://www.cnblogs.com/hehehaha/p/6147108.html
Copyright © 2011-2022 走看看