Spring 概述

Spring 是什么

Spring 是分层的 Java SE/EE 应用 full-stack (全栈式) 轻量级开源框架。

全栈式:对各种主流技术和框架都进行了整合,同时对三层架构都提供解决方案。

轻量级和重量级的划分主要依据就是看它使用了多少服务,启动时需要加载的资源多少以及耦合度等等。

提供了表现层 Spring MVC 和持久层 Spring JDBC Template 以及业务层事务管理等众多的企业级应用技术,还能整合开源世界众多著名的第三方框架和类库,逐渐成为使用最多的 Java EE 企业应用开源框架。

Spring 两大核心:

  • IOC - Inverse Of Control 控制反转:把对象的创建权交给 Spring

  • AOP - Aspect Oriented Programming 面向切面编程:在不修改源码的情况下,对方法进行增强

Spring 发展历程

EJB - Enterprise Java Beans:

  • 1997 年,IBM 提出了 EJB 的思想

  • 1998 年,SUN 制定开发标准规范 EJB 1.0

  • 1999 年,EJB 1.1 发布

  • 2001 年,EJB 2.0 发布

  • 2003 年,EJB 2.1 发布

  • 2006 年,EJB 3.0 发布

Spring:

  • Rod Johnson( Spring 之父)改变 Java 世界的大师级人物

  • 2002 年编著《Expert one on one J2EE design and development》

  • 指出了 Java EE 和 EJB 组件框架中的存在的一些主要缺陷;提出普通 java 类依赖注入更为简单的解决方案

  • 2004 年编著《Expert one-on-one J2EE Development without EJB》阐述了 Java EE 开发时不使用 EJB 的解决方式(Spring 雏形),同年 4 月 spring 1.0 诞生

  • 2006 年 10 月,发布 Spring 2.0

  • 2009 年 12 月,发布 Spring 3.0

  • 2013 年 12 月,发布 Spring 4.0

  • 2017 年 9 月, 发布最新 Spring 5.0 通用版(GA)

Spring 优势

耦合:程序间的依赖关系

解耦:降低程序间的依赖关系;体现在编译期不依赖,运行期才依赖

JDBC 例子:

public class JDBCTest {

    @Test    public void test1() throws ClassNotFoundException, SQLException {        // 1.注册驱动        // 存在编译期依赖:耦合重的体现        // DriverManager.registerDriver(new com.mysql.jdbc.Driver());        // 去掉 new 关键字:编译期不依赖,运行期才依赖。虽然解决了编译期依赖,但是仍存在硬编码问题        Class.forName("com.mysql.jdbc.Driver");        ...    }}

解耦思路:配置文件 + 反射

Spring:

1)方便解耦,简化开发

  • Spring 就是一个容器,可以将所有对象创建和关系维护交给 Spring 管理

  • 什么是耦合度?对象之间的关系,通常说当一个模块 (对象) 更改时也需要更改其他模块 (对象),这就是耦合,耦合度过高会使代码的维护成本增加。要尽量解耦

2)AOP 编程的支持

  • Spring 提供面向切面编程,方便实现程序进行权限拦截,运行监控等功能。

3)声明式事务的支持

  • 通过配置完成事务的管理,无需手动编程

4)方便测试,降低 Java EE API 的使用

  • Spring 对 Junit 4 支持,可以使用注解测试

5)方便集成各种优秀框架

  • 不排除各种优秀的开源框架,内部提供了对各种优秀框架的直接支持

Spring 体系结构

以下八大模块可根据需求引入项目使用:

Data Access/Integration

  • JDBC

  • ORM

  • OXM

  • JMS

  • Transactions

Web

  • WebSocket

  • Servlet

  • Web

  • Portlet

AOP

Aspects

Instrumentation

Messaging

Core Container - 相当于盖房子的地基

  • Beans

  • Core

  • Context

  • SpEL

Test

初识 IOC

概述

控制反转(Inverse Of Control)是一种设计思想,它的目的是指导我们设计出更加松耦合的程序。

控制:在 java 中指的是对象的控制权限(创建、销毁)。

反转:指的是对象控制权从由“开发者在类中手动控制”反转到由“ Spring 容器控制”。

例子:

  • 传统方式 - 需要一个 userDao 实例,需要开发者自己手动创建 new UserDao();

  • IOC 方式 - 需要一个 userDao 实例,直接从 spring 的 IOC 容器获得,对象的创建权交给了spring 控制

自定义 IOC 容器

介绍

需求:实现 Service 层与 Dao 层代码解耦合

步骤分析:

  1. 创建 java 项目,导入自定义 IOC 相关坐标

  2. 编写 Dao 接口和实现类

  3. 编写 Service 接口和实现类

  4. 编写测试代码

实现

创建 java 项目,导入自定义 IOC 相关坐标

<?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/xsd/maven-4.0.0.xsd">    <modelVersion>4.0.0modelVersion>

    <groupId>com.rendagroupId>    <artifactId>jdbc_springartifactId>    <version>1.0-SNAPSHOTversion>

    <properties>        <project.build.sourceEncoding>UTF-8project.build.sourceEncoding>        <maven.compiler.encoding>UTF-8maven.compiler.encoding>        <java.version>1.11java.version>        <maven.compiler.source>1.11maven.compiler.source>        <maven.compiler.target>1.11maven.compiler.target>    properties>

    <dependencies>        <dependency>            <groupId>dom4jgroupId>            <artifactId>dom4jartifactId>            <version>1.6.1version>        dependency>        <dependency>            <groupId>jaxengroupId>            <artifactId>jaxenartifactId>            <version>1.1.6version>        dependency>        <dependency>            <groupId>junitgroupId>            <artifactId>junitartifactId>            <version>4.12version>        dependency>    dependencies>

project>

编写 Dao 接口和实现类

public interface IUserDao {    void save();}
public class UserDaoImpl implements IUserDao {    @Override    public void save() {        System.out.println("UserDao: Successfully Saved.");    }}

编写 Service 接口和实现类

public interface IUserService {    void save();}
public class UserServiceImpl implements IUserService {    private IUserDao userDao = new UserDaoImpl();

    @Override    public void save() {        userDao.save();    }}

编写测试代码

public class SpringTest {    @Test    public void test1() {        // 获取业务层对象        IUserService userService = new UserServiceImpl();        // 调用 save 方法        userService.save();    }}

问题:当前 service 对象和 Dao 对象耦合度太高,而且每次 new 的都是一个新的对象,导致服务器压力过大。

解耦合的原则是编译期不依赖,而运行期依赖就行了。

采用反射方式:

public class UserServiceImpl implements IUserService {    private IUserDao userDao;

    public UserServiceImpl() throws IllegalAccessException, ClassNotFoundException, InstantiationException {        // 传统方式使用 new 方式,导致编译期依赖,耦合度太高        // userDao = new UserDaoImpl();

        // 反射方式,存在硬编码问题        userDao = (IUserDao) Class.forName("com.renda.dao.impl.UserDaoImpl").newInstance();    }

    @Override    public void save() {        userDao.save();    }}

测试代码:

@Testpublic void test1() throws IllegalAccessException, InstantiationException, ClassNotFoundException {    IUserService userService = new UserServiceImpl();    userService.save();}

改造步骤分析:

  1. 准备一个配置文件

  2. 编写一个工厂工具类,工厂类使用 dom4j 来解析配置文件,获取到类的全路径

  3. 使用反射生成对应类的实例对象,存到 Map 中,这个 Map 就是 IOC 容器

为了解决反射方式的硬编码问题,先编写 beans.xml 配置文件

<beans>

    <bean id="userDao" class="com.renda.dao.impl.UserDaoImpl">bean>beans>

编写 BeanFactory 工具类

public class BeanFactory {

    private static Map iocMap = new HashMap<>();// 程序启动时,初始化对象实例static {// 读取配置文件        InputStream resourceAsStream = BeanFactory.class.getClassLoader().getResourceAsStream("beans.xml");try {// 解析 xml(dom4j)            SAXReader saxReader = new SAXReader();            Document document = saxReader.read(resourceAsStream);// 编写 xpath 表达式            String xpath = "//bean";// 获取到所有的 bean 标签            List list = document.selectNodes(xpath);// 遍历并使用反射创建对象实例,存到 map 集合(ioc 容器)中for (Element element : list) {                String id = element.attributeValue("id");                String className = element.attributeValue("class");// 使用反射生成实例对象                Object o = Class.forName(className).newInstance();// 存到 map 中:key-id,value-o                iocMap.put(id, o);            }        } catch (DocumentException | InstantiationException | IllegalAccessException | ClassNotFoundException e) {            e.printStackTrace();        }    }public static Object getBean(String beanId) {return iocMap.get(beanId);    }}

修改 UserServiceImpl 实现类

public UserServiceImpl() throws IllegalAccessException, ClassNotFoundException, InstantiationException {    // 传统方式使用 new 方式,导致编译期依赖,耦合度太高    // userDao = new UserDaoImpl();

    // 反射方式,存在硬编码问题    // userDao = (IUserDao) Class.forName("com.renda.dao.impl.UserDaoImpl").newInstance();

    // 反射方式 + 配置文件    userDao = (IUserDao) BeanFactory.getBean("userDao");}

小结

  • 其实升级后的 BeanFactory 就是一个简单的 Spring 的 IOC 容器所具备的功能。

  • 之前需要一个 userDao 实例,开发者自己手动创建 new UserDao();

  • 现在需要一个 userDao 实例,直接从 spring 的 IOC 容器获得,对象的创建权交给了 spring 控制

  • 最终目标:代码解耦合

Spring 快速入门

介绍

需求:借助 spring 的 IOC 实现 service 层与 DAO 层代码解耦合

步骤分析:

  1. 创建 java 项目,导入 spring 开发基本坐标

  2. 编写 DAO 接口和实现类

  3. 创建 spring 核心配置文件

  4. 在 spring 配置文件中配置 UserDaoImpl

  5. 使用 spring 相关 API 获得 Bean 实例

实现

创建 java 项目,导入 spring 开发基本坐标

<dependencies>    <dependency>        <groupId>org.springframeworkgroupId>        <artifactId>spring-contextartifactId>        <version>5.1.5.RELEASEversion>    dependency>    <dependency>        <groupId>junitgroupId>        <artifactId>junitartifactId>        <version>4.12version>    dependency>dependencies>

编写 Dao 接口和实现类

public interface IUserDao {    void save();}
public class UserDaoImpl implements IUserDao {    @Override    public void save() {        System.out.println("UserDao: save...");    }}

创建 spring 核心配置文件 applicationContext.xml,并在 spring 配置文件中配置 UserDaoImpl 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:p="http://www.springframework.org/schema/p"xsi:schemaLocation="http://www.springframework.org/schema/beans        http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="userDao" class="com.renda.dao.impl.UserDaoImpl"/>

beans>

使用 spring 相关 API 获得 Bean 实例

@Testpublic void test1(){    // 获取到了 spring 上下文对象,借助上下文对象可以获取到 IOC 容器中的 bean 对象 ,加载的同时就创建了 bean 对象存到容器中    // ApplicationContext XmlApplicationContext = new FileSystemXmlApplicationContext("D:\\gitee_repository\\stage-6-module-2\\code\\spring_quickstart\\src\\main\\resources\\applicationContext.xml");    ApplicationContext XmlApplicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");

    // 使用上下文对象从 IOC 容器中获取到了 bean 对象    // 方法一:根据 bean id 在容器中找对应的 bean 对象    // IUserDao userDao = (IUserDao) XmlApplicationContext.getBean("userDao");    // 方法二:根据类型从容器中匹配 Bean 实例,当容器中相同类型的 Bean 有多个时,则此方法会报错。    // IUserDao userDao = XmlApplicationContext.getBean(IUserDao.class);    // 方法三:根据 Bean 的 id 和类型获得 Bean 实例,解决容器中相同类型 Bean 有多个情况。    IUserDao userDao = XmlApplicationContext.getBean("userDao", IUserDao.class);

    // 调用方法    userDao.save();}

Spring 的开发步骤总结

  1. 导入坐标

  2. 创建 Bean

  3. 创建 applicationContext.xml

  4. 在配置文件中进行 Bean 配置

  5. 创建 ApplicationContext 对象,执行 getBean

Spring 相关 API

API 继承体系介绍

Spring 的 API 体系异常庞大,现在只关注两个 BeanFactory 和 ApplicationContext

BeanFactory 是 ApplicationContext 的父接口,ApplicationContext 接口下又有 FileSystemXmlApplicationContext 和 ClassPathXmlApplicationContext 子接口

BeanFactory

BeanFactory 是 IOC 容器的核心接口,它定义了 IOC 的基本功能

特点:在第一次调用 getBean() 方法时,创建指定对象的实例

@Testpublic void test2(){    // 核心接口,不会创建 bean 对象存到容器中    BeanFactory xmlBeanFactory = new XmlBeanFactory(new ClassPathResource("applicationContext.xml"));

    // getBean 的时候才真正创建 bean 对象    IUserDao userDao = (IUserDao) xmlBeanFactory.getBean("userDao");

    // 调用方法    userDao.save();}

ApplicationContext

代表应用上下文对象,可以获得 spring 中 IOC 容器的 Bean 对象

特点:在 spring 容器启动时,加载并创建所有对象的实例

常用实现类:

  1. ClassPathXmlApplicationContext - 它是从类的根路径下加载配置文件,推荐使用这种。

  2. FileSystemXmlApplicationContext - 它是从磁盘路径上加载配置文件,配置文件可以在磁盘的任意位置。

  3. AnnotationConfigApplicationContext - 当使用注解配置容器对象时,需要使用此类来创建 spring 容器,它用来读取注解。

常用方法:

  1. Object getBean(String name); - 根据 Bean 的 id 从容器中获得 Bean 实例,返回是 Object,需要强转。

  2. T getBean(ClassrequiredType); - 根据类型从容器中匹配 Bean 实例,当容器中相同类型的 Bean 有多个时,则此方法会报错。

  3. T getBean(String name,ClassrequiredType); - 根据 Bean 的 id 和类型获得 Bean 实例,解决容器中相同类型 Bean 有多个情况。

Spring 配置文件

Bean 标签基本配置

用于配置对象交由 Spring 来创建。

默认情况下它调用的是类中的无参构造函数,如果没有无参构造函数则不能创建成功。

<bean id="" class=""/>

Bean 标签范围配置

<bean id="" class="" scope="">bean>

scope 属性指对象的作用范围,取值如下:

  • singleton - 默认值,单例的

  • prototype - 多例的

  • request - WEB 项目中,Spring 创建一个 Bean 的对象,将对象存入到 request 域中

  • session - WEB 项目中,Spring 创建一个 Bean 的对象,将对象存入到 session 域中

  • global session - WEB 项目中,应用在 Portlet 环境,如果没有 Portlet 环境那么 global Session 相当于 session

测试 scope 属性:

@Testpublic void test3(){    ApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");

    IUserDao userDao_1 = (IUserDao) classPathXmlApplicationContext.getBean("userDao");    IUserDao userDao_2 = (IUserDao) classPathXmlApplicationContext.getBean("userDao");

    System.out.println(userDao_1);    System.out.println(userDao_2);}

当 scope 的取值为 singleton 时:

- Bean 的实例化个数:1 个- Bean 的实例化时机:当 Spring 核心文件被加载时,实例化配置的 Bean 实例- Bean 的生命周期:        + 对象创建:当应用加载,创建容器时,对象就被创建了        + 对象运行:只要容器在,对象一直活着        + 对象销毁:当应用卸载,销毁容器时,对象就被销毁了

当 scope 的取值为 prototype 时:

- Bean 的实例化个数:多个- Bean 的实例化时机:当调用 getBean() 方法时实例化 Bean- Bean 的生命周期:          + 对象创建:当使用对象时,创建新的对象实例          + 对象运行:只要对象在使用中,就一直活着          + 对象销毁:当对象长时间不用时,被 Java 的垃圾回收器回收了

Bean 生命周期配置

applicationContext.xml

<bean id="userDao" class="com.renda.dao.impl.UserDaoImpl" init-method="init" destroy-method="destroy"/>
  • init-method:指定类中的初始化方法名称

  • destroy-method:指定类中销毁方法名称

public class UserDaoImpl implements IUserDao {    public void init() {        System.out.println("UserDao: Initialize...");    }

    public void destroy() {        System.out.println("UserDao: Destroy");    }

    ...}

测试代码

@Testpublic void test4(){    ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");

    IUserDao userDao_1 = (IUserDao) classPathXmlApplicationContext.getBean("userDao");    IUserDao userDao_2 = (IUserDao) classPathXmlApplicationContext.getBean("userDao");

    System.out.println(userDao_1);    System.out.println(userDao_2);

    // 触发 destroy 方法    classPathXmlApplicationContext.close();}

Bean 实例化三种方式

无参构造方法实例化

它会根据默认无参构造方法来创建类对象,如果 bean 中没有默认无参构造函数,将会创建失败

<bean id="userDao" class="com.renda.dao.impl.UserDaoImpl"/>
工厂静态方法实例化

应用场景:依赖的 jar 包中有个 A 类,A 类中有个静态方法 m1,m1 方法的返回值是一个 B 对象。如果频繁使用 B 对象,此时可以将 B 对象的创建权交给 spring 的 IOC 容器,以后在使用 B 对象时,无需调用 A 类中的 m1 方法,直接从 IOC 容器获得。

public class StaticFactoryBean {    public static IUserDao createUserDao(){        return  new UserDaoImpl();    }}
<?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:p="http://www.springframework.org/schema/p"xsi:schemaLocation="http://www.springframework.org/schema/beans        http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="userDao" class="com.renda.factory.StaticFactoryBean" factory-method="createUserDao"/>

beans>
工厂普通方法实例化

应用场景:依赖的 jar 包中有个 A 类,A 类中有个普通方法 m1,m1 方法的返回值是一个 B 对象。如果我们频繁使用 B 对象,此时我们可以将 B 对象的创建权交给 spring 的 IOC 容器,以后我们在使用 B 对象时,无需调用 A 类中的 m1 方法,直接从 IOC 容器获得。

public class DynamicFactoryBean {    public IUserDao createUserDao(){        return  new UserDaoImpl();    }}
<?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:p="http://www.springframework.org/schema/p"xsi:schemaLocation="http://www.springframework.org/schema/beans        http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="dynamicFactoryBean" class="com.renda.factory.DynamicFactoryBean"/>    <bean id="userDao" factory-bean="dynamicFactoryBean" factory-method="createUserDao"/>

beans>

Bean 依赖注入方式

依赖注入 DI(Dependency Injection):它是 Spring 框架核心 IOC 的具体实现。

在编写程序时,通过控制反转,把对象的创建交给了 Spring,但是代码中不可能出现没有依赖的情况。IOC 解耦只是降低他们的依赖关系,但不会消除。例如:业务层仍会调用持久层的方法。

这种业务层和持久层的依赖关系,在使用 Spring 之后,就让 Spring 来维护了。简单的说,就是通过框架把持久层对象传入业务层,而不用手动去获取。

Bean 依赖注入方式

构造方法

UserServiceImpl 中创建有参构造

public class UserServiceImpl implements IUserService {    IUserDao userDao;

    public UserServiceImpl(IUserDao userDao) {        this.userDao = userDao;    }

    public UserServiceImpl() {    }

    @Override    public void save() {        // 调用 dao 层的 save 方法        userDao.save();    }}

配置 Spring 容器调用有参构造时进行注入

<bean id="userDao" class="com.renda.dao.impl.UserDaoImpl"/><bean id="userService" class="com.renda.service.impl.UserServiceImpl">

    <constructor-arg name="userDao" ref="userDao"/>bean>
`set` 方法

UserServiceImpl 中创建 set 方法

public class UserServiceImpl implements IUserService {    IUserDao userDao;

    public UserServiceImpl(IUserDao userDao) {        this.userDao = userDao;    }

    public UserServiceImpl() {    }

    @Override    public void save() {        // 调用 dao 层的 save 方法        userDao.save();    }

    public void setUserDao(IUserDao userDao) {        this.userDao = userDao;    }}

配置 Spring 容器调用 set 方法进行注入

<bean id="userDao" class="com.renda.dao.impl.UserDaoImpl"/><bean id="userService" class="com.renda.service.impl.UserServiceImpl">

    <property name="userDao" ref="userDao">property>bean>
P 命名空间注入

P 命名空间注入本质也是 set 方法注入,但比起上述的 set 方法注入更加方便,主要体现在配置文件 applicationContext.xml 中。

首先,需要引入 P 命名空间:

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

其次,需要修改注入方式:

<bean id="userDao" class="com.renda.dao.impl.UserDaoImpl"/><bean id="userService" class="com.renda.service.impl.UserServiceImpl" p:userDao-ref="userDao"/>

Bean 依赖注入的数据类型

上面操作,都是注入 Bean 对象,除了对象的引用可以注入,普通数据类型和集合都可以在容器中进行注入。

注入数据的三种数据类型:

  1. 普通数据类型

  2. 引用数据类型

  3. 集合数据类型

之前的操作都是对 UserDao 对象的引用进行注入的,属于引用数据类型注入。下面将以 set 方法注入为例,演示普通数据类型和集合数据类型的注入。

注入普通数据类型
public class UserDaoImpl implements IUserDao {    private String username;    private Integer age;

    public void setUsername(String username) {        this.username = username;    }

    public void setAge(Integer age) {        this.age = age;

    @Override    public void save() {        System.out.println(username);        System.out.println(age);        System.out.println("UserDao: save...");    }}
<bean id="userDao" class="com.renda.dao.impl.UserDaoImpl">

    <property name="username" value="张人大"/>    <property name="age" value="25"/>bean>
注入集合数据类型

List 集合注入

public class User {    private String username;    private Integer age;

    public void setUsername(String username) {        this.username = username;    }

    public void setAge(Integer age) {        this.age = age;    }}
public class UserDaoImpl implements IUserDao {    private List list;public void setList(List list) {this.list = list;    }@Overridepublic void save() {        System.out.println("List:" + list);    }}
<bean id="user" class="com.renda.domain.User">    <property name="username" value="布莱尔"/>    <property name="age" value="18"/>bean>

<bean id="userDao" class="com.renda.dao.impl.UserDaoImpl">    <property name="list">        <list>            <value>aaavalue>            <ref bean="user"/>        list>    property>bean>

Set 集合注入

public class UserDaoImpl implements IUserDao {    private Set set;public void setSet(Set set) {this.set = set;    }@Overridepublic void save() {        System.out.println("Set:" + set);    }}
<bean id="user" class="com.renda.domain.User">    <property name="username" value="布莱尔"/>    <property name="age" value="18"/>bean>

<bean id="userDao" class="com.renda.dao.impl.UserDaoImpl">    <property name="set">        <set>            <value>bbbvalue>            <ref bean="user"/>        set>    property>bean>

Array 数组注入

public class UserDaoImpl implements IUserDao {    private Object[] array;

    public void setArray(Object[] array) {        this.array = array;    }

    @Override    public void save() {        System.out.println("Array:" + Arrays.toString(array));    }}
<bean id="userDao" class="com.renda.dao.impl.UserDaoImpl">    <property name="array">        <array>            <value>cccvalue>            <ref bean="user"/>        array>    property>bean>

Map 集合注入

public class UserDaoImpl implements IUserDao {    private Map map;public void setMap(Map map) {this.map = map;    }@Overridepublic void save() {        System.out.println("Map:" + map);    }}
<bean id="userDao" class="com.renda.dao.impl.UserDaoImpl">    <property name="map">        <map>            <entry key="k1" value="ddd"/>            <entry key="k2" value-ref="user"/>        map>    property>bean>

Properties 配置注入

public class UserDaoImpl implements IUserDao {    private Properties properties;

    public void setProperties(Properties properties) {        this.properties = properties;    }

    @Override    public void save() {        System.out.println("Properties:" + properties);    }}
<bean id="userDao" class="com.renda.dao.impl.UserDaoImpl">    <property name="properties">        <props>            <prop key="k1">v1prop>            <prop key="k2">v2prop>            <prop key="k3">v3prop>        props>    property>bean>

配置文件模块化

实际开发中,Spring 的配置内容非常多,这就导致 Spring 配置很繁杂且体积很大,所以,可以将部分配置拆解到其他配置文件中,也就是所谓的配置文件模块化。

并列的多个配置文件:

ApplicationContext ac = new ClassPathXmlApplicationContext("beans1.xml", "beans2.xml", "...");

主从配置文件:

<import resource="applicationContext-xxx.xml"/>

注意:

同一个 xml 中不能出现相同名称的 bean,如果出现会报错。

多个 xml 如果出现相同名称的 bean,不会报错,但是后加载的会覆盖前加载的 bean

小结

Spring 的重点配置

<bean> 标签:创建对象并放到 spring 的 IOC 容器    id 属性: 在容器中 Bean 实例的唯一标识,不允许重复    class 属性: 要实例化的 Bean 的全限定名    scope 属性: Bean 的作用范围,常用是 Singleton (默认) 和 prototype

<constructor-arg> 标签:属性注入    name 属性:属性名称    value 属性:注入的普通属性值    ref 属性:注入的对象引用值

<property> 标签:属性注入    name 属性:属性名称    value 属性:注入的普通属性值    ref 属性:注入的对象引用值    <list>    <set>    <array>    <map>    <properties>

<import> 标签: 导入其他的 Spring 的分文件

IOC 实战 - DbUtils

DbUtils 是什么?

DbUtils 是 Apache 的一款用于简化 Dao 代码的工具类,它底层封装了 JDBC 技术。

核心对象:

QueryRunner queryRunner = new QueryRunner(DataSource dataSource);

核心方法:

  • int update() - 执行增、删、改语句

  • T query() - 执行查询语句

  • ResultSetHandler - 这是一个接口,主要作用是将数据库返回的记录封装到实体对象

查询数据库所有账户信息到 Account 实体中:

public class DbUtilsTest {    @Test    public void findAllTest() throws Exception {        // 创建 DBUtils 工具类,传入连接池        QueryRunner queryRunner = new QueryRunner(JdbcUtils.getDataSource());        // 编写 sql        String sql = "select * from account";        // 执行 sql        List list = queryRunner.query(sql, new BeanListHandler (Account.class));// 打印结果for (Account account : list) {            System.out.println(account);        }    }}

Spring 的 `xml` 整合 `DbUtils`

介绍

需求:基于 Spring 的 xml 配置实现账户的 CRUD 案例

步骤分析:

  1. 准备数据库环境

  2. 创建 java 项目,导入坐标

  3. 编写 Account 实体类

  4. 编写 AccountDao 接口和实现类

  5. 编写 AccountService 接口和实现类

  6. 编写 spring 核心配置文件

  7. 编写测试代码

实现

准备数据库环境

CREATE DATABASE `spring_db`;USE `spring_db`;CREATE TABLE `account` (    `id` int(11) NOT NULL AUTO_INCREMENT,    `name` varchar(32) DEFAULT NULL,    `money` double DEFAULT NULL,    PRIMARY KEY (`id`));insert  into `account`(`id`,`name`,`money`) values (1,'tom',1000),(2,'jerry',1000);

创建 java 项目,导入坐标

<?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/xsd/maven-4.0.0.xsd">    <modelVersion>4.0.0modelVersion>

    <groupId>com.rendagroupId>    <artifactId>spring_dbutilsartifactId>    <version>1.0-SNAPSHOTversion>

    <properties>        <project.build.sourceEncoding>UTF-8project.build.sourceEncoding>        <maven.compiler.encoding>UTF-8maven.compiler.encoding>        <java.version>1.11java.version>        <maven.compiler.source>1.11maven.compiler.source>        <maven.compiler.target>1.11maven.compiler.target>    properties>

    <dependencies>        <dependency>            <groupId>mysqlgroupId>            <artifactId>mysql-connector-javaartifactId>            <version>5.1.47version>        dependency>        <dependency>            <groupId>com.alibabagroupId>            <artifactId>druidartifactId>            <version>1.1.9version>        dependency>        <dependency>            <groupId>commons-dbutilsgroupId>            <artifactId>commons-dbutilsartifactId>            <version>1.6version>        dependency>        <dependency>            <groupId>org.springframeworkgroupId>            <artifactId>spring-contextartifactId>            <version>5.1.5.RELEASEversion>        dependency>        <dependency>            <groupId>junitgroupId>            <artifactId>junitartifactId>            <version>4.12version>        dependency>

        <dependency>            <groupId>javax.annotationgroupId>            <artifactId>javax.annotation-apiartifactId>            <version>1.3.2version>        dependency>    dependencies>project>

编写 Account 实体类

public class Account {

    private Integer id;    private String name;    private Double money;

    @Override    public String toString() {        return "Account{" +                "id=" + id +                ", name='" + name + '\'' +                ", money=" + money +                '}';    }

    public Integer getId() {        return id;    }

    public void setId(Integer id) {        this.id = id;    }

    public String getName() {        return name;    }

    public void setName(String name) {        this.name = name;    }

    public Double getMoney() {        return money;    }

    public void setMoney(Double money) {        this.money = money;    }}

编写 AccountDao 接口和实现类

public interface AccountDao {

    List findAll();

    Account findById(Integer id);

    void save(Account account);

    void update(Account account);

    void delete(Integer id);

}
public class AccountDaoImpl implements AccountDao {    private QueryRunner queryRunner;

    public void setQueryRunner(QueryRunner queryRunner) {        this.queryRunner = queryRunner;    }

    @Override    public List findAll() {        List list = null;// 编写 sql        String sql = "select * from account";try {// 执行 sql            list = queryRunner.query(sql, new BeanListHandler(Account.class));        } catch (SQLException e) {            e.printStackTrace();        }return list;    }@Overridepublic Account findById(Integer id) {        Account query = null;// 编写 sql        String sql = "select * from account where id = ?";try {            query = queryRunner.query(sql, new BeanHandler(Account.class), id);        } catch (SQLException e) {            e.printStackTrace();        }return query;    }@Overridepublic void save(Account account) {        String sql = "insert into account values(null, ?, ?)";try {            queryRunner.update(sql, account.getName(), account.getMoney());        } catch (SQLException e) {            e.printStackTrace();        }    }@Overridepublic void update(Account account) {        String sql = "update `account` set `name` = ?, `money` = ? where `id` = ?";try {            queryRunner.update(sql, account.getName(), account.getMoney(), account.getId());        } catch (SQLException e) {            e.printStackTrace();        }    }@Overridepublic void delete(Integer id) {        String sql = "delete from `account` where `id` = ?";try {            queryRunner.update(sql, id);        } catch (SQLException e) {            e.printStackTrace();        }    }}

编写 AccountService 接口和实现类

public interface AccountService {    List findAll();

    Account findById(Integer id);

    void save(Account account);

    void update(Account account);

    void delete(Integer id);}
public class AccountServiceImpl implements AccountService {    private AccountDao accountDao;

    public void setAccountDao(AccountDao accountDao) {        this.accountDao = accountDao;    }

    @Override    public List findAll() {        return accountDao.findAll();    }

    @Override    public Account findById(Integer id) {        return accountDao.findById(id);    }

    @Override    public void save(Account account) {        accountDao.save(account);    }

    @Override    public void update(Account account) {        accountDao.update(account);    }

    @Override    public void delete(Integer id) {        accountDao.delete(id);    }}

编写 spring 核心配置文件

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: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">

    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">        <property name="driverClassName" value="com.mysql.jdbc.Driver"/>        <property name="url" value="jdbc:mysql:///spring_db?characterEncoding=utf8&useSSL=false"/>        <property name="username" value="root"/>        <property name="password" value="password"/>    bean>

    <bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner">        <constructor-arg name="ds" ref="dataSource"/>    bean>

    <bean id="accountDao" class="com.renda.dao.impl.AccountDaoImpl">        <property name="queryRunner" ref="queryRunner"/>    bean>

    <bean id="accountService" class="com.renda.service.impl.AccountServiceImpl">        <property name="accountDao" ref="accountDao"/>    bean>

beans>

编写测试代码

public class AccountServiceTest {

    ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");    AccountService accountService = (AccountService) applicationContext.getBean("accountService");

    // 测试添加    @Test    public void testSave(){        Account account = new Account();        account.setName("renda");        account.setMoney(888d);        accountService.save(account);    }

    // 测试查询    @Test    public void testFindById(){        Account account = accountService.findById(3);        System.out.println(account);    }

    // 测试查询所有    @Test    public void testFindAll(){        List all = accountService.findAll();for (Account account : all) {            System.out.println(account);        }    }// 测试更新@Testpublic void testUpdate(){        Account account = new Account();        account.setId(3);        account.setName("Blair");        account.setMoney(2000d);        accountService.update(account);    }// 测试删除@Testpublic void testDelete(){        accountService.delete(3);    }}

抽取 JDBC 配置文件

applicationContext.xml 加载 jdbc.properties 配置文件获得连接信息。
首先,需要引入 context 命名空间和约束路径:

* 命名空间:    xmlns:context="http://www.springframework.org/schema/context"* 约束路径:    http://www.springframework.org/schema/context     http://www.springframework.org/schema/context/spring-context.xsd
<context:property-placeholder location="classpath:jdbc.properties"/>

<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">    <property name="driverClassName" value="${jdbc.driverClassName}"/>    <property name="url" value="${jdbc.url}"/>    <property name="username" value="${jdbc.username}"/>    <property name="password" value="${jdbc.password}"/>bean>

小结

* DataSource 的创建权交由 Spring 容器去完成* QueryRunner 的创建权交由 Spring 容器去完成,使用构造方法传递 DataSource* Spring 容器加载 properties 文件    <context:property-placeholder location="xx.properties"/><property name="" value="${key}"/>

Spring 注解开发

Spring 是轻代码而重配置的框架,配置比较繁重,影响开发效率,所以注解开发是一种趋势,注解代替 xml 配置文件可以简化配置,提高开发效率。

Spring 常用注解

介绍

Spring 常用注解主要是替代 的配置

  • @Component - 使用在类上用于实例化 Bean

  • @Controller - 使用在 web 层类上用于实例化 Bean

  • @Service - 使用在 service 层类上用于实例化 Bean

  • @Repository - 使用在 dao 层类上用于实例化 Bean

  • @Autowired - 使用在字段上用于根据类型依赖注入;当使用注解注入属性时,set 方法可以省略

  • @Qualifier - 结合 @Autowired 一起使用,根据名称进行依赖注入;在自动按照类型注入基础之上,再按照 Bean 的 id 注入;它给字段注入时必须和 @Autowired 一起使用,但是给方法参数注入时可以独立使用

  • @Resource - 相当于 @Autowired + @Qualifier,按照名称进行注入

  • @Value - 注入普通属性

  • @Scope - 标注 Bean 的作用范围

  • @PostConstruct - 使用在方法上标注该方法是 Bean 的初始化方法

  • @PreDestroy - 使用在方法上标注该方法是 Bean 的销毁方法

说明:JDK 11 以后完全移除了 javax 扩展导致不能使用 @resource 注解,如果要使用它需要在 Maven 引入依赖:

<dependency>    <groupId>javax.annotationgroupId>    <artifactId>javax.annotation-apiartifactId>    <version>1.3.2version>dependency>

注意:使用注解进行开发时,需要在 applicationContext.xml 中配置组件扫描,作用是指定哪个包及其子包下的 Bean 需要进行扫描以便识别使用注解配置的类、字段和方法。

<context:component-scan base-package="com.renda">context:component-scan>
实现
Bean 实例化(IOC)
<bean id="userDao1" class="com.renda.dao.impl.UserDaoImpl">bean>

使用 @Component@Repository 标识 UserDaoImpl 需要 Spring 进行实例化。

// @Component(value = "userDao")@Repository // 如果没有写 value 属性值,Bean 的 id 为:类名首字母小写public class UserDaoImpl implements UserDao {    ...}
属性依赖注入(DI)
<bean id="userService" class="com.lagou.service.impl.UserServiceImpl">    <property name="userDao" ref="userDao1"/>bean>

使用 @Autowired 或者 @Autowired + @Qulifier 或者 @Resource 进行 userDao 的注入

@Servicepublic class UserServiceImpl implements UserService {    @Autowired    // @Qualifier("userDao1")    // @Resource(name = "userDao1")    private UserDao userDao;

    public void setUserDao(UserDao userDao) {        this.uDao = userDao;    }}
`@Value`

使用 @Value 进行字符串的注入,结合 SpEL (Spring Expression Language) 表达式获得配置参数

@Servicepublic class UserServiceImpl implements UserService {    @Value("注入普通数据")    private String str;

    @Value("${jdbc.driver}")    private String driver;}
`@Scope`
<bean scope=""/>

使用 @Scope 标注 Bean 的范围

@Service@Scope("singleton")public class UserServiceImpl implements UserService {    ...}
`Bean` 生命周期
<bean init-method="init" destroy-method="destory" />

使用 @PostConstruct 标注初始化方法,使用 @PreDestroy 标注销毁方法

@PostConstructpublic void init(){    System.out.println("初始化方法....");}

@PreDestroypublic void destroy(){    System.out.println("销毁方法.....");}
Spring 常用注解整合 `DbUtils`

步骤分析:

  1. 拷贝 xml 配置项目,改为注解配置项目

  2. 修改 AccountDaoImpl 实现类

  3. 修改 AccountServiceImpl 实现类

  4. 修改 Spring 核心配置文件

  5. 编写测试代码

修改 `AccountDaoImpl` 实现类
@Repositorypublic class AccountDaoImpl implements AccountDao {    @Autowired    private QueryRunner queryRunner;    ...}
修改 `AccountServiceImpl` 实现类
@Service(value = "accountService")public class AccountServiceImpl implements AccountService {    @Autowired    private AccountDao accountDao;    ...}
修改 spring 核心配置文件
<?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:component-scan base-package="com.renda"/>

    <context:property-placeholder location="classpath:jdbc.properties"/>

    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">        <property name="driverClassName" value="${jdbc.driverClassName}"/>        <property name="url" value="${jdbc.url}"/>        <property name="username" value="${jdbc.username}"/>        <property name="password" value="${jdbc.password}"/>    bean>

    <bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner">        <constructor-arg name="ds" ref="dataSource"/>    bean>

beans>
Spring 新注解

使用上面的注解还不能全部替代 xml 配置文件,还需要使用注解替代的配置如下:

非自定义的 Bean 的配置:<bean>加载 properties 文件的配置:<context:property-placeholder>组件扫描的配置:<context:component-scan>引入其他文件:<import>
  • @Configuration - 用于指定当前类是一个 Spring 配置类,当创建容器时会从该类上加载注解

  • @Bean - 使用在方法上,标注将该方法的返回值存储到 Spring 容器中

  • @PropertySource - 用于加载 properties 文件中的配置

  • @ComponentScan - 用于指定 Spring 在初始化容器时要扫描的包

  • @Import - 用于导入其他配置类

Spring 纯注解整合 `DbUtils`

步骤分析:

  1. 编写 Spring 核心配置类

  2. 编写数据库配置信息类

  3. 编写测试代码

编写 Spring 核心配置类
@Configuration@ComponentScan("com.renda")@Import(DataSourceConfig.class)public class SpringConfig {    @Bean("queryRunner")    public QueryRunner getQueryRunner(@Autowired DataSource dataSource){        return new QueryRunner(dataSource);    }}
编写数据库配置信息类
@PropertySource("classpath:jdbc.properties")public class DataSourceConfig {    @Value("${jdbc.driverClassName}")    private String driver;    @Value("${jdbc.url}")    private String url;    @Value("${jdbc.username}")    private String username;    @Value("${jdbc.password}")    private String password;

    @Bean("dataSource")    public DataSource getDataSource(){        DruidDataSource druidDataSource = new DruidDataSource();        druidDataSource.setDriverClassName(driver);        druidDataSource.setUrl(url);        druidDataSource.setUsername(username);        druidDataSource.setPassword(password);        return druidDataSource;    }}
编写测试代码
public class AccountServiceTest {

    /* ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("com/renda/service/applicationContext.xml");    AccountService accountService = (AccountService) applicationContext.getBean("accountService"); */

    // 当前改成了纯注解形式    AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);    AccountService accountService = (AccountService) applicationContext.getBean("accountService");

    ...}

Spring 整合 Junit

普通 Junit 测试问题

在普通的测试类中,需要开发者手动加载配置文件并创建 Spring 容器,然后通过 Spring 相关 API 获得 Bean 实例;如果不这么做,那么无法从容器中获得对象。

开发者可以让 Spring Junit 负责创建 Spring 容器来简化这个操作,直接在测试类注入 Bean 实例;但是需要将配置文件的名称告诉它。

Spring 整合 Junit

步骤分析:

  1. 导入 spring 集成 Junit 的坐标

  2. 使用 @Runwith 注解替换原来的运行器

  3. 使用 @ContextConfiguration 指定配置文件或配置类

  4. 使用 @Autowired 注入需要测试的对象

  5. 创建测试方法进行测试

导入 spring 集成 Junit 的坐标
<dependency>    <groupId>org.springframeworkgroupId>    <artifactId>spring-testartifactId>    <version>5.1.5.RELEASEversion>dependency><dependency>    <groupId>junitgroupId>    <artifactId>junitartifactId>    <version>4.12version>dependency>
使用 `@Runwith` 注解替换原来的运行器
@RunWith(SpringJUnit4ClassRunner.class)public class AccountServiceTest {    ...}
使用 `@ContextConfiguration` 指定配置文件或配置类
@RunWith(SpringJUnit4ClassRunner.class)// @ContextConfiguration(value = {"classpath:applicationContext.xml"})@ContextConfiguration(classes = {SpringConfig.class})public class AccountServiceTest {     ...   }
使用 `@Autowired` 注入需要测试的对象
@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration(classes = {SpringConfig.class})public class AccountServiceTest {    @Autowired    private AccountService accountService;    ...   }

控制反转_Spring:IOC 控制反转相关推荐

  1. 控制反转(ioc)和 面向切面(AOP)

    1. IOC(Inversion of control)不是技术,是一种编程思想.ioc意味着将设计好的对象交给容器控制,而不是传统的在对象内部直接控制. 2. IOC(控制反转)  '谁控制了谁 控 ...

  2. 控制反转(IOC)模式

    依赖倒置:依赖抽象,而不依赖具体实现. 控制反转(Inversion of Control):提倡实现松耦合层.组件和类的设计原则,颠倒程序的控制流程.IoC使用分离执行特定问题处理代码的概念: Io ...

  3. Spring的控制反转(IOC)和依赖注入(DI)具体解释

    Spring的控制反转(IOC)和依赖注入(DI)具体解释 首先介绍下(IOC)控制反转: 所谓控制反转就是应用本身不负责依赖对象的创建及维护,依赖对象的创建及维护是由外部容器负责的.这样控制器就有应 ...

  4. Spring IOC(控制反转)详解及示例

    控制反转--Spring通过一种称作控制反转(IOC)的技术促进了低耦合.当应用了IoC,一个对象依赖的其它对象会通过被动的方式传递进来,而不是这个对象自己创建或者查找依赖对象.你可以认为IoC与JN ...

  5. 【IOC 控制反转】Android 事件依赖注入 ( 事件依赖注入具体的操作细节 | 创建 事件监听器 对应的 动态代理 | 动态代理的数据准备 | 创建调用处理程序 | 创建动态代理实例对象 )

    文章目录 前言 一.创建 事件监听器 对应的 动态代理 二.动态代理 数据准备 三.动态代理 调用处理程序 四.动态代理 实例对象创建 前言 Android 依赖注入的核心就是通过反射获取 类 / 方 ...

  6. 【IOC 控制反转】Android 事件依赖注入 ( 事件依赖注入具体的操作细节 | 获取要注入事件的 View 对象 | 通过反射获取 View 组件的事件设置方法 )

    文章目录 前言 一.获取要注入事件的 View 对象 二.通过反射获取 View 组件的事件设置方法并执行 前言 Android 依赖注入的核心就是通过反射获取 类 / 方法 / 字段 上的注解 , ...

  7. 【IOC 控制反转】Android 事件依赖注入 ( 事件依赖注入具体的操作细节 | 获取 Activity 中的所有方法 | 获取方法上的注解 | 获取注解上的注解 | 通过注解属性获取事件信息 )

    文章目录 前言 一.获取 Activity 中的所有方法 二.获取方法上的注解 三.获取注解上的注解 四.通过注解属性获取相关事件信息 前言 Android 依赖注入的核心就是通过反射获取 类 / 方 ...

  8. 【IOC 控制反转】Android 事件依赖注入 ( 事件依赖注入代码示例 )

    文章目录 总结 一.Android 事件依赖注入示例 1.创建依赖注入库 2.声明注解 (1).修饰注解的注解 (2).修饰方法的注解 3.Activity 基类 4.动态代理类调用处理程序 5.依赖 ...

  9. 【IOC 控制反转】Android 视图依赖注入 ( 视图依赖注入步骤 | 视图依赖注入代码示例 )

    文章目录 总结 一.Android 视图依赖注入步骤 二.Android 布局依赖注入示例 1.创建依赖注入库 2.声明注解 3.Activity 基类 4.依赖注入工具类 5.客户端 Activit ...

  10. 小菜学习设计模式(五)—控制反转(Ioc)

    写在前面 设计模式目录: 小菜学习设计模式(一)-模板方法(Template)模式 小菜学习设计模式(二)-单例(Singleton)模式 小菜学习设计模式(三)-工厂方法(Factory Metho ...

最新文章

  1. 造句简单_零基础也能说一口流利英语,用简单的you are造句学英语
  2. 网站内容页面如何优化才利于排名提升?
  3. Json反序列化与Java泛型
  4. boost::hana模块使用 Hana 实现基本维度分析的示例
  5. Oracle脚本笔记
  6. python使用print不换行
  7. 获取当前周和前一周周一和周天,下一周周一和周天
  8. Confluence 6 配置 Office 转换器
  9. 会员制营销系统_想提升门店经营水平?会员制营销法可以帮到你
  10. Mac OS开启黑暗模式
  11. 奶块最新服务器叫什么,奶块5.4.0版本更新公告
  12. c++ pdflib 生成中文内容
  13. android 歌曲的流派信息,音乐流派分类介绍.doc
  14. 2019python二级真题_2019年3月二级python真题,上岸必备!
  15. 【NEW02】Servlet 基础
  16. 有一天,你不上班后,打算干什么?
  17. 语音信号处理(一):对声母和韵母进行录音并时域分析
  18. 【信息安全案例】——身份与访问安全(学习笔记)
  19. Tkinter 的text使用方法
  20. iOS 通过github自动打包ipa

热门文章

  1. react学习(72)--row上面加样式
  2. react学习(54)--注意传递请求
  3. 前端学习(3048):vue+element今日头条管理-分页布局
  4. [html] 移动端如何禁止用户手动缩放页面?
  5. 25利他行为可以学习和模仿吗
  6. [jQuery] 你知道自定义事件吗?jQuery里的fire函数是什么意思,什么时候用?
  7. [css] 写出你知道的CSS水平和垂直居中的方法
  8. 前端学习(2667):退出编辑状态
  9. 前端学习(2531):Vuex中getter
  10. “约见”面试官系列之常见面试题之第七十四篇之v-if和v-for优先级(建议收藏)