SSM-SpringBoot(完结)
本文最后更新于36 天前,其中的信息可能已经过时,如有错误请发送邮件到big_fw@foxmail.com

四.Springboot篇

一句话定性:最简单的方式,快速整合所有技术栈

1.快速入门

1.SpringBoot特性

  • SpringBoot 帮我们简单、快速地创建一个独立的、生产级别的 Spring 应用;
  • 大多数 SpringBoot 应用只需要编写少量配置即可快速整合 Spring 平台以及第三方技术
  • 特性:
    • 快速创建独立 Spring 应用
    • 直接嵌入Tomcat、Jetty or Undertow
    • 提供可选的 starter,简化应用整合
    • 按需自动配置 Spring 以及 第三方库
    • 提供生产级特性:如 监控指标、健康检查、外部化配置等
    • 无代码生成、无xml; 都是基于自动配置技术
  • 总结:
    • 简化开发,简化配置,简化整合,简化部署,简化监控,简化运维

2.SpringBoot特性 – 快速部署

<!--    SpringBoot应用打包插件-->
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

打包:mvn clean package

运行:java -jar demo.jar

3.Springboot-依赖管理

详细见JavaWeb+Ai(下)

依赖管理的底层就是maven的依赖传递

场景启动器

  • 场景启动器:导入相关的场景,拥有相关的功能。
  • 默认支持的所有场景:
    • https://docs.spring.io/spring-boot/docs/current/reference/html/using.html#using.build-systems.starters
    • 官方提供的场景:命名为:spring-boot-starter-*
    • 第三方提供场景:命名为:*-spring-boot-starter

4.自动配置

完整流程-源码:导入场景会导入一堆自动配置类,这些配置类导入基于条件注解一堆组件

完整流程-源码:自动配置类给容器中放组件、组件属性来自于属性类、属性类绑定配置文件

  • 核心流程总结:
    • 1: 导入 starter,就会导入autoconfigure 包。
    • 2: autoconfigure 包里面 有一个文件 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports,里面指定的所有启动要加载的自动配置类
    • 3: @EnableAutoConfiguration 会自动的把上面文件里面写的所有自动配置类都导入进来。xxxAutoConfiguration 是有条件注解进行按需加载
    • 4: xxxAutoConfiguration 给容器中导入一堆组件,组件都是从 xxxProperties 中提取属性值
    • 5: xxxProperties 又是和配置文件进行了绑定
  • 效果:导入starter、修改配置文件,就能修改底层行为。

2基础使用

1.@ConfigurationProperties属性绑定

  • 将容器中任意组件的属性值和配置文件的配置项的值进行绑定
    • 1、给容器中注册组件(@Component、@Bean)
    • 2、使用 @ConfigurationProperties 声明组件和配置文件的哪些配置项进行绑定

对比@ConfigurationProperties和@Value

@ConfigurationProperties

  • 作用: @ConfigurationProperties 用于将配置文件中的属性(如application.properties或application.yml中的属性)绑定到一个POJO(Plain Old Java Object)上。通过这个注解,Spring会自动将配置文件中的属性映射到POJO类的字段上。
  • 特点: 属性的值是在启动时从配置文件中读取的,这些属性值不会经过SpEL(Spring Expression Language)表达式的计算。它们是直接从配置文件中读取并注入到POJO中的。

@Value

  • 作用: @Value 注解用于将配置属性注入到Spring Bean中的字段、方法或构造函数参数。它支持使用SpEL表达式,这意味着可以在注解的值中使用Spring表达式语言来动态计算属性值。
  • 特点: @Value 可以用于直接注入配置值,也可以利用SpEL表达式来处理更复杂的动态计算或逻辑。

参考文档:

@ConfigurationProperties & @EnableConfigurationProperties 注解-CSDN博客

2.YAML

  • 大小写敏感
  • 键值对写法 k: v,使用空格分割k,v
  • 使用缩进表示层级关系
    • 缩进时不允许使用Tab键,只允许使用空格。换行
    • 缩进的空格数目不重要,只要相同层级的元素左侧对齐即可
  • # 表示注释,从这个字符一直到行尾,都会被解析器忽略。
  • Value支持的写法
    • 对象:键值对的集合,如:映射(map)/ 哈希(hash) / 字典(dictionary)
    • 数组:一组按次序排列的值,如:序列(sequence) / 列表(list)
    • 字面量:单个的、不可再分的值,如:字符串、数字、bool、日期

3.启动Spring应用的其他方式

  • 自定义 banner
    • 类路径添加banner.txt或设置spring.banner.location就可以定制 banner
    • https://www.bootschool.net/ascii
  • 自定义 SpringApplication
    • new SpringApplication
    • new SpringApplicationBuilder

        SpringApplicationBuilder builder = new SpringApplicationBuilder();

        //链式调用
        builder
                .sources(Springboot01DemoApplication.class)
                .bannerMode(Banner.Mode.CONSOLE)
                .environment(null)
//                .listeners(null)
                .run(args);

3.日志

1.简介

•规范:项目开发不要写System.out.println(),用日志记录信息

•SpringBoot 默认使用 slf4j + logback

2.日志入门

@Slf4j
@SpringBootTest
public class LogTest {
    //1、获取一个日志记录器
//    Logger logger = LoggerFactory.getLogger(LogTest.class);


    @Test
    void test02() throws InterruptedException {
        int i = 0;
//        while (true) {
//            log.info("info日志.... 数字:【{}】,ok={}", i++, "哈哈");
//            Thread.sleep(3);
//        }
    }

    @Test
    void test01(){


//        System.out.println("djkaljdalkjdklaj");
        //2、记录日志
        //级别:由低到高:ALL -- TRACE -- DEBUG -- INFO -- WARN -- ERROR -- OFF
        //越打印,越粗糙; 日志有一个默认级别(INFO);只会打印这个级别之上的所有信息;

        log.trace("追踪日志......");
        if("1".equals(log)){
            log.debug("调试日志.......");
            //业务流程

            try {
                //关键点
                log.info("信息日志........");

                //容易出问题点
//                aa.bb(){
//                    log.warn("警告日志........");
//                };

            }catch (Exception e){
                log.error("错误日志......"+e.getMessage());
            }

        }




        //格式: 时间  级别  进程id --- 项目名 --- 线程名 --- 当前类名: 日志内容

    }
}

日志格式

  • 默认输出格式:
    • 时间和日期:毫秒级精度
    • 日志级别:ERROR, WARN, INFO, DEBUG, or TRACE.
    • 进程 ID
    • —: 消息分割符
    • 线程名: 使用[]包含
    • Logger 名: 通常是产生日志的类名
    • 消息: 日志记录的内容 注意: logback 没有FATAL级别,对应的是ERROR
  • 注意: logback 没有FATAL级别,对应的是ERROR

日志级别

由低到高:ALL,TRACE, DEBUG, INFO, WARN, ERROR,FATAL,OFF;

只会打印指定级别及以上级别的日志

  • ALL:打印所有日志
  • TRACE:追踪框架详细流程日志,一般不使用
  • DEBUG:开发调试细节日志
  • INFO:关键、感兴趣信息日志
  • WARN:警告但不是错误的信息日志,比如:版本过时
  • ERROR:业务错误日志,比如出现各种异常
  • FATAL:致命错误日志,比如jvm系统崩溃
  • OFF:关闭所有日志记录

不指定级别的所有类,都使用 root 指定的级别作为默认级别

SpringBoot日志默认级别是 INFO

修改日志级别-在配置文件中修改

logging:
  level:
    com.example.mypackage: DEBUG
    root: WARN

日志分组

将相关的logger分组在一起,统一配置。SpringBoot 支持分组统一配置

logging.group.tomcat=org.apache.catalina,org.apache.coyote,org.apache.tomcat
logging.level.tomcat=trace

SpringBoot 预定义两个组

组名范围
weborg.springframework.core.codec, org.springframework.http, org.springframework.web, org.springframework.boot.actuate.endpoint.web, org.springframework.boot.web.servlet.ServletContextInitializerBeans
sqlorg.springframework.jdbc.core, org.hibernate.SQL, org.jooq.tools.LoggerListener

日志-文件输出

SpringBoot 默认只把日志写在控制台,如果想额外记录到文件,可以在application.properties中添加 logging.file.name 或 logging.file.path 配置项。两个都配置以文件名为准

logging.file.namelogging.file.path示例效果
未指定未指定仅控制台输出
指定未指定my.log写入指定文件。可以加路径
未指定指定/var/log写入指定目录,文件名为spring.log
指定指定以logging.file.name为准
# 两个都配置以文件名为准
logging.file.name=boot.log
#logging.file.path=D://aaa.log

#logging.logback.rollingpolicy.max-file-size=2MB

日志-文件归档与滚动切割

  • 归档:每天的日志单独存到一个文档中。
  • 切割:每个文件10MB,超过大小切割成另外一个文件。

默认滚动切割与归档规则如下:

配置项描述
logging.logback.rollingpolicy.file-name-pattern日志存档的文件名格式 默认值:${LOG_FILE}.%d{yyyy-MM-dd}.%i.gz
logging.logback.rollingpolicy.clean-history-on-start应用启动时是否清除以前存档;默认值:false
logging.logback.rollingpolicy.max-file-size每个日志文件的最大大小;默认值:10MB
logging.logback.rollingpolicy.total-size-cap日志文件被删除之前,可以容纳的最大大小(默认值:0B)。设置1GB则磁盘存储超过 1GB 日志后就会删除旧日志文件
logging.logback.rollingpolicy.max-history日志文件保存的最大天数;默认值:7

日志-引入框架自己的日志配置文件

通常我们配置 application.properties 就够了。当然也可以自定义。比如:

日志系统自定义
Logbacklogback-spring.xml  /  logback.xml
Log4j2log4j2-spring.xml  /  log4j2.xml
JDK (Java Util Logging)logging.properties

日志-切换日志组合

3.日志系统 – 最佳实践

  • 1、导入任何第三方框架,先排除它的日志包,因为Boot底层控制好了日志
  • 2、修改 application.properties 配置文件,就可以调整日志的所有行为。如果不够,可以编写日志框架自己的配置文件放在类路径下就行,比如logback-spring.xml,log4j2-spring.xml
  • 3、如需对接专业日志系统,也只需要把 logback 记录的日志灌倒 kafka之类的中间件,这和SpringBoot没关系,都是日志框架自己的配置,修改配置文件即可
  • 4、业务中使用slf4j-api记录日志。不要再 sout 了

4.进阶使用

1.profiles 环境隔离-@Profile

  • 环境隔离能力;快速切换开发、测试、生产环境
  • 步骤:
    • 1. 标识环境:指定哪些组件、配置在哪个环境生效
      • @Profile 标记组件生效环境
    • 2. 切换环境:这个环境对应的所有组件和配置就应该生效
    • 激活环境:
      • 配置文件:spring.profiles.active=production,hsqldb
      • 命令行:java -jar demo.jar –spring.profiles.active=dev,hsqldb
    • 环境包含:
      • spring.profiles.include[0]=common
      • spring.profiles.include[1]=local
    • 生效的配置 = 默认环境配置 + 激活的环境  + 包含的环境配置
    • 注意:激活的配置优先级高于默认配置 profile高于application
  • 项目里面这么用
    • 基础的配置mybatis、log、xxx:写到包含环境中
    • 需要动态切换变化的 db、redis:写到激活的环境中

循环引用

Profiles环境隔离 – 分组

  • 创建 prod 组,指定包含 db 和 mq 配置
    • spring.profiles.group.prod[0]=db
    • spring.profiles.group.prod[1]=mq
  • 使用 –spring.profiles.active=prod ,激活prod,db,mq配置文件

Profiles环境隔离 – 配置文件

  • application-{profile}.properties 可以作为指定环境的配置文件
  • 激活这个环境,配置就会生效。最终生效的所有配置是
    • application.properties:主配置文件,任意时候都生效
    • application-{profile}.properties:指定环境配置文件,激活指定环境生效
  • profile优先级 > application

2.外部化配置

外部配置优先于内部配置

属性占位符:

app.name=MyApp app.description=${app.name}  Hello

激活优先 外部优先

3.断言

测试注解

  • @Test :表示方法是测试方法。
  • @ParameterizedTest :表示方法是参数化测试,下方会有详细介绍
  • @RepeatedTest :表示方法可重复执行,下方会有详细介绍
  • @DisplayName :为测试类或者测试方法设置展示名称
  • @BeforeEach :表示在每个单元测试之前执行
  • @AfterEach :表示在每个单元测试之后执行
  • @BeforeAll :表示在所有单元测试之前执行
  • @AfterAll :表示在所有单元测试之后执行
  • @Tag :表示单元测试类别,类似于JUnit4中的@Categories
  • @Disabled :表示测试类或测试方法不执行,类似于JUnit4中的@Ignore
  • @Timeout :表示测试方法运行如果超过了指定时间将会返回错误
  • @ExtendWith :为测试类或测试方法提供扩展类引用

断言机制

方法说明
assertEquals判断两个对象或两个原始类型是否相等
assertNotEquals判断两个对象或两个原始类型是否不相等
assertSame判断两个对象引用是否指向同一个对象
assertNotSame判断两个对象引用是否指向不同的对象
assertTrue判断给定的布尔值是否为 true
assertFalse判断给定的布尔值是否为 false
assertNull判断给定的对象引用是否为 null
assertNotNull判断给定的对象引用是否不为 null
assertArrayEquals数组断言
assertAll组合断言
assertThrows异常断言
assertTimeout超时断言
fail快速失败
 @Test
    void test02(){
        //1、业务规定,返回hello字符串才算成功,否则就是失败
        String result = helloService.sayHello();
        //2、断言:判断字符串是否等于hello
//        Assertions.assertEquals("hello",result,"helloservice并没有返回hello");




        Assertions.assertThrows(ArithmeticException.class, () -> {
            helloService.hello();
        });

4.可观测性

•可观测性(Observability)指应用的运行数据,可以被线上进行观测、监控、预警等

  • SpringBoot 提供了 actuator 模块,可以快速暴露应用的所有指标
  • 导入: spring-boot-starter-actuator
  • 访问 http://localhost:8080/actuator;
  • 展示出所有可以用的监控端点

可观测性 – Endpoints

端点名描述
auditevents暴露当前应用程序的审核事件信息。需要一个AuditEventRepository组件
beans显示应用程序中所有Spring Bean的完整列表
caches暴露可用的缓存
conditions显示自动配置的所有条件信息,包括匹配或不匹配的原因
configprops显示所有@ConfigurationProperties
env暴露Spring的属性ConfigurableEnvironment
flyway显示已应用的所有Flyway数据库迁移。需要一个或多个Flyway组件。
health显示应用程序运行状况信息
httptrace显示HTTP跟踪信息(默认情况下,最近100个HTTP请求-响应)。需要一个HttpTraceRepository组件
info显示应用程序信息
integrationgraph显示Spring integrationgraph 。需要依赖spring-integration-core
loggers显示和修改应用程序中日志的配置
liquibase显示已应用的所有Liquibase数据库迁移。需要一个或多个Liquibase组件。
metrics显示当前应用程序的“指标”信息
端点名描述
mappings显示所有@RequestMapping路径列表
scheduledtasks显示应用程序中的计划任务
sessions允许从Spring Session支持的会话存储中检索和删除用户会话。需要使用Spring Session的基于Servlet的Web应用程序
shutdown使应用程序正常关闭。默认禁用
startup显示由ApplicationStartup收集的启动步骤数据。需要使用SpringApplication进行配置BufferingApplicationStartup
threaddump执行线程转储
heapdump返回hprof堆转储文件
jolokia通过HTTP暴露JMX bean(需要引入Jolokia,不适用于WebFlux)。需要引入依赖jolokia-core
logfile返回日志文件的内容(如果已设置logging.file.name或logging.file.path属性)。支持使用HTTPRange标头来检索部分日志文件的内容
prometheus以Prometheus服务器可以抓取的格式公开指标。需要依赖micrometer-registry-prometheus

5.SpringBoot核心原理

1.生命周期监听

监听器感知生命周期

/**
 * 监听到SpringBoot启动的全生命周期
 */
@Slf4j
public class MyListener implements SpringApplicationRunListener {

    @Override
    public void starting(ConfigurableBootstrapContext bootstrapContext) {
        System.out.println("MyListener...starting...");
    }

    @Override
    public void started(ConfigurableApplicationContext context, Duration timeTaken) {
        log.info("MyListener...started...");
    }

    @Override
    public void ready(ConfigurableApplicationContext context, Duration timeTaken) {
        log.info("MyListener...ready...");
    }

    @Override
    public void failed(ConfigurableApplicationContext context, Throwable exception) {
        log.info("MyListener...failed...");
    }

    @Override
    public void environmentPrepared(ConfigurableBootstrapContext bootstrapContext, ConfigurableEnvironment environment) {
        log.info("MyListener...environmentPrepared...");
    }

    @Override
    public void contextLoaded(ConfigurableApplicationContext context) {
        log.info("MyListener...contextLoaded...");
    }

    @Override
    public void contextPrepared(ConfigurableApplicationContext context) {
        log.info("MyListener...contextPrepared...");
    }
}
监听器感知阶段配置方式
BootstrapRegistryInitializer特定阶段:引导初始化1、META-INF/spring.factories 2、application.addBootstrapRegistryInitializer()
ApplicationContextInitializer特定阶段:ioc容器初始化1、META-INF/spring.factories 2、application.addInitializers()
ApplicationListener全阶段1、META-INF/spring.factories 2、SpringApplication.addListeners(…) 3、@Bean 或 @EventListener
SpringApplicationRunListener全阶段META-INF/spring.factories
ApplicationRunner特定阶段:感知应用就绪@Bean
CommandLineRunner特定阶段:感知应用就绪@Bean

最佳实践:

1、应用启动后做事:ApplicationRunner、CommandLineRunner

2、事件驱动开发:ApplicationListener

2生命周期事件

3.事件驱动开发

  • 应用启动过程生命周期事件感知(9大事件)
  • 应用运行中事件感知(无数种)
  • 事件驱动开发
    • 定义事件:
      • 任意事件:任意类可以作为事件类,建议命名 xxxEvent
      • 系统事件:继承 ApplicationEvent
    • 事件发布:
      • 组件实现 ApplicationEventPublisherAware
      • 自动注入 ApplicationEventPublisher
    • 事件监听:
      • 组件 + 方法标注@EventListener

4.SpringBoot完整项目启动流程

5.自动配置原理

6.自定义Starter

1.场景设计

  • 场景:抽取聊天机器人场景,它可以打招呼。
  • 效果:任何项目导入此starter都具有打招呼功能,并且问候语中的人名需要可以在配置文件中修改

EnableRobot

package com.atguigu.robot.starter.annotation;


import com.atguigu.robot.starter.RobotAutoConfiguration;
import org.springframework.context.annotation.Import;

import java.lang.annotation.*;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(RobotAutoConfiguration.class)
public @interface EnableRobot {

}

RobotController

package com.atguigu.robot.starter.controller;



import com.atguigu.robot.starter.service.RobotService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class RobotController {



    @Autowired
    RobotService robotService;

    @GetMapping("/robot/hello")
    public String sayHello(){
        String msg = robotService.sayHello();
        return msg;
    }
}

RobotProperties

package com.atguigu.robot.starter.properties;


import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;


@Component
@ConfigurationProperties(prefix = "robot")
@Data
public class RobotProperties {

    private String name;
    private String model;
}

RobotService

package com.atguigu.robot.starter.service;

public interface RobotService {

    public String sayHello();
}

RobotServiceImpl

package com.atguigu.robot.starter.service.impl;


import com.atguigu.robot.starter.properties.RobotProperties;
import com.atguigu.robot.starter.service.RobotService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class RobotServiceImpl implements RobotService {


    @Autowired
    RobotProperties robotProperties;

    @Override
    public String sayHello() {
        return "我是机器人【"+robotProperties.getName()+"】,使用底层大模型:【"+robotProperties.getModel()+"】;我们开始聊天吧";
    }
}

RobotAutoConfiguration

package com.atguigu.robot.starter;


import com.atguigu.robot.starter.controller.RobotController;
import com.atguigu.robot.starter.properties.RobotProperties;
import com.atguigu.robot.starter.service.RobotService;
import com.atguigu.robot.starter.service.impl.RobotServiceImpl;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;


@EnableConfigurationProperties(RobotProperties.class)
@Configuration //把这个场景要用的所有组件导入到容器中
public class RobotAutoConfiguration {

    @Bean
    public RobotController robotController() {
        return new RobotController();
    }

    @Bean
    public RobotService  robotService() {
        return new RobotServiceImpl();
    }

}

2.基础抽取

  • 1. 创建自定义starter项目,引入spring-boot-starter基础依赖
  • 2. 编写模块功能,引入模块所有需要的依赖。
  • 3. 编写xxxAutoConfiguration自动配置类,帮其他项目导入这个模块需要的所有组件
  <dependency>
            <groupId>com.atguigu</groupId>
            <artifactId>robot-spring-boot-starter</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>

在需要robot这个starter的项目中

package com.atguigu.boot;

import com.atguigu.robot.starter.RobotAutoConfiguration;
import com.atguigu.robot.starter.annotation.EnableRobot;
import com.atguigu.robot.starter.controller.RobotController;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Import;


/**
 * spring-boot-start-web
 *
 * 为什么导入  robot-spring-boot-starter ,访问 controller 是 404?
 * 原因:主程序只会扫描到自己所在的包及其子包下的所有组件
 *
 * 自定义starter:
 * 1、第一层抽取:编写一个自动配置类,别人导入我的starter,
 *              无需关心需要给容器中导入哪些组件,只需要导入自动配置类,
 *              自动配置类帮你给容器中导入所有这个场景要用的组件
 *               @Import(RobotAutoConfiguration.class)
 * 2、第二层抽取:只需要标注功能开关注解。@EnableRobot
 * 3、第三层抽取:只需要导入starter,所有功能就绪
 */
@EnableRobot
@SpringBootApplication
public class Springboot02DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(Springboot02DemoApplication.class, args);
    }

}

在application.properties中

spring.application.name=springboot-02-demo

# 配置依然在别人这里
robot.name=小哈哈
robot.model=chatgpt666

捋一下流程:02-demo这个项目导入了robot的starter,项目一启动的时候还导入了robot项目的自动配置类(@EnableRobot中标了@Import(RobotAutoConfiguration.class)),这个自动配置类开启了配置文件绑定,配置文件正好配置类相应的信息,跟RobotProperties绑好了,然后在自动配置类中在容器中还放了组件controller和service

如何实现第三层抽取

在resource中创建META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports里面写:

com.atguigu.robot.starter.RobotAutoConfiguration

测试:

7.SpringBoot总结

需要掌握的源码

1.SpringBoot 自动配置原理

2.SpringMVC DispatcherServlet 流程

3.Spring IOC容器(三级缓存机制)

4.Spring 事务原理(TransactionMapper、TransactionInterceptor)

需要掌握的开发技巧:RESTful、CRUD

Spring篇完结

文末附加内容
暂无评论

发送评论 编辑评论

|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇