Spring 核心技术(4)
接上篇:Spring 核心技术(3)
version 5.1.8.RELEASE
1.4.2 依赖关系及配置详情
如上一节所述,你可以将 bean 属性和构造函数参数定义为对其他托管 bean(协作者)的引用,或者作为内联定义的值。Spring 基于 XML 的配置元数据为此目的支持子元素<property/>
和<constructor-arg/>
。
直接值(基本类型,字符串等)
<property/>
元素的 value
属性指定一个属性或构造器参数为可读的字符串。Spring 的转换服务用于将这些值从 String
转换为属性或参数的实际类型。以下示例展示了要设置的各种值:
<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<!-- results in a setDriverClassName(String) call -->
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mydb"/>
<property name="username" value="root"/>
<property name="password" value="masterkaoli"/>
</bean>
以下示例使用 p 命名空间进行更简洁的 XML 配置:
<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="myDataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close"
p:driverClassName="com.mysql.jdbc.Driver"
p:url="jdbc:mysql://localhost:3306/mydb"
p:username="root"
p:password="masterkaoli"/>
</beans>
前面的 XML 更简洁。但是,除非您在创建 bean 定义时使用支持自动属性完成的 IDE(例如 IntelliJ IDEA 或 Spring Tool Suite),否则会在运行时而不是设计时发现拼写错误。强烈建议使用此类 IDE 帮助。
还可以配置 java.util.Properties
实例,如下所示:
<bean id="mappings"
class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<!-- typed as a java.util.Properties -->
<property name="properties">
<value>
jdbc.driver.className=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mydb
</value>
</property>
</bean>
Spring 容器通过使用 JavaBeans PropertyEditor
机制将 <value/>
元素内的文本转换为 java.util.Properties
实例。这是一个很好的快捷方式,也是 Spring 团队建议嵌套 <value/>
元素而不是 value
属性的少数几个地方之一。
idref
元素
idref
元素只是一种防错方法,可以将容器中另一个 bean 的 id
属性(字符串值 - 而不是引用)传递给 <constructor-arg/>
或 <property/>
元素。以下示例展示了如何使用它:
<bean id="theTargetBean" class="..."/>
<bean id="theClientBean" class="...">
<property name="targetName">
<idref bean="theTargetBean"/>
</property>
</bean>
前面的 bean 定义代码段与以下代码段完全等效(运行时):
<bean id="theTargetBean" class="..." />
<bean id="client" class="...">
<property name="targetName" value="theTargetBean"/>
</bean>
第一种形式优于第二种形式,因为使用 idref
标签可以让容器在部署时验证引用的命名 bean 是否存在。在第二个变体中,不会对传递给 client
bean targetName
属性的值执行验证。只有在 client
bean 真正实例化时才会发现拼写错误(很可能是致命的结果)。如果 client
bean 是原型 bean,那么可能在部署容器后很长时间才能发现此错误和产生的异常。
4.0 beans XSD 不再支持
idref
元素的local
属性,因为它不再提供除常规bean
引用以外的值。升级到 4.0 版本时,需要将现有idref local
引用更改idref bean
。
其中一个共同的地方(至少早于 Spring 2.0 版本),<idref/>
元素的值是 ProxyFactoryBean
bean 定义中 AOP 拦截器的配置项。指定拦截器名称时使用 <idref/>
元素可防止拼写错误的拦截器 ID。
引用其他 Bean (协作者)
ref
元素是 <constructor-arg/>
或 <property/>
定义元素内部的最终元素。在这里,你将 bean 指定属性的值设置为对容器管理的另一个 bean(协作者)的引用。引用的 bean 是要设置属性的 bean 的依赖项,并且在设置该属性之前根据需要对其进行初始化。(如果协作者是单例 bean,它可能已经被容器初始化。)所有引用最终都是对另一个对象的引用。作用域和有效性取决于是否通过 bean
、local
或 parent
属性指定其他对象的 ID 或名称。
通过 <ref />
标签的 bean
属性指定目标 bean 是最常用的方式,允许创建对同一容器或父容器中的任何 bean 的引用,不管它是否在同一 XML 文件中。bean
属性的值可以与目标 Bean 的 id
属性相同,也可以与目标 bean 的 name
属性之一相同。以下示例演示如何使用 ref
元素:
<ref bean="someBean"/>
通过 parent
属性指定目标 bean 会创建对当前容器的父容器中的 bean 的引用。parent
属性的值可以与目标 bean 的 id
属性相同,也可以与目标 bean 的 name
属性之一相同。目标 bean 必须位于当前 bean 的父容器中。当容器具备层次结构,且希望和父级 bean 一样通过代理的方式包装父容器中现有的 bean 时,应该主要使用 bean 引用变量。以下一对列表演示了如何使用该 parent
属性:
<!-- in the parent context -->
<bean id="accountService" class="com.something.SimpleAccountService">
<!-- insert dependencies as required as here -->
</bean>
<!-- in the child (descendant) context -->
<bean id="accountService" <!-- bean name is the same as the parent bean -->
class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target">
<ref parent="accountService"/> <!-- notice how we refer to the parent bean -->
</property>
<!-- insert other configuration and dependencies as required here -->
</bean>
4.0 beans XSD 不再支持
ref
元素的local
属性,因为它不再提供除常规bean
引用以外的值。升级到 4.0 架构时,需要将现有ref local
引用更改ref bean
。
内部 Bean
<property/>
或 <constructor-arg/>
元素中的 <bean/>
元素定义一个内部 bean,如下面的示例所示:
<bean id="outer" class="...">
<!-- instead of using a reference to a target bean, simply define the target bean inline -->
<property name="target">
<bean class="com.example.Person"> <!-- this is the inner bean -->
<property name="name" value="Fiona Apple"/>
<property name="age" value="25"/>
</bean>
</property>
</bean>
内部 bean 定义不需要定义 ID 或名称。即使指定,容器也不使用其作为标识符。容器也会在创建时忽略 scope
标志,因为内部 bean 始终是匿名的,并且始终和外部 bean 一起创建。内部 bean 无法单独访问或注入协作 bean 中。
作为一个极端情况,可以从自定义作用域接收销毁回调,例如包含在单例 bean 中作用域为 request 的内部 bean。内部 bean 实例的创建与包含它的 bean 相关联,但是销毁回调允许它参与 request 作用域的生命周期。这不是常见的情况。内部 bean 通常只是共享包含它的 bean 的作用域。
集合
<list/>
、<set/>
、<map/>
和 <props/>
元素分别设置 Java Collection
类型 List
、Set
、Map
和 Properties
的属性和参数。以下示例演示了如何使用它们:
<bean id="moreComplexObject" class="example.ComplexObject">
<!-- results in a setAdminEmails(java.util.Properties) call -->
<property name="adminEmails">
<props>
<prop key="administrator">administrator@example.org</prop>
<prop key="support">support@example.org</prop>
<prop key="development">development@example.org</prop>
</props>
</property>
<!-- results in a setSomeList(java.util.List) call -->
<property name="someList">
<list>
<value>a list element followed by a reference</value>
<ref bean="myDataSource" />
</list>
</property>
<!-- results in a setSomeMap(java.util.Map) call -->
<property name="someMap">
<map>
<entry key="an entry" value="just some string"/>
<entry key ="a ref" value-ref="myDataSource"/>
</map>
</property>
<!-- results in a setSomeSet(java.util.Set) call -->
<property name="someSet">
<set>
<value>just some string</value>
<ref bean="myDataSource" />
</set>
</property>
</bean>
map 的键或值,或 set 的值,可以是以下任何元素:
bean | ref | idref | list | set | map | props | value | null
集合合并
Spring 容器支持合并集合。应用程序开发人员可以定义父级 <list/>
、<map/>
、<set/>
或 <props/>
元素,并定义子元素 <list/>
、<map/>
、<set/>
或 <props/>
继承并覆盖父集合的值。也就是说,子集合的元素会覆盖父集合中指定的值,子集合的值是合并父集合和子集合的元素的结果。
关于合并的这一部分讨论了父子 bean 机制。不熟悉父子 bean 定义的读者可能希望在继续阅读之前阅读之前阅读相关部分。
以下示例演示了集合合并:
<beans>
<bean id="parent" abstract="true" class="example.ComplexObject">
<property name="adminEmails">
<props>
<prop key="administrator">administrator@example.com</prop>
<prop key="support">support@example.com</prop>
</props>
</property>
</bean>
<bean id="child" parent="parent">
<property name="adminEmails">
<!-- the merge is specified on the child collection definition -->
<props merge="true">
<prop key="sales">sales@example.com</prop>
<prop key="support">support@example.co.uk</prop>
</props>
</property>
</bean>
<beans>
注意 child
bean 定义中 adminEmails
属性的 <props/>
元素的 merge=true
属性的使用。当容器解析并实例化 child
bean 时,生成的实例有一个 adminEmails
Properties
集合,其中包含将子集合 adminEmails
与父 adminEmails
集合合并的结果 。以下清单显示了结果:
administrator=administrator@example.com
sales=sales@example.com
support=support@example.co.uk
子元素的 Properties
集合的值继承了父元素 <props/>
的所有属性元素,子元素中 support
的值将覆盖父集合的值。
这一合并行为同样适用于 <list/>
、<map/>
和 <set/>
集合类型。在 <list/>
元素的特定场景中,建议保持与 List
集合类型(即有序集合的概念)相关联的语义。父级的值位于所有子级列表的值之前。在 Map
、Set
和 Properties
集合类型场景中,不存在次序的。因此,容器内部使用的 Map
、Set
以及 Properties
实现类也是无序的。
集合合并的局限性
你无法合并不同的集合类型(例如 Map
和 List
)。如果你尝试这样做,则会抛出对应的 Exception
。merge
属性必须在较低层级的子定义上指定。在父集合定义上指定 merge
属性是多余的,不会导致所需的合并。
强类型集合
通过在 Java 5 中引入的泛型类型,你可以使用强类型集合。也就是说,可以声明一种 Collection
类型,使得它只能包含(例如)String
元素。如果使用 Spring 将强类型 Collection
依赖注入到 bean 中,可以使用 Spring 的类型转换支持,以便强类型 Collection
实例的元素在添加到 Collection
之前转换为适当的类型。以下 Java 类和 bean 定义演示了如何执行此操作:
public class SomeClass {
private Map<String, Float> accounts;
public void setAccounts(Map<String, Float> accounts) {
this.accounts = accounts;
}
}
<beans>
<bean id="something" class="x.y.SomeClass">
<property name="accounts">
<map>
<entry key="one" value="9.99"/>
<entry key="two" value="2.75"/>
<entry key="six" value="3.99"/>
</map>
</property>
</bean>
</beans>
当 something
bean 的 accounts
属性已经准备注入时,通过反射可获得关于强类型的元素 Map<String, Float>
的泛型信息。因此,Spring 的类型转换机制将各种值识别为 Float
类型,并将字符串值(9.99
, 2.75
和 3.99
)转换为实际 Float
类型。
null 和空字符串
Spring 将属性等的空参数视为空字符串。以下基于 XML 的配置元数据片段将 email
属性设置为空字符串("")。
<bean class="ExampleBean">
<property name="email" value=""/>
</bean>
上面的示例等效于以下 Java 代码:
exampleBean.setEmail("");
<null/>
元素可以处理 null
值。以下显示了一个示例:
<bean class="ExampleBean">
<property name="email">
<null/>
</property>
</bean>
上述配置等同于以下 Java 代码:
exampleBean.setEmail(null);
带有 p 命名空间的 XML 快捷方式
p 命名空间允许你使用 bean
元素的属性(而不是嵌套 <property/>
元素)来描述属性值或协作 bean
。
Spring 支持具备命名空间的可扩展配置格式,这些命名空间基于 XML Schema 定义。本章中讨论的 beans
配置格式在一个 XML Schema 文档中定义。但是,p 命名空间未在 XSD 文件中定义,仅存在于 Spring 的核心中。
以下示例显示了两个 XML 片段(第一个使用标准 XML 格式,第二个使用 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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean name="classic" class="com.example.ExampleBean">
<property name="email" value="someone@somewhere.com"/>
</bean>
<bean name="p-namespace" class="com.example.ExampleBean"
p:email="someone@somewhere.com"/>
</beans>
该示例演示了 bean 定义中调用的 p 命名空间设置 email
属性。这告诉 Spring 包含一个属性声明。如之前所述,p 命名空间没有模式定义,因此可以将属性的名称设置为属性名。
下一个示例包括另外两个 bean 定义,它们都引用了另一个 bean:
<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 name="john-classic" class="com.example.Person">
<property name="name" value="John Doe"/>
<property name="spouse" ref="jane"/>
</bean>
<bean name="john-modern"
class="com.example.Person"
p:name="John Doe"
p:spouse-ref="jane"/>
<bean name="jane" class="com.example.Person">
<property name="name" value="Jane Doe"/>
</bean>
</beans>
此示例不仅包含使用 p 命名空间的属性值,还使用特殊格式来声明属性引用。第一个 bean 定义使用 <property name="spouse" ref="jane"/>
创建从 bean john
到 bean jane
的引用 ,而第二个 bean 定义使用 p:spouse-ref="jane"
作为属性来执行完全相同的操作。在这种情况下,spouse
是属性名称,而 -ref
部分表示这不是直接值,而是对另一个 bean 的引用。
p 命名空间不如标准 XML 格式灵活。例如,声明属性引用的格式与
Ref
结尾的属性冲突,而标准 XML 格式则不会。我们建议仔细选择你的方法并将其传达给其他团队成员,以避免生成同时使用三种方法的 XML 文档。
带有 c 命名空间的 XML 快捷方式
与带有 p 命名空间的 XML 快捷方式类似,Spring 3.1 中引入的 c 命名空间允许使用内联属性来配置构造函数参数,而不是嵌套 constructor-arg
元素。
以下示例使用 c:
命名空间执行与基于构造函数的依赖注入相同的操作:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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">
<bean id="beanTwo" class="x.y.ThingTwo"/>
<bean id="beanThree" class="x.y.ThingThree"/>
<!-- traditional declaration with optional argument names -->
<bean id="beanOne" class="x.y.ThingOne">
<constructor-arg name="thingTwo" ref="beanTwo"/>
<constructor-arg name="thingThree" ref="beanThree"/>
<constructor-arg name="email" value="something@somewhere.com"/>
</bean>
<!-- c-namespace declaration with argument names -->
<bean id="beanOne" class="x.y.ThingOne" c:thingTwo-ref="beanTwo"
c:thingThree-ref="beanThree" c:email="something@somewhere.com"/>
</beans>
通过名称设置构造函数参数时,c:
命名空间和 p:
使用相同的约定(尾部 -ref 的 bean 引用)。类似地,它需要在 XML 文件中声明,即使它没有在 XSD schema 中定义(它存在于 Spring 核心内部)。
对于构造函数参数名称不可用的罕见情况(通常在没有调试信息的情况下编译字节码),可以使用备用的参数索引,如下所示:
<!-- c-namespace index declaration -->
<bean id="beanOne" class="x.y.ThingOne" c:_0-ref="beanTwo" c:_1-ref="beanThree"
c:_2="something@somewhere.com"/>
因为采用 XML 语法,且 XML 属性名称不能以数字开头(即使某些 IDE 允许),所以索引表示法要求存在前缀
_
。<constructor-arg>
元素也可以使用相应的索引符号,但不常用,因为声明顺序通常就足够了。
实际上,构造函数解析机制在匹配参数时非常有效,因此除非确实需要,否则我们建议在整个配置中使用名称表示法。
复合属性名称
设置 bean 属性时,可以使用复合或嵌套属性名称,只要除最终属性名称之外的路径的所有组件都不是 null。请看以下 bean 定义:
<bean id="something" class="things.ThingOne">
<property name="fred.bob.sammy" value="123" />
</bean>
something
bean 具有一个 fred
属性,fred
属性包含 bob
属性,bob
属性包含 sammy
属性,最终 sammy
属性的值被设置为 123。为了确保其可以正常运行,在构造 bean 之后,something
的 fred
属性和 fred
属性的 bob
属性不得为 null。否则将会抛出 NullPointerException
。
1.4.3 运用 depends-on
如果一个 bean 是另一个 bean 的依赖项,那通常意味着将一个 bean 设置为另一个 bean 的属性。通常情况下可以使用基于 XML 的配置元数据中的 <ref/>
元素来完成此操作。但是,有时 bean 之间的依赖关系不那么直接。例如需要在类中触发的静态初始化程序,例如数据库驱动程序注册。depends-on
属性可以在初始化使用此元素的 bean 之前显式的强制初始化一个或多个 bean。以下示例使用 depends-on
属性表示对单个bean的依赖关系:
<bean id="beanOne" class="ExampleBean" depends-on="manager"/>
<bean id="manager" class="ManagerBean" />
要表示对多个 bean 的依赖关系,请提供 bean 名称列表作为 depends-on
属性的值(逗号,空格和分号是有效的分隔符):
<bean id="beanOne" class="ExampleBean" depends-on="manager,accountDao">
<property name="manager" ref="manager" />
</bean>
<bean id="manager" class="ManagerBean" />
<bean id="accountDao" class="x.y.jdbc.JdbcAccountDao" />
depends-on
属性可以指定初始化时的依赖性,也可以指定单例 bean 销毁时的依赖性。通过depends-on
定义的依赖项会在指定 bean 本身销毁之前被销毁。这样,depends-on
也可以控制停止顺序。
1.4.4 延迟初始化的 Bean
默认情况下,ApplicationContext
实现会在初始化时创建和配置所有单例 bean,作为初始化过程的一部分。通常,这种预先实例化是合理的,因为配置或环境中的错误可以立刻被发现,而不是几小时甚至几天后。当不希望出现这种情况时,可以通过将 bean 定义标记为延迟初始化来阻止单例 bean 的预实例化。延迟初始化的 bean 告诉 IoC 容器在第一次请求时创建 bean 实例,而不是在启动时。
在 XML 中,此行为由 <bean/>
元素的 lazy-init
属性控制,如以下示例所示:
<bean id="lazy" class="com.something.ExpensiveToCreateBean" lazy-init="true"/>
<bean name="not.lazy" class="com.something.AnotherBean"/>
当前面的配置被 ApplicationContext
使用时,lazy
bean 不会在 ApplicationContext
启动时预先实例化,而 not.lazy
bean 会被预先实例化。
但是当延迟初始化的 bean 是未进行延迟初始化的单例 bean 的依赖项时,ApplicationContext
会在启动时创建延迟初始化的 bean
,因为它必须满足单例的依赖关系。延迟初始化的 bean
会被注入到其他不是延迟初始化的单例 bean 中。
你还可以通过使用 <beans/>
元素的 default-lazy-init
上的属性来控制容器级别的延迟初始化,如以下示例显示:
<beans default-lazy-init="true">
<!-- no beans will be pre-instantiated... -->
</beans>
Spring 核心技术(4)的更多相关文章
- Spring核心技术(八)——Spring自动装载的注解
本文针对自动装载的一些注解进行描述. 基于注解的容器配置 @Required注解 @Required注解需要应用到Bean的属性的setter方法上面,如下面的例子: public class Sim ...
- Spring核心技术(六)——Spring中Bean的生命周期
前文已经描述了Bean的作用域,本文将描述Bean的一些生命周期作用,配置还有Bean的继承. 定制Bean 生命周期回调 开发者通过实现Spring的InitializeingBean和Dispos ...
- Spring核心技术(五)——Spring中Bean的作用域
前文概述了Spring的容器,Bean,以及依赖的一些信息,本文将描述一下Bean的作用域 Bean的作用域 当开发者定义Bean的时候,同时也会定义了该如何创建Bean实例.这些具体创建的过程是很重 ...
- Spring核心技术(四)——Spring的依赖及其注入(续二)
前面两篇文章描述了IoC容器中依赖的概念,包括依赖注入以及注入细节配置.本文将继续描述玩全部的依赖信息. 使用 depends-on 如果一个Bean是另一个Bean的依赖的话,通常来说这个Bean也 ...
- Spring 核心技术(3)
接上篇:Spring 核心技术(2) version 5.1.8.RELEASE 1.4 依赖 典型的企业应用程序不会只包含单个对象(或 Spring 术语中的 bean).即使是最简单的应用程序也是 ...
- Spring 核心技术(2)
接上篇:Spring 核心技术(1) version 5.1.8.RELEASE 1.3 Bean概述 Spring IoC 容器管理一个或多个bean,他们都是根据提供的配置元数据(例如 XML 中 ...
- Spring 核心技术(5)
接上篇:Spring 核心技术(4) version 5.1.8.RELEASE 1.4.5 自动装配协作者 Spring 容器可以自动连接协作 bean 之间的关系.你可以让 Spring 通过检查 ...
- Spring 核心技术(6)
接上篇:Spring 核心技术(5) version 5.1.8.RELEASE 1.5 Bean 作用域 创建 bean 定义时,你创建了一种用于创建 bean 定义中定义的类实例的方法.bean定 ...
- Spring 核心技术(7)
接上篇:Spring 核心技术(6) version 5.1.8.RELEASE 1.6 定制 Bean 的特性 Spring Framework 提供了许多可用于自定义 bean 特性的接口.本节将 ...
随机推荐
- 【Linux】linux中删除指定日期之前的文件
要删除系统中就的备份文件,就需要使用命令了: #find /tmp -mtime +30 -type f -name *.sh[ab] -exec rm -f {} \; 假如在一个目录中保留最近30 ...
- Flask学习之旅--数据库
一.写在前面 在Web开发中,数据库操作是很重要的一部分,因为网站的很多重要信息都保存在数据库之中.而Flask在默认情况下是没有数据库.表单验证等功能的,但是可以用Flask-extension为W ...
- 分享Node.js + Koa2 + MySQL + Vue.js 实战开发一套完整个人博客项目网站
这是个什么的项目? 使用 Node.js + Koa2 + MySQL + Vue.js 实战开发一套完整个人博客项目网站. 博客线上地址:www.boblog.com Github地址:https: ...
- Elasticsearch-head插件使用小结
1.ElasticSearch-head是什么? ElasticSearch是一个基于Lucene的搜索服务器.它提供了一个分布式多用户能力的全文搜索引擎,基于RESTful web接口.Elasti ...
- ZooKeeper 系列(二)—— Zookeeper单机环境和集群环境搭建
一.单机环境搭建 1.1 下载 1.2 解压 1.3 配置环境变量 1.4 修改配置 1.5 启动 1. ...
- Hyperledger Fabric1.4的多机部署
之前的文章深入解析Hyperledger Fabric启动的全过程主要讲解了Fabric的网络搭建,以及启动的整体流程,但是都是通过单机完成的.而区块链本身就是去中心化的,所以最终还是要完成Fabri ...
- vs2017无法启动iis express
当遇到vs2017无法启动iis express时,可尝试删除项目下的.vs文件夹,通常可解决问题
- iOS开发(5):设备唯一标识与全局变量
这里记录两个iOS开发中经常用到的知识点,一个是唯一标识,一个是全局变量. (1)唯一标识 唯一标识一台设备(比如iPhone.iPad等)是一个基本的实现与业务上的需求,因为这个唯一标识在许多场景都 ...
- PHP和javascript判断用户使用的是手机还是电脑
PHP判断手机还是电脑 <?php $is_mobile = (is_mobile() == true) ? "手机" : "电脑"; echo '< ...
- 安装win7和linux [ubuntu14]双系统
想体验一把ubuntu18.10最新桌面版的快感,但是windows上面的数据又删除不得,所以百度了一下,win7和linux双系统的安装教程. 一.首先在win7上创建新的分区 https://ji ...