2024年3月

System.Diagnostics.DiagnosticSource 可以丰富地记录程序中地日志,包括不可序列化的类型(例如 HttpResponseMessage 或 HttpContext)。

System.Diagnostics.DiagnosticSource 通过订阅发布模式运行,我们可以根据自己地需要发现数据源并订阅感兴趣的数据源。

DiagnosticSource 与 ILogger 区别

一般来说,DiagnosticSource主要强类型诊断,它可以记录诸如"Microsoft.AspNetCore.Mvc.ViewNotFound"之类的事件。

而,ILogger用于记录更具体的信息,例如"Executing JsonResult, writing value {Value}。

示例

添加必要的依赖项

我们首先将需要的 NuGet 包添加到我们的project中

<PackageReference Include="Microsoft.Extensions.DiagnosticAdapter" Version="3.1.32" />

发出Event

首先需要注入DiagnosticSource, 然后通过其write方法发出Event

private readonly ILogger<WeatherForecastController>_logger;private readonlyDiagnosticSource _diagnosticSource;const string DKEY = "Invoke_WeatherForecast";public WeatherForecastController(ILogger<WeatherForecastController>logger, DiagnosticSource diagnosticSource)
{
_logger
=logger;
_diagnosticSource
=diagnosticSource;
}

[HttpGet]
public stringGet()
{
if(_diagnosticSource.IsEnabled(DKEY))
{
_diagnosticSource.Write(DKEY,
new{
time
=DateTime.Now,
data
= "ttt"});
}
return "OK";
}

定义Listener

有多种方法可以创建使用DiagnosticSource事件的Listener,但最简单的方法之一是使用Microsoft.Extensions.DiagnosticAdapter包提供的功能。

要创建侦听器,您可以创建一个类。然后,您可以使用属性来装饰该方法[DiagnosticName],并提供要侦听的事件名称:

public classDemoDiagnosticListener
{
const string DKEY = "Invoke_WeatherForecast";
[DiagnosticName(DKEY)]
public virtual void CallWeatherForecast (DateTime time, stringdata)
{
Console.WriteLine($
"WeatherForecast called: {time} {data}");
}
}

启动监听

剩下的就是在Program.cs中启动监听

var app =builder.Build();var diagnosticListener = app.Services.GetRequiredService<DiagnosticListener>();var listener = newDemoDiagnosticListener();
diagnosticListener.SubscribeWithAdapter(listener);

...

app.Run();

效果

v-model
是 Vue.js 框架中用于实现双向数据绑定的指令。它充分体现了 MVVM(Model-View-ViewModel)模式中的双向数据绑定特性。下面我们将详细解释
v-model
如何体现 MVVM 和双向绑定:

1.MVVM 模式

MVVM 模式是一种软件架构设计模式,它将应用程序分为三个部分:

  1. Model(模型)
    :代表应用程序的数据和业务逻辑。
  2. View(视图)
    :用户界面,负责数据的展示。
  3. ViewModel(视图模型)
    :连接 Model 和 View 的桥梁,它负责将 Model 的数据转换为 View 可以展示的格式,并处理 View 的事件,更新 Model。

2.双向数据绑定

双向数据绑定意味着 Model 的变化会自动更新 View,同时 View 的变化也会自动更新 Model。这样,开发者无需手动编写代码来同步数据。

3.
v-model
如何体现 MVVM 和双向绑定

在 Vue.js 中,
v-model
指令用于在 input、textarea 或 select 等表单元素上创建双向数据绑定。它简化了 Model 和 View 之间的数据同步。

示例:

假设我们有一个简单的 Vue 应用,其中有一个输入框和一个显示文本的区域。


<div id="app">
  <input v-model="message" placeholder="编辑我...">
  <p>消息内容是:{{ message }}</p>
</div>


<script>
new Vue({
  el: '#app',
  data: {
    message: ''
  }
})
</script>

在这个例子中:

  • Model
    :是 Vue 实例中的
    data
    对象,这里包含了
    message
    属性。
  • View
    :是 HTML 模板部分,包括输入框和显示文本的区域。
  • ViewModel
    :是 Vue 实例本身,它连接了 Model 和 View。

当用户在输入框中输入文字时:

  1. View 到 Model
    :输入框的值变化会触发
    input
    事件,
    v-model
    会捕获这个事件,并更新 Vue 实例中
    message
    的值。这是从 View 到 Model 的更新。

  2. Model 到 View
    :当你通过JavaScript修改
    message
    属性的值时,由于
    v-model
    的双向绑定特性,输入框的显示内容也会随之改变,以及使用插值表达式
    {{ message }}
    的文本区域。这是从 Model 到 View 的更新。

因此,
v-model
通过监听 DOM 事件和更新数据属性,在 View 和 Model 之间建立了双向数据绑定,从而简化了 MVVM 模式中的数据同步过程。开发者无需手动编写代码来处理数据的同步,提高了开发效率和代码的可维护性。

面向对象

和面向过程的辨析

  • 面向过程编程(Procedure Oriented Programming)

    核心思想
    :面向过程的核心是以过程或者说函数作为程序设计的基本单元,强调的是解决问题的步骤和顺序。

    特点


    • 程序结构清晰,通常体现为函数的组合和调用,关注的是如何一步步地执行操作。
    • 数据和处理数据的函数通常是分开的,通过函数参数传递数据。
    • 优点在于简洁明了,执行效率较高,适用于小型、逻辑相对简单的项目。
    • 缺点在于随着程序规模扩大,各部分之间的耦合度会增加,难以复用和维护,不便于管理复杂的系统。
  • 面向对象编程(Object Oriented Programming)

    核心思想
    :面向对象编程则是以“对象”为核心,将数据(属性)和操作数据的行为(方法)封装在一起,形成具有独立职责的对象。程序设计围绕着对象及其交互来进行

    特点
    :


    • 强调概念抽象,
      封装、继承和多态
      这三大特性。
    • 封装使得数据隐藏,对外提供接口,增强了安全性和减少耦合度。
    • 继承允许子类继承父类的属性和方法,减少代码重复并构建出层次化的类结构。
    • 多态使得同一接口可以有不同的表现形式,增加了代码的灵活性和扩展性。
    • 优点在于提高了代码的复用性、可维护性和可扩展性,更适应大型软件系统的开发。
    • 缺点包括学习成本相对较高,且过度设计可能导致性能损失(尤其是在大量创建对象时)

总结来说,面向过程着重于算法和逻辑控制的流程,而面向对象则更关注现实世界的实体模型和其行为,强调的是利用对象间的交互来解决问题。在实际开发中,两者并不是绝对对立,现代编程实践中往往结合两者的优势,灵活运用在不同的场景中

一个案例

需求:

要设计一个简单的银行账户管理系统,需求如下:创建账户、存款、取款、查询余额

面向过程编程

以方法,流程,全局变量来实现解决问题,实现目的

import java.util.HashMap;
import java.util.Map;

public class BankProcess {
    // 全局账户字典用于存储账户信息
    static Map<String, Double> accounts = new HashMap<>();

    public static void main(String[] args) {
        createAccount("A001", 1000.0);
        deposit("A001", 500.0);
        System.out.println("Balance after deposit: " + checkBalance("A001"));
        withdraw("A001", 200.0);
        System.out.println("Balance after withdrawal: " + checkBalance("A001"));
    }

    // 创建账户
    public static void createAccount(String accountId, double initialBalance) {
        accounts.put(accountId, initialBalance);
    }

    // 存款操作
    public static void deposit(String accountId, double amount) {
        if (accounts.containsKey(accountId)) {
            accounts.put(accountId, accounts.get(accountId) + amount);
        }
    }

    // 取款操作
    public static void withdraw(String accountId, double amount) {
        if (accounts.containsKey(accountId) && accounts.get(accountId) >= amount) {
            accounts.put(accountId, accounts.get(accountId) - amount);
        }
    }

    // 查询余额
    public static double checkBalance(String accountId) {
        return accounts.getOrDefault(accountId, 0.0);
    }
}

面向对象编程

以对象,对象的属性,对象的行为(方法)来解决问题,实现目的

import java.util.HashMap;
import java.util.Map;

class BankAccount {
    private String accountId;
    private double balance;

    public BankAccount(String accountId, double initialBalance) {
        this.accountId = accountId;
        this.balance = initialBalance;
    }

    public static BankAccount getAccount(String accountId) {
        return allAccounts.get(accountId);
    }

    public void deposit(double amount) {
        if (amount > 0) {
            balance += amount;
        }
    }

    public void withdraw(double amount) {
        if (amount > 0 && balance >= amount) {
            balance -= amount;
        }
    }

    public double checkBalance() {
        return balance;
    }

}

@Test
public void test(){
    List<BankAccount> allAccounts = new ArrayList<>();
    BankAccount account = new BankAccount("A001", 1000.0);
    allAcounts.add()
    account.deposit(500.0);
    System.out.println("Balance after deposit: " + account.checkBalance());
    account.withdraw(200.0);
    System.out.println("Balance after withdrawal: " + account.checkBalance());
}

面向对象的三大特性

封装

将数据(属性)和操作数据的方法(函数或者方法)绑定在一起,作为一个整体(对象)进行考虑,通过封装,可以隐藏对象的内部细节,仅对外提供公共访问方式,从而提高代码的复用性和安全性。在程序设计中,对象的状态通常由其属性来体现,而行为则通过方法来执行

继承

继承是一种创建新类的方式,新类可以从已有的类中派生出来,继承父类的属性和方法,同时也可以扩展新的属性和方法.这样可以提高代码的重用性,使得类和类之间产生了层次结构,便于系统的维护和扩展

多态

多态,指为不同数据类型的实体提供统一的接口,以Java语言为例,多态有以下要素

  • 继承,多个子类继承/实现了一个父类
  • 重写,多个子类重写了父类的同名方法,有不同实现
  • 父类引用指向子类对象(默认调用子类的重写方法)
//1.继承,父类
public class Animal {
  public void shout();
}

//1.继承,一个子类
@Slf4j
public class Dog extends Animal {
    
  //2.重写了父类的方法  
  @Override
  public void shout() {
    log.info("wanwan");
  }
}

@Slf4j
public class Cat extends Animal {
  @Override
  public void shout() {
    log.info("mimi");
  }
}

@Test
public void test() {
    
  Animal ani = new Dog();//3.父类引用指向子类对象
  ani.shout();//3.调用子类dog重写后的方法 wanwan
    
}

上述代码展示了多态,支持多态就意味着,所有需要具体子类的地方都可以用父类笼统概括,最直观的莫过于,同一个方法用父类做形参,可以传入不同子类实现不同的功能,降低了耦合度,提高了扩展性

面向对象的五大原则

单一职责原则(Single Responsibility Principle)

一个类或者模块应该有且只有一个改变的理由,即一个类应该只有一个职责,当且仅当它负责的功能发生变化时才需要修改

开放封闭原则(Open/Close Principle)

类模块应该是可扩展的,但不可修改,在不修改已有代码的情况下,能够通过扩展模块的行为来添加新的功能

为什么叫开放封闭原则?

对扩展开放,对修改关闭

里氏替换原则(Liskov Substitution Principle)

子类型必须能够替换掉它们的基类型。这意味着在继承体系中,子类应当可以在任何需要基类出现的地方无差别地替代基类,而且不会破坏原有的正确性

为什么叫里氏替换原则?

里斯科夫(Liskov)女士在 1987 年的“面向对象技术的高峰会议”(OOPSLA)上发表的一篇文章《数据抽象和层次》(Data Abstraction and Hierarchy)里提出来:"继承必须确保超类所拥有的性质在子类中仍然成立"

接口隔离原则(Interface Segregation Principle)

接口隔离原则建立在使用接口的情况下,客户端不应该被迫使用对其无用的方法或者功能,一个类对另一个类的依赖应该建立在最小的接口上

实践起来就是接口不应该过于臃肿和冗余,依赖一个臃肿冗余的接口,不如把这个接口拆成多个,来按需依赖

依赖倒置原则(Dependence Inversion Principle)

  • 高层模块(如业务逻辑层)不应该依赖于底层模块(如数据访问层),二者都应该依赖其抽象
  • 抽象不应该依赖细节,细节应该依赖抽象
  • 依赖倒置的核心是"面向接口编程"
  • 抽象对比细节,要稳定,以抽象为基础搭建的框架比细节的框架要稳定的多

依赖倒置原则的依赖倒置体现在哪里?

在传统的自底向上设计中,高层模块(如业务逻辑层)会
直接
依赖底层模块(如数据访问层)形成一种
具体依赖具体
的依赖关系

个人理解的
倒置
,应该和高层,底层没什么关系,是一种理念上的倒置,即由
依赖具体
转变为
依赖抽象

1、为什么要进行小程序开发

最喜欢看的电影,当属:《大决战之淮海战役》,当时从华中急行军支援黄百韬的第十二兵团司令黄维说的一句话令我印象颇深,即:“自古不谋全局者,不足谋一域,不谋万事者,不足谋一时。”
身为程序员,我非常有危急意识,刚刚踏入工作岗位的第二年,也就是2014年,我就再思考怎样才能做一款自己的程序,并能盈利了。
但是奈何能力不足,不能胜任,那个时候也没有小程序,都是用原生的android或者ios开发。 为此我自学android,最后做了一款读书app,各个方面做得都不是很好,也没能上线,属于完全自嗨。
一直到了2018年,那个时候微信小程序特别的火爆,近乎是小程序盛宴。 再基本了解小程序的开发流程后,炽热的内心再也按捺不住,立即着手进行开发。

2、开发了一个什么样的小程序?

小程序名称为:“爱自律”,也就是现在常说的自由开发者加速死亡三级套,即:“todo,记账,番茄钟”。
2018年,出于对小程序未来的无限憧憬,我拉起团队就开干了。
团队中有一名ui设计师,2名前端设计师,2名后端开发,2名运营。 经过3个月的开发,“爱自律”小程序上线。
小程序的核心功能是做计划功能,其次是圈子功能,就是用户可以发帖讨论如何自律,以及写日记,观看自律相关视频的功能。

3、小程序盈利吗?当时开发小程序的主要思想是什么?

开发时就没有想怎么盈利,因为2018年,微信小程序也没有放广告的功能,接入支付需要开公司,所以当时的想法就是先把人气搞上来,相信只要有人气,有流量,就一定有机会挣到钱。而且我相信大部分小程序都是不挣钱的,那我就一直坚持下去,熬也要把竞争对手都熬死,一直坚持下去。

4、这个小程序后续的发展怎么样?

经过2年的运营,小程序的访问者还是寥寥无几。
2020年,团队解散,这个解散不是说我们解散啦这样,而是群内悄无声息,我也不再好意思给大家分配任务,就这样,整个团队冷分手了,群也好多年没人说一句话了。
从2018年-2024年,为了这个小程序,一共投资了2w以上,主要开销是在服务器上。
2024年年初,小程序上线了广告功能,但是点的人很少,主要是用户基数少,几个月下来,收入只有几元,但是好歹也突破0了。
所以我们技术最大的问题还是推广,不知道该怎么推广自己的程序,所以一直导致小程序。

5、有没有想过换换其他小程序?

不仅想过换其他小程序,而且也实际动手干了,2022年,觉得“爱自律”可能的确不能吸引人使用,做个有趣的工具或许是个好主意,于是开发了一款图片识别工具,他调用了阿里云的图片识别工具,有自动抠图合成图像、对比两张图的相似度、在线做自定义奖状等功能。
但是这个小程序还是面临和“爱自律”一样的问题,那就是没人用,我发公众号,发头条号去推广,只要推广了,就有人用,只要不推广,人数又变成了0,他甚至都还不如“爱自律”,“爱自律”每天好歹还有百把人访问,新做的工具真是一个人访问都没有,纵使他真的很强大,纵使他体验相当好。

6、小程序开发过程中,你是怎么被虐的?

我算是第一批开发小程序的,并且一直坚持到现在,几乎见证了小程序发展的整个过程。
第一次被虐,当属是GetUserInfo接口被废弃了,腾讯废接口那绝对是毫不客气,说废弃就废弃,废弃后就连夜改接口,心想这下好了吧,没多久,又让改,最后干脆不给提供获取昵称和头像的接口了。那就接着改,我是顺民,腾讯让怎么改,咱就怎么改,折腾就折腾吧。
第二次被虐是隐私协议以及图片上传权限调整。 隐私协议还好,虽然有波折,但是最后也顺利搞定了,图片上传以及下载权限,官方给了明确的截止时间,必须按照官方的说明,提供隐私弹窗。
当时看文档看的云里雾里,眼看日期临近,焦虑不已,最后按照自己的想法更改了,隐私弹窗以及隐私内容全部自己裸写,再截止日没多久,又出公告,说系统提供自动隐私弹窗方法,哦,好吧,我谢谢您,我正觉得我写的不好,但是也是折腾的够呛。
第三次被虐当属 备案+认证 无敌两件套了。 认证300块,是的,“爱自律”小程序为了增加评论功能,不辞劳苦又创建了一个公司, 备案主要是核查小程序的名称,如果不通过则需要认证改名,改名一次300块,然后再重新备案,备案一次至少一周,名字再不通过,则再次认证改名,再来300,如此无限循环。 当时着实把我吓得够呛,还没备案,就已经心生退意,因为“爱自律”很容易不通过,因为这个名字太大了。 不过备案很顺利,两周的时间通过了,我觉得通过的原因可能是以前我用“爱自律”这个名字备案了域名,所以直接给我通过了,真是让人心有余悸。

7、以后有什么打算?

“爱自律”小程序还会一直坚持下去,虽然他不挣钱,但是依然会坚持下去。
当前,小程序增加了备案以及认证流程,这导致一大波个人小程序开发者直接离场了,甚至有些“爱自律”的同类产品,也下架了。
这对我们真正想好好搞好小程序,呕心沥血去开发小程序的开发者来说,是一件好事情,相信备案+认证这一套组合拳下来,直接市面上50%的小程序要下架,而且新加入的开发者会比以前少很多,我们的竞争对手一下子减少了一半,而我们付出的代价是每年300块钱,当然如果是个人小程序,每年是30.
就像孔子那样,明知不可为而为之,也像刘备说的,大丈夫当“屈身守分,以待天时,不可与命争也。“,咱就当个顺民,老老实实做自己该做的事情,认为是对的事情,屈身守分,仔细思考如何推广,如何优化,有道是苦心人天不负,相信坚持下去会有好的结果。

微服务的通讯方式主要有以下几种:

  1. RESTful API
    :基于 HTTP 协议的 RESTful API 是最常用的微服务通讯方式之一。服务之间通过 HTTP 请求和响应进行通讯,实现数据交换。这种方式简单、通用,适用于各种场景,但可能不适合对实时性要求非常高的场景。
  2. RPC(远程过程调用)
    :RPC 允许一个服务像调用本地方法一样调用另一个服务的方法。它通过将方法调用封装成网络数据包并在不同的进程之间传输,实现不同服务之间的互相调用。RPC 方式可以提高调用的效率和性能,但可能需要更多的配置和管理工作。
  3. 消息队列通讯
    :如 RabbitMQ、Kafka、RocketMQ 等,服务之间不直接调用,而是通过消息队列进行异步消息传递,实现服务之间的解耦和异步处理。
  4. 事件驱动通讯
    :服务之间通过事件触发通讯,一旦某个服务发生了某个事件,就会触发其他服务的响应。这种方式可以实现服务的松耦合和事件的实时处理,典型的实现如 Event Bus。
  5. WebSocket(长连接通信)
    :使用 WebSocket 实现双向通信,常用于实时推送场景,服务间可以维持长期的 TCP 连接进行数据交换。

其中,RESTful API 和 RPC 是微服务间最常用的通讯方式,但它们的使用场景又略有不同:

  • RESTful API 通常用于外部接口或第三方接口通讯。
  • RPC 通常用于内部微服务之间的方法调用。

1.RESTful API VS RPC

它们的区别主要体现在以下几点:

  1. 功能和用途不同

    1. RESTful API
      常用于浏览器和服务器之间的通信,第三方接口通讯等
      ,它可以实现基于请求-响应模式的通信,支持无状态和有状态的交互。
    2. RPC 是一种用于远程过程调用的协议,用于不同计算节点之间的通信,
      多用于微服务内部间的调用
      。它允许应用程序通过网络调用远程服务,并像调用本地方法一样轻松实现分布式系统的集成。
  2. 数据格式不同

    1. RESTful API 使用文本格式来传输数据,通常使用 JSON 或 XML 进行序列化。
    2. RPC 通常使用二进制格式来传输数据,例如 Protocol Buffers(ProtoBuf)或 Apache Thrift。
  3. 性能不同
    :RPC 通常比 RESTful API 更高效。这是因为 RPC 的协议设计更加轻量级,并且它可以对传输的数据进行二进制压缩,使得请求报文体积更小,从而提高传输效率。而 RESTful API 基于 HTTP 协议,其报文头等信息可能使得传输的数据量相对较大,传输效率较低。

2.RESTful API 通讯实现

RESTful API 目前主流的实现方式有以下两种:

  • RestTemplate
    :Spring 内置的用于执行 HTTP 请求的类。
  • Spring Cloud OpenFegin
    :OpenFeign 是 Spring Cloud 对 Feign 库的封装,提供声明式的 HTTP 客户端,简化了服务调用的编码工作。

具体实现如下。

2.1 RestTemplate 使用

@LoadBalanced
@Bean
public RestTemplate restTemplate() {
    return new RestTemplate();
}

// 使用时
@Autowired
private RestTemplate restTemplate;

public void callOtherService(String serviceName) {
    String url = "http://" + serviceName + "/api/path";
    ResponseEntity<String> response = restTemplate.getForEntity(url, String.class);
}

2.2
Spring Cloud OpenFegin 使用

OpenFegin 引入到项目之后,需要先在 Spring Boot 启动类上添加 @EnableFeignClients 注解,之后使用以下代码就可以实现 RESTful 通讯了:

import org.springframework.cloud.openfeign.FeignClient;  
import org.springframework.web.bind.annotation.GetMapping;  
  
@FeignClient(name = "service-provider")  
public interface ServiceProviderClient {  
  
    @GetMapping("/api/hello")  
    String hello();  
}

3.RPC 通讯实现

RPC 目前主流的通讯方式有以下两种:

  • Dubbo
    :阿里巴巴公司开源的一个 Java 高性能优秀的服务框架,它基于 TCP 或 HTTP 的 RPC 远程过程调用,支持负载均衡和容错,自动服务注册和发现。
  • gRPC
    :Google 开发的高性能、通用的开源 RPC 框架,它主要面向移动应用开发并基于 HTTP/2 协议标准设计。gRPC 使用 ProtoBuf(Protocol Buffers)作为序列化工具和接口定义语言,要求在调用前需要先定义好接口契约,并使用工具生成代码,然后在代码中调用这些生成的类进行服务调用。

课后思考

RestTemplate 底层是如何实现?Spring Cloud OpenFeign 底层是如何实现的?说说它的执行流程?

本文已收录到我的面试小站
www.javacn.site
,其中包含的内容有:Redis、JVM、并发、并发、MySQL、Spring、Spring MVC、Spring Boot、Spring Cloud、MyBatis、设计模式、消息队列等模块。