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

Spring通关笔记:从“Hello Bean”到循环依赖的奇幻漂流

        在学习之前,容我先讲两句,本博客所有内容全部由本人自主完成。我也只是一个学习编程之路上(某站狂神),喜欢把所学的做成笔记或者博客,可以一起相互讨论,若有错误请在评论区指导一下

        此外,你可能还需要这些文件

本文所有代码整合:点击跳转下载

本人个人博客网站:了解笨猫

一、 Spring介绍

                Spring是一个开源的免费的框架

                Spring是一个轻量级的、非入侵式的框架

                控制反转IOC、面向切面AOP

                支持事务的处理,对框架整合的支持

        总结一句话:Spring就是一个轻量级的控制反转(IOC)和面向切面编程(AOP)的框架

一般需要导入的spring依赖

    <dependencies><dependency><groupId>org.springframework</groupId><artifactId>spring-webmvc</artifactId><version>5.2.0.RELEASE</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-jdbc</artifactId><version>5.2.0.RELEASE</version></dependency></dependencies>

 二、Spring的组成及扩展

扩展

弊端:人称“配置地狱”,配置十分繁琐,所以后面需要学SpringBoot快速开发框架才解放

 学习SpringBoot的前提,需要完全掌握Spring和SpringMVC


 三、IOC理论推导

        以前写代码就是 UserDao接口、UserDaoImpl实现类、UserService业务接口、UserServiceImpl业务实现类,dao里面有三个实现类,而service只有一个业务类,需要测试哪个实现类都需要去service里更改源代码显得特别繁琐,用户的需求可能会影响我们原来的代码,我们需要根据用户的需求修改代码。通过一个set接口实现注入,这就是控制反转的底层思想,程序员不用再管理对象的创建,系统的耦合性大大降低,可以更在专注业务的实现上——不碰代码,动态调用

         IOC的原型——可以在service业务实现类不直接写死了,留个心眼。

//写死-只调用一个dao实现类,调用其他的就需要改代码
public class UserServiceImpl implements UserService {private UserDao userDao = new UserDaoOracleImpl();@Overridepublic void getUser() {userDao.getUser();}
}//留心眼-通过set方法动态调用
public class UserServiceImpl implements UserService {private UserDao userDao;@Overridepublic void setUserDao(UserDao userDao) {this.userDao = userDao;}@Overridepublic void getUser() {userDao.getUser();}
}

 四、IOC本质

        控制反转IOC(lnversion of control),是一种设计思想,DI(依赖注入)是实现IOC的一种方法,也有人认为D!只是IOC的另一种说法。没有IOC的程序中,我们使用面向对象编程,对象的创建与对象间的依赖关系完全硬编码在程序中,对象的创建由程序自己控制,控制反转后将对象的创建转移给第三方,个人认为所谓控制反转就是:获得依赖对象的方式反转了
        采用XML方式配置Bean的时候,Bean的定义信息是和实现分离的,而采用注解的方式可以把两者合为一体,Bean的定义信息直接以注解的形式定义在实现类中,从而达到了零配置的目的。
        控制反转是一种通过描述(XML或注解)并通过第三方去生产或获取特定对象的方式。在Spring中实现控制反转的是IOC容器,其实现方法是依赖注入(DependencyInjection,Dl)。


五、第一个Spring程序-HelloSpring

        xml文件配置-来源于Spring官网

        容器实例化-来源于Spring官网

        以下是个小实例 ,先写一个类,通过xml文件配置,最后实际操作如何调用

package com.benmao.pojo;public class HelloSpring {private String name;public String getName() {return name;}public void setName(String name) {this.name = name;}public void show(){System.out.println("Hello"+name);}
}

必须得有set方法,核心就是依赖注入 

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beanshttps://www.springframework.org/schema/beans/spring-beans.xsd"><!--使用Spring来创建对象,在Spring都称为Bean--><bean id="hello" class="com.benmao.pojo.HelloSpring"><property name="name" value="笨猫"/></bean></beans>

        以前的写法就是:类 变量名 = new 类();   对Spring来说: id代表变量名,class代表new的对象,property相当于给对象的属性设置一个值

public class MyTest {public static void main(String[] args) {//获取Spring的上下午对象ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");//现在对象都给Spring管理了,要使用谁就把谁调出来HelloSpring helloSpring = (HelloSpring) context.getBean("hello");helloSpring.show();}
}

        对象是由谁创建的?对象是由spring创建的。对象的属性是这么设置的?对象的属性是由spring容器设置的。这个过程就叫控制反转

        控制:传统应用程序的对象是由程序本身控制创建的,使用Spring的对象是由Spring来创建的

        反转:程序本身不创建对象,而变成被动的接收对象

        IOC就是一种编程思想,由主动的编程变成被动的接收。所谓IOC,一句话搞定:对象由Spring来创建、管理、装配


六、IOC创建对象的方式

1. 默认使用无参构造对象

2. 如果需要使用有参构造对象,一般有以下三种方法

    <!-- 第一种:下标赋值--><bean id="user" class="com.benmao.pojo.User"><constructor-arg index="0" value="笨猫"/></bean>
    <!-- 第二种:类型赋值--><bean id="user" class="com.benmao.pojo.User"><constructor-arg type="java.lang.String" value="笨猫"/></bean>
    <!-- 第三种:直接通过参数名--><bean id="user" class="com.benmao.pojo.User"><constructor-arg name="name" value="笨猫"/></bean>

        注意:在beans.xml配置文件加载的时候,容器中管理的对象就已经初始化好了(所有的bean所对应类的无参构造方法都会被调用),你想要哪个对象就去getBean


七、Spring配置说明

        alias

                这个标签可以给bean取个别名,比如有个bean的id为user

<alias name="user" alias="qojjdqodoqjqdjjqodj"/>

        import

        这个import,一般用于团队开发使用,他可以将多个配置文件,导入合并到一个ApplicationContext.xml去,可以在ApplicationContext.xml写下。

<import resource="beans.xml1"/>小王负责

<import resource="beans.xml2"/>小吴负责

<import resource="beans.xml"3/>小朱负责

        Bean的配置

        id: bean 的唯一标识符,也就是相当于我们学的对象名

        class: bean对象所对应的全限定名——包名+类名

        name: 也是别名,但比alias更好用,可以同时取多个别名,能够有随意的分隔符(逗号,空格,分号)

<bean id="userT" class="com.benmao.pojo.UserT" name="userT1, userT2; userT3 userT4"> </bean>


八、DI依赖注入环境

        一共有三种依赖注入方式,第一种就是构造器注入,前文已讲,第二种注入方式——Set方式注入  

        依赖注入:依赖——bean对象的创建依赖于Spring容器;注入:bean对象中所有的属性,由容器来注入

        Set方式注入

        以下是一个例子,写了字符串、数组、各类集合以及空值的注入方式。准备文件Address.java、Student.java、beans.xml、测试类。

package com.benmao.pojo;public class Address {private String address;public String getAddress() {return address;}public void setAddress(String address) {this.address = address;}@Overridepublic String toString() {return "Address{" +"address='" + address + '\'' +'}';}
}
package com.benmao.pojo;import java.awt.print.Book;
import java.util.*;public class Student {private String name;private Address address;private String[] books;private List<String> hobbies;private Map<String, String> card;private Set<String> games;private String kong;private Properties info;...get、set和toString方法...
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beanshttps://www.springframework.org/schema/beans/spring-beans.xsd"><bean id="address" class="com.benmao.pojo.Address"/><bean id="student" class="com.benmao.pojo.Student"><!--普通值注入——value--><property name="name" value="笨猫"/><!--引用注入——bean注入,ref--><property name="address" ref="address"/><!--数组注入——array--><property name="books"><array><value>红楼梦</value><value>西游记</value><value>水浒传</value><value>三国演义</value></array></property><!--list集合注入——list--><property name="hobbies"><list><value>敲代码</value><value>旅游</value><value>睡觉</value></list></property><!--map集合注入——map--><property name="card"><map><entry key="身份证" value="360124"/><entry key="银行卡" value="111111"/></map></property><!--Set集合注入注入——set--><property name="games"><set><value>王者</value><value>金铲铲</value><value>部落</value></set></property><!--空值注入--><property name="kong"><null/></property><!--Properties注入--><property name="info"><props><prop key="学号">20220819</prop><prop key="性别">男</prop><prop key="username">Benmao</prop><prop key="password">123456</prop></props></property></bean></beans>
public class MyTest {public static void main(String[] args) {ApplicationContext contest = new ClassPathXmlApplicationContext("beans.xml");Student student = (Student) contest.getBean("student");System.out.println(student);}
}

         CP——c命名和p命名注入

        要使用这两个注入方式需要在配置文件写入对应标签的代码

        p标签代码:xmlns:p="http://www.springframework.org/schema/p"

        看以下代码,可以发现p注入方式<property name=""></property>标签的简化,可以直接注入属性

<bean id="user" class="com.benmao.pojo.User" p:name="笨猫" p:age="18"/>
    //测试一下@Testpublic void test2(){ApplicationContext contest = new ClassPathXmlApplicationContext("userbeans.xml");User user = contest.getBean("user", User.class);System.out.println(user);}

        c标签代码:xmlns:c="http://www.springframework.org/schema/c"

        使用c注入方式必须得有有参构造器,在User类里需要补一个有参构造器

    public User(String name, int age) {this.name = name;this.age = age;}

         看以下代码,可以发现c注入方式<constructor-arg name="" value=""/>标签的简化,通过有参构造注入属性

<bean id="user2" class="com.benmao.pojo.User" c:name="笨猫1" c:age="19" />

九、Bean的作用域

        Spring官网给出了好几个作用域

        singleton

                单例模式,Spring默认机制,全局只共享一个对象

    //输出的结果为true@Testpublic void test2(){ApplicationContext contest = new ClassPathXmlApplicationContext("userbeans.xml");User user = contest.getBean("user", User.class);User user2 = contest.getBean("user", User.class);System.out.println(user==user2);}

         想要把bean变为单例模式,也可以在bean标签显式的添加singleton

<bean id="user3" class="com.benmao.pojo.User" scope="singleton"/>

         prototype

                原型模式,每一次从容器中getBean都会产生一个新对象

<bean id="user" class="com.benmao.pojo.User" scope="prototype"/>
    //输出的结果为false@Testpublic void test2(){ApplicationContext contest = new ClassPathXmlApplicationContext("userbeans.xml");User user = contest.getBean("user", User.class);User user2 = contest.getBean("user", User.class);System.out.println(user==user2);}

        扩展: request、session、application、websocket, 这些只能在web开发中使用到


十、Bean的自动装配

        自动装配是Spring满足bean依赖的一种方式,Spring会在上下文中自动寻找,并自动给bean装配属性(前文都是需要自己手动装配)。在Spring中,有三种装配的方式

        1. 在xml中显示的配置(前文就是xml手动配置)

        2. 在Java中显示配置

        3. 隐式的自动装配【重要】

        测试:一个人有两只宠物

//猫
package com.benmao.pojo;public class Cat {public void shout(){System.out.println("cat shout");}
}//狗
package com.benmao.pojo;public class Dog {public void shout(){System.out.println("dog shout");}
}//人
package com.benmao.pojo;public class Person {private Cat cat;private Dog dog;private String name;...get、set、toString..
}
    <bean id="cat" class="com.benmao.pojo.Cat"/><bean id="dog" class="com.benmao.pojo.Dog"/><bean id="person" class="com.benmao.pojo.Person"><property name="name" value="笨猫"/><property name="cat" ref="cat"/><property name="dog" ref="dog"/></bean>
    @Testpublic void test1() {ApplicationContext contest = new ClassPathXmlApplicationContext("beans.xml");Person person = contest.getBean("person", Person.class);person.getCat().shout();person.getDog().shout();}

         byName自动装配

        会自动在容器中上下文中查找,和自己对象set方法后面对应的值对应的 bean的 id。也是就是说person中set有对应的cat和dog,beans中也有bean cat和dog,就会自动装配到person里

        也就是说,比如dog不能是dog1111,否则报错

    <bean id="person" class="com.benmao.pojo.Person" autowire="byName"><property name="name" value="笨猫"/></bean>

        byType自动装配

        会自动在容器中上下文中查找,和自己对象属性相同的 bean,所以id都可以不用写。也是就是说person中set有对应的cat和dog,beans中也有bean cat和dog,就会自动装配到person里

    <bean id="cat" class="com.benmao.pojo.Cat"/><bean id="dog111" class="com.benmao.pojo.Dog"/>只能有一个对应类型的bean
<!-- <bean id="dog222" class="com.benmao.pojo.Dog"/> --><bean id="person" class="com.benmao.pojo.Person" autowire="byType"><property name="name" value="笨猫"/></bean>

        但是这种装配方式必须报错只有一个唯一类型的bean

       注解实现自动装配

        使用注解版本:jdk1.5以上、Spring2.5以上

        要使用注解,就需要context导入约束配置注解的支持<context:annotation-config/>

    配置约束
<?xml version="1.0" encoding="UTF-8"?>
<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"xsi:schemaLocation="http://www.springframework.org/schema/beanshttps://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttps://www.springframework.org/schema/context/spring-context.xsd"><context:annotation-config/></beans>

        @Autowired

        直接在属性上使用即可,也可以在set方法上使用。其实可以都不需要写person里的set方法,因为它是通过反射来注入的

        @AutorwiAured优先按类型查找,找不到报错,如果找到多个就按名字

   在Person类中,对应属性上写上注解@Autowiredprivate Cat cat;@Autowiredprivate Dog dog;private String name;
    写上这一行,开启注解的支持<context:annotation-config/><bean id="cat" class="com.benmao.pojo.Cat"/><bean id="dog" class="com.benmao.pojo.Dog"/><bean id="person" class="com.benmao.pojo.Person"/>

        科普1:@Nullable代表值可以为空,以防报错空指针异常

    public Person(@Nullable String name) {this.name = name;}

        如果显示定义了@AutorwiAured的required属性为false,说明这个对象可以为null, 否则就不允许

    @Autowired (required=false)private Cat cat;@Autowiredprivate Dog dog;private String name;

         科普2:如果bean的id为dog222, 甚至有很多不一样的dog就会报错, 有一个注解可以和@Autowired 搭配使用去专门指定一个bean, 这个注解就是@Qualifier

    @Autowired (required=false)@Qualifier(value = "dog222")private Cat cat;@Autowiredprivate Dog dog;private String name;

        科普3:用@Resource,也能做到自动装配,功能强大但效率比较低,这里就不介绍这个注解,因为不是很重要,@AutorwiAured的Spring生态更通用

        区别@AutorwiAured默认按 类型(byType)注入,如果存在多个同类型 Bean,需配合 @Qualifier指定名称。 @Resource默认按 ​​名称(byName)注入,如果未指定名称,则回退到按类型注入。


十一、Spring注解开发

        在Spring4之后,要使用注解开发,必须要保证aop的包导入了。使用注解需要导入context约束,增加注解的支持(上文已说明)

        @Component注解

        组件,放在类上,说明这个类被Spring管理了,代表的就是bean

        通过component-scan扫描指定的包,可以直接写在实体类中注解说明,不懂看以下代码

 <!-- 指定要扫描的包,这个包下的注解就会生效--><context:component-scan base-package="com.benmao.pojo"/><context:annotation-config/><!--不需要写这行代码了--><bean id="user" class="com.benmao.pojo.User"/>
//等价于 <bean id="user" class="com.benmao.pojo.User"/>
@Componentpublic class User {private String name = "Benmao";
}

        注意:这里jdk版本过高可能会导致不兼容而报错,我这里报错之后在pom.xml文件修改为jdk11,如果还是不行就得在idea - project structure去修改jdk版本

        接下来,如果要注解属性的话,在对于的属性上方使用@Value注解

@Componentpublic class User {//等价于 <property name="name" value="笨猫"/>@Value("笨猫")public String name;
}

        同样地,若该实体类包含 set 方法,在 set 方法上添加注解的方式也是相同的 

@Componentpublic class User {public String name;@Value("笨猫")public void setName(String name) {this.name = name;}
}

         @Component衍生注解

        @Component 有几个衍生注解,在web开发中,会按照mvc三层架构分层(dao层、service层、controller层)

        dao——@Repository

        service——@Service

        cotroller——@Controller

        这四个注解都是一样的,都是代码将某个类注册到Spring容器中,装配为Bean

xml: 更加万能,适用于任何场合,维护简单方便

注解:不是自己的类使用不了,维护相对复杂

实际开发经验中:xml用来管理bean,注解只负责完成属性的注入,使用的过程中,只需要注意一个问题:必须让注解生效,开启注解的支持(上文已讲这么让注解生效)


十二、使用JavaConfig实现配置

        JavaConfig是Spring的一个子项目,Spring4 之后的核心功能

        这个就是完全使用Java的方式配置Spring,不需要配置文件,说白了还是注解。我们现在要完全不使用Spring的xml配置了,全权交给Java来做

        先来个实体类User,给name注解值为“笨猫”

public class User {private String name;public String getName() {return name;}@Value("笨猫")public void setName(String name) {this.name = name;}@Overridepublic String toString() {return "User{" +"name='" + name + '\'' +'}';}
}

        接下来是写一个Config配置类,这个配置类有一些注解,其中@Configuration它的底层逻辑注解还是@Component

        之前是把bean写在xml配置文件中创建bean对象,在此配置文件中,使用@Bean,具体解释请阅读代码

        注意:这里使用@ComponentScan("com.benmao.pojo")注解时可能出现的类文件版本不兼容错误。这个问题与Spring框架版本和JDK版本的兼容性有关。

package com.benmao.config;//把BenmaoConfig这个类注册到容器中,因为它也是一个Component
//@Configuration代表这是一个配置类,就和beans.xml一样
@Configuration//把多个config配置类整合一起
@Import(BenmaoConfig2.class)//显式的指定一个包
@ComponentScan("com.benmao.pojo")
public class BenmaoConfig {//注册一个bean,就相当于一个bean标签,这个方法的名字,就是bena标签id的属性、这个方法的返回值,就是bean标签的class属性@Beanpublic User getUser() {return new User();//就是要返回要注入到bean的对象}
}

         最后就是测试类,和以往不同,这是new一个AnnotationConfigApplicationContext类,参数就是所对应的配置文件

public class MyTest {@Testpublic void test() {//如果完全使用了配置方式去做,我们就只能通过 AnnotationConfig 上下文来获取容器,通过配置类的class对象来加载ApplicationContext context = new AnnotationConfigApplicationContext(BenmaoConfig.class);User user = context.getBean("getUser", User.class);System.out.println(user.getName());}
}

小结:这种纯Java的配置方式,在SpringBoot中随处可见! 


十二、代理模式

        类似于中介、或者演唱会黄牛党,来帮你做一些事情。学习Spring AOP,必须要精通代理模式,否则你搞不懂AOP这个Spring底层实现

        对于面试来说,IOC不一定问,但是AOP和SpringMVC是必问的

        静态代理

        角色分析:

                抽象角色:一般使用接口或者抽象类来解决

                真实角色:被代理的角色

                代理角色:代理真实角色,代理真实角色后,可以做一些附属操作

                客户:访问代理对象的人

        为什么需要这种设计模式?因为在企业实际开发中,万一修改原来的代码可能导致程序崩溃,这个时候可以创建一个代理类来继承,不仅实现了原有类的作用,而且可以在此基础上自己添加其他的功能(收房租、签合同)

        抽象角色类——Rent

package com.benmao.demo01;// 租房
public interface Rent {public void rent();
}

         真实角色类——Host

package com.benmao.demo01;// 房东
public class Host implements Rent {public void rent(){System.out.println("房东要出租房子");}
}

        代理角色类——Rroxy 

package com.benmao.demo01;// 中介
public class Proxy implements Rent{private  Host host;public Proxy(){}public Proxy(Host host) {this.host = host;}public void rent(){seetHouse();host.rent();contract();fee();}//看房public void seetHouse() {System.out.println("中介带你看房");}//收中介费public void fee(){System.out.printf("收中介费");}//合同public void contract(){System.out.println("签合同");}
}

        客户类——测试类 

package com.benmao.demo01;// 需要租房子的客户
public class Client {public static void main(String[] args) {//房东要租房子Host host = new Host();host.rent();System.out.println("----------------------------");//代理,中介帮房东租房子,但是代理角色一般会有一些附属操作Proxy proxy = new Proxy(host);//不用面对房东,直接找房东租房proxy.rent();}
}

代理模式优点

        可以使真实角色的操作更加纯粹,不用关注一些公共的业务

        公共也就交给代理角色,实现了业务的分工

        公共业务发生扩展的时候,方便集中管理

代理模式缺点

        一个真实角色会产生一个代理角色,如果有很多客户要租房,代码量翻倍,开发效率会变低

        动态代理 

        有什么角色都是一样的,只是动态代理就是解决静态代理的缺点(上文),主要是通过反射来实现。动态代理的代理类是动态生成的,不是我们直接写好的

        动态代理分为两大类:①基于接口的动态代理  ②基于类的动态代理   

        基于接口——JDK动态代理(本文通过这个来讲解)

        基于类——cglib

        java字节码实现:javassist

        需要了解两个类:Proxy-代理, InvocationHandler-调用处理程序

        Proxy 和 InvocationHandler 是 Java 中实现 ​​动态代理​​ 的两个核心组件,它们的作用是“在不修改原始类代码的前提下,动态增强其功能

        🎭 ​​Proxy(代理)—— 替身演员​

        想象你是一个明星/房东(​​真实对象​​),但不想亲自处理琐事(如签合同、收礼物)。于是你雇佣了一个替身演员(​​代理对象​​)来替你露面

        ​​作用​​:
        Proxy 是一个“代工厂”,专门生产替身演员/中介。它根据你指定的“角色要求”(接口),动态创建一个新对象(代理对象),这个对象拥有和明星一样的外在能力(方法),但实际执行的是替身的剧本(增强逻辑)

        使用方式:
        调用 Proxy.newProxyInstance() 创建代理对象,需要提供三个参数:

                ​​ClassLoader​​:加载代理类的工具(用明星的“化妆师”加载替身);​​

                接口列表​​:声明替身能模仿明星的哪些能力(如唱歌、演戏);

​​                InvocationHandler​​:替身的“剧本”(定义替身如何行动) 

        📜 ​​InvocationHandler(调用处理程序)—— 替身的剧本​​

        替身演员虽然长得像明星,但具体怎么表演需要剧本指导。InvocationHandler 就是这个“剧本”,规定替身遇到粉丝请求时如何应对。

         ​​作用​​:

        当粉丝(​​客户端​​)要求替身唱歌(​​调用方法​​)时,替身不会直接唱,而是翻看剧本(​​invoke()方法​​),按剧本的指示行动(如先收钱再唱歌)。

        ​​核心方法​​:invoke() ​​参数​​: proxy:替身自己(代理对象); method:粉丝要求的方法(如 sing()); args:方法的参数(如“唱《青花瓷》”)

        工作流程​​: 收粉丝的钱(​​前置增强​​:如日志记录); 通知明星本人唱歌(​​调用真实对象的方法​​:method.invoke(真实对象, args)); 帮明星收礼物(​​后置增强​​:如数据统计 )

           抽象角色类——Rent

package com.benmao.demo01;// 租房
public interface Rent {public void rent();
}

         真实角色类——Host

package com.benmao.demo01;// 房东
public class Host implements Rent {public void rent(){System.out.println("房东要出租房子");}
}

         动态代理类

package com.benmao.demo03;import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;//这个类用来自动生成代理类
public class ProxyInvocationHandler implements InvocationHandler {// 被代理的接口private Rent rent;public void setRent(Rent rent) {this.rent = rent;}// 生成得到代理类// 参数:① ClassLoader loader, ② Class<?>[] interfaces 需要被代理的类, ③ InvocationHandler h 本身处理类public Object getProxy() {return Proxy.newProxyInstance(this.getClass().getClassLoader(), rent.getClass().getInterfaces(), this );}// 处理代理实例,并返回结果@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {seeHouse();fee();// 动态代理的机制——反射Object res = method.invoke(rent, args);return res;}// 添加其他操作public void seeHouse() {System.out.println("中介带看房子");}public void fee(){System.out.println("收中介费");}
}

        客户类——测试类

public class Client {public static void main(String[] args) {// 真实角色Host host = new Host();// 代理角色ProxyInvocationHandler pih = new ProxyInvocationHandler();// 通过调用程序处理角色来处理要调用的接口对象pih.setRent(host);Rent proxy = (Rent) pih.getProxy();//这个类是动态生成的,并没有写,代理类的返回值只能是接口抽象的类,不能是具体的类proxy.rent();}
}

        如果动态代理类的代码看不懂,也可以直接把它当作一个工具类来使用,以下是通用代码可供参考

//这个类用来自动生成代理类
public class ProxyInvocationHandler implements InvocationHandler {// 被代理的接口private Object target;public void setTarget(Object target) {this.target = target;}// 生成得到代理类// 参数:① ClassLoader loader, ② Class<?>[] interfaces 需要被代理的类, ③ InvocationHandler h 本身处理类public Object getProxy() {return Proxy.newProxyInstance(this.getClass().getClassLoader(), target.getClass().getInterfaces(), this );}// 处理代理实例,并返回结果@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {log(method.getName());// 动态代理的机制——反射Object res = method.invoke(target, args);return res;}// 添加其他操作// ....public void log(String msg) {System.out.println("执行了" + msg+"方法");}
}

 十三、AOP

        AOP的核心价值在于​​解耦非业务功能与核心业务逻辑​​。它通过切面模块化通用功能(如日志、事务),再通过动态代理在运行时将功能注入目标位置,实现“​​不修改源码即可增强功能​​”。这显著提升了代码的可维护性和复用性,是Spring等框架的基石技术

        对于AOP来说,实在理解不了里面的专有名词也无所谓,个人认为会使用即可,在使用的基础上再选择去理解。其实AOP与代理的底层逻辑是类似的,有着“把代码切进去的”思想

        要使用AOP,需要导入的依赖

<dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId><version>1.9.4</version>
</dependency>

        AOP实现方式一:原生Spring API接口

        我们已经导入了所需要的依赖,接下来通过具体代码来讲解如何使用。准备一个用户服务类和它的实现类

//接口
public interface UserService {public void add();public void update();public void delete();public void query();
}//实现类
public class UserServiceImpl implements UserService {@Overridepublic void add() {System.out.println("增加一个用户");}@Overridepublic void update() {System.out.println("修改一个用户");}@Overridepublic void delete() {System.out.println("删除一个用户");}@Overridepublic void query() {System.out.println("查询一个用户");}
}

        接下来,我们可以增加一些日志类,一个在方法之前运行的类Log,一个在方法之后运行的类AfterLog(并且AfterLog有方法的返回值)

public class Log  implements MethodBeforeAdvice {//method:要执行的目标对象的方法//args: 参数//target: 目标对象@Overridepublic void before(Method method, Object[] args, Object target) throws Throwable {System.out.println(target.getClass().getName()+"的"+method.getName()+"被执行了");}
}public class AfterLog implements AfterReturningAdvice {//returnValue: 方法返回值,如果这个方法没有返回值就返回null@Overridepublic void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {System.out.println("执行了"+method.getName()+"返回结果为:"+returnValue);}
}

        接下来就是在xml配置文件配置AOP的约束了,这点很重要,不然使用不了AOP标签。首先需要知道对哪项功能切入,这里是对UserServiceImpl类中所有的方法切入

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:aop="http://www.springframework.org/schema/aop"xsi:schemaLocation="http://www.springframework.org/schema/beanshttps://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/aophttps://www.springframework.org/schema/aop/spring-aop.xsd"><!--注册bean--><bean id="userService" class="com.benmao.service.UserServiceImpl"/><bean id="log" class="com.benmao.log.Log"/><bean id="aferLog" class="com.benmao.log.AfterLog"/><!--方式一:使用原生Spring API接口--><!--配置AOP: 需要导入aop的约束--><aop:config><!--切入点--><!--expression:表达式      execution():要执行的位置:修饰词、返回值、类名、方法名--><aop:pointcut id="pointcut" expression="execution(* com.benmao.service.UserServiceImpl.*(..))"/><!--执行环绕增强--><aop:advisor advice-ref="log" pointcut-ref="pointcut"/><aop:advisor advice-ref="aferLog" pointcut-ref="pointcut"/></aop:config>
</beans>

        MyTest测试类 

public class MyTest {public static void main(String[] args) {ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");//动态代理的是接口,这里要用多态的写法UserService userService = context.getBean("userService", UserService.class);userService.add();}
}

AOP实现方式二:自定义类来实现AOP

        需要自己写一个自定义类,把这个类里的方法顺序插到别的类去,请看代码解释

//随便创建一个类,定义两个方法
package com.benmao.diy;public class DiyPointCut {public void before() {System.out.println("before");}public void after() {System.out.println("after");}
}
    <!--    AOP实现方式二:自定义来实现AOP--><bean id="diy" class="com.benmao.diy.DiyPointCut"/>+<aop:config><!-- 自定义切面:ref就是引用自定义的类--><aop:aspect ref="diy"><!-- 切入点--><aop:pointcut id="point" expression="execution(* com.benmao.service.UserServiceImpl.*(..))"/><!-- 通知--><aop:before method="before" pointcut-ref="point"/><aop:after method="after" pointcut-ref="point"/></aop:aspect></aop:config>

AOP实现方法三:注解实现AOP

        只是把xml文件中的标签翻译成注解,简单明了,在一个类中定义,把这个类变成切面

        首先需要在xml配置文件创建这个切面类bean

    <!-- AOP实现方式三:注解实现AOP--><bean id="diy" class="com.benmao.diy.AnnotationPointCut"/><!-- 开启注解支持: 默认是jdk实现,也可以使用cglib--><aop:aspectj-autoproxy/><!-- 使用jdk--><aop:aspectj-autoproxy proxy-target-class="false"/><!-- 使用cglib--><aop:aspectj-autoproxy proxy-target-class="true"/>

        然后可以在这个切面类使用注解了,具体看代码解释

package com.benmao.diy;// 使用注解方式实现AOPimport org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;@Aspect
//这个注解相当于 <aop:aspect ref="这个类">,标注这个类是一个切面
public class AnnotationPointCut {@Before("execution(* com.benmao.service.UserServiceImpl.*(..))")public void before() {System.out.println("before method");}@After("execution(* com.benmao.service.UserServiceImpl.*(..))")public void after() {System.out.println("after method");}//在环绕增强中,可以给定一个参数,代表我们要获取处理的切入点@Around("execution(* com.benmao.service.UserServiceImpl.*(..))")public void around(ProceedingJoinPoint joinPoint) throws Throwable {System.out.println("环绕前");//执行方法Object proceed = joinPoint.proceed();System.out.println("proceed: " + proceed);//获取方法add()的返回结果System.out.println("环绕后");//        Signature signature = joinPoint.getSignature();//获取签名
//        System.out.println("signature: " + signature);}
}

运行结果

环绕前
before method
增加一个用户
proceed: 一个返回结果
环绕后
after method
 

        其中"一个返回结果"是add方法,具体看代码或者上文的UserServiceImpl类


十四、整合Mybatis

        Mybatis的一般使用

        回顾一下Mybatis的用法

        步骤一共就三步:导入相关的jar包——编写配置文件——测试

        1. 导包

        一般需要导入junit、mybatis、mysql数据库、spring相关的、aop、mybatis-spring(新包,mybatis和spring整合的包),具体可以直接用下面写好的依赖,直接导入即可

    <dependencies><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>3.8.1</version><scope>test</scope></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.6</version></dependency><dependency><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId><version>3.5.9</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-webmvc</artifactId><version>5.3.18</version></dependency><!--Spring操作数据库的话,还需要一个Spring-jdbc--><dependency><groupId>org.springframework</groupId><artifactId>spring-jdbc</artifactId><version>5.2.5.RELEASE</version></dependency><dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId><version>1.9.9.1</version></dependency><dependency><groupId>org.mybatis</groupId><artifactId>mybatis-spring</artifactId><version>2.0.6</version></dependency></dependencies>

        2. 编写配置文件

        首先先回忆一下mybatis框架的用法

        第一步:编写实体类

                在pojo下随便写个实体类User

package com.benmao.pojo;import lombok.Data;@Data
public class User {private int user_id;private String username;private String password;
}

        第二步:编写核心配置文件

                首先编写pom.xml文件,注意需要选择合适的版本以免冲突,这里可以直接复制使用。

需要在此文件中写maven静态资源处理问题

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.denlu</groupId><artifactId>Spring-study</artifactId><version>1.0-SNAPSHOT</version></parent><artifactId>spring-10-mybatis</artifactId><packaging>war</packaging><name>spring-10-mybatis Maven Webapp</name><url>http://maven.apache.org</url><properties><!-- 显式覆盖父POM的JDK版本(如需用JDK11) --><maven.compiler.source>11</maven.compiler.source><maven.compiler.target>11</maven.compiler.target></properties><dependencies><!-- 数据库相关 --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.33</version> <!-- 支持JDK11的最新稳定版 --></dependency><!-- MyBatis --><dependency><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId><version>3.5.13</version></dependency><dependency><groupId>org.mybatis</groupId><artifactId>mybatis-spring</artifactId><version>2.1.1</version> <!-- 匹配MyBatis 3.5.x --></dependency><!-- Spring --><dependency><groupId>org.springframework</groupId><artifactId>spring-webmvc</artifactId><version>5.3.29</version> <!-- 统一版本 --></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-jdbc</artifactId><version>5.3.29</version> <!-- 与webmvc一致 --></dependency><dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId><version>1.9.19</version> <!-- 最新稳定版 --></dependency><!-- Lombok(适配JDK11) --><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.30</version><scope>provided</scope></dependency><!-- 测试 --><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.13.2</version><scope>test</scope></dependency></dependencies><!--maven静态资源过滤问题 --><build><resources><resource><!-- 这个路径就是之后的Mapper.xml文件需要被找到 --><directory>src/main/java</directory><includes><include>**/*.xml</include></includes><filtering>true</filtering></resource></resources></build>
</project>

                然后在resource目录下创建mybatis-config.xml配置文件

                代码中取别名会自定义把类的名词全部小写当作别名,就是把User类别名成user,之后在Mapper.xml中不需要写完整路径指定User类

                然后需要注册mapper接口指定UserMapper类

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configurationPUBLIC "-//mybatis.org//DTD Config 3.0//EN""http://mybatis.org/dtd/mybatis-3-config.dtd"><configuration><!-- 别名 --><typeAliases><package name="com.benmao.pojo"/></typeAliases><!-- 数据库环境配置 --><environments default="development"><environment id="development"><!-- 事务管理器 --><transactionManager type="JDBC"/><!-- 数据源配置(使用连接池) --><dataSource type="POOLED"><property name="driver" value="com.mysql.cj.jdbc.Driver"/><property name="url" value="jdbc:mysql://localhost:3306/test?useSSL=false&amp;serverTimezone=UTC"/><property name="username" value="root"/><property name="password" value="wjc123456"/><!-- 连接池可选配置 --><property name="poolMaximumActiveConnections" value="10"/><property name="poolMaximumIdleConnections" value="5"/></dataSource></environment></environments><mappers><mapper class="com.benmao.mapper.UserMapper"/></mappers>
</configuration>

         第三步:编写mapper接口

                在mapper下创建userMapper类

package com.benmao.mapper;import com.benmao.pojo.User;import java.util.List;public interface UserMapper {public List<User> selectUsers();
}

        第四步:编写Mapper.xml

                同样地,在mapper下写出userMapper对应的userMapper.xml文件,其中id要和类中方法名称一模一样(本人报错找了很久原因...),resultType代表的是返回值,user是User类的别名,你也可以写整个路径

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Config 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><!--绑定接口-->
<mapper namespace="com.benmao.mapper.UserMapper"><select id="selectUsers" resultType="user">select * from test.users;</select>
</mapper>

        第五步:测试

import java.io.IOException;
import java.io.InputStream;
import java.util.List;public class MyTest {@Testpublic void test() throws IOException {//加载配置文件String resource = "mybatis-config.xml";InputStream resourceAsStream = Resources.getResourceAsStream(resource);SqlSessionFactory build = new SqlSessionFactoryBuilder().build(resourceAsStream);SqlSession session = build.openSession(true);//获取数据UserMapper mapper = session.getMapper(UserMapper.class);List<User> users = mapper.selectUsers();for (User user : users) {System.out.println(user);}}
}

整合Mybatis方式一

        这个就是研究一下spring-mybatis这个依赖包,将Mybatis代码无缝整合到Spring中

        需要准备的知识点:熟悉Spring和Mybatis两个框架和它们相关的术语

        创建一个spring-dao.xml文件,特别注意的是,代码中导入了在讲解mybatis一般使用中的核心配置文件mybatis-config.xml,之前在这个配置文件注册了mapper类,在spring-dao.xml最后同样需要注册mapper,导致了重复引起报错(又研究了半天这个bug...)

        可以通过这个配置文件去对比一下上一节的配置,我们这直接把jdbc的配置写在这,并且直接写了测试类的数据源导入、sqlSessionFactory和sqlSession

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:aop="http://www.springframework.org/schema/aop"xsi:schemaLocation="http://www.springframework.org/schema/beanshttps://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/aophttps://www.springframework.org/schema/aop/spring-aop.xsd"><!--DataSource:使用Spring的数据源替换Mybatis的配置 c3p0 dbcp druid--><!--这里使用Spring提供的JDBC--><bean id="datasource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"><property name="driverClassName" value="com.mysql.jdbc.Driver"/><property name="url" value="jdbc:mysql://localhost:3306/test?useSSL=false&amp;serverTimezone=UTC"/><property name="username" value="root"/><property name="password" value="wjc123456"/></bean><!--sqlSessionFactory--><bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"><property name="dataSource" ref="datasource"/><!--绑定mybatis配置文件--><property name="configLocation" value="classpath:mybatis-config.xml"/><property name="mapperLocations" value="classpath:com/benmao/mapper/*.xml"/></bean><!--sqlSession--><!--sqlSessionTemplate就是sqlSession的写好的模板,本质是一样的--><bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate"><!--只能使用构造器注入,因为这个SqlSessionTemplate类没有set方法--><constructor-arg index="0" ref="sqlSessionFactory"/></bean><!--把写好的封装类注册--><bean id="userMapper" class="com.benmao.mapper.UserMapperImpl"><property name="sqlSession" ref="sqlSession"/></bean>
</beans>

        创建UserMapper接口的实例UserMapperImpl类,这里使用SqlSessionTemplate模板直接在这就写好之前测试类中的获取数据(上一节)

package com.benmao.mapper;import com.benmao.pojo.User;
import org.mybatis.spring.SqlSessionTemplate;import java.util.List;public class UserMapperImpl implements UserMapper {// 所有操作,以前是使用sqlSession来执行,现在使用sqlSessionTemplateprivate SqlSessionTemplate sqlSession;public void setSqlSession(SqlSessionTemplate sqlSession) {this.sqlSession = sqlSession;}@Overridepublic List<User> selectUsers() {UserMapper mapper = sqlSession.getMapper(UserMapper.class);List<User> users = mapper.selectUsers();return users;}
}

        最后测试一下,写法和之前的Spring一样测试,相当于这些功能都已经整合好了,调用即可。getBean就是在配置文件注册的mapper的bean,注意一定不用循环注册mapper,不然会报错

import com.benmao.mapper.UserMapper;
import com.benmao.pojo.User;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;import java.util.List;public class MyTest1 {@Testpublic void test1() {ApplicationContext contest = new ClassPathXmlApplicationContext("spring-dao.xml");UserMapper userMapper = contest.getBean("userMapper", UserMapper.class);List<User> users = userMapper.selectUsers();for (User user : users) {System.out.println(user);}}
}

整合Mybatis方式二

        其实方法二就是简化方法一,通过继承SqlSessionDaoSupport类给出的Session直接拿来用,具体看代码解释

        可以对比UserMapperImpl类,不需要写什么sqlSession的set方法,通过getSqlSession()方法给出的Session直接给出拿来用即可

package com.benmao.mapper;import com.benmao.pojo.User;
import org.mybatis.spring.support.SqlSessionDaoSupport;import java.util.List;public class UserMapperImpl2 extends SqlSessionDaoSupport implements UserMapper {@Overridepublic List<User> selectUsers() {//下面两种返回方式是一样的// return getSqlSession().selectList("com.benmao.mapper.UserMapper.selectUsers");return getSqlSession().getMapper(UserMapper.class).selectUsers();}
}

        因此在配置文件中,只需要把sqlSessionFactory给这个被注册的UserMapperImpl2就可以了

    <bean id="userMapper2" class="com.benmao.mapper.UserMapperImpl2"><property name="sqlSessionFactory" ref="sqlSessionFactory"/></bean>

十五:声明式事务

        回顾事务:

        把一组业务当成一个业务来做,要么一起成功,要么一起失败

        事务在开发十分重要,涉及到数据的一致性

        确保完整性和一致性

        事务的ACID原则:原子性、一致性、隔离性、持久性

        之前在写数据库的操作代码中,同时调用了添加一个用户和删除一个用户的代码,可巧的是删除一个用户代码背后的sql语句是错的,导致了只添加了一个用户,需要被删除用户还活着。这不符合事务的一致性,要成功一起成功,要失败一起失败,删除一个用户会有异常,哪怕添加一个用户是对的,也不会被执行就是一起失败,哦耶~(我没成功谁也别成功)

        如何处理,那就是声明式事务  

        这个就是之前写过AOP知识点spring配置一样,只不过环绕增强把事务写进去就行了,之前环绕增强是写环绕的方法,这块就是事务绑定哪些方法,然后环绕事务

        增加两个方法

package com.benmao.mapper;import com.benmao.pojo.User;import java.util.List;public interface UserMapper {public List<User> selectUsers();//添加一个用户public int addUser(User user);//删除一个用户public int deleteUser(int id);
}

        然后实现类重写一下,注意看在selectUres方法里,添加用户和删除用户是同时被调用

package com.benmao.mapper;import com.benmao.pojo.User;
import org.mybatis.spring.support.SqlSessionDaoSupport;import java.util.List;public class UserMapperImpl extends SqlSessionDaoSupport implements UserMapper {@Overridepublic List<User> selectUsers() {User user = new User(6,"123","123321");UserMapper mapper = getSqlSession().getMapper(UserMapper.class);mapper.addUser(user);mapper.deleteUser(1);return mapper.selectUsers();}@Overridepublic int deleteUser(int id) {return getSqlSession().delete("com.benmao.mapper.UserMapper.deleteUser", id);}@Overridepublic int addUser(User user) {return getSqlSession().insert("com.benmao.mapper.UserMapper.addUser", user);}
}

        编写SQL语句

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Config 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><!--绑定接口-->
<mapper namespace="com.benmao.mapper.UserMapper"><select id="selectUsers" resultType="user">select * from test.users;</select><insert id="addUser" parameterType="user">insert into test.users (user_id, username, password) values (#{user_id},#{username},#{password});</insert><delete id="deleteUser" parameterType="int">delete from test.users where user_id = #{id};</delete></mapper>

        最后在配置文件中最后写上声明式事务标签,然后通过AOP插入,文件头需要写tx的导入(这里就不全部写了),写完就可以测试了

    <!--结合AOP实现事务的织入--><!--配置事务通知--><tx:advice id="txAdvice" transaction-manager="transactionManager"><!--给方法配置事务: propagation--><tx:attributes><tx:method name="*" propagation="REQUIRED"/></tx:attributes></tx:advice><!--配置事务切入:AOP的知识点--><aop:config><!--切入点--><aop:pointcut id="txPointCut" expression="execution(* com.benmao.mapper.*.*(..))"/><!--执行环绕增强--><aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/></aop:config>

        为什么需要事务:如果不配置事务,可能存在数据提交不一样,在项目开发中十分重要,保证数据的一致性和完整性,添加用户和删除用户要么都成功运行,只要有一个方法有问题导致异常,另一个方法本是可以成功运行,但是由于事务的约束,无法成功,所有只能一起失败


http://www.dtcms.com/a/473350.html

相关文章:

  • 【Spring Security】Spring Security 密码编辑器
  • MCU ADC外设工作原理介绍
  • k8s的ymal文件
  • 杭州公司建设网站网站建设标签
  • 博客系统小笔记
  • 后端开发和软件开发有什么区别
  • 分布式专题——41 RocketMQ集群高级特性
  • 自然语言处理分享系列-词语和短语的分布式表示及其组合性(一)
  • 从0到1实现鸿蒙智能设备状态监控:轻量级架构、分布式同步与MQTT实战全解析
  • RWKV架构讲解
  • Docker 镜像维护指南:从配置优化到 MySQL 实战运行
  • 电视盒子助手开心电视助手 v8.0 删除电视内置软件 电视远程控制ADB去除电视广告
  • 【完整源码+数据集+部署教程】 航拍杂草检测与分类系统源码和数据集:改进yolo11-RVB-EMA
  • My SQL--创建数据库、表
  • mysql高可用架构之MHA部署(三)——故障转移后邮件告警配置(保姆级)
  • 做酒的网站有哪些jsp获取网站域名
  • OpenCV(八):NumPy
  • 小微宝安网站建设有哪些做分析图用的网站
  • RabbitMQ 核心概念解析
  • 开发实战 - ego商城 - 2 nodejs搭建后端环境
  • 基于Java Swing的智能数据结构可视化系统 | 支持自然语言交互的AI算法助手
  • QQmusic sign值逆向实战 - Webpack打包分析
  • 城乡建设部网站首页网站建设公司应该怎么做推广
  • Linux环境下Hive4.0.1(最新版本)部署
  • dolphinscheduler之hivecli 任务
  • spark3访问低版本hive填坑记
  • 池化 (Pooling) 学习笔记
  • LeetCode160.相交链表【最通俗易懂版双指针】
  • Neo4j+Gephi制作社区检测染色图
  • 毕业设计代做网站机械工信部网站备案流程