zoukankan      html  css  js  c++  java
  • SpringBoot基础

    1.什么是springboot?

     是简化Spring应用开发的一个框架;整个Spring技术栈的一个大整合;J2EE开发的一站式解决方案;

    2.springboot入门

    1)创建一个maven的工程(jar),导入依赖:

    <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>1.5.1.RELEASE</version>
     </parent>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
        </dependencies>

    2)新建主程序,用来启动springboot:

    @SpringBootApplication
    // @SpringBootApplication 来标注一个主程序类,说明这是一个Spring Boot应用
    public class Test {
        public static void main(String[] args) {
            SpringApplication.run(Test.class,args);//启动springboot
        }
    }

    3)在主程序的同包下编写一个controller类,进行测试:

    @RestController
    public class MyController {
        @RequestMapping("/get")
        public String get(){
            return "hello world!";
        }
    }

    4)运行主程序。启动成功后会在控制台输入

    5)在浏览器测试。在浏览器输入localhost:8080/get.do,即可输出hello world!。此时已经入门了。需要注意的就是第二步的注解@SpringBootApplication必须要写。

    6)修改tomcat的端口。在资源目录新建一个application.properties的配置文件,添加下面一行代码,即可修改tomcat的端口,注意这个文件名是固定的:

    server.port=8888

    7)读取配置文件的信息

    我们可以在application.properties中定义自己的配置,然后来读取

    (1)在application.properties中添加:

    url=http://www.baidu.com

    (2)在controller类中添加获取配置的方法

     @Autowired
      private Environment env;
      //包:import org.springframework.core.env.Environment;
      @RequestMapping("getUrl")
      public String getUrl(){
          return env.getProperty("url");
      }

    启动springboot,在浏览器输入http://localhost:8080/getUrl 就会在浏览器显示http://www.baidu.com。

    8)热部署(不常用)

    当修改程序后,不需要重启springboot,就可以更新。添加以下依赖,那么热部署就可以实现了。

    <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-devtools</artifactId>
    </dependency>

    9)注解深究

    (1)打开SpringBoootApplication注解的类,可用看到一些注解:

    @Target({ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    @SpringBootConfiguration
    @EnableAutoConfiguration
    @ComponentScan

    @SpringBootConfiguration:是Spring Boot的配置类;标注在某个类上,表示这是一个Spring Boot的配置类。

    @EnableAutoConfiguration:开启允许自动配置。这样spring会帮我们自动配置。

    @ComponentScan:完成包扫描,是扫描启动类所在的包以及下面的包。

    (2)进入@EnableAutoConfiguration类,有如下部分注解:

    @AutoConfigurationPackage
    @Import({EnableAutoConfigurationImportSelector.class})
    public @interface EnableAutoConfiguration {

    @AutoConfigurationPackage:自动配置包

    @Import(AutoConfigurationPackages.Registrar.class):是Spring的底层注解@Import,给容器中导入一个组件;导入的组件由AutoConfigurationPackages.Registrar.class将主配置类(@SpringBootApplication标注的类)的所在包及下面所有子包里面的所有组件扫描到Spring容器。

    (3)给容器中导入组件

    EnableAutoConfigurationImportSelector:它是导入哪些组件的选择器,将所有需要导入的组件以全类名的方式返回,这些组件就会被添加到容器中。它会给容器中导入非常多的自动配置类(xxxAutoConfiguration)。就是给容器中导入这个场景需要的所有组件,并配置好这些组件。

    Spring Boot在启动的时候从类路径下的META-INF/spring.factories中获取EnableAutoConfiguration指定的值,将这些值作为自动配置类导入到容器中,自动配置类就生效,帮我们进行自动配置工作。

    10)不使用spring-boot-starter-parent

    如果需要使用自己的parent,那么就需要对依赖进行管理,添加如下代码到pom.xml:

      <dependencyManagement>
            <dependencies>
                <dependency>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-dependencies</artifactId>
                    <version>2.0.4</version>
                    <type>pom</type>
                    <scope>import</scope>
                </dependency>
            </dependencies>
        </dependencyManagement>

    此时就不需要继承spring-boot-starter-parent了,但还有一些需要配置,配置java的版本:

    <plugin>
           <groupId>org.apache.maven.plugins</groupId>
           <artifactId>maven-compiler-plugin</artifactId>
           <version>3.1</version>
           <configuration>
                 <source>1.8</source>
                 <target>1.8</target>
          </configuration>
      </plugin>

    至于格式编码,这里是使用maven创建的,需要配置,如果是按照下面的快速方法创建,就不需要配置:

    <properties>
         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
         <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    </properties>

    3.使用Spring Initializer快速创建springboot项目

    新建一个项目,选择spring initializer来创建:

     点击下一步,填写项目组和项目名称,再点击下一步,根据需要选择需要的组件,然后完成即可。这里就选择spring web。

     创建成功后会发现它已经帮我们创建好了主程序代码,我们只需要在里面进行其他的开发即可。

    1)定制banner

    springboot启动时会打印一个banner,就是spring的艺术字,这个是可以自定义的。在资源目录下创建一个banner.txt,在里面写入的文本就会在项目启动的时候打印出来。

    A.若设置艺术字,可以到下面的网站去生成艺术字,然后复制到txt文件中即可。网址:

    http://www.network-science.de/ascii/
    http://www.kammerl.de/ascii/AsciiSignature.php
    http://www.patorjk.com/software/taag
    

    B.不过这个banner也是可以设置关闭的,修改mian方法:

    public static void main(String[] args) {
            SpringApplicationBuilder builder=new SpringApplicationBuilder(DemoApplication.class);
            builder.bannerMode(Banner.Mode.OFF).run(args);
    //      SpringApplication.run(DemoApplication.class, args);
     }

    C.若要显示SpringBoot的版本信息,则可以在里面添加下面的代码

    ${spring-boot.version}
    ${spring-boot.formatted-version}

    第二行是对第一行进行格式化后显示的版本信息,如图

    2)tomcat常规配置

    在applicatopn.properties中加:

    #配置项目的端口号
    server.port=8081
    #配置项目出错时跳转的页面
    server.error.path=/error
    #配置session的生效时间,30m表示30分钟
    server.servlet.session.timeout=30m
    #配置项目的名称,配置后需在访问路径中加项目名称,若不配置则默认是/
    server.servlet.context-path=/demo
    #配置tomcat的编码
    server.tomcat.uri-encoding=UTF-8
    #配置tomcat的最大线程数
    server.tomcat.max-connections=1000
    #配置tomcat运行日志和临时文件的目录
    server.tomcat.basedir=/home/tmp

    对于applicatopn.properties配置文件,可以存放在四个位置,分别是项目的根目录下的config文件夹中、项目根目录下、资源目录的config文件夹中、资源目录下。springboot的加载优先级是:上面四个位置依次从高到低,然后加载到Spring Environment中。yml的优先级同上。

    3)jetty配置

    Jetty 是一个开源的servlet容器,它为基于Java的web容器,例如为JSP和servlet提供运行环境。使用和tomcat类似,这里就去除tomcat,添加jetty进行配置:

     <dependency>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter-web</artifactId>
           <!--去除默认的tomcat,添加jetty依赖-->
           <exclusions>
                <exclusion>
                   <groupId>org.springframework.boot</groupId>
                   <artifactId>spring-boot-starter-tomcat</artifactId>
                 </exclusion>
           </exclusions>
      </dependency>
      <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-jetty</artifactId>
      </dependency>

    undertow是红帽公司开源的java服务器,配置和jetty一样,在此略。

    4.Spring Boot的配置文件

    虽然springboot已经帮我们写好了很多的配置,但是有些配置需要使用我们自己的,就可以进行配置。springboot使用全局的配置文件。文件名是固定的,为application.properties或application.yml。两者的功能是类似的,只是内容的格式不一样,但是properties 的优先级要高于yml。

    yml原名叫yaml,现在简称为yml。使用yml的方式文件名必须是application.yml,properties的形式在这里不介绍了。对于properties的文件,要转换成yml文件,需要把"."变成树形结构

    4.1yml内容形式

    (1)k: v。键值对的形式,中间必须有空格。也可以使用缩进的方式控制层级关系,清楚明了,大小写敏感。

    server:
      port: 8888

    (2)给(对象、map)变量赋值。字符串不需要加双引号:

    person:
      name: 张三
      age: 20

    (3)也可以使用行内式,如下:

    person: {name: 张三,age: 20}

    4.2将数据注入bean

    @Component
    @ConfigurationProperties(prefix = "person")
    public class Test {
        private String name;
        private int age;
    }

    @ConfigurationProperties 表示 告诉 SpringBoot 将本类中的所有属性和配置文件中相关的配置进行绑定。prefix = "person" 表示 将配置文件中 key 为 person的下面所有的属性与本类属性进行一一映射注入值,如果配置文件中不存在 "person" 的 key,则不会为 POJO 注入值,属性值仍然为默认值

    4.3获取属性值

        @Resource
        private PersonTest personTest;
    
        @Test
        public void test1() {
            System.out.println(personTest);
        }

    4.4其他类型赋值

    (1)给数组(list,set)赋值 

    animal:
      - dog
      - cat
      - pig

    也可以使用行内式,如下:

    animal: [cat,dog,pig]
    

    4.5读取配置文件内容示例

    application.yml的内容如下:

    # 项目相关配置
    student:
        # 项目名称
        name: Student
        #文件路径
        path: c:/student

    创建项目配置的实体类,如下:

    package com.kanq.exchange.domain;
    
    import org.springframework.boot.context.properties.ConfigurationProperties;
    import org.springframework.stereotype.Component;
    
    /*
    * 读取项目相关配置
    * */
    @Component
    @ConfigurationProperties(prefix = "student")
    public class SystemConfig {
        //项目名称
        private String name;
        //文件路径
        private static String path;
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public static String getPath() {
            return path;
        }
    
        public void setPath(String path) {
            this.path = path;
        }
        //返回文件路径
        public static String getFilePath()
        {
            return getPath();
        }
    }

    获取路径:

    package com.kanq.exchange.action;
    
    import com.kanq.exchange.domain.SystemConfig;
    import org.springframework.stereotype.Controller;
    
    @Controller
    public class StuController {
    
        //获取路径
        public void getPath(){
            String path=SystemConfig.getFilePath();
        }
    
    }

    5.使用配置文件给对象注入值

    新建一个springboot的工程,使用spring initializer创建。

    1)配置文件application.yml:

    person:
      name: 张三
      age: 20
      map:
        key: v1
      brith: 2019/5/5
      list:
        - name: 测试1
        - name: 测试2
      dog:
        name: 小三
        age: 1

    2)实体类

    类Dog:

    public class Dog {
        private String name;
        private int age;
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public int getAge() {
            return age;
        }
    
        public void setAge(int age) {
            this.age = age;
        }
    
        @Override
        public String toString() {
            return "Dog{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    '}';
        }
    }

    类Student:

    public class Student {
        private String name;
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        @Override
        public String toString() {
            return "Student{" +
                    "name='" + name + '\'' +
                    '}';
        }
    }

    类Person:

    @Component
    @ConfigurationProperties(prefix = "person")
    public class Person {
        private String name;
        private int gae;
        private Date brith;
        private Map map;
        private List<Student> list;
        private Dog dog;
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public int getGae() {
            return gae;
        }
    
        public void setGae(int gae) {
            this.gae = gae;
        }
    
        public Date getBrith() {
            return brith;
        }
    
        public void setBrith(Date brith) {
            this.brith = brith;
        }
    
        public Map getMap() {
            return map;
        }
    
        public void setMap(Map map) {
            this.map = map;
        }
    
        public List<Student> getList() {
            return list;
        }
    
        public void setList(List<Student> list) {
            this.list = list;
        }
    
        public Dog getDog() {
            return dog;
        }
    
        public void setDog(Dog dog) {
            this.dog = dog;
        }
    
        @Override
        public String toString() {
            return "Person{" +
                    "name='" + name + '\'' +
                    ", gae=" + gae +
                    ", brith=" + brith +
                    ", map=" + map +
                    ", list=" + list +
                    ", dog=" + dog +
                    '}';
        }
    }

    3)导入配置文件处理器依赖,配置文件进行绑定就会有提示

     <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
     </dependency>

    4)处理乱码。properties配置文件在idea中可能会乱码,需要进行处理:

     5)测试。在测试类中修改:

       @Autowired
        private Person person;
        @Test
        public void contextLoads() {
            System.out.println(person);
        }

    运行测试类,会在控制台打印person的内容。

    6)配置文件的占位符

    (1)随机数

    ${random.int},${random.value}。。。

    (2)指定默认值。占位符获取之前配置的值,如果没有可以使用:默认值。下面是用.properties的配置演示的:

    person.last‐name=张三${random.uuid}
    person.age=${random.int}
    person.birth=2017/12/15
    person.map.k1=v1
    person.map.k2=14
    person.list=a,b,c
    person.dog.name=${person.hello:hello}_dog
    person.dog.age=15

    6.@Bean的使用

    springboot通常使用@Bean来注入对象,在方法上加这个注解,把此方法的返回值添加到spring容器中,也可以在这个类中把其他类交给spring管理。

    @Configuration
    public class Hello {
        @Bean
        public Hello getHello(){
            return new Hello();
        }
        public void say(){
            System.out.println("hello");
        }
    }

    这样也可以把Hello这个对象交给spring来管理,也是通过@Autowired来获取。

    7.profile

    不同的时期配置可能不同,可以在配置文件中定义不同的环境来做不同的配置。

    7.1使用properties方式:

    1)创建测试的环境配置文件application-dev.properties,以配置端口号为例:

    server.port=8888

    2)创建debug的环境配置文件application-debug.properties

    server.port=9999

    3)创建配置文件application.properties,指定激活的配置文件,这里激活dev的环境:

    spring.profiles.active=dev

    7.2使用yml方式:

    yml方式是在同一个配置文件中编写多个环境块,用---分隔

    #在一个文件中进行配置
    #指定激活哪个配置
    spring:
      profiles:
        active: bug
    
    ---
    server:
      port: 6666
    #指定环境的名字
    spring:
      profiles: test
    
    ---
    server:
      port: 5555
    #指定环境的名字
    spring:
      profiles: bug

    也可以通过命令行的方式来指定运行那个环境,运行方式如下:

    --spring.profiles.active=test

    点击ok,然后再运行,会发现运行的是test的环境,而不是配置文件中的bug,原因是在外部的配置大于在内部的配置。

    8.日志

    1)日志框架

    市面上的日志框架:JUL、JCL、Jboss-logging、logback、log4j、log4j2、slf4j....

    日志门面(日志的抽象层)API     日志实现
    JCL、SLF4j、Jboss-logging

    Log4j、JUL、Log4j2、Logback

    SpringBoot的底层是Spring框架,Spring框架默认是用JCL;SpringBoot选用 SLF4jlogback。

    2)SLF4j的使用

    在测试类中新建一个方法,用来测试日志。(这里默认已经创建了springboot的工程)

     @Test
        public void logTest(){
            Logger logger = LoggerFactory.getLogger(SpringbootDemoApplicationTests.class);
            logger.trace("trace");
            logger.debug("debug");
            logger.info("info");
            logger.warn("warn");
            logger.error("error");
        }

    使用properties方式配置日志级别,在application.properties加下面一行代码,运行测试类发现只打印warn以上级别的信息:

    #要配置到包,指定某一个包的日志级别
    logging.level.com=warn

    使用yml方式配置日志级别,在application.yml加下面的代码,运行测试类发现只打印trace以上级别的信息:

    spring:
      profiles:
        active: pro
    
    ---
    logging:
      level: {com: trace}
    spring:
      profiles: pro
    
    ---
    logging:
      level: {com: error}
    spring:
      profiles: release

    其他问题:使用不同的框架,就会产生不同的日志框架,让系统中所有的日志都统一到slf4j? 在api和实现层的中间添加不同的适配层即可,具体来说,先将系统中其他日志框架先排除出去,再用中间包来替换原有的日志框架,最后导入slf4j其他的实现。

    3)日志输出到文件

    path:后接目录,会把日志文件放在项目所在的目录的根目录下,默认文件名是spring.log。

    file:后接文件名,会把日志文件放在项目下。如果两个同时使用,则file生效。

    logging:
      level: {com: trace}
      path: abc/log

    这里日志文件存放位置是:springboot-demo1\abc\log\spring.log。其中springboot-demo1是项目名。

    logging:
      level: {com: trace}
      file: log.log

    这里日志文件存放位置是:springboot-demo1\log.log。其中springboot-demo1是项目名。

     4)为日志文件指定配置

    如果有自己的配置文件,就引入进来,但是必须以logback-spring.xml为名或logback.xml。

    如果名字是logback.xml,那么它直接就被日志框架识别了;如果名字是logback-spring.xml,那么日志框架就不直接加载日志的配置项,由SpringBoot解析日志配置,可以使用SpringBoot的高级Profile功能。

    新建一个配置文件,名为logback.xml,测试:

    <?xml version="1.0" encoding="UTF-8"?>
    <!--
    scan:当此属性设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true。
    scanPeriod:设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒当scan为true时,此属性生效。默认的时间间隔为1分钟。
    debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。
    -->
    <configuration scan="false" scanPeriod="60 seconds" debug="false">
        <!-- 定义日志的根目录 -->
        <property name="LOG_HOME" value="/app/log" />
        <!-- 定义日志文件名称 -->
        <property name="appName" value="atguigu-springboot"></property>
        <!-- ch.qos.logback.core.ConsoleAppender 表示控制台输出 -->
        <appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
            <!--
            日志输出格式:
                %d表示日期时间,
                %thread表示线程名,
                %-5level:级别从左显示5个字符宽度
                %logger{50} 表示logger名字最长50个字符,否则按照句点分割。 
                %msg:日志消息,
                %n是换行符
            -->
            <layout class="ch.qos.logback.classic.PatternLayout">
                <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
            </layout>
        </appender>
    
        <!-- 滚动记录文件,先将日志记录到指定文件,当符合某个条件时,将日志记录到其他文件 -->  
        <appender name="appLogAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
            <!-- 指定日志文件的名称 -->
            <file>${LOG_HOME}/${appName}.log</file>
            <!--
            当发生滚动时,决定 RollingFileAppender 的行为,涉及文件移动和重命名
            TimeBasedRollingPolicy: 最常用的滚动策略,它根据时间来制定滚动策略,既负责滚动也负责出发滚动。
            -->
            <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
                <!--
                滚动时产生的文件的存放位置及文件名称 %d{yyyy-MM-dd}:按天进行日志滚动 
                %i:当文件大小超过maxFileSize时,按照i进行文件滚动
                -->
                <fileNamePattern>${LOG_HOME}/${appName}-%d{yyyy-MM-dd}-%i.log</fileNamePattern>
                <!-- 
                可选节点,控制保留的归档文件的最大数量,超出数量就删除旧文件。假设设置每天滚动,
                且maxHistory是365,则只保存最近365天的文件,删除之前的旧文件。注意,删除旧文件是,
                那些为了归档而创建的目录也会被删除。
                -->
                <MaxHistory>365</MaxHistory>
                <!-- 
                当日志文件超过maxFileSize指定的大小是,根据上面提到的%i进行日志文件滚动 注意此处配置SizeBasedTriggeringPolicy是无法实现按文件大小进行滚动的,必须配置timeBasedFileNamingAndTriggeringPolicy
                -->
                <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                    <maxFileSize>100MB</maxFileSize>
                </timeBasedFileNamingAndTriggeringPolicy>
            </rollingPolicy>
            <!-- 日志输出格式: -->     
            <layout class="ch.qos.logback.classic.PatternLayout">
                <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [ %thread ] - [ %-5level ] [ %logger{50} : %line ] - %msg%n</pattern>
            </layout>
        </appender>
    
        <!-- 
            logger主要用于存放日志对象,也可以定义日志类型、级别
            name:表示匹配的logger类型前缀,也就是包的前半部分
            level:要记录的日志级别,包括 TRACE < DEBUG < INFO < WARN < ERROR
            additivity:作用在于children-logger是否使用 rootLogger配置的appender进行输出,
            false:表示只用当前logger的appender-reftrue:
            表示当前logger的appender-ref和rootLogger的appender-ref都有效
        -->
        <!-- hibernate logger -->
        <logger name="com" level="debug" />
        <!-- Spring framework logger -->
        <logger name="org.springframework" level="debug" additivity="false"></logger>
    
        <!-- 
        root与logger是父子关系,没有特别定义则默认为root,任何一个类只会和一个logger对应,
        要么是定义的logger,要么是root,判断的关键在于找到这个logger,然后判断这个logger的appender和level。 
        -->
        <root level="info">
            <appender-ref ref="stdout" />
            <appender-ref ref="appLogAppender" />
        </root>
    </configuration> 
    View Code

    将文件名修改为logback-spring.xml,添加两行代码,添加多个配置,不同的配置在不同的环境下生效:

      <springProfile name="staging">
                <!--指定配置在某个环境下生效-->
              .....
      </springProfile>

    配置举例:

    <appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
    <layout class="ch.qos.logback.classic.PatternLayout">
    
        <springProfile name="dev">
        <pattern>%d{yyyy‐MM‐dd HH:mm:ss.SSS} ‐‐‐‐> [%thread] ‐‐‐> %‐5level
        %logger{50} ‐ %msg%n</pattern>
        </springProfile>
        <springProfile name="!dev">
        <pattern>%d{yyyy‐MM‐dd HH:mm:ss.SSS} ==== [%thread] ==== %‐5level
        %logger{50} ‐ %msg%n</pattern>
      </springProfile>
    
    </layout>
    </appender>

    指定级别,然后测试:

    <root level="info">
            <appender-ref ref="stdout" />
            <appender-ref ref="appLogAppender" />
        </root>

     只要使用springboot依赖了不被springboot管理的框架,一定要排除日志依赖,同时引入对应的适配层。

    9.SpringBoot与SpringMvc的区别

    SrpingBoot是一个快速开发的框架,原理是利用maven依赖整合第三方框架;去除xml配置,采用注解的形式;内置了很多的容器(如tomcat服务器)。

    SpringMvc是spring的一个框架,在SpringBoot中使用SPringMvc。

    10.SpringBoot其他知识

    1)SpringBoot启动流程

    第一步:new SpringApplication

    第二步:调用run方法

    2)WebApplicationType的三种

    第一种:NONE,不嵌入web服务器,通过外部tomcat服务器运行

    第二种:SERVLET,需要使用web服务器运行

    第三种:REACTIVE,使用响应式web启动

    就是这么简单,你学废了吗?感觉有用的话,给笔者点个赞吧 !
  • 相关阅读:
    40款不容错过的个人摄影设计作品集网站
    Google的全新在线地图API演示网站 More than a map
    绝对不容错过的超棒动物瞬间抓拍摄影作品
    超全超实用的Javascript类库和jQuery插件大全之一:Web印刷排版
    Java中方法重写和方法重载的6个区别?
    面试突击15:说一下HashMap底层实现?及元素添加流程?
    查询 MySQL 字段注释的 5 种方法!
    剑指Offer补充
    Cracking the Coding Interviewch11 | System Design and Memory Limits
    Cracking the Coding Interview – ch16,17,18
  • 原文地址:https://www.cnblogs.com/zys2019/p/11603590.html
Copyright © 2011-2022 走看看