当前位置: 首页 > news >正文

Spring1~~~

目录

快速入门

分析

beanDefinitionMap 

ConcurrentHashMap$Node 

singletonObjects

beanDefinitionNames

实现简单基于XML配置程序

Spring原生容器底层结构 

id分配规则

 Spring管理Bean - IOC

基于XML配置Bean

通过id获取bean

通过类型获取bean

通过构造器配置bean

通过p名称空间配置bean

引入/注入bean对象

通过ref来配置bean

通过内部Bean来配置属性

引入/注入集合/数组类型

使用util:list进行配置

级联属性赋值

 通过静态工厂获取对象

通过实例工厂获取对象

通过FactoryBean获取对象 

bean信息重用(继承)

Bean创建顺序 

Bean的单例和多例

细节

Bean的生命周期

close关闭 

 配置bean的后置处理器

作用

  通过属性文件配置Bean

自动装配

EL表达式


快速入门

javaBean规范要有set方法,底层反射赋值

    <!--   id表示在该java对象在spring容器中的id--><bean class="spring.bean.Monster" id="monster01"><property name="Id" value="100"/><property name="name" value="牛魔王"/><property name="skill" value="芭蕉扇"/></bean>
public class SpringBeanTest {@Testpublic void getMonster() {ApplicationContext ioc =new ClassPathXmlApplicationContext("beans.xml");//第一种//Object monster01 = ioc.getBean("monster01");Monster monster01 = (Monster) ioc.getBean("monster01");System.out.println(monster01);//第二种Monster monster011 = ioc.getBean("monster01", Monster.class);System.out.println(monster011);//类加载路径//D:\Atest\spring\out\production\springFile file = new File(this.getClass().getResource("/").getPath());System.out.println(file);}

分析

beanDefinitionMap 

table存放bean对象信息

ConcurrentHashMap$Node 

singletonObjects

真正创建的对象

beanDefinitionNames

先在beanDefintionMap中查找,再判断是否单例,是的话在单例池中找到返回,否则通过反射创建再返回

实现简单基于XML配置程序

public class HspApplicationContext {private ConcurrentHashMap<String,Object> singletonObejcts = new ConcurrentHashMap();public HspApplicationContext(String iocBeanXmlFile) throws Exception {//加载类路径String path = this.getClass().getResource("/").getPath();SAXReader reader = new SAXReader();//得到Document对象Document document = reader.read(path + iocBeanXmlFile);//得到rootDocumentElement rootElement = document.getRootElement();Element bean = (Element) rootElement.elements("bean").get(0);//获取第一个bean的 id 和 全路径String id = bean.attributeValue("id");String classFullPath = bean.attributeValue("class");//获取第一个bean的属性List<Element> property = bean.elements("property");Integer monsterId = Integer.parseInt(property.get(0).attributeValue("value"));String name = property.get(1).attributeValue("value");String skill = property.get(2).attributeValue("value");//使用反射创建对象,属性赋值Class<?> cls = Class.forName(classFullPath);Monster o = (Monster)cls.newInstance();o.setId(monsterId);o.setName(name);o.setSkill(skill);//创建好的对象放入单例池singletonObejcts.put(id, o);}public Object getBean(String id) {return singletonObejcts.get(id);}
}

Spring原生容器底层结构 

id分配规则

bean不带id,系统会默认分配id,分配id的规则是 全类名#0, 全类名#1 这样的规则来分配id

<bean class="spring.bean.Monster">
</bean><bean class="spring.bean.Monster">
</bean>
Monster monster01 = ioc.getBean("spring.bean.Monster#0", Monster.class);

 Spring管理Bean - IOC

基于XML配置Bean

通过id获取bean

<bean class="spring.bean.Monster" id="monster01">
</bean>
Monster monster011 = ioc.getBean("monster01", Monster.class);

通过类型获取bean

    <bean class="spring.bean.Monster"><property name="Id" value="100"/><property name="name" value="牛魔王"/><property name="skill" value="芭蕉扇"/></bean>
public void getBeanByType() {ApplicationContext ioc =new ClassPathXmlApplicationContext("beans.xml");Monster bean = ioc.getBean(Monster.class);System.out.println(bean);}

要求ioc容器中的同一个类的bean只能有一个

应用场景:比如XxxAction/Servlet/Controller, 或 XxxService 在一个线程中只需要一个对象实例(单例)的情况

通过构造器配置bean

老师解读1. constructor-arg标签可以指定使用构造器的参数2. index表示构造器的第几个参数 从0开始计算的3. 除了可以通过index 还可以通过 name / type 来指定参数方式4. 解除大家的疑惑, 类的构造器,不能有完全相同类型和顺序的构造器,所以可以通过type来指定--><bean id="monster03" class="spring.bean.Monster"><constructor-arg value="200" index="0"/><constructor-arg value="白骨精" index="1"/><constructor-arg value="吸人血" index="2"/></bean><bean id="monster04" class="spring.bean.Monster"><constructor-arg value="200" name="monsterId"/><constructor-arg value="白骨精" name="name"/><constructor-arg value="吸人血" name="skill"/></bean><bean id="monster05" class="spring.bean.Monster"><constructor-arg value="300" type="java.lang.Integer"/><constructor-arg value="白骨精~" type="java.lang.String"/><constructor-arg value="吸人血~" type="java.lang.String"/></bean>

通过p名称空间配置bean

    //将光标放在p , 输入alt+enter , 就会自动的添加xmlns<bean class="spring.bean.Monster" id="monster01"p:id="100"p:name="红孩儿"p:skill="吐火"/>

引入/注入bean对象

service层引用dao层

通过ref来配置bean

service层

public class MemberServiceImpl {private MemberDAOImpl memberDAO;public MemberServiceImpl() {System.out.println("MemberServiceImpl() 构造器被执行");}public MemberDAOImpl getMemberDAO() {return memberDAO;}public void setMemberDAO(MemberDAOImpl memberDAO) {this.memberDAO = memberDAO;}public void add() {System.out.println("MemberServiceImpl add() 被调用..");memberDAO.add();}
}

dao层

public class MemberDAOImpl {public MemberDAOImpl() {System.out.println("MemberDAOImpl 构造器被执行...");}public void add() {System.out.println("MemberDAOImpl add()方法被执行");}
}
    <!--1. ref="memberDAO"表示  MemberServiceImpl对象属性memberDAO引用的对象是id=memberDAO的对象2. 这里就体现出spring容器的依赖注入3. 注意再spring容器中, 他是作为一个整体来执行的, 即如果你引用到一个bean对象, 对你配置的顺序没有要求4. 建议还是按顺序,好处是阅读的时候,比较方便--><bean class="spring.dao.MemberDAOImpl" id="memberDAO"/><bean class="spring.service.MemberServiceImpl" id="memberService"><property name="memberDAO" ref="memberDAO"/></bean>
通过内部Bean来配置属性

name是MemberServiceImpl类里面的属性

<bean class="spring.service.MemberServiceImpl" id="memberService"><property name="memberDAO"><bean class="spring.dao.MemberDAOImpl"/></property>
</bean>

引入/注入集合/数组类型

    <!--配置Master--><bean class="spring.bean.Master" id="master"><property name="name" value="太上老君"/><!--对List属性进行配置--><property name="monsterList"><list><!--引入其他bean--><ref bean="monster01"/><ref bean="monster02"/><!--内部bean--><bean class="spring.bean.Monster"><property name="name" value="老鼠精"/><property name="Id" value="100"/><property name="skill" value="吃粮食"/></bean></list></property><!--对Map属性进行配置--><property name="monsterMap"><map><entry><key><value>monster01</value></key><ref bean="monster01"/></entry><entry><key><value>monster02</value></key><ref bean="monster02"/></entry></map></property><!--给set属性赋值--><property name="monsterSet"><set><ref bean="monster01"/><ref bean="monster02"/></set></property><!--给数组属性赋值array标签中使用 value 还是 bean , ref .. 要根据你的业务决定--><property name="monsterName"><array><value>小妖怪</value><value>大妖怪</value></array></property><!--给Properties属性赋值 结构k(String)-v(String)--><property name="pros"><props><prop key="username">root</prop><prop key="password">123456</prop><prop key="ip">127.0.0.1</prop></props></property></bean><bean class="spring.bean.Monster" id="monster01"p:id="100"p:name="红孩儿"p:skill="吐火"/><bean class="spring.bean.Monster" id="monster02"p:id="101"p:name="红孩儿"p:skill="吐水"/>

使用util:list进行配置

普通做法,如果数据共享,太繁琐

   <!--配置Bookstore--><bean class="spring.bean.Bookstore" id="bookstore"><property name="bookList"><list><value>三国演义</value><value>红楼梦</value><value>西游记</value><value>水浒传</value></list></property></bean>

使用util:list指定id,直接ref引用过来

    <!--配置Bookstore--><bean class="spring.bean.Bookstore" id="bookstore"><property name="bookList" ref="myBookList"/></bean><!--定义util:list--><util:list id="myBookList"><value>三国演义</value><value>红楼梦</value><value>西游记</value><value>水浒传</value></util:list>

级联属性赋值

spring的ioc容器可以直接给对象属性的属性赋值

    <bean class="spring.bean.Dept" id="dept"/><bean class="spring.bean.Emp" id="emp"><property name="name" value="jack"/><property name="dept" ref="dept"/><!--级联赋值--><property name="dept.name" value="Java开发部门"/></bean>

 通过静态工厂获取对象

xml中可以没有 class = 路径

public class MyStaticFactory {private static Map<String, Monster> monsterMap;//使用 static代码块 进行初始化static  {monsterMap = new HashMap<>();monsterMap.put("monster01", new Monster(100,"牛魔王","芭蕉扇"));monsterMap.put("monster02", new Monster(200,"狐狸精","美人计"));}//提供一个方法,返回Monster对象public static Monster getMonster(String key) {return monsterMap.get(key);}
}
    <!--配置monster对象,通过静态工厂获取1. 通过静态工厂获取/配置bean2. class 是静态工厂类的全路径3. factory-method 表示是指定静态工厂类的哪个方法返回对象4. constructor-arg value="monster02" value是指定要返回静态工厂的哪个对象--><bean id="my_monster01"class="spring.factory.MyStaticFactory"factory-method="getMonster"><constructor-arg value="monster02"/></bean>

即使有多个bean,静态工厂获取的对象都是同一个

通过实例工厂获取对象

xml中必须有 class = 路径

public class MyInstanceFactory {private Map<String, Monster> monster_map;//通过普通代码块进行初始化{monster_map = new HashMap<>();monster_map.put("monster03", new Monster(300, "牛魔王~", "芭蕉扇~"));monster_map.put("monster04", new Monster(400, "狐狸精~", "美人计~"));}//写一个方法返回Monster对象public Monster getMonster(String key) {return monster_map.get(key);}
}
    <!--配置2个实例工厂对象--><bean class="spring.factory.MyInstanceFactory" id="myInstanceFactory"/><bean class="spring.factory.MyInstanceFactory" id="myInstanceFactory2"/><!--配置monster对象, 通过实例工厂老韩解读1. factory-bean 指定使用哪个实例工厂对象返回bean2. factory-method 指定使用实例工厂对象的哪个方法返回bean3. constructor-arg value="monster03" 指定获取到实例工厂中的哪个monster--><bean id="my_monster02" factory-bean="myInstanceFactory" factory-method="getMonster"><constructor-arg value="monster03"/></bean><bean id="my_monster03" factory-bean="myInstanceFactory2" factory-method="getMonster"><constructor-arg value="monster03"/></bean>
ApplicationContext ioc =new ClassPathXmlApplicationContext("beans.xml");        Monster monster02 = ioc.getBean("my_monster02", Monster.class);Monster monster03 = ioc.getBean("my_monster02", Monster.class);System.out.println(monster02 == monster03); //trueMonster monster3 = ioc.getBean("my_monster03", Monster.class);//monster03 和 monster3返回的值都是 monster03

使用同一个容器,调用my_monster02,都是用的同一个实例工厂对象
(factory-bean="myInstanceFactory"),只要它不变化,构建的就是同一个对象


可以有多个实例工厂,那么即使值一样,但不是同一个对象

通过FactoryBean获取对象 

public class MyFactoryBean implements FactoryBean<Monster> {//这个就是你配置时候,指定要获取的对象对应keyprivate String key;private Map<String, Monster> monster_map;{   //代码块,完成初始化monster_map = new HashMap<>();monster_map.put("monster03", new Monster(300, "牛魔王~", "芭蕉扇~"));monster_map.put("monster04", new Monster(400, "狐狸精~", "美人计~"));}public void setKey(String key) {this.key = key;}@Overridepublic Monster getObject() throws Exception {return monster_map.get(key);}@Overridepublic Class<?> getObjectType() {return Monster.class;}@Overridepublic boolean isSingleton() {//这里指定是否返是单例return false;}
}
    <!--配置monster对象,通过FactoryBean获取1. class 指定使用的FactoryBean2. key表示就是 MyFactoryBean 属性key3. value就是你要获取的对象对应key--><bean id="my_monster05" class="spring.factory.MyFactoryBean"><property name="key" value="monster04"/></bean>

bean信息重用(继承)

   <!--配置Monster对象--><bean id="monster10" class="spring.bean.Monster"><property name="monsterId" value="10"/><property name="name" value="蜈蚣精"/><property name="skill" value="蜇人"/></bean><!--1. 配置Monster对象2.但是这个对象的属性值和 id="monster10"对象属性一样3.parent="monster10" 指定当前这个配置的对象的属性值从 id=monster10的对象来--><bean id="monster11"class="spring.bean.Monster"parent="monster10"/><bean id="monster12" class="spring.bean.Monster" abstract="true"><property name="monsterId" value="10"/><property name="name" value="蜈蚣精"/><property name="skill" value="蜇人"/></bean><bean id="monster13"class="spring.bean.Monster"parent="monster12"/>

1. 如果bean指定了 abstract="true", 表示该bean对象, 是用于被继承
2. 本身这个bean就不能被获取/实例化

Bean创建顺序 

默认按照配置的顺序创建Bean对象

 

 如果将两个bean顺序交换

先都创建好,再执行Service里的set方法,完成引用

Bean的单例和多例

  <!--配置Cat对象1. 在默认情况下 scope属性是 singleton2. 在ioc容器中, 只要有一个这个bean对象3. 当程序员执行getBean时, 返回的的是同一个对象4. 如果我们希望每次getBean返回一个新的Bean对象,则可以scope="prototype"5. 如果bean的配置是 scope="singleton" lazy-init="true" 这时,ioc容器就不会提前创建该对象, 而是当执行getBean方法的时候,才会创建对象--><bean id="cat" class="spring.bean.Cat" scope="prototype" lazy-init="false"><property name="id" value="100"/><property name="name" value="小花猫"/></bean>
细节

Bean的生命周期

 

 创建javaBean对象(House),自己写init方法,desrtoy方法,名字自己定义

    <!--配置House对象,演示整个Bean的生命周期老师解读1. init-method="init" 指定bean的初始化方法 , 在setter方法后执行2. init方法执行的时机,由spring容器来控制3. destroy-method="destroy" 指定bean的销毁方法, 在容器关闭的时候执行4. destroy方法执行的时机,有spring容器来控制--><bean class="spring.bean.House" id="house"init-method="init"destroy-method="destroy"><property name="name" value="北京豪宅"/></bean>
close关闭 

 配置bean的后置处理器

对象都会有隐式的初始化方法,不指定init-method也会执行,多例对象每次都会执行 

bean初始化方法

作用

对ioc容器中所有的对象进行统一处理
如果类型是House的统一改成上海豪宅


/*** 这是一个后置处理器, 需要实现 BeanPostProcessor接口*/
public class MyBeanPostProcessor implements BeanPostProcessor {/*** 什么时候被调用: 在Bean的init方法前被调用* @param bean 传入的在IOC容器中创建/配置Bean* @param beanName 传入的在IOC容器中创建/配置Bean的id* @return Object 程序员对传入的bean 进行修改/处理【如果有需要的话】 ,返回* @throws BeansException*/@Overridepublic Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {System.out.println("postProcessBeforeInitialization().. bean="+ bean + " beanName=" + beanName);//初步体验案例: 如果类型是House的统一改成 上海豪宅//对多个对象进行处理/编程==>切面编程if(bean instanceof House) {((House)bean).setName("上海豪宅~");}return null;}/*** 什么时候被调用: 在Bean的init方法后被调用* @param bean  传入的在IOC容器中创建/配置Bean* @param beanName 传入的在IOC容器中创建/配置Bean的id* @return 程序员对传入的bean 进行修改/处理【如果有需要的话】 ,返回* @throws BeansException*/@Overridepublic Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {System.out.println("postProcessAfterInitialization().. bean="+ bean + " beanName=" + beanName);return bean;}
}
    <!--配置House对象--><bean class="com.hspedu.spring.bean.House" id="house"init-method="init"destroy-method="destroy"><property name="name" value="大豪宅"/></bean><bean class="com.hspedu.spring.bean.House" id="house02"init-method="init"destroy-method="destroy"><property name="name" value="香港豪宅"/></bean><!--配置了一个Monster对象--><!--配置后置处理器对象1. 当我们在beans02.xml 容器配置文件 配置了 MyBeanPostProcessor2. 这时后置处理器对象,就会作用在该容器创建的Bean对象3. 已经是针对所有对象编程->切面编程AOP--><bean class="spring.bean.MyBeanPostProcessor" id="myBeanPostProcessor"/>

@Testpublic void testBeanPostProcessor() {ApplicationContext ioc =new ClassPathXmlApplicationContext("beans02.xml");House house = ioc.getBean("house", House.class);System.out.println("使用house=" + house);House house02 = ioc.getBean("house02", House.class);System.out.println("使用house02=" + house02);((ConfigurableApplicationContext)ioc).close();}

 

  通过属性文件配置Bean

在src目录下新建xx.properties

my.properties

monsterId=1000
name=jack
skill=hello

 如果v是中文,可以转成unicode编码

    <!--文件提示修改成 all problems,引入namespacelocation="classpath:my.properties" 表示指定属性文件的位置--><context:property-placeholder location="classpath:my.properties"/><!--通过属性文件给monster对象的属性赋值属性名通过${属性名},属性名就是my.properties文件中k=v的k--><bean class="spring.bean.Monster" id="monster100"><property name="id" value="${monsterId}"/><property name="name" value="${name}"/><property name="skill" value="${skill}"/></bean>

自动装配

DAO层

public class OrderDao {//方法。。。public void saveOrder() {System.out.println("保存 一个订单...");}
}

Service层

public class OrderService {//OrderDao属性private OrderDao orderDao;//getterpublic OrderDao getOrderDao() {return orderDao;}//setterpublic void setOrderDao(OrderDao orderDao) {this.orderDao = orderDao;}
}

Controller层

public class OrderAction {//属性OrderServiceprivate OrderService orderService;//getterpublic OrderService getOrderService() {return orderService;}//setterpublic void setOrderService(OrderService orderService) {this.orderService = orderService;}
}

xml

    <!--配置OrderDao对象--><bean class="spring.dao.OrderDao" id="orderDao"/><!--配置OrderService对象1. autowire="byType" 表示 在创建 orderService时通过类型的方式 给对象属性 自动完成赋值/引用2. 比如OrderService 对象有 private OrderDao orderDao3. 就会在容器中去找有没有 OrderDao类型对象4. 如果有,就会自动的装配, 老师提示如果是按照 byType 方式来装配, 这个容器中,不能有两个的OrderDao类型对象5. 如果你的对象没有属性,  autowire就没有必要写6. 其它类推..7. 如果我们设置的是 autowire="byName" 表示通过名字完成自动装配8. 比如下面的 autowire="byName" class="com.hspedu.spring.service.OrderService"1) 先看 OrderService 属性 private OrderDao orderDao2) 再根据这个属性的setXxx()方法的 xxx 来找对象id3) public void setOrderDao() 就会找id=orderDao对象来进行自动装配4) 如果没有就装配失败--><bean autowire="byName" class="spring.service.OrderService"id="orderService"/><!--配置OrderAction--><bean autowire="byName" class="spring.web.OrderAction" id="orderAction"/>

使用byName装配,id名要与set方法名一致

在OrderAction类中,setOrderService方法名更改, class="spring.service.OrderService"的id也要更改,根据方法名找对象id

EL表达式

@Data
public class SpELBean {private String name;private Monster monster;private String monsterName;private String crySound; //叫声private String bookName;private Double result;//cry 方法会返回字符串public String cry(String sound) {return "发出 " + sound + "叫声...";}//read 返回字符串public static String read(String bookName) {return "正在看 " + bookName;}
}

xml

    <!--配置一个monster对象--><bean id="monster01" class="spring.bean.Monster"><property name="monsterId" value="100"/><property name="name" value="蜈蚣精~"/><property name="skill" value="蜇人~"/></bean><!-- spring el 表达式使用老师解读1. 通过spel给bean的属性赋值--><bean id="spELBean" class="spring.bean.SpELBean"><!-- sp el 给字面量 --><property name="name" value="#{'韩顺平教育'}"/><!-- sp el 引用其它bean --><property name="monster" value="#{monster01}"/><!-- sp el 引用其它bean的属性值 --><property name="monsterName" value="#{monster01.name}"/><!-- sp el 调用普通方法(返回值)  赋值 --><property name="crySound" value="#{spELBean.cry('喵喵的..')}"/><!-- sp el 调用静态方法(返回值) 赋值 --><property name="bookName" value="#{T(com.hspedu.spring.bean.SpELBean).read('天龙八部')}"/><!-- sp el 通过运算赋值 --><property name="result" value="#{89*1.2}"/></bean>

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • ONU测试需要那些协议的学习
  • 第三章 Mybatis 常用工具
  • 【学习笔记】手写 Tomcat -- 预备知识
  • freemarker模板学习笔记
  • 【C#编程技术总结】魔法包唤醒同一局域网设备
  • Unity解析XML开发随机名字生成模块
  • 联想泄露显示本月推出更便宜的Copilot Plus电脑
  • 虚幻引擎VR游戏开发02 | 性能优化设置
  • 不小心删除丢失了所有短信?如何在 iPhone 上查找和恢复误删除的短信
  • vue实现评论滚动效果
  • 网络编程day02(字节序、TCP编程)
  • 600 条最强 Linux 命令总结
  • 都2024年了还不明白Redis持久化?RDB文件、AOF文件、AOF重写
  • 编码器有哪些?
  • sheng的学习笔记-AI-概率图,隐马尔可夫HMM,马尔可夫随机场MRF,条件随机场CRF
  • Google 是如何开发 Web 框架的
  • 【JavaScript】通过闭包创建具有私有属性的实例对象
  • 07.Android之多媒体问题
  • 77. Combinations
  • create-react-app做的留言板
  • Java多态
  • Mybatis初体验
  • Netty+SpringBoot+FastDFS+Html5实现聊天App(六)
  • PAT A1050
  • python学习笔记 - ThreadLocal
  • weex踩坑之旅第一弹 ~ 搭建具有入口文件的weex脚手架
  • 案例分享〡三拾众筹持续交付开发流程支撑创新业务
  • 不发不行!Netty集成文字图片聊天室外加TCP/IP软硬件通信
  • 聚类分析——Kmeans
  • 微信支付JSAPI,实测!终极方案
  • media数据库操作,可以进行增删改查,实现回收站,隐私照片功能 SharedPreferences存储地址:
  • ​LeetCode解法汇总307. 区域和检索 - 数组可修改
  • ​补​充​经​纬​恒​润​一​面​
  • ​字​节​一​面​
  • #define
  • #NOIP 2014#day.2 T1 无限网络发射器选址
  • $var=htmlencode(“‘);alert(‘2“); 的个人理解
  • (12)目标检测_SSD基于pytorch搭建代码
  • (C语言)深入理解指针2之野指针与传值与传址与assert断言
  • (保姆级教程)Mysql中索引、触发器、存储过程、存储函数的概念、作用,以及如何使用索引、存储过程,代码操作演示
  • (二)测试工具
  • (图)IntelliTrace Tools 跟踪云端程序
  • (循环依赖问题)学习spring的第九天
  • (转)Google的Objective-C编码规范
  • (转)使用VMware vSphere标准交换机设置网络连接
  • *_zh_CN.properties 国际化资源文件 struts 防乱码等
  • *Algs4-1.5.25随机网格的倍率测试-(未读懂题)
  • .desktop 桌面快捷_Linux桌面环境那么多,这几款优秀的任你选
  • .net core 的缓存方案
  • .net core 管理用户机密
  • .NET Framework 3.5中序列化成JSON数据及JSON数据的反序列化,以及jQuery的调用JSON
  • .NET NPOI导出Excel详解
  • .Net Web项目创建比较不错的参考文章
  • .Net7 环境安装配置
  • @manytomany 保存后数据被删除_[Windows] 数据恢复软件RStudio v8.14.179675 便携特别版...