精通Spring+4.x++企業開發與實踐之IoC容器中裝配Bean

Spring配置概述

Spring屬性注入

JavaBean關於屬性命名的特殊規範

Spring配置文件中的<property></property>元素所指定的屬性名和Bean實現類的Setter方法知足Sun JavaBean的屬性命名規範:xxx的屬性對應的setXxx()方法。 注意:須要知足:"變量的前兩個字母要全大寫,要麼全小寫"。不然會出現具備誤導性的錯誤。java

構造函數注入

1.須要提供一個有參構造函數,能夠按類型匹配注入,按因此匹配注入,聯合類型和因此匹配注入,經過自身類型反射匹配注入 2.構造函數注入的問題,可能出現循環依賴的問題,致使程序沒法啓動,只要改爲屬性注入便可解決這個問題。 例子: ConstructorInject.javaweb

public class ConstructorInject {

	public static void main(String[] args) {
		ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:iocdemo.xml");
		Person person = (Person) ctx.getBean("p2");
		System.out.println(person.toString());
	}
}

iocdemo.xmlspring

<?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-4.0.xsd">
<bean id="person" class="com.flexible.beans.Person">
	<property name="userName"><value>zhangsan</value></property>
	<property name="userAge"><value>20</value></property>
</bean>

	<bean id="p2" class="com.flexible.beans.Person">
		<constructor-arg type="java.lang.String"><value>zhangsan</value></constructor-arg>
		<constructor-arg type="java.lang.Integer"><value>28</value></constructor-arg>
	</bean>
</beans>

Person.javaexpress

public class Person {
public String userName;
public Integer userAge;

	public Person() {
	}

	public Person(String userName, Integer userAge) {
		this.userName = userName;
		this.userAge = userAge;
	}

	public String getUserName() {
		return userName;
	}

	public void setUserName(String userName) {
		this.userName = userName;
	}

	public Integer getUserAge() {
		return userAge;
	}

	public void setUserAge(Integer userAge) {
		this.userAge = userAge;
	}

	[@Override](https://my.oschina.net/u/1162528)
	public String toString() {
		return "Person{" +
				"userName='" + userName + '\'' +
				", userAge=" + userAge +
				'}';
	}
}

工廠方法注入

1.非靜態的工廠
xml配置以下:
		<bean id="factory" class="com.flexible.factorymethod.NonStaticFactoryMethod"></bean>
	<bean id="p3" factory-bean="factory" factory-method="createPerson"></bean>
2.工廠代碼以下:
/**
 * 使用非靜態的工廠
 */
public class NonStaticFactoryMethod {
	/**
	 * 工廠的建立方法
	 * [@return](https://my.oschina.net/u/556800)
	 */
	public Person createPerson(){
	Person p = new Person();
	p.setUserName("lisi");
	p.setUserAge(20);
	return p;
}
}

測試方法以下:緩存

//非靜態的方式
		ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:iocdemo.xml");
		Person person = (Person) ctx.getBean("p3");
		System.out.println(person.toString());

靜態方法: xml配置以下: <bean id="p4" class="com.flexible.factorymethod.StaticMethod" factory-method="createPerson"></bean> 工廠類以下: public class StaticMethod {session

/**
	 * 工廠的建立方法
	 *
	 * [@return](https://my.oschina.net/u/556800)
	 */
	public static Person createPerson() {
		Person p = new Person();
		p.setUserName("lisi");
		p.setUserAge(20);
		return p;
	}
}

測試方法: //靜態的方式 Person person2 = (Person) ctx.getBean("p3"); System.out.println(person2.toString());ide

注入參數

Spring不但能夠將String,int等字面值注入Bean中,還能夠將集合,Map等類型的數據注入Bean中,還能夠注入配置文件的其餘Bean。函數

字面值

字面值標識可使用字符串表示的值,這些值能夠經過<value></value>元素注入。xml中包含5個特殊字符,分別是&,<,>,`,'。若是包含這些這些特殊字符,就須要使用<![CDATA[]]特殊標籤。測試

注意: 通常狀況下,XML解析器會忽略元素標籤內部字符串的先後空格,可是Spring卻不會忽略元素標籤內部字符串的字符串的先後空格,若是經過如下配置爲屬性注入值<property name="xx"><value> xxx xx</value></property>,那麼先後,中間的空格將會被一塊兒賦值給xx屬性。flex

引入其餘的Bean

xml文件以下:

<bean id="p5" class="com.flexible.beans.Person">
		<property name="userName" value="wangwu"></property>
		<property name="userAge" value="20"></property>
	</bean>
	<bean id="h1" class="com.flexible.beans.House">
		<property name="length" value="100"></property>
		<property name="width" value="80"></property>
		<property name="height" value="60"></property>
		<property name="person"><ref bean="p5"></ref></property>
	</bean>

<ref></ref>元素能夠經過如下3個屬性引用容器中的其餘Bean。 1.bean:經過該屬性能夠引用同一容器或者父容器中的Bean,這是最多見的形式 2.local:經過該屬性只能引用同一配置文件中定義的Bean,他能夠利用XML解析器自動檢驗引用的合法性,以便開發者再編寫配置時可以幾時發現並糾正配置的錯誤。 3.parent:引用父容器中的Bean,如<ref parent="car">的配置說明car的Bean是父容器中的Bean.

Bean類 public class House {

private Double length;

	private Double width;

	private Double height;

	private Person person;
	public House() {
	}

	public Double getLength() {
		return length;
	}

	public void setLength(Double length) {
		this.length = length;
	}

	public Double getWidth() {
		return width;
	}

	public void setWidth(Double width) {
		this.width = width;
	}

	public Double getHeight() {
		return height;
	}

	public void setHeight(Double height) {
		this.height = height;
	}

	public Person getPerson() {
		return person;
	}

	public void setPerson(Person person) {
		this.person = person;
	}

	[@Override](https://my.oschina.net/u/1162528)
	public String toString() {
		return "House{" +
				"length=" + length +
				", width=" + width +
				", height=" + height +
				", person=" + person +
				'}';
	}
}

測試例子:

public class BeanInjectDemo {

	public static void main(String[] args) {
		ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:iocdemo.xml");
		House house = (House) ctx.getBean("h1");
		System.out.println(house.toString());
	}
}

內部Bean

內部Bean和Java的匿名內部類類似,既沒有名字,也不能被其餘的Bean引用,只能在聲明處爲外部Bean提供實例注入。 內部Bean幾時提供了id,name,scope屬性,也會被忽略。scope默認prototype類型。

<bean id="h1" class="com.flexible.beans.House">
		<property name="length" value="100"></property>
		<property name="width" value="80"></property>
		<property name="height" value="60"></property>
		<property name="person">
		<bean id="p5" class="com.flexible.beans.Person">
		<property name="userName" value="wangwu"></property>
		<property name="userAge" value="20"></property>
		</bean>
	</property>
	</bean>

null值

若是但願給Bean的某個屬性賦值一個null,若是使用<property name="xx"><value></value></property>這樣是達不到預期的效果的,可是可使用<property name="xx"><null/></property>

級聯屬性

javaBean代碼

public class Car {

	Person person = new Person();

	public Car() {
	}

	public Person getPerson() {
		return person;
	}

	public void setPerson(Person person) {
		this.person = person;
	}

	[@Override](https://my.oschina.net/u/1162528)
	public String toString() {
		return "Car{" +
				"person=" + person +
				'}';
	}
}

xml配置以下:

<bean id="car" class="com.flexible.beans.Car">
		<property name="person.userName" value="zhaoliu"></property>
		<property name="person.userAge" value="20"></property>
	</bean>

測試代碼:

public class CascadeDemo {

	public static void main(String[] args) {

		ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:iocdemo.xml");
		Car car = (Car) ctx.getBean("car");
		System.out.println(car.toString());
	}
}

集合屬性注入

Favorite.java

public class Favorite {
	/**
	 * List集合
	 */
	private List<String> favoties = new ArrayList<>();
	/**
	 * map集合
	 */
	private Map<String,String> map = new HashMap<>();

	/**
	 *property,看做是特殊的map
	 */
	private Properties properties = new Properties();
	public Favorite() {
	}

	public List<String> getFavoties() {
		return favoties;
	}

	public void setFavoties(List<String> favoties) {
		this.favoties = favoties;
	}

	public Map<String, String> getMap() {
		return map;
	}

	public void setMap(Map<String, String> map) {
		this.map = map;
	}

	public Properties getProperties() {
		return properties;
	}

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

	@Override
	public String toString() {
		return "Favorite{" +
				"favoties=" + favoties +
				", map=" + map +
				", properties=" + properties +
				'}';
	}
}

xml配置以下:

<bean id="favorities" class="com.flexible.beans.Favorite">
		<property name="favoties">
			<list>
				<value>游泳</value>
				<value>足球</value>
				<value>籃球</value>
				<value>棒球</value>
			</list>
		</property>
		<property name="map">
			<map>
				<entry>
					<key><value>k1</value></key>
					<value>運動</value>
				</entry>
				<entry>
					<key><value>k2</value></key>
					<value>上班</value>
				</entry>
				<entry>
					<key><value>k3</value></key>
					<value>約會</value>
				</entry>
			</map>
		</property>
		<property name="properties">
			<props>
				<prop key="k1">value1</prop>
				<prop key="k2">value2</prop>
				<prop key="k3">value3</prop>
			</props>
		</property>
	</bean>
	<!--集合的合併-->
	<bean id="childFavorities" parent="favorities">
		<property name="favoties">
			<list merge="true">
				<value>賽車</value>
			</list>
		</property>
		<property name="map">
			<map merge="true">
				<entry>
					<key><value>k12</value></key>
					<value>運動2</value>
				</entry>
				<entry>
					<key><value>k22</value></key>
					<value>上班2</value>
				</entry>
				<entry>
					<key><value>k32</value></key>
					<value>約會2</value>
				</entry>
			</map>
		</property>
		<property name="properties">
			<props merge="true">
				<prop key="k12">value12</prop>
				<prop key="k22">value22</prop>
				<prop key="k32">value32</prop>
			</props>
		</property>
	</bean>

測試代碼:

public class CollectionAttributeInject {
	public static void main(String[] args) {
		ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:iocdemo.xml");
		Favorite favorite = (Favorite) ctx.getBean("favorities");
		System.out.println(favorite.toString());
		Favorite childFavorities = (Favorite) ctx.getBean("childFavorities");
		System.out.println(childFavorities.toString());
	}
}

方法替換

Spring支持使用一個Bean的方法取替換另外一個Bean的方法,替換別的Bean的Bean須要實現org.springframework.beans.factory.support.MethodReplacer接口,Spring利用該接口去替換目標Bean的方法.

被替換的類的方法:

public class Boss {
public House getHouse(){
House house = new House();
house.setHeight(1.0);
house.setLength(2.0);
house.setLength(3.0);
house.setWidth(4.0);
return house;
}
}

替換的類須要實現org.springframework.beans.factory.support.MethodReplacer接口。

public class Boss2 implements MethodReplacer{
	@Override
	public Object reimplement(Object obj, Method method, Object[] args) throws Throwable {
		House house = new House();
		house.setHeight(1.01);
		house.setLength(2.01);
		house.setLength(3.01);
		house.setWidth(4.01);
		return house;
	}
}

xml配置文件以下:

<bean id="boss1" class="com.flexible.inject.methodreplace.Boss">
		<replaced-method name="getHouse" replacer="boss2"></replaced-method>
	</bean>
	<bean id="boss2" class="com.flexible.inject.methodreplace.Boss2"></bean>

<bean></bean>之間的關係

繼承

父<bean></bean>主要功能是簡化子<bean></bean>的配置,因此通常聲明未abstract="true",表示這個bean不實例化爲一個對應的Bean。

例子:

xml配置以下:

<!--這裏將這個bean聲明爲abstract,代表它不會被實例化爲bean-->
	<bean id="parent" class="com.flexible.beans.Parent" abstract="true">
		<property name="height" value="1.70"></property>
		<property name="weight" value="130"></property>
	</bean>
	<bean id="child" parent="parent">
		<property name="height" value="1.80"></property>
	</bean>

Parent.java

public class Parent {

	private String height;

	private String weight;

	public Parent() {
	}

	public String getHeight() {
		return height;
	}

	public void setHeight(String height) {
		this.height = height;
	}

	public String getWeight() {
		return weight;
	}

	public void setWeight(String weight) {
		this.weight = weight;
	}

	@Override
	public String toString() {
		return "Parent{" +
				"height='" + height + '\'' +
				", weight='" + weight + '\'' +
				'}';
	}
}

測試代碼:

public class RelationBean {

	public static void main(String[] args) {
		ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:iocdemo.xml");
		Parent parent = (Parent) ctx.getBean("child");
		System.out.println(parent.toString());
	}
}

依賴

在配置中若是當前的Bean的初始化須要前置條件(即這些前置條件都得初始化徹底了才能實例化這個Bean)知足的時候,這個時候就須要使用到依賴 depends-on=""

引用

若是須要引用到另外一個bean的id,須要使用<idref bean="xx"></idref>的方式使用,若是在同一個配置文件可使用<idref local="xx"></idref> 這樣在開發的時期就能夠辨別出錯誤。

整合多哥配置文件。

導入其餘配置文件能夠是import,例如:

<import resource="classpath:iocdemo2.xml"></import>

Bean做用域

低版本的Spring值支持兩種做用域,因此纔有singleton="true|false"的配置方式。Spring爲了向後兼容,依然支持這種配置方式。推薦使用的配置方式:scope="<做用域類型>"

除了以上5種Bean做用域以外,Spring支持自定義Bean的做用域。經過org.springframework.beans.factory.config.Scope接口定義做用域,再經過org.springframework.beans.factory.config.CustomScopeConfigurer這個BeanFactoryPostprocessor註冊自定義的Bean做用域。

singleton

Spring將Bean默認的做用域都是singleton,而且將實例化的Bean緩存再ApplicationContext的容器中,若是不但願實例化以開始就進行,可使用lazy-init="true"實現懶加載,可是某些特殊的狀況依然會提早加載。

prototype

scope="prototype"非單例做用域。它會將bean交給調用者,由調用者來管理bean的生命週期。

與Web應用環境相關的Bean做用域

<!--從類路徑下加載Spring配置文件,classpath關鍵字特指類路徑下加載-->
  <!--這裏能夠多個配置文件,建議採用逗號隔開的方式-->
	<context-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>classpath:flexible-context.xml</param-value>
	</context-param>
	<!--負責啓動Spring容器的監聽器,他將引用上面的上下文件參數獲取Spring配置文件的地址-->
   <!--該監聽器在web容器啓動時自動運行,它會根據contextConfigLocation
   Web容器參數獲取Spring配置文件,而且啓動Spring容器。-->
	<listener>
		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
	</listener>

正常的狀況咱們只須要咱們只須要經過ContextLoadListoner(或者ContextLoadServlet)將Web容器與Spring容器進行整合,可是若是須要使用request,session,globalSession這三個WEB相關的做用域,就須要也配置requestContextListiner(實現了ServletRequestListener監聽器接口),ContextLoadListener只是負責監聽容器的啓動和關閉。若是Spring須要request,session,globalSession的支持就須要獲取Web容器的HTTP請求事件,此時RequestContestListener就能夠實現這個需求。

配置以下:

<!--須要三大web容器做用域時須要這個配置-->
	<listener>
		<listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
	</listener>

request:請求結束後,實例就會銷燬。 session:橫跨整個HTTPSession ,Session的全部HTTP請求共享同一個實例,HTTPSession結束後,實例銷燬。 globalSession做用域:globalSession做用域累死與session做用域,只能再Portlet的Web應用種使用。Portlet規範定義了全局Session的概念,它被組成的Portlet Web應用全部子Portlet共享,若是不在Portlet Web應用的環境下,那麼globalSession==session做用域。

做用域依賴問題

須要啓用<aop:scoped-proxy></aop:scoped-proxy>

FactoryBean

通常狀況下,Spring經過反射機制利用<bean></bean>的class屬性指定實現類實例化Bean。在某些狀況下,實例化Bean的過程比較複雜,若是按照傳統的方式,須要提供大量的配置信息。配置方式收到了限制。Spring爲此提供了一個org.springframework.beans.factory.FactoryBean工廠類結論,用戶可能夠經過實現改工廠類接口定製實例化Bean的邏輯。 例子: HouseFactoryBean.java

public class HouseFactoryBean implements FactoryBean<House> {
   Person person;

	public Person getPerson() {
		return person;
	}
	//接受參數
	public void setPerson(Person person) {
		this.person = person;
	}
	//實例化bean
	@Override
	public House getObject() throws Exception {
	   House house = new House();
	   house.setLength(1.0);
	   house.setWidth(2.0);
	   house.setHeight(3.0);
	   house.setPerson(person);
		return house;
	}
	//返回Hourse類型
	@Override
	public Class<House> getObjectType() {
		return House.class;
	}
	//比鬧事通FactoryBean返回的Bean是singleton
	@Override
	public boolean isSingleton() {
		return false;
	}

	@Override
	public String toString() {
		return "HouseFactoryBean{" +
				"person=" + person +
				'}';
	}
}

xml配置文件

<bean id="hoursebean" class="com.flexible.beans.HouseFactoryBean">
    <property name="person">
        <bean id="personp" class="com.flexible.beans.Person">
            <property name="userAge" value="20"></property>
            <property name="userName" value="zhouqi"></property>
        </bean>
    </property>
</bean>

測試代碼:

public static void main(String[] args) throws Exception {
    ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:iocdemo.xml");
    House house = (House) ctx.getBean("hoursebean");
    System.out.println(house.toString());
    //若是想拿到HouseFactoryBean實例,就須要 &hoursebean
    HouseFactoryBean houseFactoryBean = (HouseFactoryBean) ctx.getBean("&hoursebean");
    System.out.println(houseFactoryBean.toString());
    House object = houseFactoryBean.getObject();
    System.out.println(object.toString());
}

執行結果:

House{length=1.0, width=2.0, height=3.0, person=Person{userName='zhouqi', userAge=20}}
HouseFactoryBean{person=Person{userName='zhouqi', userAge=20}}
House{length=1.0, width=2.0, height=3.0, person=Person{userName='zhouqi', userAge=20}}

基於註解的配置

使用註解定義Bean

Spring提供了四個定義Bean的註解,分別是:

1.@Repository:用於對DAO實現類進行標註

2.@Service:用於對Service實現類進行標註。

3.@Controller:用於對Controller實現類進行標註

4.@Component能夠代替上面任何一個,只不過上面這些標註有特殊的用途,能一目瞭然的看出來這個bean的用途。

掃描註解定義的Bean

Spring提供了一個Context命名空間,它提供了經過掃面類包以應用註解定義Bean的方式。 例如:<context:component-scan base-package="com.flexible"></context:component-scan>

若是但願掃描的是特定的類而非基礎包下的類,可使用resource-pattern熟悉過濾

<context:component-scan base-package="com.flexible" resource-pattern="xx/*.class"></context:component-scan>

若是還須要更加具體的過濾,如實現了xx接口的,

例如:

<context:component-scan base-package="com.flexible">
		<context:include-filter type="aspectj" expression="com.flexible..*Controller+"></context:include-filter>
	</context:component-scan>

自動裝配

使用 @Autoired

@Qualifier知道注入的Bean的名稱。

對標準註解的@Resource(須要提供一個Bean的名字)和@Inject的支持。

Bean做用範圍及生命過程的方法。

@Scope("prototype")

Bean做用範圍以及生命過程方法

在使用<bean></bean>進行配置時,能夠經過init-method和destroy-method屬性指定Bean的初始化以及容器銷燬前執行的方法。Spring從2.5開始支持JSR-250中定義的@PostConstruct和@PreDestroy註解,在Spring中他們至關於init-method和destroy-method屬性的功能,不過在使用註解時,能夠在一個Bean中定義多個@PostConstruct和@PreDestroy方法。

例子:

LifeCyleBea.java

@Component
public class LifeCyleBean {
	private String info;
	@PostConstruct
	public void initMethod(){
		System.out.println(".....this is initMethod....");
	}
	@PreDestroy
	public void destroyMethod(){
		System.out.println(".....this is destroyMethod....");
	}

	public String getInfo() {
		return info;
	}

	public void setInfo(String info) {
		this.info = info;
	}
}

xml配置文件開啓包掃描:

<context:component-scan base-package="com.flexible">
	</context:component-scan>

測試代碼:

public class LifeCycleDemo {

	public static void main(String[] args) {
		ApplicationContext context = new ClassPathXmlApplicationContext("classpath:iocdemo.xml");
		((ClassPathXmlApplicationContext)context).destroy();

	}
}

執行結果: .....this is initMethod....

.....this is destroyMethod....

基於Java類的配置

使用Java類提供的Bean定義信息

JavaConfig是Spring的一個子項目,旨在經過Java類的方式提供Bean的定義信息,在Spring2.0開始發佈。普通的POJO只有標註@Configuration註解,就能夠爲Spring容器提供Bean定義的信息。每一個標註了@Bean得類方法都至關於提供了一個Bean的定義信息。經過代碼進行配置要靈活點,可是配置文件配置要簡潔些。

AppConfig.java

//將一個POJO標註爲定義Bean的配置類
@Configuration
public class AppConfig {
//能夠給bean一個名字,還能夠配置做用域
//@Bean(name = "car")
@Bean
public Car getCar(){
	Car car = new Car();
	car.setPerson(getPerson());
	return car;
}
@Bean
public Person getPerson(){
	 Person person = new Person();
	 person.setUserName("王八");
	 person.setUserAge(28);
	 return person;
}
}

測試代碼:

ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
    Car car = (Car) context.getBean(Car.class);
    System.out.println(car.toString());

使用基於Java類的配置信息啓動Spring容器

1.直接經過@Configuration類啓動Spring容器

Spring提供了一個AnnotationConfigApplicationContext類,它可以直接經過標註¥Configuration的Java啓動Spring容器。 例如:

ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);

AnnotationConfigApplicationContext還支持經過編碼的方式加載多個@Configuration配置類,而後刷新容器應用的配置類。 例如:

ApplicationContext context = new AnnotationConfigApplicationContext();
context.register(AppConfig.class);
context.register(AppConfig2.class);
context.refresh()

經過XML配置文件引用@Configuration的配置

標註了@Configuration的配置類與標註了@Component的類同樣也是一個Bean,它能夠被Spring的context:compent-scan</context:compent-scan>掃描到相應的配置類便可。 例如:

<context:component-scan base-package="com.flexible" resource-pattern="AppConfig.class">

經過@Configuration配置類引用XML配置信息

在@Configuration配置類中能夠經過@ImportResource引入XML配置文件,在RefXmlConfig配置類中便可直接經過@Autowired引用XML配置文件定義的Bean. RefXmlConfig.java

@Configuration
@ImportResource("classpath:iocdemo2.xml")
public class RefXmlConfig {
@Bean
@Autowired
public PenBox getPenBox(PenBox penBox){
	return penBox;
}
}

PenBox.java

public class PenBox {

private String markName;

private String description;

	public PenBox() {
	}

	public String getMarkName() {
		return markName;
	}

	public void setMarkName(String markName) {
		this.markName = markName;
	}

	public String getDescription() {
		return description;
	}

	public void setDescription(String description) {
		this.description = description;
	}

	@Override
	public String toString() {
		return "PenBox{" +
				"markName='" + markName + '\'' +
				", description='" + description + '\'' +
				'}';
	}
}

測試代碼:

public class RefXmlDemo {
	public static void main(String[] args) {
		ApplicationContext context = new AnnotationConfigApplicationContext(RefXmlConfig.class);
		PenBox penBox = (PenBox) context.getBean("penBox");
		System.out.println(penBox.toString());
	}
}

執行結果:

PenBox{markName='xxx', description='description'}
相關文章
相關標籤/搜索