日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

jpa jdbc jndi_没有J2EE容器的JNDI和JPA

發(fā)布時(shí)間:2023/12/3 编程问答 37 豆豆
生活随笔 收集整理的這篇文章主要介紹了 jpa jdbc jndi_没有J2EE容器的JNDI和JPA 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

jpa jdbc jndi

我們希望通過盡可能簡(jiǎn)單的設(shè)置來測(cè)試一些JPA代碼。 計(jì)劃僅使用Java和Maven,不使用應(yīng)用程序服務(wù)器或其他J2EE容器。

我們的JPA配置需要兩件事才能成功運(yùn)行:

  • 數(shù)據(jù)庫(kù)來存儲(chǔ)數(shù)據(jù),
  • JNDI訪問數(shù)據(jù)庫(kù)。

這篇文章分為兩個(gè)部分。 第一部分顯示了如何在測(cè)試中使用獨(dú)立的JNDI和嵌入式內(nèi)存數(shù)據(jù)庫(kù)。 其余各章說明了該解決方案的工作方式。

所有使用的代碼都可以在Github上找到 。 如果您對(duì)解決方案感興趣,但不想閱讀說明,請(qǐng)從Github下載項(xiàng)目并僅閱讀第一章。

JPA測(cè)試

本章說明如何在測(cè)試中使用我們的代碼來啟用獨(dú)立的JNDI和嵌入式內(nèi)存數(shù)據(jù)庫(kù)。 本文的其余部分將說明該解決方案的工作方式和原因。

該解決方案具有三個(gè)“ API”類:

  • JNDIUtil – JNDI初始化,清理和一些便捷方法,
  • InMemoryDBUtil –數(shù)據(jù)庫(kù)和數(shù)據(jù)源的創(chuàng)建/刪除,
  • AbstractTestCase –在第一次測(cè)試之前清理數(shù)據(jù)庫(kù),在每次測(cè)試之前清理JNDI。

我們使用Liquibase維護(hù)數(shù)據(jù)庫(kù)結(jié)構(gòu)。 如果您不想使用Liquibase,則必須自定義InMemoryDBUtil類。 調(diào)整方法createDatabaseStructure以執(zhí)行所需的操作。

Liquibase將所有需要的數(shù)據(jù)庫(kù)更改的列表保存在名為changelog的文件中。 除非另行配置,否則每個(gè)更改僅運(yùn)行一次。 即使將更改日志文件多次應(yīng)用于同一數(shù)據(jù)庫(kù)。

用法

從AbstractTestCase擴(kuò)展的任何測(cè)試用例都將:

  • 在第一次測(cè)試之前刪除數(shù)據(jù)庫(kù),
  • 在每次測(cè)試之前安裝獨(dú)立的JNDI或刪除其中存儲(chǔ)的所有數(shù)據(jù),
  • 每次測(cè)試之前,請(qǐng)對(duì)數(shù)據(jù)庫(kù)運(yùn)行Liquibase changelog。

JPA測(cè)試用例必須擴(kuò)展AbstractTestCase并重寫getInitialChangeLog方法。 該方法應(yīng)返回changelog文件位置。

public class DemoJPATest extends AbstractTestCase {private static final String CHANGELOG_LOCATION = "src/test/java/org/meri/jpa/simplest/db.changelog.xml";private static EntityManagerFactory factory;public DemoJPATest() {}@Overrideprotected String getInitialChangeLog() {return CHANGELOG_LOCATION;}@Test@SuppressWarnings("unchecked")public void testJPA() {EntityManager em = factory.createEntityManager();Query query = em.createQuery("SELECT x FROM Person x");List<Person> allUsers = query.getResultList();em.close();assertFalse(allUsers.isEmpty());}@BeforeClasspublic static void createFactory() {factory = Persistence.createEntityManagerFactory("Simplest");}@AfterClasspublic static void closeFactory() {factory.close();}}

注意:在每次測(cè)試之前刪除數(shù)據(jù)庫(kù)會(huì)更清潔。 但是,刪除并重新創(chuàng)建數(shù)據(jù)庫(kù)結(jié)構(gòu)是昂貴的操作。 這會(huì)使測(cè)試用例放慢太多。 僅在上課之前這樣做似乎是一種合理的折衷。

雖然數(shù)據(jù)庫(kù)僅刪除一次,但更改日志在每次測(cè)試之前運(yùn)行。 看起來似乎很浪費(fèi),但是此解決方案具有一些優(yōu)勢(shì)。 首先, getInitialChangeLog方法不必是靜態(tài)的,并且可以在每個(gè)測(cè)試中覆蓋。 其次,配置為“ runAlways”的更改將在每次測(cè)試之前運(yùn)行,因此可能包含一些廉價(jià)的清理或其他初始化操作。

日本國(guó)家發(fā)展研究院

本章說明什么是JNDI,如何使用它以及如何配置它。 如果您對(duì)理論不感興趣,請(qǐng)?zhí)料乱徽隆?在此創(chuàng)建獨(dú)立的JNDI。

基本用法

JNDI允許客戶端通過名稱存儲(chǔ)和查找數(shù)據(jù)和對(duì)象。 數(shù)據(jù)存儲(chǔ)通過Context接口的實(shí)現(xiàn)來訪問。

以下代碼顯示了如何在JNDI中存儲(chǔ)數(shù)據(jù):

Context ctx = new InitialContext(); ctx.bind("jndiName", "value"); ctx.close();

第二段代碼顯示了如何在JNDI中查找內(nèi)容:

Context ctx = new InitialContext(); Object result = ctx.lookup("jndiName"); ctx.close();

嘗試在沒有J2EE容器的情況下運(yùn)行以上代碼,您會(huì)得到一個(gè)錯(cuò)誤:

javax.naming.NoInitialContextException: Need to specify class name in environment or system property, or as an applet parameter, or in an application resource file: java.naming.factory.initialat javax.naming.spi.NamingManager.getInitialContext(Unknown Source)at javax.naming.InitialContext.getDefaultInitCtx(Unknown Source)at javax.naming.InitialContext.getURLOrDefaultInitCtx(Unknown Source)at javax.naming.InitialContext.bind(Unknown Source)at org.meri.jpa.JNDITestCase.test(JNDITestCase.java:16)at ...

該代碼不起作用,因?yàn)镮nitialContext類不是真實(shí)的數(shù)據(jù)存儲(chǔ)。 InitialContext類只能找到Context接口的另一個(gè)實(shí)例,并將所有工作委托給它。 它既無法存儲(chǔ)數(shù)據(jù)也無法找到它們。

上下文工廠

真正的上下文,即完成所有工作并能夠存儲(chǔ)/查找數(shù)據(jù)的上下文,必須由上下文工廠創(chuàng)建。 本節(jié)說明如何創(chuàng)建上下文工廠以及如何配置InitialContext以使用它。

每個(gè)上下文工廠必須實(shí)現(xiàn)InitialContextFactory接口,并且必須具有無參數(shù)構(gòu)造函數(shù):

package org.meri.jpa.jndi;public class MyContextFactory implements InitialContextFactory {@Overridepublic Context getInitialContext(Hashtable environment) throws NamingException {return new MyContext();}}

我們的工廠返回一個(gè)簡(jiǎn)單的上下文,稱為MyContext 。 其lookup方法始終返回字符串“存儲(chǔ)的值”:

class MyContext implements Context {@Overridepublic Object lookup(Name name) throws NamingException {return "stored value";}@Overridepublic Object lookup(String name) throws NamingException {return "stored value";}.. the rest ... }

JNDI配置在哈希表中的類之間傳遞。 鍵始終包含屬性名稱,而值包含屬性值。 由于初始上下文構(gòu)造函數(shù)InitialContext()沒有參數(shù),因此假定為空哈希表。 該類還具有一個(gè)替代構(gòu)造函數(shù),該構(gòu)造函數(shù)將配置屬性哈希表作為參數(shù)。

使用屬性"java.naming.factory.initial"來指定上下文工廠類名稱。 該屬性在Context.INITIAL_CONTEXT_FACTORY常量中定義。

Hashtable env = new Hashtable(); env.put(Context.INITIAL_CONTEXT_FACTORY, "className");Context ctx = new InitialContext(environnement);

下一步測(cè)試配置MyContextFactory并檢查創(chuàng)建的初始上下文是否返回“存儲(chǔ)值”,無論如何:

@Test @SuppressWarnings({ "unchecked", "rawtypes" }) public void testDummyContext() throws NamingException {Hashtable environnement = new Hashtable();environnement.put(Context.INITIAL_CONTEXT_FACTORY, "org.meri.jpa.jndi.MyContextFactory");Context ctx = new InitialContext(environnement);Object value = ctx.lookup("jndiName");ctx.close();assertEquals("stored value", value); }

當(dāng)然,僅當(dāng)您可以將具有自定義屬性的哈希表提供給初始上下文構(gòu)造函數(shù)時(shí),此方法才有效。 這通常是不可能的。 大多數(shù)庫(kù)使用開頭所示的無參數(shù)構(gòu)造函數(shù)。 他們假設(shè)初始上下文類具有可用的默認(rèn)上下文工廠,并且無參數(shù)構(gòu)造函數(shù)將使用該工廠。

命名經(jīng)理

初始上下文使用NamingManager創(chuàng)建真實(shí)上下文。 命名管理器具有靜態(tài)方法getInitialContext(Hashtable env) ,該方法返回上下文的實(shí)例。 參數(shù)env包含用于構(gòu)建上下文的配置屬性。

默認(rèn)情況下,命名管理器讀取Context.INITIAL_CONTEXT_FACTORY從env哈希表,并創(chuàng)建指定的初始上下文工廠的實(shí)例。 然后,工廠方法將創(chuàng)建一個(gè)新的上下文實(shí)例。 如果未設(shè)置該屬性,則命名管理器將引發(fā)異常。

可以自定義命名管理員的行為。 NamingManager類具有方法setInitialContextFactoryBuilder 。 如果設(shè)置了初始上下文工廠構(gòu)建器,則命名管理器將使用它來創(chuàng)建上下文工廠。

您只能使用此方法一次。 已安裝的上下文工廠生成器無法更改。

try {MyContextFactoryBuilder builder = new MyContextFactoryBuilder();NamingManager.setInitialContextFactoryBuilder(builder); } catch (NamingException e) {// handle exception }

初始上下文工廠構(gòu)建器必須實(shí)現(xiàn)InitialContextFactoryBuilder接口。 界面很簡(jiǎn)單。 它只有一個(gè)方法InitialContextFactory createInitialContextFactory(Hashtable env) 。

摘要

簡(jiǎn)而言之,初始上下文將實(shí)際的上下文初始化委托給命名管理器,命名管理器將其委托給上下文工廠。 上下文工廠由初始上下文工廠構(gòu)建器的實(shí)例創(chuàng)建。


獨(dú)立JNDI

我們將創(chuàng)建并安裝獨(dú)立的JNDI實(shí)現(xiàn)。 我們的獨(dú)立JNDI實(shí)現(xiàn)的入口點(diǎn)是JNDIUtil類。

在沒有應(yīng)用程序服務(wù)器的情況下啟用JNDI需要三件事:

  • Context和InitialContextFactory接口的實(shí)現(xiàn),
  • InitialContextFactoryBuilder接口的實(shí)現(xiàn),
  • 初始上下文工廠構(gòu)建器的安裝以及清除所有存儲(chǔ)數(shù)據(jù)的能力。

上下文和工廠

我們從osjava項(xiàng)目中獲取了SimpleJNDI實(shí)現(xiàn),并對(duì)其進(jìn)行了修改以更好地滿足我們的需求。 該項(xiàng)目使用了新的BSD許可證 。

將SimpleJNDI maven依賴項(xiàng)添加到pom.xml中:

simple-jndisimple-jndi0.11.4.1

SimpleJNDI帶有一個(gè)MemoryContext上下文,該上下文專門存在于內(nèi)存中。 它幾乎不需要任何配置,并且其狀態(tài)永遠(yuǎn)不會(huì)保存下來。 它幾乎滿足了我們的需求,除了兩件事:

  • 它的close()方法刪除所有存儲(chǔ)的數(shù)據(jù),
  • 每個(gè)實(shí)例默認(rèn)使用其自己的存儲(chǔ)。

大多數(shù)庫(kù)都假定close方法優(yōu)化了資源。 他們傾向于在每次加載或存儲(chǔ)數(shù)據(jù)時(shí)調(diào)用它。 如果close方法在存儲(chǔ)完所有數(shù)據(jù)后立即刪除它們,則上下文將無用。 我們必須擴(kuò)展MemoryContext類并重寫close方法:

@SuppressWarnings({"rawtypes"}) public class CloseSafeMemoryContext extends MemoryContext {public CloseSafeMemoryContext(Hashtable env) {super(env);}@Overridepublic void close() throws NamingException {// Original context lost all data on close();// That made it unusable for my tests. }}

按照約定,建造者/工廠系統(tǒng)會(huì)為每次使用創(chuàng)建新的上下文實(shí)例。 如果它們不共享數(shù)據(jù),則不能使用JNDI在不同庫(kù)之間傳輸數(shù)據(jù)。

幸運(yùn)的是,這個(gè)問題也很容易解決。 如果環(huán)境哈希表包含值為"true"屬性"org.osjava.sj.jndi.shared" "true" ,則創(chuàng)建的內(nèi)存上下文將使用公共靜態(tài)存儲(chǔ)。 因此,我們的初始上下文工廠將創(chuàng)建CloseSafeMemoryContext實(shí)例,并將其配置為使用公共存儲(chǔ):

public class CloseSafeMemoryContextFactory implements InitialContextFactory {private static final String SHARE_DATA_PROPERTY = "org.osjava.sj.jndi.shared";public Context getInitialContext(Hashtable environment) throws NamingException {// clone the environnementHashtable sharingEnv = (Hashtable) environment.clone();// all instances will share stored dataif (!sharingEnv.containsKey(SHARE_DATA_PROPERTY)) {sharingEnv.put(SHARE_DATA_PROPERTY, "true");}return new CloseSafeMemoryContext(sharingEnv);;}}

初始上下文工廠生成器

我們的構(gòu)建器的行為幾乎與原始命名管理器實(shí)現(xiàn)相同。 如果傳入環(huán)境中存在屬性Context.INITIAL_CONTEXT_FACTORY ,則將創(chuàng)建指定的工廠。

但是,如果缺少此屬性,則構(gòu)建器將創(chuàng)建CloseSafeMemoryContextFactory的實(shí)例。 原始的命名管理器將引發(fā)異常。

我們對(duì)InitialContextFactoryBuilder接口的實(shí)現(xiàn):

public InitialContextFactory createInitialContextFactory(Hashtable env) throws NamingException {String requestedFactory = null;if (env!=null) {requestedFactory = (String) env.get(Context.INITIAL_CONTEXT_FACTORY);}if (requestedFactory != null) {return simulateBuilderlessNamingManager(requestedFactory);}return new CloseSafeMemoryContextFactory(); }

方法simulateBuilderlessNamingManager使用類加載器加載請(qǐng)求的上下文工廠:

private InitialContextFactory simulateBuilderlessNamingManager(String requestedFactory) throws NoInitialContextException {try {ClassLoader cl = getContextClassLoader();Class requestedClass = Class.forName(className, true, cl);return (InitialContextFactory) requestedClass.newInstance();} catch (Exception e) {NoInitialContextException ne = new NoInitialContextException(...);ne.setRootCause(e);throw ne;} }private ClassLoader getContextClassLoader() {return (ClassLoader) AccessController.doPrivileged(new PrivilegedAction() {public Object run() {return Thread.currentThread().getContextClassLoader();}}); }

構(gòu)建器安裝和上下文清理

最后,我們必須安裝上下文工廠生成器。 當(dāng)我們想在測(cè)試中使用獨(dú)立的JNDI時(shí),我們還需要一種方法來清除測(cè)試之間的所有存儲(chǔ)數(shù)據(jù)。 兩者都在initializeJNDI方法內(nèi)部完成,該方法將在每次測(cè)試之前運(yùn)行:

public class JNDIUtil {public void initializeJNDI() {if (jndiInitialized()) {cleanAllInMemoryData();} else {installDefaultContextFactoryBuilder();}}}

如果已經(jīng)設(shè)置了默認(rèn)上下文工廠構(gòu)建器,那么將初始化JNDI:

private boolean jndiInitialized() {return NamingManager.hasInitialContextFactoryBuilder();}

安裝默認(rèn)上下文工廠生成器:

private void installDefaultContextFactoryBuilder() {try {NamingManager.setInitialContextFactoryBuilder(new ImMemoryDefaultContextFactoryBuilder());} catch (NamingException e) {//We can not solve the problem. We will let it go up without//having to declare the exception every time.throw new ConfigurationException(e);} }

使用原始的方法實(shí)現(xiàn)close在MemoryContext類清理存儲(chǔ)數(shù)據(jù):

private void cleanAllInMemoryData() {CleanerContext cleaner = new CleanerContext();try {cleaner.close();} catch (NamingException e) {throw new RuntimeException("Memory context cleaning failed:", e);} }class CleanerContext extends MemoryContext {private static Hashtable environnement = new Hashtable();static {environnement.put("org.osjava.sj.jndi.shared", "true");}public CleanerContext() {super(environnement);}}


內(nèi)存數(shù)據(jù)庫(kù)

Apache Derby是用Java實(shí)現(xiàn)的開源關(guān)系數(shù)據(jù)庫(kù)。 根據(jù)Apache許可證2.0版提供。 Derby能夠在嵌入式模式下運(yùn)行。 嵌入式數(shù)據(jù)庫(kù)數(shù)據(jù)存儲(chǔ)在文件系統(tǒng)或內(nèi)存中。

對(duì)Derby的Maven依賴關(guān)系:

org.apache.derbyderby10.8.2.2

創(chuàng)建數(shù)據(jù)源

使用EmbeddedDatasource類的實(shí)例連接到數(shù)據(jù)庫(kù)。 每當(dāng)數(shù)據(jù)庫(kù)名稱以“ memory:”開頭時(shí),數(shù)據(jù)源將使用一個(gè)內(nèi)存中實(shí)例。

以下代碼創(chuàng)建指向內(nèi)存數(shù)據(jù)庫(kù)實(shí)例的數(shù)據(jù)源。 如果數(shù)據(jù)庫(kù)尚不存在,將創(chuàng)建它:

private EmbeddedDataSource createDataSource() {EmbeddedDataSource dataSource = new EmbeddedDataSource();dataSource.setDataSourceName(dataSourceJndiName);dataSource.setDatabaseName("memory:" + databaseName);dataSource.setCreateDatabase("create");return dataSource; }

刪除數(shù)據(jù)庫(kù)

清理數(shù)據(jù)庫(kù)的最簡(jiǎn)單方法是刪除并重新創(chuàng)建它。 創(chuàng)建嵌入式數(shù)據(jù)源的實(shí)例,將連接屬性“ drop”設(shè)置為“ true”,并調(diào)用其getConnection方法。 它將刪除數(shù)據(jù)庫(kù)并引發(fā)異常。

private static final String DATABASE_NOT_FOUND = "XJ004";private void dropDatabase() {EmbeddedDataSource dataSource = createDataSource();dataSource.setCreateDatabase(null);dataSource.setConnectionAttributes("drop=true");try {//drop the database; not the nicest solution, but worksdataSource.getConnection();} catch (SQLNonTransientConnectionException e) {//this is OK, database was dropped} catch (SQLException e) {if (DATABASE_NOT_FOUND.equals(e.getSQLState())) {//attempt to drop non-existend database//we will ignore this errorreturn ; }throw new ConfigurationException("Could not drop database.", e);}}


數(shù)據(jù)庫(kù)結(jié)構(gòu)

我們使用Liquibase創(chuàng)建數(shù)據(jù)庫(kù)結(jié)構(gòu)和測(cè)試數(shù)據(jù)。 數(shù)據(jù)庫(kù)結(jié)構(gòu)保存在所謂的變更日志文件中。 它是一個(gè)xml文件,但是如果您不想學(xué)習(xí)另一種xml語言,則可以包含DDL或SQL代碼。

Liquibase及其優(yōu)點(diǎn)不在本文討論范圍之內(nèi)。 此演示最相關(guān)的優(yōu)勢(shì)是它能夠?qū)ν粩?shù)據(jù)庫(kù)多次運(yùn)行同一變更日志。 每次運(yùn)行僅將新更改應(yīng)用于數(shù)據(jù)庫(kù)。 如果文件未更改,則什么都不會(huì)發(fā)生。

您可以將更改日志添加到j(luò)ar或war中,并在每次啟動(dòng)應(yīng)用程序時(shí)運(yùn)行它。 這樣可以確保數(shù)據(jù)庫(kù)始終更新為最新版本。 無需配置或安裝腳本。

將Liquibase依賴項(xiàng)添加到pom.xml:

org.liquibaseliquibase-core2.0.3

以下更新日志創(chuàng)建一個(gè)名為Person的表,并將一個(gè)條目“斜杠– Simon Worth”放入其中:

<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog/1.9"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog/1.9 http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-1.9.xsd"><changeSet id="1" author="meri"><comment>Create table structure for users and shared items.</comment><createTable tableName="person"><column name="user_id" type="integer"><constraints primaryKey="true" nullable="false" /></column><column name="username" type="varchar(1500)"><constraints unique="true" nullable="false" /></column><column name="firstname" type="varchar(1500)"/><column name="lastname" type="varchar(1500)"/><column name="homepage" type="varchar(1500)"/><column name="about" type="varchar(1500)"/></createTable></changeSet><changeSet id="2" author="meri" context="test"><comment>Add some test data.</comment><insert tableName="person"><column name="user_id" valueNumeric="1" /><column name="userName" value="slash" /><column name="firstName" value="Simon" /><column name="lastName" value="Worth" /><column name="homePage" value="http://www.slash.blogs.net" /><column name="about" value="I like nature and writing my blog. The blog contains my opinions about everything." /></insert></changeSet></databaseChangeLog>

Liquibase的使用非常簡(jiǎn)單。 使用數(shù)據(jù)源創(chuàng)建新的Liquibase實(shí)例,運(yùn)行其update方法并處理所有聲明的異常:

private void initializeDatabase(String changelogPath, DataSource dataSource) {try {//create new liquibase instanceConnection sqlConnection = dataSource.getConnection();DatabaseConnection db = new DerbyConnection(sqlConnection);Liquibase liquibase = new Liquibase(changelogPath, new FileSystemResourceAccessor(), db);//update the databaseliquibase.update("test");} catch (SQLException e) {// We can not solve the problem. We will let it go up without// having to declare the exception every time.throw new ConfigurationException(DB_INITIALIZATION_ERROR, e);} catch (LiquibaseException e) {// We can not solve the problem. We will let it go up without// having to declare the exception every time.throw new ConfigurationException(DB_INITIALIZATION_ERROR, e);}}


結(jié)束

每次運(yùn)行測(cè)試時(shí),獨(dú)立的JNDI數(shù)據(jù)庫(kù)和嵌入式內(nèi)存數(shù)據(jù)庫(kù)都已啟動(dòng)并正在運(yùn)行。 盡管JNDI設(shè)置可能是通用的,但數(shù)據(jù)庫(kù)的構(gòu)建可能需要對(duì)項(xiàng)目進(jìn)行特定的修改。

可以從Github上免費(fèi)下載示例項(xiàng)目,并使用/修改任何有用的內(nèi)容。

參考:在This is Stuff博客上,我們的JCG合作伙伴 Maria Jurcovicova從JNDI和JPA Without J2EE Con??tainer運(yùn)行 。


翻譯自: https://www.javacodegeeks.com/2012/04/jndi-and-jpa-without-j2ee-container.html

jpa jdbc jndi

總結(jié)

以上是生活随笔為你收集整理的jpa jdbc jndi_没有J2EE容器的JNDI和JPA的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。