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

【Spring】Spring 对 Ioc 的实现

一、Ioc 控制反转

  • 控制反转是一种思想

  • 控制反转是为了降低程序耦合度,提高程序扩展力,达到 OCP 原则,达到 DIP 原则

  • 控制反转,反转的是什么?

    • 将对象的创建权利交出去,交给第三方容器负责

    • 将对象和对象之间关系的维护权交出去,交给第三方容器负责

  • 控制反转这种思想如何实现呢?

    • DI(Dependency Injection):依赖注入

 

二、依赖注入 

依赖注入实现了控制反转的思想

Spring通过依赖注入的方式来完成Bean管理的

Bean管理说的是:Bean对象的创建,以及Bean对象中属性的赋值(或者叫做Bean对象之间关系的维护)

依赖注入:

  • 依赖指的是对象和对象之间的关联关系

  • 注入指的是一种数据传递行为,通过注入行为来让对象和对象产生关系

依赖注入常见的实现方式包括两种:

  • 第一种:set 注入

  • 第二种:构造注入

 

三、set 注入 

set 注入,基于 set 方法实现的,底层会通过反射机制调用属性对应的set方法然后给属性赋值。这种方式要求属性必须对外提供 set 方法  

<?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.0</modelVersion><groupId>org.qiu</groupId><artifactId>spring-003-dependency-injection</artifactId><version>1.0-SNAPSHOT</version><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target></properties><dependencies><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.3.23</version></dependency><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.13.2</version><scope>test</scope></dependency><!--log4j2的依赖--><dependency><groupId>org.apache.logging.log4j</groupId><artifactId>log4j-core</artifactId><version>2.19.0</version></dependency><dependency><groupId>org.apache.logging.log4j</groupId><artifactId>log4j-slf4j2-impl</artifactId><version>2.19.0</version></dependency></dependencies></project>
package org.qiu.spring.dao;import org.slf4j.Logger;
import org.slf4j.LoggerFactory;/*** @author 秋玄* @version 1.0* @email qiu_2022@aliyun.com* @project Spring* @package org.qiu.spring.dao* @date 2022-10-31-17:46* @since 1.0*/
public class UserDao {private static final Logger logger = LoggerFactory.getLogger(UserDao.class);public void insert(){logger.info("正在保存用户数据......");}
}
package org.qiu.spring.service;import org.qiu.spring.dao.UserDao;/*** @author 秋玄* @version 1.0* @email qiu_2022@aliyun.com* @project Spring* @package org.qiu.spring.service* @date 2022-10-31-17:53* @since 1.0*/
public class UserService {private UserDao userDao;// 使用set方式注入,必须提供set方法。// 反射机制要调用这个方法给属性赋值的。public void setUserDao(UserDao userDao) {this.userDao = userDao;}public void save(){userDao.insert();}
}
<?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="userDaoBean" class="org.qiu.spring.dao.UserDao"></bean><bean id="userServiceBean" class="org.qiu.spring.service.UserService"><!--想让 Spring 调用对应的 set 方法,需要配置 property 标签name:set方法的方法名,去掉 set,剩下的单词首字母变小写ref: references,“引用”。指定要注入的 bean 的 id一般情况下 name 位置写属性名就行了--><property name="userDao" ref="userDaoBean"></property></bean></beans>
package org.qiu.spring.test;import org.junit.Test;
import org.qiu.spring.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;/*** @author 秋玄* @version 1.0* @email qiu_2022@aliyun.com* @project Spring* @package org.qiu.spring.test* @date 2022-10-31-17:55* @since 1.0*/
public class DITest {@Testpublic void testSetDI(){ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");UserService userService = applicationContext.getBean("userServiceBean", UserService.class);userService.save();}
}

运行结果:  

 

原理解析:

通过 property 标签获取到属性名:userDao

通过属性名推断出 set 方法名:setUserDao

通过反射机制调用 setUserDao() 方法给属性赋值

property 标签的 name 是属性名。

property 标签的 ref 是要注入的 bean 对象的 id。(通过ref属性来完成 bean 的装配,这是 bean 最简单的一种装配方式。装配指的是:创建系统组件之间关联的动作)

可以把 set 方法注释掉,再测试一下

通过测试得知,底层实际上调用了 setUserDao() 方法。所以需要确保这个方法的存在

另外,对于 property 标签来说,ref 属性也可以采用标签的方式,但使用 ref 属性是多数的:

<bean id="userServiceBean" class="com.powernode.spring6.service.UserService"><property name="userDao"><ref bean="userDaoBean"/></property>
</bean>

总结:set 注入的核心实现原理:通过反射机制调用 set 方法来给属性赋值,让两个对象之间产生关系  

 

四、构造注入

核心原理:通过调用构造方法来给属性赋值  

package org.qiu.spring.service;import org.qiu.spring.dao.UserDao;
import org.qiu.spring.dao.VipDao;/*** @author 秋玄* @version 1.0* @email qiu_2022@aliyun.com* @project Spring* @package org.qiu.spring.service* @date 2022-11-01-20:25* @since 1.0*/
public class CustomerService {private UserDao userDao;private VipDao vipDao;public CustomerService(UserDao userDao, VipDao vipDao) {this.userDao = userDao;this.vipDao = vipDao;}public void save(){userDao.insert();vipDao.insert();}
}
<?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="userDaoBean" class="org.qiu.spring.dao.UserDao"/><bean id="vipDaoBean" class="org.qiu.spring.dao.VipDao"/><bean id="csBean" class="org.qiu.spring.service.CustomerService"><!--构造注入public CustomerService(UserDao userDao, VipDao vipDao) {this.userDao = userDao;this.vipDao = vipDao;}指定构造方法的第一个参数,下标是 0--><constructor-arg index="0" ref="userDaoBean"/><constructor-arg index="1" ref="vipDaoBean"/></bean><!-- 也可以使用 name 注入,若不指定下标,也不指定参数名,Spring自动根据类型匹配注入 --><bean id="csBean2" class="org.qiu.spring.service.CustomerService"><constructor-arg name="userDao" ref="userDaoBean"/><constructor-arg name="vipDao" ref="vipDaoBean"/></bean></beans>
@Test
public void testConstructDI(){ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");CustomerService service = applicationContext.getBean("csBean",CustomerService.class);service.save();
}

运行结果:  

 

五、set 注入专题 

  • 注入外部 Bean

这种方式比较常用

<!-- 声明/定义 bean -->
<bean id="orderDaoBean" class="org.qiu.spring.dao.OrderDao"/><bean id="orderServiceBean" class="org.qiu.spring.service.OrderService"><!-- 使用 ref 属性来引入,就是注入外部 bean --><property name="orderDao" ref="orderDaoBean"/>
</bean>
  • 注入内部 Bean

这种方式使用较少,了解即可。

<bean id="orderServiceBean2" class="org.qiu.spring.service.OrderService"><property name="orderDao"><!-- 内部 bean --><bean class="org.qiu.spring.dao.OrderDao"/></property>
</bean>
  • 注入简单类型

package org.qiu.spring.bean;/*** @author 秋玄* @version 1.0* @email qiu_2022@aliyun.com* @project Spring* @package org.qiu.spring.bean* @date 2022-11-06-07:14* @since 1.0*/
public class User {private String username;private String password;private int age;@Overridepublic String toString() {return "User{" +"username='" + username + '\'' +", password='" + password + '\'' +", age=" + age +'}';}public void setUsername(String username) {this.username = username;}public void setPassword(String password) {this.password = password;}public void setAge(int age) {this.age = age;}
}
<!-- 注入简单类型 -->
<bean id="userBean" class="org.qiu.spring.bean.User"><property name="username" value="张三"/><property name="password" value="123"/><property name="age" value="20"/>
</bean>
 @Test
public void testSimpleTypeSet(){ApplicationContext application = new ClassPathXmlApplicationContext("set-di.xml");User userBean = application.getBean("userBean", User.class);System.out.println(userBean);
}

运行结果:  

 

分析简单类型包括哪些?

Spring 源码:BeanUtils 类

public class BeanUtils{//......./*** Check if the given type represents a "simple" property: a simple value* type or an array of simple value types.* <p>See {@link #isSimpleValueType(Class)} for the definition of <em>simple* value type</em>.* <p>Used to determine properties to check for a "simple" dependency-check.* @param type the type to check* @return whether the given type represents a "simple" property* @see org.springframework.beans.factory.support.RootBeanDefinition#DEPENDENCY_CHECK_SIMPLE* @see org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#checkDependencies* @see #isSimpleValueType(Class)*/public static boolean isSimpleProperty(Class<?> type) {Assert.notNull(type, "'type' must not be null");return isSimpleValueType(type) || (type.isArray() && isSimpleValueType(type.getComponentType()));}/*** Check if the given type represents a "simple" value type: a primitive or* primitive wrapper, an enum, a String or other CharSequence, a Number, a* Date, a Temporal, a URI, a URL, a Locale, or a Class.* <p>{@code Void} and {@code void} are not considered simple value types.* @param type the type to check* @return whether the given type represents a "simple" value type* @see #isSimpleProperty(Class)*/public static boolean isSimpleValueType(Class<?> type) {return (Void.class != type && void.class != type &&(ClassUtils.isPrimitiveOrWrapper(type) ||Enum.class.isAssignableFrom(type) ||CharSequence.class.isAssignableFrom(type) ||Number.class.isAssignableFrom(type) ||Date.class.isAssignableFrom(type) ||Temporal.class.isAssignableFrom(type) ||URI.class == type ||URL.class == type ||Locale.class == type ||Class.class == type));}//........
}
  • 简单类型:

    • 基本数据类型

    • 基本数据类型对应的包装类

    • String 或 其他的 CharSequence 子类

    • Number 子类

    • Date 子类

    • URI

    • URL

    • Temporal 子类(Java8 新特性,有关于时间时区的类)

    • Locale

    • Class

    • 另外还包括以上简单值类型对应的数组类型  

package org.qiu.spring.bean;import java.net.URI;
import java.net.URL;
import java.time.LocalDate;
import java.util.Date;
import java.util.Locale;/*** @author 秋玄* @version 1.0* @email qiu_2022@aliyun.com* @project Spring* @package org.qiu.spring.bean* @date 2022-11-06-07:37* @since 1.0*/
public class SimpleValueType {public static void main(String[] args) {System.out.println(new Date());}private byte b;private short s;private int i;private long l;private float f;private double d;private boolean flag;private char c;private Byte b1;private Short s1;private Integer i1;private Long l1;private Float f1;private Double d1;private Boolean flag1;private Character c1;private String str;private Date date;private Season season;private URI uri;private URL url;private LocalDate localDate;private Locale locale;private Class clazz;// 省略 setter 和 toString
}enum Season {SPRING, SUMMER, AUTUMN, WINTER
}
<?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="simpleValueType" class="org.qiu.spring.bean.SimpleValueType"><property name="b" value="1"/><property name="s" value="1"/><property name="i" value="1"/><property name="l" value="1"/><property name="f" value="1"/><property name="d" value="1"/><property name="flag" value="false"/><property name="c" value="a"/><property name="b1" value="2"/><property name="s1" value="2"/><property name="i1" value="2"/><property name="l1" value="2"/><property name="f1" value="2"/><property name="d1" value="2"/><property name="flag1" value="true"/><property name="c1" value="a"/><property name="str" value="zhangsan"/><!--注意:value后面的日期字符串格式不能随便写,必须是Date对象toString()方法执行的结果。--><!--如果想使用其他格式的日期字符串,就需要进行特殊处理了--><!--一般不会把 Date 当作简单类型,而是使用 ref 给 Date 类型属性赋值--><property name="date" value="Sun Nov 06 07:50:46 CST 2022"/><property name="season" value="WINTER"/><property name="uri" value="/save.do"/><!--spring6之后,会自动检查url是否有效,如果无效会报错。--><property name="url" value="http://www.baidu.com"/><!--java.util.Locale 主要在软件的本地化时使用。它本身没有什么功能,更多的是作为一个参数辅助其他方法完成输出的本地化。--><property name="locale" value="CHINESE"/><property name="clazz" value="java.lang.String"/></bean></beans>
@Test
public void testAllSimpleType(){ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-all-simple-type.xml");SimpleValueType simpleValueType = applicationContext.getBean("simpleValueType", SimpleValueType.class);System.out.println(simpleValueType);
}

运行结果:  

需要注意的是:

  • 如果把 Date 当做简单类型的话,日期字符串格式不能随便写。格式必须符合 Date 的 toString() 方法格式。显然这就比较鸡肋了。如果我们提供一个这样的日期字符串:2010-10-11,在这里是无法赋值给 Date 类型的属性的。

  • Spring6 之后,当注入的是 URL,那么这个 url 字符串是会进行有效性检测的。如果是一个存在的 url,那就没问题。如果不存在则报错。  

  • 级联属性赋值(了解)

package org.qiu.spring.bean;/*** @author 秋玄* @version 1.0* @email qiu_2022@aliyun.com* @project Spring* @package org.qiu.spring.bean* @date 2022-11-06-08:58* @since 1.0*/
public class Clazz {private String name;public void setName(String name) {this.name = name;}@Overridepublic String toString() {return "Clazz{" +"name='" + name + '\'' +'}';}
}
package org.qiu.spring.bean;/*** @author 秋玄* @version 1.0* @email qiu_2022@aliyun.com* @project Spring* @package org.qiu.spring.bean* @date 2022-11-06-09:01* @since 1.0*/
public class Student {private String name;public void setName(String name) {this.name = name;}@Overridepublic String toString() {return "Student{" +"name='" + name + '\'' +'}';}
}
<bean id="studentBean" class="org.qiu.spring.bean.Student"><property name="name" value="张三"/>
</bean><bean id="clazzBean" class="org.qiu.spring.bean.Clazz"><property name="name" value="高三一班"/>
</bean>
@Test
public void testCascade(){ApplicationContext application = new ClassPathXmlApplicationContext("cascade.xml");Student studentBean = application.getBean("studentBean", Student.class);System.out.println(studentBean);Clazz clazzBean = application.getBean("clazzBean", Clazz.class);System.out.println(clazzBean);
}

运行结果:  

package org.qiu.spring.bean;/*** @author 秋玄* @version 1.0* @email qiu_2022@aliyun.com* @project Spring* @package org.qiu.spring.bean* @date 2022-11-06-09:01* @since 1.0*/
public class Student {private String name;private Clazz clazz;public void setClazz(Clazz clazz) {this.clazz = clazz;}public void setName(String name) {this.name = name;}@Overridepublic String toString() {return "Student{" +"name='" + name + '\'' +", clazz=" + clazz +'}';}
}
<bean id="studentBean" class="org.qiu.spring.bean.Student"><property name="name" value="张三"/><property name="clazz" ref="clazzBean"/>
</bean>
@Test
public void testCascade(){ApplicationContext application = new ClassPathXmlApplicationContext("cascade.xml");Student studentBean = application.getBean("studentBean", Student.class);System.out.println(studentBean);Clazz clazzBean = application.getBean("clazzBean", Clazz.class);System.out.println(clazzBean);
}

运行结果:  

 

<!--级联属性赋值顺序不能颠倒使用级联属性赋值,clazz 属性需要提供对应的 getter 方法
-->
<bean id="studentBean" class="org.qiu.spring.bean.Student"><property name="name" value="张三"/><property name="clazz" ref="clazzBean"/><property name="clazz.name" value="高三二班"/>
</bean><bean id="clazzBean" class="org.qiu.spring.bean.Clazz"/>
public Clazz getClazz() {return clazz;
}

运行结果:  

 

  • 注入数组

数组中的元素是简单类型

package org.qiu.spring.bean;import java.util.Arrays;/*** @author 秋玄* @version 1.0* @email qiu_2022@aliyun.com* @project Spring* @package org.qiu.spring.bean* @date 2022-11-06-22:16* @since 1.0*/
public class QianDaYe {private String[] aiHaos;public void setAiHaos(String[] aiHaos) {this.aiHaos = aiHaos;}@Overridepublic String toString() {return "QianDaYe{" +"aiHaos=" + Arrays.toString(aiHaos) +'}';}
}
<bean id="yuQian" class="org.qiu.spring.bean.QianDaYe"><!-- 数组属性的元素是简单类型 --><property name="aiHaos"><array><value>抽烟</value><value>喝酒</value><value>烫头</value></array></property>
</bean>
@Test
public void testArray(){ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-array.xml");QianDaYe yuQian = applicationContext.getBean("yuQian", QianDaYe.class);System.out.println(yuQian);
}

运行结果:

 

数组中元素不是简单类型  

package org.qiu.spring.bean;import java.util.Arrays;/*** @author 秋玄* @version 1.0* @email qiu_2022@aliyun.com* @project Spring* @package org.qiu.spring.bean* @date 2022-11-06-22:16* @since 1.0*/
public class QianDaYe {private String[] aiHaos;private Woman[] women;public void setAiHaos(String[] aiHaos) {this.aiHaos = aiHaos;}public void setWomen(Woman[] women) {this.women = women;}@Overridepublic String toString() {return "QianDaYe{" +"aiHaos=" + Arrays.toString(aiHaos) +", women=" + Arrays.toString(women) +'}';}
}
package org.qiu.spring.bean;/*** @author 秋玄* @version 1.0* @email qiu_2022@aliyun.com* @project Spring* @package org.qiu.spring.bean* @date 2022-11-06-22:24* @since 1.0*/
public class Woman {private String name;public void setName(String name) {this.name = name;}@Overridepublic String toString() {return "Woman{" +"name='" + name + '\'' +'}';}
}
<!-- 数组属性的元素不是简单类型 -->
<property name="women"><array><ref bean="w1" /><ref bean="w2" /><ref bean="w3" /><ref bean="w4" /></array>
</property>

运行结果:  

 

  • 注入 List 集合、Set 集合

注入集合,集合中存放简单类型元素就用 <value> 标签,存放的是非简单类型元素就用 <ref> 标签

package org.qiu.spring.bean;import java.util.List;
import java.util.Set;/*** @author 秋玄* @version 1.0* @email qiu_2022@aliyun.com* @project Spring* @package org.qiu.spring.bean* @date 2022-11-07-07:12* @since 1.0*/
public class Person {// 注入 List 集合private List<String> names;// 注入 Set 集合private Set<String> addrs;@Overridepublic String toString() {return "Person{" +"names=" + names +", addrs=" + addrs +'}';}public void setNames(List<String> names) {this.names = names;}public void setAddrs(Set<String> addrs) {this.addrs = addrs;}
}
<bean id="personBean" class="org.qiu.spring.bean.Person"><property name="names"><!-- List 集合有序可重复--><list><value>张三</value><value>张三</value><value>张三</value><value>李四</value><value>李四</value><value>王五</value><value>王五</value></list></property><property name="addrs"><!-- Set 集合无序不可重复--><set><value>北京</value><value>广东</value><value>上海</value><value>上海</value><value>上海</value></set></property>
</bean>
 @Test
public void testCollection(){ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-collection.xml");Person person = applicationContext.getBean("personBean", Person.class);System.out.println(person);
}

运行结果:  

 

  • 注入 Map 集合

// 注入 Map 集合
private Map<Integer,String> phones;@Override
public String toString() {return "Person{" +"names=" + names +", addrs=" + addrs +", phones=" + phones +'}';
}
<property name="phones"><map><!-- key/value 都是简单类型,使用 key/value --><entry key="1" value="110"/><entry key="3" value="119"/><entry key="5" value="120"/><!-- key/value 不是简单类型,使用 key-ref/value-ref --><!-- <entry key-ref="" value-ref="" /> --></map>
</property>

运行结果:  

 

  • 注入 Properties 属性类对象

/*** 注入属性类对象* Properties 本质上也是一个 Map 集合* Properties 的父类是 HashTable,HashTable 实现了 Map 接口* 虽然也是一个 Map 集合,但是注入方式相似,但不同* Properties 的 key/value 只能是 String 类型*/
private Properties properties;public void setProperties(Properties properties) {this.properties = properties;
}@Override
public String toString() {return "Person{" +"names=" + names +", addrs=" + addrs +", phones=" + phones +", properties=" + properties +'}';
}
<property name="properties"><props><prop key="driver">com.nmysql.cj.jdbc.Driver</prop><prop key="url">jdbc:mysql://localhost:3306/spring</prop><prop key="username">root</prop><prop key="password">123</prop></props>
</property>

运行结果:  

 

  • 注入 null 和 空字符串

package org.qiu.spring.bean;/*** @author 秋玄* @version 1.0* @email qiu_2022@aliyun.com* @project Spring* @package org.qiu.spring.bean* @date 2022-11-07-07:42* @since 1.0*/
public class Cat {private String name;private int age;@Overridepublic String toString() {return "Cat{" +"name='" + name + '\'' +", age=" + age +'}';}public void setName(String name) {this.name = name;}public void setAge(int age) {this.age = age;}
}
<bean id="catBean" class="org.qiu.spring.bean.Cat"><!-- <property name="name" value=""></property> --><!-- 手动注入 null --><property name="name"><null/></property><property name="age" value="3"></property>
</bean>
@Test
public void testNull(){ApplicationContext application = new ClassPathXmlApplicationContext("set-di.xml");Cat cat = application.getBean("catBean", Cat.class);System.out.println(cat);
}

运行结果:  

不给属性值注入,默认就是 null,也可以手动注入  

<bean id="catBean" class="org.qiu.spring.bean.Cat"><!-- 注入 空字符串 --><property name="name" value=""></property><!-- 方式二 --><!--<property name="name"><value/></property>--><property name="age" value="3"></property>
</bean>

运行结果:  

 

  • 注入特殊字符

XML中有5个特殊字符,分别是:<、>、'、"、&

以上5个特殊符号在XML中会被特殊对待,会被当做XML语法的一部分进行解析,如果这些特殊符号直接出现在注入的字符串当中,会报错。

解决方案包括两种:

  • 第一种:特殊符号使用转义字符代替。

  • 第二种:将含有特殊符号的字符串放到:<![CDATA[]]> 当中。因为放在 CDATA 区中的数据不会被 XML 文件解析器解析。

5个特殊字符对应的转义字符分别是:

特殊字符转义字符
>&gt;
<&lt;
'&apos;
"&quot;
&&amp;

 

package org.qiu.spring.bean;/*** @author 秋玄* @version 1.0* @email qiu_2022@aliyun.com* @project Spring* @package org.qiu.spring.bean* @date 2022-11-07-07:58* @since 1.0*/
public class Math {private String result;public void setResult(String result) {this.result = result;}@Overridepublic String toString() {return "Math{" +"result='" + result + '\'' +'}';}
}
<!-- 方式一 -->
<bean id="mathBean" class="org.qiu.spring.bean.Math"><property name="result" value="2 &lt; 3"/>
</bean>
@Test
public void testSpecial(){ApplicationContext applicationContext = new ClassPathXmlApplicationContext("set-di.xml");Math mathBean = applicationContext.getBean("mathBean", Math.class);System.out.println(mathBean);
}

运行结果:  

 

使用 CDATA 方式:  

<bean id="mathBean" class="com.powernode.spring6.beans.Math"><property name="result"><!--只能使用 value 标签--><value><![CDATA[2 < 3]]></value></property>
</bean>

注意:使用CDATA时,不能使用value属性,只能使用value标签。

运行结果:

 

六、p 命名空间注入

目的:简化配置

使用 p 命名空间注入的前提条件包括两个:

  • 第一:在 XML 头部信息中添加 p 命名空间的配置信息:xmlns:p="http://www.springframework.org/schema/p"

  • 第二:p 命名空间注入是基于 setter 方法的,所以需要对应的属性提供 setter 方法

package org.qiu.spring.bean;import java.util.Date;/*** @author 秋玄* @version 1.0* @email qiu_2022@aliyun.com* @project Spring* @package org.qiu.spring.bean* @date 2022-11-07-08:10* @since 1.0*/
public class Dog {private String name;private int age;private Date birth;@Overridepublic String toString() {return "Dog{" +"name='" + name + '\'' +", age=" + age +", birth=" + birth +'}';}public void setName(String name) {this.name = name;}public void setAge(int age) {this.age = age;}public void setBirth(Date birth) {this.birth = birth;}
}
<?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/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd"><!--第一步:在 Spring 配置文件头部添加 p 命名空间xmlns:p="http://www.springframework.org/schema/p"第二步:使用p:属性名 = "属性值"(简单类型)p:属性名-ref = "属性值"(非简单类型)--><bean id="dogBean" class="org.qiu.spring.bean.Dog" p:name="小花" p:age="3" p:birth-ref="birthBean"/><!-- 获取当前系统时间 --><bean id="birthBean" class="java.util.Date"/></beans>
@Test
public void testP(){ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-p.xml");Dog dog = applicationContext.getBean("dogBean", Dog.class);System.out.println(dog);
}

运行结果:  

 

七、C 命名空间注入 

c 命名空间是简化构造方法注入的

使用 c 命名空间的两个前提条件:

第一:需要在 xml 配置文件头部添加信息:xmlns:c="http://www.springframework.org/schema/c"

第二:需要提供构造方法

package org.qiu.spring.bean;/*** @author 秋玄* @version 1.0* @email qiu_2022@aliyun.com* @project Spring* @package org.qiu.spring.bean* @date 2022-11-07-21:34* @since 1.0*/
public class People {private String name;private int age;private boolean sex;@Overridepublic String toString() {return "People{" +"name='" + name + '\'' +", age=" + age +", sex=" + sex +'}';}public People(String name, int age, boolean sex) {this.name = name;this.age = age;this.sex = sex;}
}
<?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:c="http://www.springframework.org/schema/c"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd"><!--第一步:在 Spring 配置文件头部添加 xmlns:c="http://www.springframework.org/schema/c"第二步:使用可以使用属性名字或下标注入--><bean id="peopleBean" class="org.qiu.spring.bean.People" c:_0="张三" c:age="18" c:sex="true"></bean></beans>
@Test
public void testC(){ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-c.xml");People people = applicationContext.getBean("peopleBean", People.class);System.out.println(people);
}

运行结果:  

 

八、util 命名空间 

util 命名空间让配置复用

使用util命名空间的前提是:在spring配置文件头部添加配置信息。如下:

package org.qiu.spring.bean;import java.util.Properties;/*** @author 秋玄* @version 1.0* @email qiu_2022@aliyun.com* @project Spring* @package org.qiu.spring.bean* @date 2022-11-07-21:34* @since 1.0*/
public class MyDataSource1 {private Properties properties;public void setProperties(Properties properties) {this.properties = properties;}@Overridepublic String toString() {return "MyDataSource1{" +"properties=" + properties +'}';}
}
package org.qiu.spring.bean;import java.util.Properties;/*** @author 秋玄* @version 1.0* @email qiu_2022@aliyun.com* @project Spring* @package org.qiu.spring.bean* @date 2022-11-07-21:34* @since 1.0*/
public class MyDataSource2 {private Properties properties;public void setProperties(Properties properties) {this.properties = properties;}@Overridepublic String toString() {return "MyDataSource2{" +"properties=" + properties +'}';}
}
<?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:util="http://www.springframework.org/schema/util"xsi:schemaLocation="http://www.springframework.org/schema/beans 				http://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"><util:properties id="prop"><prop key="driver">com.mysql.cj.jdbc.Driver</prop><prop key="url">jdbc:mysql://localhost:3306/spring</prop><prop key="username">root</prop><prop key="password">123456</prop></util:properties><bean id="dataSource1" class="org.qiu.spring.bean.MyDataSource1"><property name="properties" ref="prop"/></bean><bean id="dataSource2" class="org.qiu.spring.bean.MyDataSource2"><property name="properties" ref="prop"/></bean>
</beans>
@Test
public void testUtil(){ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-util.xml");MyDataSource1 dataSource1 = applicationContext.getBean("dataSource1", MyDataSource1.class);System.out.println(dataSource1);MyDataSource2 dataSource2 = applicationContext.getBean("dataSource2", MyDataSource2.class);System.out.println(dataSource2);
}

运行结果:  

 

九、基于 XML 自动装配 

Spring 还可以完成自动化的注入,自动化注入又被称为自动装配。它可以根据名字进行自动装配,也可以根据类型进行自动装配

  • 根据名称自动装配

package org.qiu.spring.dao;import org.slf4j.Logger;
import org.slf4j.LoggerFactory;/*** @author 秋玄* @version 1.0* @email qiu_2022@aliyun.com* @project Spring* @package org.qiu.spring.dao* @date 2022-11-05-20:40* @since 1.0*/
public class OrderDao {private static final Logger logger = LoggerFactory.getLogger(OrderDao.class);public void generate(){logger.info("订单正在生成......");}
}
package org.qiu.spring.service;import org.qiu.spring.dao.OrderDao;/*** @author 秋玄* @version 1.0* @email qiu_2022@aliyun.com* @project Spring* @package org.qiu.spring.service* @date 2022-11-05-20:42* @since 1.0*/
public class OrderService {private OrderDao orderDao;public void setOrderDao(OrderDao orderDao) {this.orderDao = orderDao;}/*** 生成订单的业务方法*/public void generate(){orderDao.generate();}
}
<!--id一般也叫做 bean 的名称根据名称自动装配,这个 id 需要是 setter 方法的名字,去掉 set,再将首字母改为小写
-->
<bean id="orderDao" class="org.qiu.spring.dao.OrderDao"/><!--根据名字进行自动装配自动装配也是基于 set 注入实现的
-->
<bean id="orderService" class="org.qiu.spring.service.OrderService" autowire="byName"></bean>
@Test
public void testAutowire(){ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-autowire.xml");OrderService orderService = applicationContext.getBean("orderService", OrderService.class);orderService.generate();
}

运行结果:  

 

  • 根据类型自动装配

package org.qiu.spring.dao;import org.slf4j.Logger;
import org.slf4j.LoggerFactory;/*** @author 秋玄* @version 1.0* @email qiu_2022@aliyun.com* @project Spring* @package org.qiu.spring.dao* @date 2022-10-31-20:27* @since 1.0*/
public class VipDao {private static final Logger logger = LoggerFactory.getLogger(UserDao.class);public void insert(){logger.info("正在保存VIP信息......");}
}
package org.qiu.spring.dao;import org.slf4j.Logger;
import org.slf4j.LoggerFactory;/*** @author 秋玄* @version 1.0* @email qiu_2022@aliyun.com* @project Spring* @package org.qiu.spring.dao* @date 2022-10-31-17:46* @since 1.0*/
public class UserDao {private static final Logger logger = LoggerFactory.getLogger(UserDao.class);public void insert(){logger.info("正在保存用户数据......");}
}
package org.qiu.spring.service;import org.qiu.spring.dao.UserDao;
import org.qiu.spring.dao.VipDao;/*** @author 秋玄* @version 1.0* @email qiu_2022@aliyun.com* @project Spring* @package org.qiu.spring.service* @date 2022-11-01-20:25* @since 1.0*/
public class CustomerService {private UserDao userDao;private VipDao vipDao;public void setUserDao(UserDao userDao) {this.userDao = userDao;}public void setVipDao(VipDao vipDao) {this.vipDao = vipDao;}/*public CustomerService(UserDao userDao, VipDao vipDao) {this.userDao = userDao;this.vipDao = vipDao;}*/public void save(){userDao.insert();vipDao.insert();}
}
<!--根据类型注入自动装配是基于 setter 方法的,必须提供 setter 方法根据类型进行自动装配的时候,在有效的配置文件当中,某种类型的实例只能有一个
-->
<bean class="org.qiu.spring.dao.VipDao"/>
<bean class="org.qiu.spring.dao.UserDao"/>
<bean id="cs" class="org.qiu.spring.service.CustomerService" autowire="byType"/>
@Test
public void testAutowireByType(){ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-autowire.xml");CustomerService customerService = applicationContext.getBean("cs", CustomerService.class);customerService.save();
}

运行结果:  

 

十、Spring 引入外部属性配置文件

我们都知道编写数据源的时候是需要连接数据库的信息的,例如:driver url username password等信息。这些信息可以单独写到一个属性配置文件中,这样用户修改起来会更加的方便

第一步:写一个数据源类,提供相关属性:

package org.qiu.spring.bean;import javax.sql.DataSource;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.logging.Logger;/*** @author 秋玄* @version 1.0* @email qiu_2022@aliyun.com* @project Spring* @package org.qiu.spring.bean* @date 2022-11-09-11:01* @since 1.0*/
public class MyDataSource implements DataSource {private String driver;private String url;private String username;private String password;@Overridepublic String toString() {return "MyDataSource{" +"driver='" + driver + '\'' +", url='" + url + '\'' +", username='" + username + '\'' +", password='" + password + '\'' +'}';}public String getDriver() {return driver;}public void setDriver(String driver) {this.driver = driver;}public String getUrl() {return url;}public void setUrl(String url) {this.url = url;}public String getUsername() {return username;}public void setUsername(String username) {this.username = username;}public String getPassword() {return password;}public void setPassword(String password) {this.password = password;}// 省略实现 DataSource 接口中的方法
}

第二步:在类路径下新建 jdbc.properties 文件,并配置信息:  

driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/spring
username=root
password=root

第三步:在 spring 配置文件中引入 context 命名空间  

<?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.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"></beans>

第四步:在 spring 中配置使用 jdbc.properties 文件  

<?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/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsd"><context:property-placeholder location="jdbc.properties"/><bean id="dataSource" class="org.qiu.spring.bean.MyDataSource"><property name="driver" value="${driver}"/><property name="url" value="${url}"/><property name="username" value="${username}"/><property name="password" value="${password}"/></bean></beans>

测试程序:  

@Test
public void testProperties(){ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-properties.xml");MyDataSource dataSource = applicationContext.getBean("dataSource", MyDataSource.class);System.out.println(dataSource);
}

运行结果:  

 

一  叶  知  秋,奥  妙  玄  心

相关文章:

【Spring】Spring 对 Ioc 的实现

一、Ioc 控制反转 控制反转是一种思想 控制反转是为了降低程序耦合度&#xff0c;提高程序扩展力&#xff0c;达到 OCP 原则&#xff0c;达到 DIP 原则 控制反转&#xff0c;反转的是什么&#xff1f; 将对象的创建权利交出去&#xff0c;交给第三方容器负责 将对象和对象之…...

QT学习文件操作类 QFile

&#xff08;一&#xff09;QFile QFile 是 Qt 框架中用于文件处理的一个类。它提供了读取和写入文件的功能&#xff0c;支持文本和二进制文件。QFile 继承自 QIODevice &#xff0c;因此它可以像其他 IO 设备一样使用。 &#xff08;1&#xff09;主要功能 1. 文件读写…...

VOL_常用记录!!

目录 前端1.js如何获取当前时间(yy-MM-dd HH:MM:SS)2.http请求3.grid扩展js常用 后端1.待补充 前端 1.js如何获取当前时间(yy-MM-dd HH:MM:SS) getCurrentTime() {const now new Date();return ${now.getFullYear()}-${(now.getMonth() 1).toString().padStart(2, "0&…...

解决Typora导出HTML不显示图片

解决Typora导出HTML不显示图片 产生原因 Typora导出HTML不显示图片&#xff0c;可能时图片存放在我们的硬盘中。 我们可以将markdown中的图片转化为base64格式&#xff0c;嵌入到html中。 解决步骤 首先&#xff0c;下载 TyporaToBase64.jar 密码:45jq 其次&#xff0c;将…...

React Native开发iOS实战录

文章目录 背景环境准备主要工具xcode安装安装CocoaPods 基本步骤采用Expo go运行iOS模拟器运行安装在真机上测试发布到苹果商店 常见问题ruby3在macOS上编译失败import of module ‘glog.glog.log_severity’ appears within namespace ‘google’yarn网络问题pod安装失败unabl…...

C++局部变量与全局变量

在C中&#xff0c;可以为函数的参数指定默认值。这样做的好处是在调用函数时&#xff0c;如果没有提供对应的参数&#xff0c;那么将会使用默认值。 下面是一个求2个或3个数中最大数的函数的示例&#xff0c;其中使用了默认参数&#xff1a; #include <iostream> using…...

深入理解ES的倒排索引

目录 数据写入过程 词项字典 term dictionary 倒排表 posting list FOR算法 RBM算法 ArrayContainer BitMapContainer 词项索引 term index 在Elasticsearch中&#xff0c;倒排索引的设计无疑是惊为天人的&#xff0c;下面看下倒排索引的结构。 倒排索引分为词项索引【…...

HTML世界之第一重天

一、HTML 元素 注&#xff1a;HTML 文档由 HTML 元素定义。 1.HTML 元素 开始标签 * 元素内容 结束标签 * <p> 这是一个段落 </p> <a href"default.htm"> 这是一个链接 </a> <br> 换行 开始标签常被称为起始标签&…...

docker run报 docker: Error response from daemon: no command specified.

docker run报 docker: Error response from daemon: no command specified. 1. export出mysql的container为tar, 拷贝到另一台虚拟机, import该tar为image, docker run该image时报 docker: Error response from daemon: no command specified. 时间240211 export出mysql的con…...

vue3 之 商城项目—详情页

整体认识 路由配置 准备组件模版 <script setup></script><template><div class"xtx-goods-page"><div class"container"><div class"bread-container"><el-breadcrumb separator">">&…...

Linux笔记之Docker进行镜像备份与迁移

Linux笔记之Docker进行镜像备份与迁移 ——2024-02-11 code review! 文章目录 Linux笔记之Docker进行镜像备份与迁移1. 导出容器文件系统为 tar 归档文件2. 将 tar 归档文件导入为新的 Docker 镜像3. 运行新的 Docker 镜像并创建容器 1. 导出容器文件系统为 tar 归档文件 要导…...

C#,欧拉常数(Euler Constant)的算法与源代码

1 欧拉常数 欧拉常数最先由瑞士数学家莱昂哈德 欧拉 (Leonhard Euler) 在1735年发表的文章《De Progressionibus harmonicus observationes》中定义。欧拉曾经使用γ作为它的符号&#xff0c;并计算出了它的前6位&#xff0c;1761年他又将该值计算到了16位 。 欧拉常数最先由瑞…...

asio监听eventfd

c - Does BOOST asio supports eventfd? like epoll - Stack Overflow asio的官方example并没有asio监听eventfd的例子&#xff0c;但asio支持posix::stream_descriptor&#xff0c; 如果将eventfd包装成posix::stream_descriptor&#xff0c;并注册到io_context里&#xf…...

《统计学简易速速上手小册》第9章:统计学在现代科技中的应用(2024 最新版)

文章目录 9.1 统计学与大数据9.1.1 基础知识9.1.2 主要案例&#xff1a;社交媒体情感分析9.1.3 拓展案例 1&#xff1a;电商销售预测9.1.4 拓展案例 2&#xff1a;实时交通流量分析 9.2 统计学在机器学习和人工智能中的应用9.2.1 基础知识9.2.2 主要案例&#xff1a;预测客户流…...

问题排查利器 - 分布式 trace

在分布式系统开发中&#xff0c;系统间的调用往往会横跨多个应用之间的接口。负责的调用链路也导致了&#xff0c;当线上环境出现问题时&#xff0c;例如请求失败、延迟增加或错误发生&#xff0c;我们无法第一时间确定是哪个环节出了问题&#xff0c;这给故障排查和修复带来了…...

C++进阶(十四)智能指针

&#x1f4d8;北尘_&#xff1a;个人主页 &#x1f30e;个人专栏:《Linux操作系统》《经典算法试题 》《C》 《数据结构与算法》 ☀️走在路上&#xff0c;不忘来时的初心 文章目录 一、为什么需要智能指针&#xff1f;二、内存泄漏1、 什么是内存泄漏&#xff0c;内存泄漏的危…...

GPT最新进展:推出视频功能!迭代即将来临!

随着人工智能的不断进步&#xff0c;ChatGPT正准备以其全新的视频功能大跃进&#xff0c;同时&#xff0c;备受期待的GPT-5也即将在今年露面&#xff0c;预示着AI领域即将迎来一场变革。 在最近一期充满激情的Unconfuse Me播客中&#xff0c;OpenAI的首席执行官Sam Altman与技…...

各款Excel、word在线预览工具对比分析以及onlyoffice预览Excel加载时间长的解决方案

对于onlyoffice插件预览慢的问题分析&#xff1a; 研究了一下onlyoffice&#xff0c;得出以下结论&#xff01; 对于预览慢的问题&#xff0c;原因出在文件类型上&#xff0c;文件类型为低版本xls而非新版xlsx文件&#xff0c;onlyoffice服务器会自动将该文件转换为xlsx文件再…...

【课程作业_01】国科大2023模式识别与机器学习实践作业

国科大2023模式识别与机器学习实践作业 作业内容 从四类方法中选三类方法&#xff0c;从选定的每类方法中 &#xff0c;各选一种具体的方法&#xff0c;从给定的数据集中选一 个数据集&#xff08;MNIST&#xff0c;CIFAR-10&#xff0c;电信用户流失数据集 &#xff09;对这…...

LeetCode374. Guess Number Higher or Lower——二分查找

文章目录 一、题目二、题解 一、题目 We are playing the Guess Game. The game is as follows: I pick a number from 1 to n. You have to guess which number I picked. Every time you guess wrong, I will tell you whether the number I picked is higher or lower th…...

继承

1.继承的作用 有些类与类之间存在特殊关系&#xff0c;下级别的成员除了拥有上一级别的共性&#xff0c;还有自己的特性。 这个时候我们就可以考虑利用继承技术&#xff0c;减少重复代码。 总结&#xff1a; 继承的好处&#xff1a;可以减少重复的代码 class A : public B;…...

北斗卫星在物联网时代的应用探索

北斗卫星在物联网时代的应用探索 在当今数字化时代&#xff0c;物联网的应用已经深入到人们的生活中的方方面面&#xff0c;让我们的生活更加智能便捷。而北斗卫星系统作为我国自主研发的卫星导航系统&#xff0c;正为物联网的发展提供了强有力的支撑和保障。本文将全面介绍北…...

SQL注入 - 利用报错函数 floor 带回回显

环境准备:构建完善的安全渗透测试环境:推荐工具、资源和下载链接_渗透测试靶机下载-CSDN博客 一、原理 利用COUNT(), FLOOR(), RAND(), 和 GROUP BY来生成主键重复错误 函数解释 count(): 这个函数用于计算满足某一条件下的行数,是SQL中的一个聚合函数,常用于统计查询结…...

NLP_Bag-Of-Words(词袋模型)

文章目录 词袋模型用词袋模型计算文本相似度1.构建实验语料库2.给句子分词3.创建词汇表4.生成词袋表示5.计算余弦相似度6.可视化余弦相似度 词袋模型小结 词袋模型 词袋模型是一种简单的文本表示方法&#xff0c;也是自然语言处理的一个经典模型。它将文本中的词看作一个个独立…...

C语言rand随机数知识解析和猜数字小游戏

rand随机数 rand C语言中提供了一个可以随机生成一个随机数的函数&#xff1a;rand&#xff08;&#xff09; 函数原型&#xff1a; int rand(void);rand函数返回的值的区间是&#xff1a;0~RAND_MAX(32767)之间。大部分编译器都是32767。 #include<stdlib.h> int ma…...

django中的缓存功能

一&#xff1a;介绍 Django中的缓存功能是一个重要的性能优化手段&#xff0c;它可以将某些耗时的操作&#xff08;如数据库查询、复杂的计算等&#xff09;的结果存储起来&#xff0c;以便在后续的请求中直接使用这些缓存的结果&#xff0c;而不是重新执行耗时的操作。Django…...

三、搜索与图论

DFS 排列数字 #include<iostream> using namespace std; const int N 10; int a[N], b[N]; int n;void dfs(int u){if(u > n){for(int i 1; i < n; i)cout<<a[i]<<" ";cout<<endl;return;}for(int i 1; i < n; i){if(!b[i]){b[…...

【翻译】Processing安卓模式的安装使用及打包发布(内含中文版截图)

原文链接在下面的每一章的最前面。 原文有三篇&#xff0c;译者不知道贴哪篇了&#xff0c;这篇干脆标了原创。。 译者声明&#xff1a;本文原文来自于GNU协议支持下的项目&#xff0c;具备开源二改授权&#xff0c;可翻译后公开。 文章目录 Install&#xff08;安装&#xff0…...

MATLAB图像处理——边缘检测及图像分割算法

1.检测图像中的线段 clear clc Iimread(1.jpg);%读入图像 Irgb2gray(I); %转换为灰度图像 h1[-1, -1. -1; 2, 2, 2; -1, -1, -1]; %模板 h2[-1, -1, 2; -1, 2, -1; 2, -1, -1]; h3[-1, 2, -1; -1, 2, -1; -1, 2, -1]; h4[2, -1, -1; -1, 2, -1; -1, -1, 2]; J1imfilter(I, h1)…...

探索设计模式:原型模式深入解析

探索设计模式&#xff1a;原型模式深入解析 设计模式是软件开发中用于解决常见问题的标准解决方案。它们不仅能提高代码的可维护性和可复用性&#xff0c;还能让其他开发者更容易理解你的设计决策。今天&#xff0c;我们将聚焦于创建型模式之一的原型模式&#xff08;Prototyp…...