Spring 入门

文章源码

Spring 概述

Spring

Spring 是分层的 Java SE/EE 应用全栈式轻量级开源框架,以 IOC(Inverse Of Control,反转控制)和 AOP(Aspect Oriented Programming,面向切面编程)为内核,提供了 表现层 Spring MVC 和 持久层 Spring JDBC 以及 业务层事务管理等众多技术。而且可以方便的整合其他开源框架和类库。

Spring 优势

  • 方便解耦,简化开发:通过 IOC 容器可以将对象间的依赖关系交由 Spring 进行控制,可以避免过度的程序耦合。而且也不需要再为单例模式、属性解析等底层需求编写代码。
  • 面向切面:通过 AOP,可以方便进行面向切面的编程,弥补面向对象的一切缺陷。
  • 声明式事务:通过 声明式方式灵活的进行事务管理,可以提高开发效率和质量。
  • 源码学习典范:Spring 的源代码设计精妙、结构清晰、匠心独用,处处体现着对 Java 设计模式灵活运用以及对 Java 技术的高深造诣。它的源代码是 Java 技术的最佳实践的范例。

Spring 体系结构

程序的耦合和解耦

耦合性是对模块间关联程度的度量。耦合的强弱取决于模块间接口的复杂性、调用模块的方式以及通过界面传送数据的多少。

耦合的分类

  • 内容耦合:当一个模块直接修改或操作另一个模块的数据时,或一个模块不通过正常入口而转入另一个模块时。内容耦合是最高程度的耦合,应该避免使用。
  • 公共耦合:两个或两个以上的模块共同引用一个全局数据项,这种耦合被称为公共耦合。在具有大量公共耦合的结构中,确定究竟是哪个模块给全局变量赋了一个特定的值是十分困难的。
  • 外部耦合:一组模块都访问同一全局简单变量而不是同一全局数据结构,而且不是通过参数表传递该全局变量的信息,则称之为外部耦合。
  • 控制耦合:一个模块通过接口向另一个模块传递一个控制信号,接受信号的模块根据信号值而进行适当的动作,这种耦合被称为控制耦合。
  • 标记耦合:若一个模块 A 通过接口向两个模块 B 和 C 传递一个公共参数,那么称模块 B 和 C 之间存在一个标记耦合。
  • 数据耦合:模块之间通过参数来传递数据,那么被称为数据耦合。数据耦合是最低的一种耦合形式,系统中一般都存在这种类型的耦合,因为为了完成一些有意义的功能,往往需要将某些模块的输出数据作为另一些模块的输入数据。
  • 非直接耦合:两个模块之间没有直接关系,它们之间的联系完全是通过主模块的控制和调用来实现的。

总结起来,就是如果模块间必须存在耦合,就尽量使用数据耦合,少用控制耦合,限制公共耦合的范围,尽量避免使用内容耦合。

程序耦合举例

  • AccountDAOImpl.java

    package cn.parzulpan.dao;
    
    /**
    * @Author : parzulpan
    * @Time : 2020-12
    * @Desc : 账户持久层接口的实现类
    */ public class AccountDAOImpl implements AccountDAO{
    /**
    * 模拟保存账户
    */
    public void saveAccount() {
    System.out.println("保存了账户...");
    }
    }
  • AccountServiceImpl.java

    package cn.parzulpan.service;
    
    import cn.parzulpan.dao.AccountDAO;
    import cn.parzulpan.dao.AccountDAOImpl; /**
    * @Author : parzulpan
    * @Time : 2020-12
    * @Desc : 账户业务层接口的实现类
    */ public class AccountServiceImpl implements AccountService{
    private AccountDAO accountDAO = new AccountDAOImpl(); // 这里发生了耦合 /**
    * 模拟保存账户
    */
    public void saveAccount() {
    accountDAO.saveAccount();
    }
    }
  • Client.java

    package cn.parzulpan.ui;
    
    import cn.parzulpan.service.AccountServiceImpl;
    
    /**
    * @Author : parzulpan
    * @Time : 2020-12
    * @Desc : 模拟一个表现层,用于调用业务层,实际开发中应该是一个 Servlet 等
    */ public class Client {
    public static void main(String[] args) {
    AccountServiceImpl accountService = new AccountServiceImpl(); // 这里发生了耦合
    accountService.saveAccount();
    }
    }

Factory 解耦

在实际开发中可以把三层的对象都使用配置文件配置起来,当启动服务器应用加载的时候,让一个类中的方法通过读取配置文件,把这些对象创建出来并且存起来(用容器存储)。在接下来的使用的时候,直接拿过来用就行。

那么,这个读取配置文件,创建和获取三层对象的类就是工厂类。即两个步骤:

  • 通过读取配置文件来获取创建对象的全限定类名。
  • 使用反射来创建对象,避免使用 new 关键字。

工厂就是负责给从容器中获取指定对象的类,这时候获取对象的方式发生了改变。之前,在获取对象时,采用 new 的方式是主动的。现在,在获取对象时,采用跟工厂要的方式,工厂会查找或者创建对象,是被动的

之前

现在

  • bean.properties

    accountService=cn.parzulpan.service.AccountServiceImpl
    accountDAO = cn.parzulpan.dao.AccountDAOImpl
  • BeanFactory.java

    package cn.parzulpan.factory;
    
    import java.io.InputStream;
    import java.util.*; /**
    * @Author : parzulpan
    * @Time : 2020-12
    * @Desc : 工厂类,负责给从容器中获取指定对象的类
    */ public class BeanFactory {
    private static Properties properties; private static Map<String, Object> beans; // Factory 解耦的优化,存放创建的对象,称为容器 static {
    try {
    // 实例化对象
    properties = new Properties();
    // 获取文件流对象,使用类加载器
    InputStream is = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
    properties.load(is); beans = new HashMap<>();
    Enumeration<Object> keys = properties.keys();
    while (keys.hasMoreElements()) {
    String key = keys.nextElement().toString();
    String beanPath = properties.getProperty(key);
    Object instance = Class.forName(beanPath).newInstance();
    beans.put(key, instance);
    }
    } catch (Exception e) {
    e.printStackTrace();
    throw new ExceptionInInitializerError("初始化 Properties 失败!");
    }
    } /**
    * 获取指定对象的类
    * @param beanName
    * @return
    */
    public static Object getBean(String beanName){
    try {
    // return Class.forName(properties.getProperty(beanName)).newInstance(); // 两个步骤
    System.out.println(beanName + " " + beans.get(beanName));
    return beans.get(beanName); // 两个步骤
    } catch (Exception e) {
    e.printStackTrace();
    }
    return null;
    }
    }
  • AccountServiceImpl.java

    package cn.parzulpan.service;
    
    import cn.parzulpan.dao.AccountDAO;
    import cn.parzulpan.dao.AccountDAOImpl;
    import cn.parzulpan.factory.BeanFactory; /**
    * @Author : parzulpan
    * @Time : 2020-12
    * @Desc : 账户业务层接口的实现类
    */ public class AccountServiceImpl implements AccountService{
    // private AccountDAO accountDAO = new AccountDAOImpl(); // 这里发生了耦合 /**
    * 模拟保存账户
    */
    public void saveAccount() {
    AccountDAO accountDAO = (AccountDAO) BeanFactory.getBean("accountDAO"); // 通过 Factory 解耦
    if (accountDAO != null) {
    accountDAO.saveAccount();
    }
    }
    }
  • Client.java

    package cn.parzulpan.ui;
    
    import cn.parzulpan.factory.BeanFactory;
    import cn.parzulpan.service.AccountService;
    import cn.parzulpan.service.AccountServiceImpl; /**
    * @Author : parzulpan
    * @Time : 2020-12
    * @Desc : 模拟一个表现层,用于调用业务层,实际开发中应该是一个 Servlet 等
    */ public class Client {
    public static void main(String[] args) {
    // AccountServiceImpl accountService = new AccountServiceImpl(); // 这里发生了耦合 AccountService accountService = (AccountService) BeanFactory.getBean("accountService"); // 通过 Factory 解耦 if (accountService != null) {
    accountService.saveAccount();
    } // 通过 Factory 解耦存在的问题
    for (int i = 0; i < 5; ++i) {
    System.out.println(BeanFactory.getBean("accountService")); // 对象被创建多次
    }
    }
    }

IOC 解耦

  • bean.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
    https://www.springframework.org/schema/beans/spring-beans.xsd"> <!-- 把对象的创建交给 Spring 来管理-->
    <bean id="accountService" class="cn.parzulpan.service.AccountServiceImpl"/>
    <bean id="accountDAO" class="cn.parzulpan.dao.AccountDAOImpl"/> </beans>
  • ClientIOC.java

    package cn.parzulpan.ui;
    
    import cn.parzulpan.dao.AccountDAO;
    import cn.parzulpan.service.AccountService;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext; /**
    * @Author : parzulpan
    * @Time : 2020-12
    * @Desc : 使用 IOC
    */ public class ClientIOC {
    public static void main(String[] args) {
    // 使用 ApplicationContext 接口,获取 Spring 核心容器
    ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
    // 根据 id 获取 Bean 对象
    AccountService as = ac.getBean("accountService", AccountService.class);
    System.out.println(as);
    AccountDAO ad = ac.getBean("accountDAO", AccountDAO.class);
    System.out.println(ad);
    } }

IOC

IOC(Inverse Of Control,反转控制),把创建对象的权利交给 Spring 框架,它包括 DI(Dependency Injection,依赖注入)和 DL(Dependency Lookup,依赖查找)。

简单的说,IOC 是一种以被动接收的方式获取对象的思想,它主要是为了降低程序的耦合

bean 标签

  • 作用:用于配置对象让 Spring 来创建。默认情况下它调用的是类中的无参构造函数,如果没有无参构造函数则不能创建成功。
  • 属性
    • id:给对象在容器中提供一个唯一标识,用于获取对象
    • class:指定类的全限定类名,用于反射创建对象,默认情况下调用无参构造函数。
    • scope:指定对象的作用范围
      • singleton 默认值,单例的
      • prototype 多例的
      • request WEB 项目中,Spring 创建一个 Bean 的对象,将对象存入到 request 域中
      • session WEB 项目中,Spring 创建一个 Bean 的对象,将对象存入到 session 域中
      • global session WEB 项目中,应用在集群环境,如果没有集群环境那么 globalSession 相当于 session
    • init-method:指定类中的初始化方法名称。
    • destroy-method:指定类中销毁方法名称。

bean 的三种创建方式

第一种方式:使用默认无参构造函数。它会根据默认无参构造函数来创建类对象。如果 bean 中没有默认无参构造函数,将会创建失败。

    <bean id="accountServiceIOC" class="cn.parzulpan.service.AccountServiceImplIOC"/>
<bean id="accountDAOIOC" class="cn.parzulpan.dao.AccountDAOImplIOC"/>

第二种方式:使用实例工厂的方法创建对象。先把工厂的创建交给 Spring 来管理,然后在使用工厂的 bean 来调用里面的方法。

  • factory-bean 属性:用于指定实例工厂 bean 的 id
  • factory-method 属性:用于指定实例工厂中创建对象的方法
package cn.parzulpan.factory;

import cn.parzulpan.service.AccountService;
import cn.parzulpan.service.AccountServiceImplIOC; /**
* @Author : parzulpan
* @Time : 2020-12
* @Desc : Spring 管理实例工厂。模拟一个工厂类,该类可能存在于 jar 包中,无法通过修改源码来提供默认构造函数
*/ public class InstanceFactory {
public AccountService getAccountService() {
return new AccountServiceImplIOC();
}
}
    <bean id="instanceFactory" class="cn.parzulpan.factory.InstanceFactory"/>
<bean id="accountServiceIOC" factory-bean="instanceFactory" factory-method="getAccountService"/>
<bean id="accountDAOIOC" class="cn.parzulpan.dao.AccountDAOImplIOC"/>

第三种方式:使用静态工厂的方法创建对象。使用某个类中的静态方法创建对象,并存入 Spring 核心容器。

  • id 属性:指定 bean 的 id,用于从容器中获取
  • class 属性:指定静态工厂的全限定类名
  • factory-method 属性:指定生产对象的静态方法
package cn.parzulpan.factory;

import cn.parzulpan.service.AccountService;
import cn.parzulpan.service.AccountServiceImplIOC; /**
* @Author : parzulpan
* @Time : 2020-12
* @Desc : Spring 管理静态工厂。模拟一个工厂类,该类可能存在于 jar 包中,无法通过修改源码来提供默认构造函数
*/ public class StaticFactory {
public static AccountService getAccountService() {
return new AccountServiceImplIOC();
}
}
    <bean id="accountServiceIOC" class="cn.parzulpan.factory.StaticFactory" factory-method="getAccountService"/>
<bean id="accountDAOIOC" class="cn.parzulpan.dao.AccountDAOImplIOC"/>

测试 ClientIOC.java:

public class ClientIOC {
public static void main(String[] args) {
// 使用 ApplicationContext 接口,获取 Spring 核心容器
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml"); System.out.println("------"); //
AccountService asi = ac.getBean("accountServiceIOC", AccountService.class);
System.out.println(asi);
AccountDAO adi = ac.getBean("accountDAOIOC", AccountDAO.class);
System.out.println(adi);
adi.saveAccount(); } }

bean 的作用范围和生命周期

对于单例对象scope="singleton"

一个应用只有一个对象的实例,它的作用范围就是整个引用。

生命周期

  • 对象出生:当应用加载,创建容器时,对象就被创建了。
  • 对象活着:只要容器在,对象一直活着。
  • 对象死亡:当应用卸载,销毁容器时,对象就被销毁了。
    <!-- bean 的作用范围和生命周期 -->
<bean id="accountServiceIOC" class="cn.parzulpan.service.AccountServiceImplIOC" scope="singleton"
init-method="init" destroy-method="destroy"/>
<bean id="accountDAOIOC" class="cn.parzulpan.dao.AccountDAOImplIOC" scope="singleton"
init-method="init" destroy-method="destroy"/>

对于多例对象scope="prototype"

每次访问对象时,都会重新创建对象实例。

生命周期

  • 对象出生:当使用对象时,创建新的对象实例。
  • 对象活着:只要对象在使用中,就一直活着。
  • 对象死亡:当对象长时间不用时,被 java 的垃圾回收器回收了。
    <bean id="accountServiceIOC" class="cn.parzulpan.service.AccountServiceImplIOC" scope="prototype"
init-method="init" destroy-method="destroy"/>
<bean id="accountDAOIOC" class="cn.parzulpan.dao.AccountDAOImplIOC" scope="prototype"
init-method="init" destroy-method="destroy"/>

测试

        ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
AccountService asi = ac.getBean("accountServiceIOC", AccountService.class);
System.out.println(asi);
AccountDAO adi = ac.getBean("accountDAOIOC", AccountDAO.class);
System.out.println(adi);
adi.saveAccount(); ac.close(); // 手动关闭容器

DI

DI(Dependency Injection,依赖注入),它是 Spring IOC 的具体实现。因为 IOC 作用是降低耦合,那么依赖关系的维护都交给了 Spring,依赖关系的维护就称之为依赖注入。

能依赖注入的数据,有三类:

  • 基本数据类型和 String
  • 其他 Bean 类型,在配置文件中或者其他注解配置过的 Bean
  • 集合类型

依赖注入的方法,有三种:

  • 使用构造函数注入
  • 使用 set 方法注入
  • 使用注解注入

使用构造函数注入

类中需要提供一个对应参数列表的构造函数。

属性:

  • index 指定参数在构造函数参数列表的索引位置
  • type 指定参数在构造函数中的数据类型
  • name 指定参数在构造函数中的名称
  • value 它能赋的值是基本数据类型和 String 类型
  • ref 它能赋的值是其他 bean 类型,也就是说,必须得是在配置文件中配置过的 bean
  • 前三个都是找给谁赋值,后两个指的是赋什么值的
    <!-- 构造函数注入
类中需要提供一个对应参数列表的构造函数
属性:
index 指定参数在构造函数参数列表的索引位置
type 指定参数在构造函数中的数据类型
name 指定参数在构造函数中的名称
value 它能赋的值是基本数据类型和 String 类型
ref 它能赋的值是其他 bean 类型,也就是说,必须得是在配置文件中配置过的 bean
前三个都是找给谁赋值,后两个指的是赋什么值的
-->
<bean id="accountServiceDI" class="cn.parzulpan.service.AccountServiceImplDI">
<constructor-arg name="name" value="parzulpan"/>
<constructor-arg name="age" value="100"/>
<constructor-arg name="birthday" ref="now"/>
</bean>
<bean id="now" class="java.util.Date"/>

ClientDI.java

package cn.parzulpan.ui;

import cn.parzulpan.service.AccountService;
import org.springframework.context.support.ClassPathXmlApplicationContext; /**
* @Author : parzulpan
* @Time : 2020-12
* @Desc :
*/ public class ClientDI {
public static void main(String[] args) {
ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
AccountService asi = ac.getBean("accountServiceDI", AccountService.class);
System.out.println(asi);
asi.saveAccount(); // call saveAccount() parzulpan 100 Sun Dec 20 19:27:49 CST 2020
}
}

这种注入方式的优点:在获取 bean 对象时,注入数据是必须的操作,否则无法创建成功。

缺点:改变了 bean 对象的实例化方式,在创建对象时,如果用不到这些属性,也必须提供。

使用 set 方法注入

类中需要提供属性的 set 方法。

属性:

  • name:找的是类中 set 方法后面的部分
  • ref:给属性赋值是其他 bean 类型的
  • value:给属性赋值是基本数据类型和 string 类型的
    <!-- set 方法 注入
类中需要提供属性的 set 方法
属性:
name:找的是类中 set 方法后面的部分
ref:给属性赋值是其他 bean 类型的
value:给属性赋值是基本数据类型和 string 类型的
-->
<bean id="accountServiceDI2" class="cn.parzulpan.service.AccountServiceImplDI2">
<property name="name" value="库里"/>
<property name="age" value="30"/>
<property name="birthday" ref="nowSet"/>
</bean>
<bean id="nowSet" class="java.util.Date"/>

ClientDI.java

        AccountService asi2 = ac.getBean("accountServiceDI2", AccountService.class);
System.out.println(asi2);
asi2.saveAccount(); // call saveAccount() 库里 30 Sun Dec 20 20:11:01 CST 2020

这种注入方式的优点:创建对象时没有明确的限制,可以直接使用默认构造函数。

缺点:如果某个成员必须有值,则 set 方法无法保证一定执行。

但是,set 方式是更常用的方式。

注入集合属性

注入集合属性,在注入集合数据时,只要结构相同,标签可以互换。

List 结构的:array, list, set

Map 结构的:map, entry, props, prop

    <!-- 注入集合属性
在注入集合数据时,只要结构相同,标签可以互换
List 结构的:array, list, set
Map 结构的:map, entry, props, prop
-->
<bean id="accountServiceDI3" class="cn.parzulpan.service.AccountServiceImplDI3">
<property name="myStr">
<set>
<value>AAA</value>
<value>BBB</value>
<value>CCC</value>
</set>
</property>
<property name="myList">
<list>
<value>AAA</value>
<value>BBB</value>
<value>CCC</value>
</list>
</property>
<property name="mySet">
<set>
<value>AAA</value>
<value>BBB</value>
<value>CCC</value>
</set>
</property>
<property name="myMap">
<map>
<entry key="testA" value="aaa"/>
<entry key="testB" value="bbb"/>
</map>
</property>
<property name="myProps">
<props>
<prop key="testA">aaa</prop>
<prop key="testB">bbb</prop>
</props>
</property>
</bean>

练习和总结


ApplicationContext 接口的三个实现类?

  • ClassPathXmlApplicationContext 它是从类的根路径下加载配置文件,推荐使用这种
  • FileSystemXmlApplicationContext 它是从磁盘路径上加载配置文件,配置文件可以在磁盘的任意位置,不推荐使用这种
  • AnnotationConfigApplicationContext 使用注解配置容器对象时,需要使用此类来创建 Spring 核心容器,它用来读取注解

BeanFactory 和 ApplicationContext 的区别?

  • BeanFactory 是 Spring 核心容器中的顶层接口
  • ApplicationContext 是 BeanFactory 的子接口
  • 它们两者创建对象的时间点不一样
    • ApplicationContext 立即加载,只要读取了配置文件,默认情况下就会创建对象,适用于单例对象,推荐使用这种
    • BeanFactory 延迟加载,什么时候使用什么时候创建对象,适用于多例对象

【Spring】Spring 入门的更多相关文章

  1. spring快速入门(四)

    一.在spring快速入门(三)的基础上,我们来了解BeanFactory及配置. Client package com.murong.client; import org.springframewo ...

  2. spring快速入门(三)

    一.在spring快速入门(二)的基础上,原先我们是采用构造方法完成对象的注入.这里还有其他的方法可以完成注入,通过set方法来完成. 修改UserActionImpl package com.mur ...

  3. spring快速入门(二)

    一.在spring快速入门(一)的基础上,我们来了解spring是如何解决对象的创建以及对象之间的依赖关系的问题 (比如client中依赖UserAction的具体实现,UserActionImpl中 ...

  4. Spring快速入门

    什么是Spring Spring是分层的JavaSE/EE full-stack(一站式) 轻量级开源框架 分层 SUN提供的EE的三层结构:web层.业务层.数据访问层(持久层/集成层) Strut ...

  5. 161103、Spring Boot 入门

    Spring Boot 入门 spring Boot是Spring社区较新的一个项目.该项目的目的是帮助开发者更容易的创建基于Spring的应用程序和服务,让更多人的人更快的对Spring进行入门体验 ...

  6. Spring MVC 入门教程示例 (一)

    今天和大家分享下  Spring MVC  入门教程 首先还是从 HelloWorld  web 工程开始 -------------------------- 1.首先创建一个Maven Web工程 ...

  7. spring boot 入门操作(二)

    spring boot入门操作 使用FastJson解析json数据 pom dependencies里添加fastjson依赖 <dependency> <groupId>c ...

  8. spring boot 入门操作(三)

    spring boot入门操作 devtools热部署 pom dependencies里添加依赖 <dependency> <groupId>org.springframew ...

  9. Spring Boot入门教程1、使用Spring Boot构建第一个Web应用程序

    一.前言 什么是Spring Boot?Spring Boot就是一个让你使用Spring构建应用时减少配置的一个框架.约定优于配置,一定程度上提高了开发效率.https://zhuanlan.zhi ...

  10. Spring Boot入门教程2-1、使用Spring Boot+MyBatis访问数据库(CURD)注解版

    一.前言 什么是MyBatis?MyBatis是目前Java平台最为流行的ORM框架https://baike.baidu.com/item/MyBatis/2824918 本篇开发环境1.操作系统: ...

随机推荐

  1. 手把手教你写DI_3_小白徒手支持 `Singleton` 和 `Scoped` 生命周期

    手把手教你写DI_3_小白徒手支持 Singleton 和 Scoped 生命周期 在上一节:手把手教你写DI_2_小白徒手撸构造函数注入 浑身绷带的小白同学:我们继续开展我们的工作,大家都知道 Si ...

  2. 别再说你不懂什么是API了

    API 全称 Application Programming Interface, 即应用程序编程接口. 看到这里,急性子的小白同学马上就憋不住了:这不管是英文还是中文我每个字都懂啊,只是凑一块就不知 ...

  3. PluginOK中间件高级版-支持在Chrome、Edge、Firefox等浏览器网页中真正内嵌ActiveX等控件运行的版本已获多家上市公司采购

    PluginOK(牛插)中间件(原名:本网通WebRunLocal)是一个实现WEB浏览器(Web Browser)与本地程序(Local Application)之间进行双向调用的低成本.强兼容.安 ...

  4. mysql 8.0 主从复制配置

    背景: 主库: 192.168.211.128 从库: 192.168.211.129 一.关闭防火墙 [root@node01 ~]# systemctl disable firewalld [ro ...

  5. matlab多项式拟合以及指定函数拟合

    clc;clear all;close all;%% 多项式拟合指令:% X = [1 2 3 4 5 6 7 8 9 ];% Y = [9 7 6 3 -1 2 5 7 20]; % P= poly ...

  6. NET 调用人脸识别算法

    以前有个OpenCV 移植版EMCV可以用作图像识别等 https://github.com/emgucv/emgucv 现在有各种接口 比如虹软SDK  https://ai.arcsoft.com ...

  7. LINQ to Entities 不识别方法“System.String ToString(“yyyy-MM-dd”)”

    将Queryable转化为IEnumerable或者直接Tolist()

  8. CountDownLatch深度剖析

    场景引入 日常开发中,有个需求,要求主线程开启多个线程去并行执行任务,并且主线程需要等待所有的子线程执行完成后进行汇总.我们很容易找到 jion()方法来实现这个功能 缺点:由于工作中,我们不会直接创 ...

  9. 基础才是重中之重~BouncyCastle实现的DES3加密~java通用

    对于BouncyCastle类库(包)来说,他提供了很多加密算法,在与.net和java进行相互加解密过程中,得到了不错的应用,本文以DES3为例,来说一下DES3加解密的过程. 加密过程 明文字符转 ...

  10. 【linux】系统编程-2-消息队列

    目录 前言 4. 消息队列 4.1 概念 4.2 对比 4.3 函数及使用流程 4.3.1 msgget() 4.3.2 msgsng() 4.3.3 msgrcv() 4.3.4 msgctl() ...