IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> Spring5 -> 正文阅读

[Java知识库]Spring5

Spring5-2021.8.5

1、Spring

1.1、简介

  • Spring框架是由于软件开发的复杂性而创建的

  • Spring的形成,最初来自Rod Jahnson所著的一本很有影响力的书籍《Expert One-on-One J2EE Design and Development》,就是在这本书中第一次出现了Spring的一些核心思想,该书出版于2002年。

  • spring框架即以interface21框架为基础,经过重新设计,不断丰富其内涵,于2004年3月24发布了1.0正式版

  • **Rod Jahnson:**Spring框架的创始人,同时也是SpringSource的联合创始人。Spring是面向切面编程(AOP)和控制反转(IoC)的容器框架。Rod的畅销书Expert One-on-One J2EE Design and Development(2002年出版)是迄今为止J2EE领域最具影响力的书之一。

  • spring理念:使现有技术更加容易使用,本身是一个大杂烩,整合了现有技术框架

  • SSH:Struct + Spring + Hibernate

  • SSM:SpringMvc +Spring + Mybatis

官网:https://spring.io/projects/spring-framework

中文文档:https://www.docs4dev.com/docs/zh/spring-framework/5.1.3.RELEASE/reference

官方下载地址:http://repo.spring.io/release/org/springframework/spring

GitHub:

spring官方各版本jar包:https://www.cnblogs.com/borter/p/9543583.html

1.2、优点和弊端

  • spring是一个开源的免费的框架(容器)
  • spring是一个轻量级,非入侵式的框架
  • 控制反转(IOC),面向切面编程(AOP)
  • 支持事务的处理,对框架整合的支持

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

**弊端:**由于发展太久,配置繁琐

1.3、组成

在这里插入图片描述

1.4、拓展

在spring的官网有这个介绍:现代化的Java开发就是基于spring的开发

  • spring boot–> spring cloud–> spring cloud data flow
  • 构建一切 --> 协调一切 --> 连接一切

spring boot

  • 一个快速开发的脚手架
  • 基于spring boot可以快速开发单个微服务
  • 约定大于配置

spring cloud

  • springcloud是基于spring boot实现的

现在大多数公司都在使用spring boot进行快速开发,学习spring boot的前提,需要完全掌握spring及springMVC(承上启下的作用)

2、IOC理念

1、导入依赖

报错:Cannot resolve org.springframework:spring-web:5.2.0.RELEASE

**分析:**依赖没删干净,或换成其他版本依赖

2、思想

  • 之前的业务中,用户的需求可能会影响我们原来的代码,我们需要根据用户的需求去修改源代码,如果代码量十分大,修改一次成本代价十分昂贵

  • 我们使用一个set接口实现,已经发生了革命性的变化

private UserDao userdao;

//利用set进行动态实现值得注入!
public void setUserDao(UserDao userDao){
    this.userDao = userDao;
}
  • 之前,程序主动创建对象!控制权在程序员手上

    使用了set注入后,程序不再具有主动性,而是变成了被动的接收对象(主动权在用户手里)

3、IOC本质

https://mp.weixin.qq.com/s/VM6INdNB_hNfXCMq3UZgTQ

  • 控制反转IoC(Inversion of Control),是一种设计思想,DI(依赖注入)是实现IoC的一种方法,也有人认为DI只是IoC的另一种说法。没有IoC的程序中 , 我们使用面向对象编程 , 对象的创建与对象间的依赖关系完全硬编码在程序中,对象的创建由程序自己控制,控制反转后将对象的创建转移给第三方,个人认为所谓控制反转就是:获得依赖对象的方式反转了。

在这里插入图片描述

  • IoC是Spring框架的核心内容,使用多种方式完美的实现了IoC,可以使用XML配置,也可以使用注解,新版本的Spring也可以零配置实现IoC。

Spring容器在初始化时先读取配置文件,根据配置文件或元数据创建与组织对象存入容器中,程序使用时再从Ioc容器中取出需要的对象。

在这里插入图片描述

  • 采用XML方式配置Bean的时候,Bean的定义信息是和实现分离的,而采用注解的方式可以把两者合为一体,Bean的定义信息直接以注解的形式定义在实现类中,从而达到了零配置的目的。

控制反转是一种通过描述(XML或注解)并通过第三方去生产或获取特定对象的方式。在Spring中实现控制反转的是IoC容器,其实现方法是依赖注入(Dependency Injection,DI)。

4、HelloSpring

1、导入依赖

<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>5.2.0.RELEASE</version>
</dependency>

     <!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
     <dependency>
         <groupId>org.springframework</groupId>
         <artifactId>spring-context</artifactId>
         <version>5.3.8</version>
     </dependency>

2、编写代码

  • 编写一个Hello实体类
package com.xdy.pojo;

public class Hello {
    private String str;

    public String getStr(){
        return str;
    }

    public void setStr(String str){
        this.str = str;
    }

    @Override
    public String toString() {
        return "Hello{" +
                "str='" + str + '\'' +
                '}';
    }
}
  • 编写spring文件,这里命名为beans.xml
<?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/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">

<!-- 这里就相当于spring的容器,事情交给它做,用时拿它就行-->
<!-- 使用spring创建对象,在spring这些都称为Bean
        以前:类型 变量名 = new 类型();
            Hello  hello = new Hello()
        现在:id = 变量名  class = new的对象
             property value相当于给对象中的属性设置一个值
     -->
    <bean id="hello" class="com.xdy.pojo.Hello">
        <!-- 给str赋值-->
        <property name="str" value="Spring"/>
    </bean>
</beans>
  • 测试
import com.xdy.pojo.Hello;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Mytest {
    public static void main(String[] args) {
        //获取ApplicationContext:拿到spring的容器
        ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
       //我们的对象现在都在spring中管理了,要使用直接去里面取出来就可以
        //hello是从容器中拿出来的
        Hello hello = (Hello) context.getBean("hello");
        System.out.println(hello.toString());
    }
}

思考问题 ?
Hello 对象是谁创建的 ?

hello 对象是由Spring创建的

Hello 对象的属性是怎么设置的 ?

hello 对象的属性是由Spring容器设置的 ,

这个过程就叫控制反转 :

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

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

依赖注入 : 就是利用set方法来进行注入的.

IOC是一种编程思想 , 由主动的编程变成被动的接收 .

可以通过newClassPathXmlApplicationContext去浏览一下底层源码 .

OK , 到了现在 , 我们彻底不用再程序中去改动了 , 要实现不同的操作 , 只需要在xml配置文件中进行修改 , 所谓的IoC,一句话搞定 : 对象由Spring 来创建 , 管理 , 装配 !

5、IOC创建对象的方式

1、默认使用无参构造创建对象

2、假设我们要使用有参构造创建对象

  • 下标赋值
<!--第一种,下标赋值-->
<bean id="user" class="com.xdy.pojo.User">
    <constructor-arg index="0" value="xdy"/>
</bean>

报错:Cannot find constructor with argument index 0 in class com.xdy.pojo.User 在类com.xdy.pojo.User中找不到参数索引为0的构造函数

分析:实体类中没有写有参构造

    public User(String name) {
        this.name = name;
    }
  • 类型创建
    <!--第二种,通过类型创建,如果有两个相同类型,就不好赋值,所以不建议使用-->
    <bean id="user" class="com.xdy.pojo.User">
    <constructor-arg type="java.lang.String" value="oi"/>
</bean>
  • 参数名创建
    <!--第三种,直接通过参数名创建-->
    <bean id="user" class="com.xdy.pojo.User">
    <constructor-arg name="name" value=""/>
</bean>

6、spring配置

6.1、别名

    <!--别名:如果添加了别名,也可以通过别名获取到这个对象-->
    <alias name="user" alias="user2"/>

6.2、bean的配置

    <!--
    id:bean的唯一标识符,相当于我们学的对象名
    class:bean对象所对应的全限定名:包名+类型
    name:别名,而且name可以同时取多个别名,还可用不同符号隔开
    -->
    <bean id="user" class="com.xdy.pojo.User" name="user2 u2,u3;u4">
        <constructor-arg value="name"/>
        <property name="name" value="df"/>
    </bean>

6.3、import

  • 一般用于团队开发使用,可以将多个配置文件,导入合并为一个

  • 假设项目有多个人开发,这三个人复制不同的类开发,不同的类需要注册在不同的bean中,我们可以利用import将所有人的beans.xml合并为一个总的

  • 张三

  • 李四

  • 王五

  • applicationContext.xml

<import resource="beans1.xml"/>
<import resource="beans2.xml"/>
<import resource="beans3.xml"/>

使用的时候,直接使用总配置即可

7、DI依赖注入环境

7.1、构造器注入

7.2、set方式注入【重点】

  • 依赖注入本质是:set注入

    • 依赖:bean对象的创建依赖于容器
    • 注入:bean对象的所有属性,由容器来注入!
  • 环境搭建

    • 复杂类型
    package com.xdy.pojo;
    
    public class Address {
    
    private String address;
    
        public String getAddress() {
            return address;
        }
    
        public void setAddress(String address) {
            this.address = address;
        }
    }
    
    • 真实测试对象
    package com.xdy.pojo;
    
    import java.util.*;
    
    public class Student {
        private String name;
        private Address address ;
        private String[] book;
        private List<String> hobbys;
        private Map<String,String> card;
        private Set<String> games;
        private String wife;
        private Properties info;
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public Address getAddress() {
            return address;
        }
    
        public void setAddress(Address address) {
            this.address = address;
        }
    
        public String[] getBook() {
            return book;
        }
    
        public void setBook(String[] book) {
            this.book = book;
        }
    
        public List<String> getHobbys() {
            return hobbys;
        }
    
        public void setHobbys(List<String> hobbys) {
            this.hobbys = hobbys;
        }
    
        public Map<String, String> getCard() {
            return card;
        }
    
        public void setCard(Map<String, String> card) {
            this.card = card;
        }
    
        public Set<String> getGames() {
            return games;
        }
    
        public void setGames(Set<String> games) {
            this.games = games;
        }
    
        public String getWife() {
            return wife;
        }
    
        public void setWife(String wife) {
            this.wife = wife;
        }
    
        public Properties getInfo() {
            return info;
        }
    
        public void setInfo(Properties info) {
            this.info = info;
        }
    
        @Override
        public String toString() {
            return "Address{" +
                    "name='" + name + '\'' +
                    ", address=" + address +
                    ", book=" + Arrays.toString(book) +
                    ", hobbys=" + hobbys +
                    ", card=" + card +
                    ", games=" + games +
                    ", wife='" + wife + '\'' +
                    ", info=" + info +
                    '}';
        }
    }
    
    • beans.xml
    <?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/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd">
    
    <bean id="student" class="com.xdy.pojo.Student">
        <!--第一种:普通值注入,通过value -->
        <property name="name" value="小薛"/>
    </bean>
    
    </beans>
    
    • 测试
    import com.xdy.pojo.Student;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    public class MyTest {
    
        public static void main(String[] args) {
            //拿到容器
            ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
            //拿到容器里的学生
            Student student = (Student) context.getBean("student");
            //输出名字这个属性
            System.out.println(student.getName());
        }
    
  • 完善注入信息

<bean id="address" class="com.xdy.pojo.Address">
    <property name="address" value="123@qq.com"/>

</bean>


<bean id="student" class="com.xdy.pojo.Student">
    <!--第一种:普通值注入,value -->
    <property name="name" value="小薛"/>

    <!--第二种:Bean注入,ref -->
    <property name="address" ref="address"/>

    <!--第三种:ref -->
    <property name="books">
        <array>
            <value>红楼梦</value>
            <value>西游记</value>
            <value>三国演义</value>
        </array>
    </property>

    <!--第四种:list -->
    <property name="hobbys">
        <list>
            <value>听歌</value>
            <value>写字</value>
            <value>敲代码</value>
        </list>
    </property>

    <!--第五种:map -->
    <property name="card">
        <map>
            <entry key="身份证" value="123456"/>
            <entry key="银行卡" value="Z6597"/>
            <entry key="学生卡" value="654987"/>
        </map>
    </property>

    <!--第六种:set -->
    <property name="games">
        <set>
            <value>LOL</value>
        </set>
    </property>

    <!--第七种:null-->
    <property name="wife">
        <null/>
    </property>

    <!--第八种:properties-->
    <property name="info">
        <props>
            <prop key="学号">123654</prop>
            <prop key="性别"></prop>
        </props>
    </property>
</bean>

7.3、拓展方式

c命名和p命名空间注入

  • 官方解释

  • 使用

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:c="http://www.springframework.org/schema/c"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!--p命名空间注入,可以直接注入属性的值:property-->
<bean id="user" class="com.xdy.pojo.User" p:name="薛薛" p:age="18"/>
    <!--c命名空间注入,通过构造器注入:construct-arg-->
<bean id="user2" class="com.xdy.pojo.User" c:name="ee" c:age="19">


</bean>
    
</beans>
  • 测试
public class MyTest {

    public static void main(String[] args) {
        //拿到容器
        ApplicationContext context = new ClassPathXmlApplicationContext("userbeans.xml");
        //拿到容器里的学生
        User user = (User) context.getBean("user2");
        //输出名字这个属性
        System.out.println(user.toString());
    }
}
  • 注意点

p命名和c命名空间不能直接使用,需要导入xml约束!

xmlns:p="http://www.springframework.org/schema/p"
xmlns:c="http://www.springframework.org/schema/c"

8、bean的作用域(scope)面试重点

Bean 作用域
ScopeDescription
singleton(默认)将每个 Spring IoC 容器的单个 bean 定义范围限定为单个对象实例。
prototype将单个 bean 定义的作用域限定为任意数量的对象实例。
request将单个 bean 定义的范围限定为单个 HTTP 请求的生命周期。也就是说,每个 HTTP 请求都有一个在单个 bean 定义后面创建的 bean 实例。仅在可感知网络的 Spring ApplicationContext中有效。
session将单个 bean 定义的范围限定为 HTTP Session的生命周期。仅在可感知网络的 Spring ApplicationContext上下文中有效。
application将单个 bean 定义的范围限定为ServletContext的生命周期。仅在可感知网络的 Spring ApplicationContext上下文中有效。
websocket将单个 bean 定义的范围限定为WebSocket的生命周期。仅在可感知网络的 Spring ApplicationContext上下文中有效。

**1、单例模式(spring默认机制)**一般单线程时用

<bean id="user" class="com.xdy.pojo.User" p:name="薛薛" p:age="18" scope="singleton"/>

**2、原型模式:**每次从容器种get的时候,都会产生一个新的对象 (一般多线程用)

<bean id="user" class="com.xdy.pojo.User" p:name="薛薛" p:age="18" scope="prototype"/>

3、其余的request、session、application、websocket只能在web开发中使用

9、bean的自动装配

  • 自动装配就是自动获取容器里的实例并返回
  • 自动装配是spring满足bean依赖的一种方式
  • spring会在上下文中自动寻找,并自动给bean装配属性

在spring中有三种装配的方式

1、在xml中显示的配置(上面所学)

2、在Java中显示配置

3、隐式的自动装配bean**【重要】**

9.1、测试

环境搭建:一个人有两个宠物

9.2、ByName自动装配

<!--
byname:会自动在容器上下文中查找,和自己对象set方法后面的值(People中setCat/setDog)对应的bean id
 -->
<bean id="people" class="com.xdy.pojo.People" autowire="byName">
    <property name="name"  value="薛薛"/>
</bean>

9.3、ByType自动装配

<bean  class="com.xdy.pojo.Cat"/>
<bean  class="com.xdy.pojo.Dog"/>
<!--
byname:会自动在容器上下文中查找,和自己对象set方法后面的值(People中setCat/setDog)对应的bean id
bytype:会自动在容器上下文中查找,和自己对象属性类型相同的bean
 -->
<bean id="people" class="com.xdy.pojo.People" autowire="byType">
    <property name="name"  value="薛薛"/>
</bean>

小结:

  • byname:需要保证所有bean的id唯一,并且这个id的值需要和实体类set方法后的值一致
  • bytype:需要保证所有bean的class唯一,并且这个bean需要和自动注入的属性类型一致

9.4、注解实现自动装配

jdk1.5支持注解,spring2.5支持注解

要使用注解须知:

  • 导入约束
xmlns:context="http://www.springframework.org/schema/context"
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
  • 配置注解支持
   <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/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">

    <context:annotation-config/>

</beans>

@Autowired

1、直接在属性上使用即可,也可以在set方式上使用

2、使用@Autowired我们可以不用编写set方法了,前提是这个自动装配的属性在ioc(spring)容器中存在,且符合名字byname

3、科普

  • //如果显示定义了autowired的required属性为false,说明这个对象可以为null,否则不允许为空
    @Autowired(required = false)
    
  • @Nullable 字段标记了这个注解,说明这个字段可以为null
    

4、如果@Autowired自动装配的环境比较复杂,自动装配无法通过一个注解@Autowired完成的时候,我们可以使用@Qualifier(value = “xxx”)去设置@Autowired的使用,指定一个唯一的bean对象注入!

private String name;
@Autowired
private Cat cat;
@Autowired
@Qualifier(value = "dog11")
private Dog dog;

@Resources

@Resources(name = "cat1")
    private Cat cat;
@Resources
    private Dog dog;

小结

@Autowired和**@Resources**区别

  • 都是用来自动装配的,都可以放在属性字段上

  • @Autowired通过byname的方式实现

  • @Resources默认通过byname的方式实现,如果找不到名字,则通过bytype实现,如果两个都找不到的情况下,就报错

  • 执行顺序不同:

    @Autowired通过bytype方式实现

@Resources默认通过byname方式实现

10、使用注解开发

在spring4之后使用注解开发,必须要保证aop的包导入了

使用注解需要导入context约束,增加注解的支持

1、bean

<?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"
       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
        https://www.springframework.org/schema/aop/spring-aop.xsd">
    <!--指定要扫描的包,这个包下的注解就会生效 -->
    <context:component-scan base-package="com.xdy.pojo"/>
    <context:annotation-config/>

    
</beans>

2、属性如何注入

package com.xdy.pojo;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;


//@Component 组件
@Component //等价于 <bean id="user" class="com.xdy.pojo.User"/>
public class User {
    
@Value("xdy") //等价于 <property name="name" value="xdy"/>
    public String name ;
}

3、衍生的注解

@Component有几个衍生注解,在web开发中,会按照MVC三层架构分层!

  • dao 【@Repository】
  • service 【@Service】
  • controller 【@Controller】

这四个注解功能都是组件,只是在不同包下名字不同,代表将某个类注册到spring容器中,装配bean

4、自动装配置 见上

5、作用域

@Component //等价于 <bean id="user" class="com.xdy.pojo.User"/>
@Scope("singleton") //@Scope("prototype")

public class User {
}

6、小结

xml与注解

  • xml更加万能,适用于任何场合,维护简单方便
  • 注解不是自己的类使用不了,维护相对复杂

xml与注解最佳实践

  • xml用来管理bean
  • 注解只负责完成属性的注入
  • 我们使用过程中只需要注意一个问题:必须让注解生效,就需要开启注解的支持
 <!--指定要扫描的包,这个包下的注解就会生效 -->
    <context:component-scan base-package="com.xdy.pojo"/>
    <context:annotation-config/>

11、使用Java的方式配置spring

我们现在完全不使用spring的xml配置了,全权交给Java来做!

JavaConfig是spring的一个子项目,在spring4之后,它成为了一个核心功能!

  • 实体类
package com.xdy.pojo;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component //这个注解的意思就是说明这个类被spring接管了,注册到了容器中
public class User {
    private String name;

    public String getName() {
        return name;
    }
@Value("xdy")
    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                '}';
    }
}
  • 配置文件
package com.xdy.config;

import com.xdy.pojo.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.stereotype.Component;
//这是一个配置类,和beans.xml是一样的,本质是@Component,这个类会被注册到spring容器中
@Configuration 
@Import(JavaConfig2.class)
public class JavaConfig {

    //@Bean相当于一个bean标签
    //这个方法的名字,相当于bean标签中的id
    //这个方法的返回值,相当于bean标签中的class属性

    @Bean
    public User user(){
        return new User();//返回注入到bean的对象
    }
}
  • 测试
import com.xdy.config.JavaConfig;
import com.xdy.pojo.User;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class MyTesst {
    public static void main(String[] args) {
        //如果完全使用配置类方式去做,我们只能通过AnnotationConfig上下文来取容器,通过配置类的class对象加载
        ApplicationContext context = new AnnotationConfigApplicationContext(JavaConfig.class);
        User getUser = (User) context.getBean("user",User.class);
        System.out.println(getUser.getName());
    }
}

**报错:**Exception in thread “main” org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean

**分析:**注解配置文件路径没写对,JavaConfig.class不需要“ ”

  • 小结

纯Java的配置方式,在spring boot中随处可见

12、代理模式

1、为什么学习代理模式? 因为是springAOP的底层!

面试必问:springAOP和springMVC

2、代理模式的分类:

  • 静态代理
  • 动态代理

3、代理模式可以想象成租房中介,帮真实角色做事

12.1、静态代理

角色分析:

  • 抽象角色(租房):一般会使用接口或抽象类来解决
  • 真实角色(房东):被代理的角色
  • 代理角色:代理真实角色,代理后我们一般会做一些附属操作
  • 客户(租客):访问代理对象的人

代理步骤:

  • 接口
package com.xdy.demo01;

public interface Rent {

    public void rent();

}
  • 真实角色
package com.xdy.demo01;

public class Host implements Rent{

    @Override
    public void rent() {
        System.out.println("房东要出租房子");
    }
}
  • 代理角色
package com.xdy.demo01;

public class Proxy implements Rent{
//房东的事情
    private Host host;

    public Proxy() {
    }

    public Proxy(Host host) {
        this.host = host;
    }

    @Override
    public void rent() {
        host.rent();
        seeHouse();
        fare();
        hetong();
    }
    //代理的事情
    //看房
    public void seeHouse(){
        System.out.println("中介带你看房");
    }
    //收中介费
    public void fare(){
        System.out.println("收中介费");
    }
    //签合同
    public void hetong(){
        System.out.println("签合同");
    }
}
  • 客户端访问
package com.xdy.demo01;

public class Client {
    public static void main(String[] args) {
        //房东要租房子
        Host host = new Host();
        //代理,中介帮房东租房子,但是中介会有一些附属操作
        Proxy proxy = new Proxy(host);
        //你不用面对房东,直接找中介租房即可
        proxy.rent();
    }
}

代理模式的好处:不修改原有业务的基础上增加一些其他业务

  • 可以使真实角色的操作更加纯粹,不用去关注一些公共的业务
  • 公共业务就交给代理角色,实现了业务的分工
  • 公共业务发生扩展的时候,方便集中管理!

缺点:

  • 一个真实角色就会产生一个代理角色,代码量会翻倍,开发效率会变低

12.2、加深理解

在这里插入图片描述

代码

  • 接口
package com.xdy.demo02;

public class Client {
    public static void main(String[] args) {
        UserServiceImpl userService = new UserServiceImpl();

        //找代理去做
        UserServiceProxy Proxy = new UserServiceProxy();
        Proxy.setUserService(userService);
        Proxy.add();
    }
}
  • 真实对象
package com.xdy.demo02;
//真实对象
public class UserServiceImpl implements UserService{
    @Override
    public void add() {
        System.out.println("增加了一个用户");
    }

    @Override
    public void delete() {
        System.out.println("删除了一个用户");
    }

    @Override
    public void update() {
        System.out.println("修改了一个用户");
    }

    @Override
    public void query() {
        System.out.println("查找了一个用户");
    }
}
  • 代理
package com.xdy.demo02;

public class UserServiceProxy implements UserService{
    //代理角色也做真实角色的事
    private UserServiceImpl userService;

    public void setUserService(UserServiceImpl userService) {
        this.userService = userService;
    }

    //代理角色也做增删改查
    @Override
    public void add() {
        log("add");
        userService.add();
    }

    @Override
    public void delete() {
        log("delete");
        userService.delete();
    }

    @Override
    public void update() {
        log("update");
        userService.update();
    }

    @Override
    public void query() {
        log("query");
        userService.query();
    }
    //日志方法
    public void log(String msg){
        System.out.println("使用了"+msg+"方法");
    }
}
  • 客户端
package com.xdy.demo02;

public class Client {
    public static void main(String[] args) {
        UserServiceImpl userService = new UserServiceImpl();

        //找代理去做
        UserServiceProxy Proxy = new UserServiceProxy();
        Proxy.setUserService(userService);
        Proxy.add();
    }
}

12.3、动态代理

  • 动态代理和静态代理角色一样

  • 动态代理的代理类是动态生成的,不是我们直接写好的

  • 动态代理分为两大类:基于接口的动态代理,基于类的动态代理

    • 基于接口:JDK动态代理【我们在这里使用】
    • 基于类:cglib
    • Java字节码实现:Javasist
  • 需要了解2个类:

    proxy 代理

    invocationHandier:调用处理程序

动态代理的好处:

  • 静态代理的好处都有
  • 一个动态代理类代理的是一个接口,一般就是对应的一类业务
  • 一个动态代理类可以代理多个类,只要实现了同一个接口即可
package com.xdy.demo04;
//万能的模板
import com.xdy.demo03.Rent;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

//用这个类自动生成代理类(做两件事:生成代理类,处理代理实例返回结果)
public class ProxyInvocationHandler implements InvocationHandler {

    //被代理的接口
    private Object target;

    public void setTarget(Object target) {
        this.target = target;
    }

    //得到和生成代理类
    public Object getProxy(){

        return Proxy.newProxyInstance
                (this.getClass().getClassLoader(),target.getClass().getInterfaces(),this );
    }

    //处理代理实例,并返回结果
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        log(method.getName());
        Object result = method.invoke(target, args);
        return result;
    }
    //生成日志
    public void log(String msg){
        System.out.println("执行了"+msg+"方法");
    }
}
 
package com.xdy.demo04;

import com.xdy.demo02.UserService;
import com.xdy.demo02.UserServiceImpl;
import com.xdy.demo03.Host;
import com.xdy.demo03.Rent;

public class Client {
    public static void main(String[] args) {
        //真实角色
        UserServiceImpl userService = new UserServiceImpl();

        //代理角色:现在没有,要new出来
        ProxyInvocationHandler pih = new ProxyInvocationHandler();

        pih.setTarget(userService); //设置要代理的对象
        //动态生成代理类
        UserService proxy = (UserService) pih.getProxy();
        proxy.add();

    }
}

报错:Cannot resolve symbol ‘xxx’ 无法解析符号“xxx”

分析:InvocationHandler打错了,导致识别不了这个接口

13、AOP实现方式(重点)

1、简介

AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

2、AOP在spring中的作用

在这里插入图片描述

3、实现机制

在这里插入图片描述

4、导入jar包

<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.4</version>
    <scope>runtime</scope>
</dependency>

 <!-- https://mvnrepository.com/artifact/org.aspectj/aspectjrt -->
     <dependency>
         <groupId>org.aspectj</groupId>
         <artifactId>aspectjrt</artifactId>
         <version>1.9.6</version>
         <scope>runtime</scope>
     </dependency>
     
     <!-- https://mvnrepository.com/artifact/aopalliance/aopalliance -->
     <dependency>
         <groupId>aopalliance</groupId>
         <artifactId>aopalliance</artifactId>
         <version>1.0</version>
     </dependency>

5、接口

package com.xdy.service;

public interface UserService {
    public void add();
    public void delete();
    public void update();
    public void select();
}

6、实体类

package com.xdy.service;

public class UserServiceImpl implements UserService{

    @Override
    public void add() {
        System.out.println("增加了一个用户");
    }

    @Override
    public void delete() {
        System.out.println("删除了一个用户");
    }

    @Override
    public void update() {
        System.out.println("修改了一个用户");
    }

    @Override
    public void select() {
        System.out.println("查找了一个用户");
    }
}

13.1、方式一:使用spring的API接口

  • 接口可以多实现,直接实现多个这样的接口,然后把所有的步骤写在一个类中

  • 写了很多类,把这些类注册到spring中

  • 日志没有改,自己做自己的事,通过applicationContext.xml(spring)这个中间商修改,需要在哪里执行你的代码,执行什么代码,使用原生态接口

  • 接口

package com.xdy.service;

public interface UserService {
    public void add();
    public void delete();
    public void update();
    public void select();
}
  • 实体类
package com.xdy.service;

public class UserServiceImpl implements UserService{

    @Override
    public void add() {
        System.out.println("增加了一个用户");
    }

    @Override
    public void delete() {
        System.out.println("删除了一个用户");
    }

    @Override
    public void update() {
        System.out.println("修改了一个用户");
    }

    @Override
    public void select() {
        System.out.println("查找了一个用户");
    }
}
  • applicationContext.xml
<?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/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">

    <!--注册bean -->
<bean id="userService" class="com.xdy.service.UserServiceImpl"/>
<bean id="log" class="com.xdy.log.Log"/>
<bean id="afterLog" class="com.xdy.log.AfterLog"/>

<!--    方式一:使用原生的spring API接口 -->
<!--    配置aop:需要导入aop的约束 -->
    <aop:config>
        <!--切入点:在哪个地方执行spring的方法 execution(要执行的位置 修饰词,返回值,类名,方法名,参数) -->
        <!--(..)代表可以任意个参数 -->
        <aop:pointcut id="pointcut" expression="execution(* com.xdy.service.UserServiceImpl.*(..))"/>

        <!--执行环绕增加! -->
        <!--哪个类 切入到哪里 -->
        <aop:advisor advice-ref="log" pointcut-ref="pointcut"/>
        <aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
    </aop:config>
  • 测试
import com.xdy.service.UserService;
import com.xdy.service.UserServiceImpl;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MyTest {
    public static void main(String[] args) {
       ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
       //动态代理代理的是接口:
        UserService userService = (UserService) context.getBean("userService");

        userService.add();
    }
}

13.2、方式二:自定义类(如果接口找不到或没有)

  • 自定义切面
package com.xdy.diy;

public class DiyPointCut {

    public void  before(){
        System.out.println("====方法执行前=====");
    }

    public void  after(){
        System.out.println("=====方法执行后");
    }
}
  • application.xml
    <!--方式二:自定义类 -->
<bean id="diy" class="com.xdy.diy.DiyPointCut"/>
 <aop:config>
     <!--自定义切面 ref要引用的类 -->
     <aop:aspect ref="diy">
         <!--切入点 -->
         <aop:pointcut id="point" expression="execution(* com.xdy.service.UserServiceImpl.*(..))"/>
         <!--通知什么时候执行 -->
         <aop:before method="before" pointcut-ref="point"/>
         <aop:after method="after" pointcut-ref="point"/>
     </aop:aspect>

 </aop:config>

13.3、方式三:注解实现AOP

  • 自定义切面
package com.xdy.diy;

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 //标注这个类是一个切面
public class AnnotationPointCut {

    @Before("execution(* com.xdy.service.UserServiceImpl.*(..))")  //切入点
      public void before(){
          System.out.println("=====方法执行前=====");
      }
      @After("execution(* com.xdy.service.UserServiceImpl.*(..))")
    public void after(){
          System.out.println("=====方法执行后=====");
      }
    
}
  • applicationContext.xml
<!--    方式三-->
    <bean id="annotationPointCut" class="com.xdy.diy.AnnotationPointCut"/>
<!--    开启注解支持-->
    <aop:aspectj-autoproxy/>

14、整合Mybatis

步骤

  • 导入相关jar包

    • junit
    • mybatis
    • mysql
    • spring相关的
    • aop植入
    • mybatis-spring

在这里插入图片描述

    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.47</version>

        </dependency>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version> 3.5.6</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version> 5.3.8</version>
        </dependency>
<!--Spring操作数据库的话,需要一个Spring-jdbc-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version> 5.3.8</version>
        </dependency>

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

        <!-- https://mvnrepository.com/artifact/org.mybatis/mybatis-spring -->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>2.0.6</version>
        </dependency>
    </dependencies>
  • 编写配置文件
  • 测试

14.1、回忆mybatis

  • 编写实体类
package com.xdy.pojo;

import lombok.Data;

@Data
public class User {
    private int id;
    private String NAME;
    private String PASSWORD;
}
  • 编写核心配置文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">

<configuration>

    <typeAliases>
        <package name="com.xdy.pojo"/>
    </typeAliases>

    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&amp;useUnicode=true&amp;characterEncoding=UTF-8&amp;serverTimezone=GMT%2B8"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>

    <mappers>
        <mapper class="com.xdy.dao.UserMapper"/>
    </mappers>
</configuration>
  • 编写接口
package com.xdy.dao;

import com.xdy.pojo.User;

import java.util.List;

public interface UserMapper {

    public List<User> selectUser();
}
  • 编写Mapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.xdy.dao.UserMapper">
<select id="selectUser" resultType="user">
    select * from mybatis.user;
</select>

</mapper>
  • 测试
public class MyTest {

    @Test
    public void test() throws IOException {

        String resources = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resources);

        SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

        SqlSession sqlSession = sessionFactory.openSession(true);

        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        List<User> userList = mapper.selectUser();
        for (User user : userList) {
            System.out.println(user);
        }
    }
}

报错1:Type interface com.xdy.dao.UserMapper is not known to the MapperRegistry.

分析:没有在核心配置文件中注册接口

<mappers>
    <mapper class="com.xdy.dao.UserMapper"/>
</mappers>

报错2:Invalid bound statement (not found): com.xdy.dao.UserMapper.selectUser

分析:先检测接口是否正确,正确则在pom.xml中配置build

<build>
    <resources>
        <resource>
            <directory>src/main/resources</directory>
            <includes>
                <include>**/*.properties</include>
                <include>**/*.xml</include>
            </includes>
            <filtering>true</filtering>
        </resource>
        <resource>
            <directory>src/main/java</directory>
            <includes>
                <include>**/*.properties</include>
                <include>**/*.xml</include>
            </includes>
            <filtering>true</filtering>
        </resource>
    </resources>
</build>

14.2、整合Mybatis方式一

思路:User —UserMapper(interface)—UserMapper.xml — spring-dao.xml —UserMapperImpl — MyTest

在这里插入图片描述

  • spring-dao.xml(spring的配置文件Ⅰ-Ⅳ步骤是固定的
  • 实体类UserMapperImpl
  • 测试
<?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/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">

    <!--Ⅰ:spring来管理数据源 (连接池 c3p0  dbcp druid)-->
    <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/mybatis?useSSL=true&amp;useUnicode=true&amp;characterEncoding=UTF-8"/>
        <property name="username" value="root"/>
        <property name="password" value="123456"/>
    </bean>

<!-- Ⅱ:spring创建sqlSessionFactory -->
    <!-- Ⅲ:绑定mybatis配置文件-->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="datasource"/>
    <property name="configLocation" value="classpath:mybatis-config.xml"/>
    <property name="mapperLocations" value="classpath:com/xdy/mapper/UserMapper.xml"/>
    </bean>

<!--  Ⅳ:  SqlSessionTemplate:就是我们使用的sqlSession-->
    <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
<!--  只能使用构造器注入sqlSessionFactory,因为它没有set方法-->
        <constructor-arg index="0" ref="sqlSessionFactory"/>
    </bean>

<!--    实现类注入到spring中-->
<!--在spring中直接用UserMapper调方法就行-->
    <bean id="userMapper" class="com.xdy.mapper.UserMapperImpl">
        <property name="sqlSession" ref="sqlSession"/>
    </bean>
</beans>
package com.xdy.mapper;

import com.xdy.pojo.User;
import org.mybatis.spring.SqlSessionTemplate;

import java.util.List;
//原来我们的所有操作都使用sqlSession来执行,现在都使用SqlSessionTemplate
public class UserMapperImpl implements UserMapper{

    private SqlSessionTemplate sqlSession;

    public void setSqlSession(SqlSessionTemplate sqlSession) {
        this.sqlSession = sqlSession;
    }

    @Override
    public List<User> selectUser() {
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        return mapper.selectUser();
    }
}
import com.xdy.mapper.UserMapper;
import com.xdy.pojo.User;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import java.io.IOException;

public class MyTest {

    @Test
    public void test() throws IOException {

        ApplicationContext context = new ClassPathXmlApplicationContext("spring-dao.xml");
        UserMapper userMapper = context.getBean("userMapper", UserMapper.class);
        for (User user : userMapper.selectUser()) {
            System.out.println(user);
        }
    }
}

报错:1 字节的 UTF-8 序列的字节 1 无效

分析:1、编码格式 2、xml中有中文

1、

原xml文件首行: <?xml version="1.0" encoding="UTF-8" ?>

修改为GBK就可以了,即下所示

<?xml version="1.0" encoding="GBK"?>

经过测试,启动正常,功能正常,linux环境下 正常。

14.2、整合Mybatis方式二:

SqlSessionDaoSupport

思路:User —UserMapper(interface)—UserMapper.xml — applicationContext.xml—UserMapperImpl2 — —MyTest

  • UserMapperImpl2
package com.xdy.mapper;

import com.xdy.pojo.User;
import org.mybatis.spring.support.SqlSessionDaoSupport;

import java.util.List;

public class UserMapperImpl2 extends SqlSessionDaoSupport implements UserMapper {

    @Override
    public List<User> selectUser() {
        return getSqlSession().getMapper(UserMapper.class).selectUser();
    }
}
  • applicationContext.xml
<?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/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">

    <import resource="spring-dao.xml"/>

    <bean id="userMapper" class="com.xdy.mapper.UserMapperImpl">
        <property name="sqlSession" ref="sqlSession"/>
    </bean>

    <bean id="userMapper2" class="com.xdy.mapper.UserMapperImpl2">
        <property name="sqlSessionFactory" ref="sqlSessionFactory"/>
    </bean>
</beans>
  • 测试
import com.xdy.mapper.UserMapper;
import com.xdy.pojo.User;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import java.io.IOException;

public class MyTest {

    @Test
    public void test() throws IOException {

        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserMapper userMapper = context.getBean("userMapper2", UserMapper.class);
        for (User user : userMapper.selectUser()) {
            System.out.println(user);
        }
    }
}

15、声明式事务

回顾事务:要么都成功,要么都失败

事务ACID原则

  • 原子性(automic):要么都成功,要么都失败
  • 一致性(consist):事务前后数据保持一致
  • 隔离性(isolate):互不干扰,互相隔离
    • 脏读:一个事务读取了另一个事务未提交的数据
    • 不可重复读:一个事务内读取表中某一行数据时,多次读取结果不同(不一定错,只是某些场合不对)
    • 幻读(虚读):一个事务内读取到别人的事务插入的数据,导致前后读取不一致
  • 持久性(durable):事务一旦提交,不可逆
  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2021-08-12 16:28:00  更:2021-08-12 16:29:56 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2025年3日历 -2025/3/4 6:27:14-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码