Spring5

 
 
# Spring概述

0|11、Spring是轻量级开源JavaEE框架

0|22、Spring可以解决企业应用开发的复杂性

0|33、组成核心IOC、Aop

  • IOC:控制反转,把创建对象过程交给Spring进行管理

  • Aop:面向切面,不修改源代码进行功能增强

0|44、Spring特点

  • 方便解耦,简化开发
  • Aop编程支持
  • 方便程序测试
  • 方便集成各种优秀框架
  • 方便进行事务管理
  • 降低java api的使用难度

*此文档版本为Spring5

1|0IOC

1|1什么是IOC

​ (1)控制反转,把对象的创建和对象之间调用的过程,交给Spring进行管理

​ (2) 使用IOC的目的:为了降低耦合

​ (3) 做入门案例就是IOC的实现

1|0IOC底层原理

​ (1) xml解析、工厂模式、反射

image-20220223170558636

1|0IOC过程 (进一步降低耦合度)

​ 第一步 xml配置文件,配置创建的对象

<bean id="dao" class="com.atguigu.UserDao"></bean>

​ 第二步 有service类和dao类 创建工厂类

class UserFactory{ public static UserDao getDao(){ String classValue = class属性值;//1 xml解析 Class class = Class.forName(classValue);//2 通过反射创建对象 return (UserDao)class.newInstance();//返回对象 } }

1|0IOC接口

  1. IOC思想基于IOC容器完成,IOC容器底层就是对象工厂

  2. Spring提供IOC容器实现两种方式:(两个接口)

  • BeanFactory :IOC容器基本实现,是Spring内部的使用接口,不提供给开发人员使用
    • 加载配置文件时不会创建对象,获取\使用对象时才会创建对象
  • ApplicationContext :BeanFactory接口的子接口,提供更多更强大的功能,一般由开发人员使用
    • 加载配置文件时就会把配置文件中对象创建(服务器启动时创建)

1|0ApplicationContext接口有实现类

image-20220223185151278

1|2IOC操作 Bean管理

1|0什么是Bean管理

包含以下两个操作

  1. Spring创建对象
  2. Spring注入属性

1|0Bean管理操作

1|01 bean创建对象

1 基于xml配置文件方式

<!--配置User对象创建--> <bean id="user" class="com.atguigu.spring5.User"></bean>

(1)在Spring配置文件中,使用bean标签,标签里添加对应属性,就可以实现对象创建
(2)在bean标签中有很多属性:

  • id属性:唯一标识(不能加特殊符号)

  • class属性:类全路径(包类路径)

  • name:类似id(可添加特殊符号)

(3)创建对象的时候,默认也是执行无参构造方法

2 基于注解方式

1|02 基于xml注入属性

(1) DI:依赖注入,就是注入属性(DI是IOC的一种具体实现,在创建对象的基础之上进行)

1|0第一种注入方式:使用set方法进行注入

第一步:创建类、创建属性、创建对应的set方法

public class Book { //创建属性 private String bname; private String bauthor; //创建属性对应的set方法 public void setBname(String bname) { this.bname = bname; } public void getBauthor(String bauthor) { this.bauthor = bauthor; } }

第二步:在Spring配置文件配置对象创建,配置属性注入

<!--2 set方法注入属性--> <bean id="book" class="com.atguigu.spring5.Book"> <!--使用property完成属性注入 name:类里面属性名称 value:向属性注入的值 --> <property name="bname" value="张三日记"></property> <property name="bauthor" value="法外狂徒张三"></property> </bean>
public class Book { //创建属性 private String bname; private String bauthor; //创建属性对应的set方法 public void setBname(String bname) { this.bname = bname; } public void setBauthor(String bauthor) { this.bauthor = bauthor; } }
1|0第二种注入方法:使用有参构造进行注入

第一步:创建类 ,定义属性,创建属性对应有参构造方法

/** * 使用有参构造注入 * */ public class Orders { private String oname; private String address; public Orders(String oname,String address){ this.oname = oname; this.address = address; } }

第二步:在Spring的配置文件中进行配置

<!--3 使用有参构造注入属性--> <bean id="orders" class="com.atguigu.spring5.Orders"> <!-- constructor-arg标签用于有参构造注入属性--> <constructor-arg name="oname" value="电脑"></constructor-arg> <constructor-arg name="address" value="China"></constructor-arg> </bean>
1|0第三种注入方式:P名称空间注入(底层使用的还是set方法注入)

使用p名称空间注入,可以简化基于xml配置方式

  • 添加p名称空间在配置文件中

image-20220224175509505

  • 进行属性注入,在bean标签里面进行操作

    <!--4 使用p名称空间注入--> <bean id="book" class="com.atguigu.spring5.Book" p:bname="张三的一生" p:bauthor="罗翔"> </bean>
1|0xml注入其他类型属性

字面量:固定值

  1. null值

    <!--null值--> <property name="address"> <null/> </property> </bean>
  2. 属性包含特殊符号

    <!--注入特殊符号 1 拔尖括号进行转义 &lt;&gt; 2 把特殊符号内容写到CDATA --> <property name="address"> <value><![CDATA[<<南京>>]]></value> </property>
1|0注入属性-外部bean
  1. 创建两个类service类和dao类

  2. 在service调用dao里面的方法

  3. 在Spring配置文件中进行文件配置

    <!--1 service和Dao对象创建--> <bean id="userService" class="com.atguigu.spring5.service.UserService"> <!--注入userDao对象 name属性值:类里面的属性名称 ref属性:创建userDao对象bean标签id值 --> <property name="userDao" ref="userDaoImpl"></property> </bean> <bean id="userDaoImpl" class="com.atguigu.spring5.dao.UserDaoImpl"></bean>
    public class UserService { //创建UserDao类型属性,生成set方法 private UserDao userDao; public void setUserDao(UserDao userDao) { this.userDao = userDao; } public void add(){ System.out.println("service add............"); userDao.update(); //原始方式:创建UserDao对象 // UserDao userDao = new UserDaoImpl(); // userDao.update(); } }
1|0注入属性-内部bean
  1. 一对多关系,部门和员工

    一个部门有多个员工,一个员工属于一个部门。 部门是一 员工是多

  2. 在实体类之间表示一对多关系

    员工表示所属部门,使用对象类型进行表示

    //部门类 public class Dept { private String dname; public void setDname(String dname) { this.dname = dname; } }

    //员工类 public class Emp { private String ename; private String gender; //员工属于某一个部门,使用对象形式表示 private Dept dept; public void setDept(Dept dept) { this.dept = dept; } public void setEname(String ename) { this.ename = ename; } public void setGender(String gender) { this.gender = gender; } }
  3. 在Spring配置文件中进行配置

    <!--内部bean--> <bean id="emp" class="com.atguigu.spring5.bean.Emp"> <!--设置两个普通属性--> <property name="ename" value="lucy"></property> <property name="gender" value="女"></property> <!--设置对象类型属性--> <property name="dept"> <bean id="dept" class="com.atguigu.spring5.bean.Dept"> <property name="dname" value="安保部门"></property> </bean> </property> </bean>
1|0注入属性-级联赋值

第一种写法

<!--级联赋值--> <bean id="emp" class="com.atguigu.spring5.bean.Emp"> <!--设置两个普通属性--> <property name="ename" value="lucy"></property> <property name="gender" value="女"></property> <!--级联赋值--> <property name="dept" ref="dept"></property> </bean> <bean id="dept" class="com.atguigu.spring5.bean.Dept"> <property name="dname" value="财务部"></property> </bean>

第二种写法 类中需要写get方法

//员工类 public class Emp { private String ename; private String gender; //员工属于某一个部门,使用对象形式表示 private Dept dept; //生成dept的get方法 public Dept getDept() { return dept; } public void setDept(Dept dept) { this.dept = dept; } public void setEname(String ename) { this.ename = ename; } public void setGender(String gender) { this.gender = gender; } public void add(){ System.out.println(ename+"::"+gender+"::"+dept); } }
<!--级联赋值--> <bean id="emp" class="com.atguigu.spring5.bean.Emp"> <!--设置两个普通属性--> <property name="ename" value="lucy"></property> <property name="gender" value="女"></property> <!--级联赋值--> <property name="dept" ref="dept"></property> <property name="dept.dname" value="技术部" ></property> </bean> <bean id="dept" class="com.atguigu.spring5.bean.Dept"> <property name="dname" value="财务部"></property> </bean>
1|0xml注入集合属性
  1. 注入数组类型属性

  2. 注入List集合属性

  3. 注入Map集合类型属性

  4. 注入set集合类型属性

    创建类,定义数组、list、map、set类型属性,生成对应set方法

    public class Stu { //1 数组类型属性 private String[] courses; //2 list集合类型属性 private List<String> list; //3 map集合类型属性 private Map<String,String> maps; //4 set集合类型属性 private Set<String> set; public void setSet(Set<String> set) { this.set = set; } public void setCourses(String[] courses) { this.courses = courses; } public void setList(List<String> list) { this.list = list; } public void setMaps(Map<String, String> maps) { this.maps = maps; } }

    在Spring配置文件进行配置

    <!--1 集合类型属性注入--> <bean id="stu" class="com.atguigu.spring5.collectiontype.Stu"> <!--数组类型属性注入--> <property name="courses" > <array> <value>java课程</value> <value>sql课程</value> </array> </property> <!--list集合属性注入--> <property name="list"> <list> <value>张三</value> <value>小三</value> </list> </property> <!--map类型属性注入--> <property name="maps"> <map> <entry key="JAVA" value="java"></entry> <entry key="PHP" value="php"></entry> </map> </property> <!--set类型属性注入--> <property name="set"> <set> <value>MySQL</value> <value>Redis</value> </set> </property> </bean>
  5. 在集合里设置对象类型值

    <!--注入list集合类型,值是对象--> <property name="courseList"> <list> <ref bean="course1"></ref> <ref bean="course2"></ref> </list> </property>
    <!--创建多个course对象--> <bean id="course1" class="com.atguigu.spring5.collectiontype.Course"> <property name="cname" value="Spring5框架"></property> </bean> <bean id="course2" class="com.atguigu.spring5.collectiontype.Course"> <property name="cname" value="Mybatis框架"></property>
  6. 把集合注入部分提取出来

    1. 在Spring配置文件中引入空间名称util
    <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:util="http://www.springframework.org/schema/util" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"> </beans>
    1. 使用util标签完成list集合注入提取

      <!--1 提取list集合类型属性注入--> <util:list id="bookList" > <!--若引入对象使用ref标签--> <value>张三日记</value> <value>张三的悔改</value> <value>法外狂徒</value> </util:list> <!--2 提取list集合类型属性注入使用--> <bean id="book" class="com.atguigu.spring5.collectiontype.Book"> <property name="list" ref="bookList"></property> </bean>

1|0FactorBean

Spring里有两种bean,一种普通的bean。另外一种是FactoryBean(Spring内置的)

  1. 普通bean

    Spring配置文件中定义bean类型即为返回类型

  2. FactoryBean

    Spring配置文件中定义bean类型可与返回类型不同

  1. 创建类,让这个类作为工厂bean,实现接口FactoryBean

  2. 实现接口里面的方法,在实现的方法中定义返回的bean类型

    public class MyBean implements FactoryBean <Course>{ //定义返回bean @Override public Course getObject() throws Exception { Course course = new Course(); course.setCname("abc"); return course; } @Override public Class<?> getObjectType() { return null; } @Override public boolean isSingleton() { return false; } } //测试方法 @Test public void testCollection3(){ ApplicationContext context=new ClassPathXmlApplicationContext("bean3.xml"); Course course=context.getBean("myBean",Course.class); System.out.println(course); }

    配置文件

    <bean id="myBean" class="com.atguigu.spring5.factorybean.MyBean"> </bean>

1|0bean的作用域

  1. 在Spring里,默认设置下,bean是单实例对象

    @Test public void testCollection2(){ ApplicationContext context=new ClassPathXmlApplicationContext("bean2.xml"); Book book1=context.getBean("book", Book.class); Book book2=context.getBean("book", Book.class); //book.test(); System.out.println(book1); System.out.println(book2); }

输出显示地址相同为单实例对象:

image-20220303171118846

  1. 如何设置单实例或多实例

    (1)在Spring配置文件bean标签里面有用于设置的属性(scope)

    (2)scope属性值

    • 默认值:singleton,表示单实例对象
    • prototype,表示多实例对象
    <!--2 提取list集合类型属性注入使用--> <bean id="book" class="com.atguigu.spring5.collectiontype.Book" scope="prototype"> <property name="list" ref="bookList"></property> </bean>

    两对象地址不同:

    image-20220303171843088

  2. singleton和prototype区别

    • singleton表示单实例,prototype多实例
    • 设置scope值是singleton时,加载Spring配置文件时就会创建单实例对象
    • 设置scope值是prototype时,不是在加载Spring配置文件时创建对象,在调用getBean方法时创建多例对象

1|0Bean生命周期

从对象的创建到生命的销毁的过程

bean生命周期:

  1. 通过构造器创建bean实例(无参数构造)

  2. 为bean的属性设置值和对其他bean的引用(调用set方法)

    把bean实例传给bean后置处理器的方法postProcessBeforeInitialization(bean的后置处理器BeanPostProcessor,bean共有7步)

  3. 调用bean初始化的方法(需要进行配置)

    把bean实例传给bean后置处理器的另外一个方法postProcessAfterInitialization(bean的后置处理器BeanPostProcessor,bean共有7步)

  4. bean可以使用了(对象获取到了)

  5. 当容器关闭时,调用bean的销毁的方法(需要进行配置销毁的方法)

    <bean id="orders" class="com.atguigu.spring5.bean.Orders" init-method="initMethod" destroy-method="destoryMethod"> <property name="oname" value="手机"> </property> </bean>
    public class Orders { //1 无参构造 public Orders(){ System.out.println("第一步 执行无参构造方法创建bean实例"); } private String oname; public void setOname(String oname) { this.oname = oname; System.out.println("第二步 调用set方法设置属性值"); } //3 创建执行的初始化方法 public void initMethod(){ System.out.println("第三步 执行初始化方法"); } //5 创建执行的销毁方法 public void destoryMethod(){ System.out.println("第五步 执行销毁方法"); } } //测试类 @Test public void testBean3(){ // ApplicationContext context=new ClassPathXmlApplicationContext("bean4.xml"); ClassPathXmlApplicationContext context=new ClassPathXmlApplicationContext("bean4.xml"); Orders orders=context.getBean("orders",Orders.class); System.out.println("第四步 获取创建bean实例对象"); System.out.println(orders); //手动让bean实例销毁 context.close(); }

添加后置处理器之后:

  1. 创建类,实现接口BeanPostProcessor,创建后置处理器

    public class MyBeanPost implements BeanPostProcessor { @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { System.out.println("在初始化之前执行的方法"); return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { System.out.println("在初始化之后执行的方法"); return bean; } }
    <!--配置后置处理器--> <bean id="myBeanPost" class="com.atguigu.spring5.bean.MyBeanPost"></bean>

1|0IOC操作Bean管理(xml自动装配)

什么是自动装配?

根据指定装配规则(属性名称或属性类型),Spring自动将匹配的属性值进行注入

演示自动装配过程:

根据属性名称自动注入

<!--实现自动装配 bean标签属性autowire,配置自动装配 autowire属性常用的两个值: byName根据属性名称,注入值bean的id和类属性名称一样 byType根据属性类型注入 --> <bean id="emp" class="com.atguigu.spring5.autowire.Emp" autowire="byName"> <!-- <property name="dept" ref="dept"></property>--> </bean> <bean id="dept" class="com.atguigu.spring5.autowire.Dept"></bean> </beans>

根据属性类型自动注入

<!--实现自动装配 bean标签属性autowire,配置自动装配 autowire属性常用的两个值: byName根据属性名称,注入值bean的id和类属性名称一样 byType根据属性类型注入 --> <bean id="emp" class="com.atguigu.spring5.autowire.Emp" autowire="byType"> <!-- <property name="dept" ref="dept"></property>--> </bean> <bean id="dept" class="com.atguigu.spring5.autowire.Dept"></bean> </beans>

1|0IOC操作Bean管理(外部属性文件)

  1. 直接配置数据库信息

    ​ 1.配置德鲁伊连接池(Druid连接池,阿里巴巴)

    ​ 2.引入德鲁伊连接池依赖jar包

    <!--直接配置连接池--> <bean id="database" class="com.alibaba.druid.pool.DruidDataSource"> <property name="DriverClassName" value="com.mysql.jdbc.Driver"></property> <property name="url" value="jdbc:mysql://locahost:3306/userDb"></property> <property name="username" value="root"></property> <property name="password" value="root"></property> </bean>
  2. 引入外部属性文件配置数据库连接池

    (1)创建外部属性文件,properties格式文件,写数据库信息

    properties文件存储key:value格式配置

    prop.driverClass=com.mysql.jdbc.Driver prop.url=jdbc:mysql://localhost:3306/userDb prop.userName=root prop.password=root

    (2)把外部properties属性文件引入到Spring配置文件中

    • 引入context名称空间

      <beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> </beans>
    • 在Spring配置文件标签引入外部属性文件

      <!--引入外部属性文件--> <context:property-placeholder location="classpath:jdbc.properties"/> <!--配置连接池--> <bean id="database" class="com.alibaba.druid.pool.DruidDataSource"> <property name="DriverClassName" value="${prop.driverClass}"></property> <property name="url" value="${prop.url}"></property> <property name="username" value="${prop.userName}"></property> <property name="password" value="${prop.password}"></property> </bean>

1|0IOC操作Bean管理(基于注解方式)

什么是注解?

  • 注解是代码特殊标记,格式:@注解名称(属性名称=属性值,属性名称=属性值。。。)

  • 使用注解,注解作用在类、方法、属性上面

  • 使用注解的目的:简化xml配置

Spring针对Bean管理中创建对象提供注解:

  • @Component

  • @Service

  • @Controller

  • @Repository

    *四个注解功能相同,都可以用来创建bean对象

基于注解方式实现对象的创建:

  1. 引入依赖

  2. 开启组件扫描

    <!--1 开启组件扫描 1 扫描多个包时,使用逗号隔开 2 扫描包上层目录可扫描包下多个包 --> <context:component-scan base-package="com.atguigu"></context:component-scan>
  3. 创建类,在类上面添加创建对象注解

    //在注解里面value属性值可以不写 //默认值是类名称,首字母小写 //UserService.class---userService @Service(value = "userService")//<bean id="userService" class="..">相同 public class UserService { public void add(){ System.out.println("service add...."); } }
  4. 开启组件扫描的细节配置

    <!--示例1 use-default-filters="false"表示不使用默认filter(扫描所有子目录),自己配置filter context:include-filter,设置扫描哪些内容 --> <context:component-scan base-package="com.atguigu" use-default-filters="false"> <context:include-filter type="annotation" expression="org.springframework.stereotype.Service"/> </context:component-scan> <!--示例2 下面示例扫描包下所有内容 context:exclude-filter ,设置哪些不扫描 --> <context:component-scan base-package="com.atguigu"> <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Service"/> </context:component-scan>
  5. 基于注解方式实现属性注入

    • @AutoWired:根据属性类型进行自动装配

      ​ ①把service和dao对象创建,在service和dao类添加创建对象注解

      ​ ②在service注入dao对象,在service类添加dao类型属性,在属性前使用注解

      @Service(value = "userService")//<bean id="userService" class="..">相同 public class UserService { //定义dao类型属性 //不需要添加set方法(spring已经封装了这一步) //添加注入属性的注解 @Autowired private UserDao userDao; public void add(){ System.out.println("service add...."); userDao.add(); } }
    • @Qualifier:根据属性名称进行注入

      此注解的使用需要与@Autowired一起使用

      @Repository(value = "userDaoImpl1") public class UserDaoImpl implements UserDao{ @Override public void add() { System.out.println("dao add..."); } }
      @Service(value = "userService")//<bean id="userService" class="..">相同 public class UserService { //定义dao类型属性 //不需要添加set方法(spring已经封装了这一步) //添加注入属性的注解 @Autowired //根据类型进行注入 @Qualifier(value = "userDaoImpl1") private UserDao userDao; public void add(){ System.out.println("service add...."); userDao.add(); } }
    • @Resource:(javax中的注解,jdk11之后移除)可以根据类型注入,可以根据名称注入

    // @Resource//默认根据类型注入 @Resource(name = "userDaoImpl1") private UserDao userDao;
    • @Value:注入普通类型属性

      @Value(value = "abc") private String name;
  6. 完全注解开发

    (1)创建配置类,替代xml配置文件

    @Configuration//作为配置类,替代xml配置文件 @ComponentScan(basePackages = {"com.atguigu"}) public class SpringConfig { }

    (2)编写测试类

    @Test public void testService2(){ ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class); UserService userService = context.getBean("userService",UserService.class); System.out.println(userService); userService.add(); }

2|0AOP

什么是AOP?

​ 面向切面编程,利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各个部分之间的耦合度降低,提高程序的可重用性,同时提高开发效率。

通俗描述:可不修改源代码,在主干功能里添加新的功能

例子:在原登录基础上添加权限判断

image-20220308163433475

2|1AOP底层原理

AOP底层使用动态代理

有两种情况的动态代理

  1. 有接口的情况 ,使用JDK动态代理

    创建接口实现类代理对象,增强类的方法

    image-20220308164729412

  2. 没有接口的情况 ,使用CGLIB动态代理

    创建子类的代理对象,增强类的方法

    image-20220308165207366

2|2AOP底层原理(JDK动态代理)

使用JDK动态代理,使用Proxy类里面的方法创建代理对象

image-20220308170122797

调用newProxyInstans方法

image-20220308170308516方法有三个参数:

  • 类加载器
  • 增强方法所在的类,这个类实现的接口,支持多个接口
  • 实现这个接口InvocationHandler,创建代理对象,写增强方法

编写JDK动态代理代码:

  1. 创建接口,定义方法

    public interface UserDao { public int add(int a,int b); public String update(String id); }
  2. 创建接口实现类,实现方法(原功能)

    public class UserDaoImpl implements UserDao{ @Override public int add(int a, int b) { return a+b; } @Override public String update(String id) { return id; } }
  3. 使用Proxy类创建接口代理对象(加新功能)

    public class JDKProxy { public static void main(String[] args){ // //创建接口实现类代理对象 Class[] interfaces = {UserDao.class}; // Proxy.newProxyInstance(JDKProxy.class.getClassLoader(), interfaces, new InvocationHandler() { // @Override // public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // return null; // } // }); UserDaoImpl userDao = new UserDaoImpl(); UserDao dao = (UserDao) Proxy.newProxyInstance(JDKProxy.class.getClassLoader(), interfaces, new UserDaoProxy(userDao)); int result = dao.add(1,2); System.out.println("result:"+result); } } //创建代理对象代码 class UserDaoProxy implements InvocationHandler{ //1 把创建的是谁的代理对象,把谁传递进来 //有参数的构造 private Object obj; public UserDaoProxy(Object obj){ this.obj = obj; } //增强的逻辑 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //方法之前 System.out.println("方法之前执行。。。。"+method.getName()+":传递的参数。。。"+ Arrays.toString(args)); //被增强的方法执行 Object res = method.invoke(obj,args); //方法之后 System.out.println("方法之后执行。。。。"+obj); return res; } }

2|3AOP(术语)

  1. 连接点:类里面可以被增强的方法

  2. 切入点:类里面实际被增强的方法

  3. 通知(增强):实际增强的逻辑部分(新加的部分)

    *假如add()方法被增强

    • 前置通知:add()之前执行的增强
    • 后置通知:add()之后执行的增强
    • 环绕通知:add()之前之后都执行的增强
    • 异常通知:add()异常时执行的增强
    • 最终通知:return之后执行的增强,后置通知之后,有异常时不执行
  4. 切面(动作)

    把通知应用到切入点的过程

2|4AOP操作(准备)

1、Spring框架中一般基于AspectJ实现AOP操作

AspectJ:AspectJ不是Spring组成部分,独立AOP框架,一般把AspectJ和Spring框架一起使用,进行AOP操作

2、基于AspectJ实现AOP操作有两种方式:

  1. 基于xml配置文件使用
  2. 基于注解方式实现(使用)

3、在项目工程里引入AOP相关依赖

image-20220309102111127

4、切入点表达式

  1. 切入点表达式的作用:知道对哪个类里面哪个方法进行增强

  2. 语法结构:

    execution([权限修饰符] [返回类型] [类全路径] [方法名称] ([参数列表]))

    例1:对com.atguigu.dao.BookDao类里面的add方法进行增强

    execution(public void com.atguigu.dao.BookDao.add(int a,int b))

    权限修饰符可省略,默认public

    返回类型可以用*号表示全类型

    execution(* com.atguigu.dao.BookDao.add(int a,int b))

    例2:对com.atguigu.dao.BookDao类里面的所有方法进行增强

    execution(* com.atguigu.dao.BookDao.*(..))

    例3:对com.atguigu.dao包里的所有类,类里的所有方法进行增强

    execution(* com.atguigu.dao..(..))

2|5AOP(AspectJ注解)

演示:

  1. 创建类,在类里定义方法

    public class User { public void add(){ System.out.println("add..."); } }
  2. 创建增强类

    1. 在增强类里面,创建方法,让不同方法代表不同通知类型

      //增强的类 public class UserProxy { //前置通知 public void before(){ System.out.println("before..."); } }
  3. 进行通知的配置

    1. 在Spring配置文件中,开启注解的扫描

      <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <!--开启注解扫描--> <context:component-scan base-package="com.atguigu.spring5.aopanno"></context:component-scan> </beans>
    2. 使用注解创建User和UserProxy对象

    3. 在增强类上面添加注解@Aspect

      //增强的类 @Component @Aspect //生产代理对象 public class UserProxy { //前置通知 public void before(){ System.out.println("before..."); } }
    4. 在Spring配置文件中开启生成代理对象

      <!--开启Aspect生成代理对象--> <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
    5. 配置不同类型通知

      • 在增强类的里面,在通知方法上面添加通知类型的注解,使用切入点表达式配置

      • @Before

      • @After

      • @AfterReturning

      • @AfterThrowing

      • @Around

      //增强的类 @Component @Aspect //生产代理对象 public class UserProxy { //前置通知 //@Before注解表示前置通知 @Before(value = "execution(* com.atguigu.spring5.aopanno.User.add(..))") public void before(){ System.out.println("before..."); } @After(value = "execution(* com.atguigu.spring5.aopanno.User.add(..))") public void after(){ System.out.println("after..."); } @AfterReturning(value = "execution(* com.atguigu.spring5.aopanno.User.add(..))") public void afterReturning(){ System.out.println("afterReturning..."); } @AfterThrowing(value = "execution(* com.atguigu.spring5.aopanno.User.add(..))") public void afterThrowing(){ System.out.println("afterThrowing...异常"); } @Around(value = "execution(* com.atguigu.spring5.aopanno.User.add(..))") public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{ System.out.println("around...之前"); //被增强方法 proceedingJoinPoint.proceed(); System.out.println("around...之前后"); } }
    6. 公共切入点

      //相同切入点抽取 @Pointcut(value = "execution(* com.atguigu.spring5.aopanno.User.add(..))") //切入点注解 public void pointdemo(){ } //前置通知 //@Before注解表示前置通知 @Before(value = "pointdemo()") public void before(){ System.out.println("before..."); }
    7. 多个增强类对同一个切入点增强时,可设置优先级

      在增强类上面加一个注释@Order(数字类型值),数字类型值越小它的优先级越高(值从0开始)

    8. 完全使用注解开发

      创建配置类(config),不需要创建xml配置文件

      @Configuration @ComponentScan(basePackages = "com.atguigu") //开注解扫描 @EnableAspectJAutoProxy(proxyTargetClass = true) //开启Aspect生成代理对象 public class ConfigAop { }

2|6AOP(AspectJxml配置文件)

  1. 创建两个类:增强类和被增强类,创建方法

  2. 在Spring配置文件中创建两个类的对象

  3. 在Spring配置文件中配置切入点

    <!-- 创建两个类的对象--> <bean id="book" class="com.atguigu.spring5.aopxml.Book"></bean> <bean id="bookProxy" class="com.atguigu.spring5.aopxml.BookProxy"></bean> <!-- 配置切入点--> <aop:config> <!-- 切入点--> <aop:pointcut id="buy" expression="execution(* com.atguigu.spring5.aopxml.Book.buy()) "/> <!-- 配置切面(把增强应用到切入点的过程)--> <aop:aspect ref="bookProxy"> <!-- 增强作用在具体方法上--> <aop:before method="before" pointcut-ref="buy"></aop:before> </aop:aspect> </aop:config>

3|0JdbcTemplate

Spring框架对JDBC进行封装,使用JdbcTemplate方便实现对数据库操作

3|1JdbcTemplate使用准备工作

  1. 引入相关jar包

    image-20220310175133948

  2. 在Spring配置文件中配置数据库连接池

    <!--数据库连接池--> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close"> <property name="name" value="jdbc:mysql:///user_db"/> <property name="username" value="root"/> <property name="password" value="root"/> <property name="driverClassName" value="com.mysql.jdbc.Driver"/> </bean>
  3. 配置JdbcTemplate对象,注入Datasource

    <!--JdbcTemplate对象--> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <!--注入DataSource--> <property name="dataSource" ref="dataSource"></property> </bean>
  4. 创建service类,创建dao类,在dao注入JdbcTemplate对象

    1. <!--1 开启组件扫描--> <context:component-scan base-package="com.atguigu"></context:component-scan>
    2. @Service public class BookService { //注入Dao @Autowired private BookDao bookDao; }
    3. @Repository public class BookDaoImpl implements BookDao{ //注入JdbcTemplate @Autowired private JdbcTemplate jdbcTemplate; }

3|2JdbcTemplate操作数据库(添加)

  1. 对应数据库创建

  2. 编写service和dao

    1. 在dao进行数据库添加操作

      jdbcTemplate.update();
    2. 调用JdbcTemplate中的update()实现添加操作

      有两个参数:

      1. sql语句 2. 可变参数设置sql语句中的值
      @Repository public class BookDaoImpl implements BookDao{ //注入JdbcTemplate @Autowired private JdbcTemplate jdbcTemplate; //添加方法 @Override public void add(Book book) { //1 创建sql语句 String sql="insert into t_book value(?,?,?)"; //2 调用方法实现 Object[] args = {book.getUser_id(),book.getUsername(),book.getUstatus()}; int update = jdbcTemplate.update(sql,args); System.out.println(update); } }
    3. 测试类

      public class TestBook { @Test public void testJdbcTemplate(){ ApplicationContext context= new ClassPathXmlApplicationContext("bean1.xml"); BookService bookServcie = context.getBean("bookService", BookService.class); Book book = new Book(); book.setUser_id(1); book.setUsername("张三"); book.setUstatus("a"); bookServcie.addBook(book); } }

3|3JdbcTemplate操作数据库(修改和删除)

  1. 方法的实现
//修改的方法 @Override public void updateBook(Book book) { //1 创建sql语句 String sql="update t_book set username=?,ustatus=? where user_id=?"; //2 调用方法实现 Object[] args = {book.getUsername(),book.getUstatus(),book.getUser_id()}; int update = jdbcTemplate.update(sql,args); System.out.println(update); } //删除的方法 @Override public void deleteBook(String id) { //1 创建sql语句 String sql="delete from t_book where user_id=?"; //2 调用方法实现 int update = jdbcTemplate.update(sql,id); System.out.println(update); }
  1. 测试

    public class TestBook { @Test public void testJdbcTemplate(){ ApplicationContext context= new ClassPathXmlApplicationContext("bean1.xml"); BookService bookServcie = context.getBean("bookService", BookService.class); // //添加 // Book book = new Book(); // book.setUser_id(1); // book.setUsername("张三"); // book.setUstatus("a"); // bookServcie.addBook(book); //修改 Book book = new Book(); book.setUser_id(1); book.setUsername("javaupup"); book.setUstatus("atguigu"); bookServcie.updateBook(book); // //删除 // bookServcie.deleteBook("1"); }

3|4JdbcTemplate操作数据库(查询)

1|0查询返回某个值

  1. 场景:查询表里有多少条记录,返回是某个值

  2. 使用JdbcTemplate实现查询返回某个值

    jdbcTemplate.queryForObject(sql,Integer.class);

    有两个参数:

    1. sql语句
    2. 返回类型Class
    //查询表中的记录数 @Override public int selectCount() { String sql="SELECT COUNT(*) FROM t_book"; Integer count = jdbcTemplate.queryForObject(sql, Integer.class); return count; }

1|0查询返回对象

  1. 场景:查询图书详情

  2. JdbcTemplate实现查询返回对象

    jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper(Book.class), id);

    三个参数:

    1. sql语句

    2. RowMapper,是接口,返回不同类型的数据,使用这个接口里面的实现类完成数据封装

    3. 传递sql语句中?(占位符)的值

      @Override public Book findBookInfo(int id) { String sql="SELECT * FROM t_book where user_id=?"; //调用方法 Book book = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<Book>(Book.class), id); return book; }

1|0查询返回集合

  1. 场景:查询图书列表分页

  2. 调用JdbcTemplate实现查询返回集合

    jdbcTemplate.query(sql, new BeanPropertyRowMapper(Book.class));

    三个参数:

    1. sql语句

    2. RowMapper,是接口,返回不同类型的数据,使用这个接口里面的实现类完成数据封装

    3. 传递sql语句中?(占位符)的值

      *第三个参数也可不写

      @Override public List<Book> findAllBook() { String sql = "select * from t_book"; List<Book> bookList = jdbcTemplate.query(sql, new BeanPropertyRowMapper<Book>(Book.class)); return bookList; }

3|5JdbcTemplate操作数据库(批量)

  1. 批量添加

    image-20220311202913560

    两个参数:

    1. sql语句

    2. List集合,添加多条记录数据

      //批量添加 @Override public void batchAddBook(List<Object[]> batchArgs) { String sql = "insert into t_book value(?,?,?)"; int[] ints = jdbcTemplate.batchUpdate(sql, batchArgs); System.out.println(Arrays.toString(ints)); }
  2. 批量修改

    @Override public void batchUpdateBook(List<Object[]> batchArgs) { String sql = "update t_book set username=?,ustatus=? where user_id=?"; int[] ints = jdbcTemplate.batchUpdate(sql, batchArgs); System.out.println(Arrays.toString(ints)); }

    测试

    //批量修改 List<Object[]> batchArgs = new ArrayList<>(); Object[] objects1 = {"cc00","ccc00","3"}; Object[] objects2 = {"dd00","ddd00","4"}; Object[] objects3 = {"ee00","eee00","5"}; batchArgs.add(objects1); batchArgs.add(objects2); batchArgs.add(objects3); bookServcie.batchUpdate(batchArgs);
  3. 批量删除

    @Override public void batchDeleteBook(List<Object[]> batchArgs) { String sql="delete from t_book where user_id=?"; int[] ints = jdbcTemplate.batchUpdate(sql, batchArgs); System.out.println(Arrays.toString(ints)); }

    测试

    List<Object[]> batchArgs = new ArrayList<>(); Object[] objects1 = {"3"}; Object[] objects2 = {"4"}; Object[] objects3 = {"5"}; batchArgs.add(objects1); batchArgs.add(objects2); batchArgs.add(objects3); bookServcie.batchDelete(batchArgs);

4|0事务操作

1、事务是数据库操作最基本单元,逻辑上一组操作,要么都成功,如果有一个失败则全失败

典型场景:银行转账:

  • lucy转账100元给mary
  • lucy少100,mary多100

2、事务的四个特性(ACID):

(1)原子性(atomicity):成功都成功,失败都失败

(2)一致性(consistency):操作之前和操作之后总量不变

(3)隔离性(isolation):多事务操作时,之间不会产生影响

(4)持久性(durability):提交之后表中数据就会发生变化

4|1事务操作(搭建事务操作环境)

典型场景:银行转账:

转账环境:image-20220311214628302

  1. 创建数据库,添加记录

image-20220311215159992

  1. 创建service,搭建dao,完成对象创建和注入关系

    1. service注入dao,在dao注入JdbcTemplate,在JdbcTemplate注入DateSource

    2. @Service public class UserService { //注入Dao @Autowired private UserDao userDao; }
    3. @Repository public class UserDaoImpl implements UserDao{ @Autowired private JdbcTemplate jdbcTemplate; }
    4. <!--1 开启组件扫描--> <context:component-scan base-package="com.atguigu"></context:component-scan> <!--数据库连接池--> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close"> <property name="url" value="jdbc:mysql:///user_db"/> <property name="username" value="root"/> <property name="password" value="root"/> <property name="driverClassName" value="com.mysql.jdbc.Driver"/> </bean> <!--JdbcTemplate对象--> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <!--注入DataSource--> <property name="dataSource" ref="dataSource"></property> </bean>
  2. 在dao创建两个方:多钱和少钱,在service创建转账方法

  3. 以上代码如果正常执行是没有问题的,但是如果以上代码出现异常,有问题,则应符合原子性和一致性,账户数据都不变

4|2事务管理(Spring事务管理介绍)

  1. 事务一般添加到JavaEE三层结构里的service层(业务逻辑层)
  2. 在Spring进行事务管理操作有两种方式
    1. 编程式事务管理
    2. 声明式事务管理(常用)
  3. 声明式事务管理
    1. 基于注解方式实现(常用)
    2. 基于xml配置文件方式
  4. 在Spring进行声明式事务管理,底层使用AOP原理
  5. Spring事务管理API
    1. 提供了一个接口,代表事务管理器,这个接口针对不同的框架提供不同的实现类

image-20220312110914933

4|3事务操作(注解声明式事务管理)

  1. 在Spring配置文件配置事务管理器

    <!-- 创建事务管理器--> <bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <!-- 注入数据源--> <property name="dataSource" ref="dataSource"></property> </bean>
  2. 在Spring配置文件中开启事务注解

    1. 在Spring配置文件中引入名称空间tx

      xmlns:tx="http://www.springframework.org/schema/tx"
    2. 开启事务注解

      <!-- 开启事务注解--> <tx:annotation-driven transaction-manager="dataSourceTransactionManager"></tx:annotation-driven>
  3. 在service类上面添加事务注解

    @Transactional
    1. 如果添加到类上面,则类中所有方法都添加事务
    2. 如果添加到方法上面,则只给此方法添加事务

4|4事务操作(声明式事务管理参数配置)

  1. 在service类上面添加@Transactional,在这个注解里可以配置事务相关的参数

    image-20220312113822417

    1. propagation:事务传播行为

      *多事务方法直接进行调用,这个过程中事务是如何进行管理的

      image-20220312114612365image-20220312114741323

    2. isolation:事务隔离级别

      默认值:@Transactional(propagation = Propagation.REQUIRED,isolation = Isolation.REPEATABLE_READ)

      *事务有特性称为隔离性,多事务操作之间不会产生影响,不考虑隔离性会产生很多问题

      *有三个读的问题:脏读、不可重复读、虚(幻)读

      • 脏读:一个提交事务读取到了另一个未提交事务的数据
      • 不可重复度:一个未提交事务读取到另一提交事务修改数据
      • 虚读:一个未提交事务读取到另一提交事务添加****数据

      解决:通过设置事务隔离级别就能解决读问题

      image-20220312145603002

    3. timeout:超时时间

      • 事务需要在一定时间内提交,如果不提交就会进行回滚
      • 默认值:-1(不超时)
      • 设置以秒为单位
    4. readOlay:是否只读

      • 读:查询操作;写:添加修改删除操作
      • 默认值:false(表示可以读也可以写)
      • 设置成true时只能读不能写
    5. rollbackFor:回滚

      • 设置出现哪些异常进行事务回滚
    6. noRollbackFor:不回滚

      • 设置出现哪些异常不进行事务回滚

4|5事务操作(xml声明式事务管理)

  1. 在Spring管理文件中进行配置

    1. 配置事务管理器

    2. 配置通知

    3. 配置切入点和切面

      <!--1 创建事务管理器--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <!-- 注入数据源--> <property name="dataSource" ref="dataSource"></property> </bean> <!--2 配置通知--> <tx:advice id="txadvice"> <!--配置事务参数--> <tx:attributes> <!--指定在哪种规则的方法上面添加事务--> <tx:method name="accountMoney" propagation="REQUIRES_NEW" isolation="REPEATABLE_READ" read-only="false"/> <!-- <tx:method name="account*"/>--> </tx:attributes> </tx:advice> <!--3 配置切入点和切面--> <aop:config> <!-- 切入点--> <aop:pointcut id="pt" expression="execution(* com.atguigu.spring5.service.UserService.*(..))"/> <!-- 切面--> <aop:advisor advice-ref="txadvice" pointcut-ref="pt"/> </aop:config>

4|6事务操作(完全注解声明式注解管理)

创建配置类,使用配置类代替xml配置文件

@Configuration //配置类 @ComponentScan(basePackages = "com.atguigu") //组件扫描 @EnableTransactionManagement //开启事务 public class TxConfig { //1. 创建数据库连接池 @Bean public DruidDataSource getDruidDataSource(){ DruidDataSource druidDataSource = new DruidDataSource(); druidDataSource.setDriverClassName("com.mysql.jdbc.Driver"); druidDataSource.setUrl("jdbc:mysql:///user_db"); druidDataSource.setUsername("root"); druidDataSource.setPassword("root"); return druidDataSource; } //2. 创建JdbcTemplate对象 @Bean public JdbcTemplate getJdbcTemplate(DataSource dataSource){ //到IOC容器中根据类型找到DateSource JdbcTemplate jdbcTemplate = new JdbcTemplate(); //注入DateSource jdbcTemplate.setDataSource(dataSource); return jdbcTemplate; } //3. 创建事务管理器对象 @Bean public DataSourceTransactionManager getDataSourceTransactionManager(DataSource dataSource){ DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager(); dataSourceTransactionManager.setDataSource(dataSource); return dataSourceTransactionManager; } }

5|0Spring5框架新功能

5|1整个Spring5框架基于java8,运行时兼容JDK9,许多不建议使用的类和方法在代码库中删除

5|2Spiring5框架自带了通用的日志框架

  • Spring5已经移除了Log4jConfigListener,官方建议使用Log4j2
  • Spring5框架整合Log4j2

Log4j2使用:

1. 引入jar包

image-20220312160311370

  1. 创建Log4j2.xml配置文件

    <?xml version="1.0" encoding="UTF-8"?> <!--日志级别以及优先级排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL --> <!--Configuration后面的status用于设置log4j2自身内部的信息输出,可以不设置,当设置成trace时,可以看到log4j2内部各种详细输出--> <?import org.apache.logging.log4j.core.layout.PatternLayout?> <configuration status="INFO"> <!--先定义所有的appender--> <appenders> <!--输出日志信息到控制台--> <console name="Console" target="SYSTEM_OUT"> <!--控制日志输出的格式--> <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/> </console> </appenders> <!--然后定义logger,只有定义了logger并引入的appender,appender才会生效--> <!--root:用于指定项目的根日志,如果没有单独指定Logger,则会使用root作为默认的日志输出--> <loggers> <root level="info"> <appender-ref ref="Console"/> </root> </loggers> </configuration>

5|3Spring5框架核心容器支持@Nullable注解

@Nullable注解可以使用在方法上面,属性上面,参数上面,表示方法返回可以为空,属性值可以为空,参数值可以为空

5|4Spring5核心容器支持函数风格GenericApplicationContext(lambda表达式)

public void testGenericApplicationContext(){ //创建GenericApplicationContext对象 GenericApplicationContext context = new GenericApplicationContext(); //调用context方法注册对象 context.refresh(); context.registerBean("user1",User.class,()-> new User()); //获取Spring注册的对象 // User user = (User) context.getBean("com.atguigu.spring5.test.User"); User user = (User) context.getBean("user1"); System.out.println(user); }

5|5Spring5支持整合JUnit5

  1. 整合JUnit4

    1. 引入Spring测试相关的依赖

      image-20220312171459491

    2. @RunWith(SpringJUnit4ClassRunner.class) //指定单元测试框架的版本 @ContextConfiguration("classpath:bean1.xml") //加载配置文件 public class JTest4 { @Autowired private UserService userService; @Test public void test1(){ userService.accountMoney(); } }
  2. 整合JUnit5

    1. 引入JUnit5的jar包

    image-20220312180019155

    1. 创建测试类,使用注解完成

      package com.atguigu.spring5.test; import com.atguigu.spring5.service.UserService; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @ExtendWith(SpringExtension.class) @ContextConfiguration("classpath:bean1.xml") //加载配置文件 public class JTest5 { @Autowired private UserService userService; @Test public void test1(){ userService.accountMoney(); } }

复合注解@SpringJUnitConfig(locations = "classpath:bean1.xml")

//@ExtendWith(SpringExtension.class) //@ContextConfiguration("classpath:bean1.xml") //加载配置文件 @SpringJUnitConfig(locations = "classpath:bean1.xml") public class JTest5 { @Autowired private UserService userService; @Test public void test1(){ userService.accountMoney(); } }

5|6SpringWebFlux

image-20220312181857297

webflux介绍

  1. 基本概念

    • Spring5添加的新的模块,用于web开发的,功能与SpringMVC相似,webflux使用一种响应式编程出现的框架

    • 使用传统的web框架,比如SpringMVC,这些基于Servlet容器,webflux是一种异步非阻塞的框架,异步非阻塞的框架在servlet3.1之后才支持,核心是基于Reactor的相关API实现的

  2. 响应式编程

  3. webflux执行流程和核心API

  4. Springwebflux(基于注解编程模型)

  5. Springwebflux(基于函数式编程模型)


__EOF__

本文作者YK Han
本文链接https://www.cnblogs.com/YKHan/p/16202135.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   米斯特寒  阅读(90)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 【杭电多校比赛记录】2025“钉耙编程”中国大学生算法设计春季联赛(1)
点击右上角即可分享
微信分享提示