- 浏览: 20378 次
- 性别:
- 来自: 广州
最新评论
作者 Enterra Inc. 译者 王丽娟 发布于 2007年12月29日
在开发企业级业务应用(企业规模)时,客户往往要求在不修改系统源代码的情况下对应用对象模型的扩展性提供支持。利用可扩展域模型可以实现新功能的开发,而不需要额外的精力和成本
- 应用的使用周期将被延长;
- 外部因素改变时,系统工作流也可以随之被修改;
- 已经被部署的应用可以被“设定”,使其符合企业的特定情况。
完成以上功能需求最简单、最具成本效益的方法应该是在应用中实现支持自定义字段的可扩展业务实体。
什么是“自定义字段”?
什么是自定义字段?最终用户如何从中受益呢?自定义字段是一种对象属性,它不是由系统开发人员在开发阶段创建的,而是在系统实际使用中由系统用户在不改变任何源代码的情况下添加到对象中的。
可能会需要哪些功能呢?
让我们举一个CRM(客户关系管理系统)应用的例子来领会一下。 假设我们有一个客户“Client”对象。理论上讲,这个对象可以有任意多的各种属性:几个email地址、若干电话号码和地址等。某公司的销售部门可能会使用其中一个属性,但其它公司却会完全忽略它。将最终用户可能会用到的(也可能不会用到的)所有属性都加入到对象当中,这是很浪费并很不合理的。
既然这样,允许系统用户(或者管理员)来创建他们公司的销售经理们需要的属性,也许是更好的做法。例如,如果有需要,管理员可以创建“工作电话”或者“家庭地址”等属性。 此外,这些属性还可以用到数据过滤和查询中去。
简要说明
在实施Enterra CRM项目时,客户提出了在应用中支持自定义字段的目标,“系统管理员不需要重启系统就可以创建或删除自定义字段”。
系统后端开发使用了Hibernate 3.0框架,这个因素(技术约束)是考虑实现这个需求的关键。
实现
在这一章里面我们将介绍采用Hibernate框架实现的关键环节。
环境
例子的开发环境如下所示:
- JDK 1.5;
- Hibernate 3.2.0框架;
- MySQL 4.1。
限制
简单起见,我们不使用Hibernate EntityManager(译注一)和Hibernate Annotations(译注二)。持久化对象的映射关系将基于xml映射文件。此外,值得一提的是,由于演示用例是基于xml映射文件管理映射,所以使用Hibernate Annotations的话,它将不能正常运行。
功能定义
我们必须实现一种机制——允许实时地创建/删除自定义字段而不重启应用,向其中添加值并保证值能保存到应用的数据库中。此外我们还必须保证自定义字段能用于查询。
解决方案
域模型
首先,我们需要一个进行试验的业务实体类。假设是Contact类,它有两个持久化字段:id和name。
但是,除了这些持久不变的字段外,这个类还应该有一些存储自定义字段值的数据结构。Map也许是针对于此的理想数据结构。
为所有支持自定义字段的业务实体创建一个基类——CustomizableEntity,它包含处理自定义字段的Map类型属性customProperties:
package com.enterra.customfieldsdemo.domain; import java.util.Map; import java.util.HashMap; public abstract class CustomizableEntity { private Map customProperties; public Map getCustomProperties() { if (customProperties == null) customProperties = new HashMap(); return customProperties; } public void setCustomProperties(Map customProperties) { this.customProperties = customProperties; } public Object getValueOfCustomField(String name) { return getCustomProperties().get(name); } public void setValueOfCustomField(String name, Object value) { getCustomProperties().put(name, value); } }
清单1-基类CustomizableEntity
Contact类继承上面的基类:
package com.enterra.customfieldsdemo.domain; import com.enterra.customfieldsdemo.domain.CustomizableEntity; public class Contact extends CustomizableEntity { private int id; private String name; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
清单2-继承自CustomizableEntity的Contact类
别忘了这个类的映射文件:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Configuration DTD//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping auto-import="true" default-access="property" default-cascade="none" default-lazy="true"> <class abstract="false" name="com.enterra.customfieldsdemo.domain.Contact" table="tbl_contact"> <id column="fld_id" name="id"> <generator class="native"/> </id> <property name="name" column="fld_name" type="string"/> <dynamic-component insert="true" name="customProperties" optimistic-lock="true" unique="false" update="true"> </dynamic-component> </class> </hibernate-mapping>
清单3-Contact类的映射
注意id和name属性都是当作普通的属性来处理,但对于customProperties,我们使用了(动态组件)标签。Hibernate 3.2.0GA文档里面关于dynamic-component的要点如下:
<dynamic-component>映射的语义与<component>是一样的。该映射的优点是仅仅通过编辑映射文件,就能在部署时确定bean的现行属性。使用DOM解析器,映射文件的运行时操作也是可行的。甚至,你可以通过Configuration对象,来访问(和修改)Hibernate的配置时元模型。
基于Hibernate文档中的这段规则,我们来建立前面要求的功能机制。
HibernateUtil和hibernate.cfg.xml
定义了应用中的域模型之后,我们需要创建Hibernate框架运转的必要条件。为此我们必须创建一个配置文件hibernate.cfg.xml和一个处理Hibernate核心功能的类。
<?xml version='1.0' encoding='utf-8'?> <!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD//EN" "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd"> <hibernate-configuration> <session-factory> <property name="show_sql">true</property> <property name="dialect"> org.hibernate.dialect.MySQLDialect </property> <property name="cglib.use_reflection_optimizer">true</property> <property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property> <property name="hibernate.connection.url"> jdbc:mysql://localhost:3306/custom_fields_test </property> <property name="hibernate.connection.username">root</property> <property name="hibernate.connection.password"></property> <property name="hibernate.c3p0.max_size">50</property> <property name="hibernate.c3p0.min_size">0</property> <property name="hibernate.c3p0.timeout">120</property> <property name="hibernate.c3p0.max_statements">100</property> <property name="hibernate.c3p0.idle_test_period">0</property> <property name="hibernate.c3p0.acquire_increment">2</property> <property name="hibernate.jdbc.batch_size">20</property> <property name="hibernate.hbm2ddl.auto">update</property> </session-factory> </hibernate-configuration>
清单4-Hibernate配置文件
hibernate.cfg.xml文件没有什么需要特别关注的,除了下面这句:
<property name="hibernate.hbm2ddl.auto">update</property>
清单5-使用auto-update(自动更新)
后面我们将详细解释其目的,并更多地讲解没有它我们怎样实现。HibernateUtil类有好几种实现方式。由于Hibernate配置文件内容的不同,我们的实现与已知的那些将有一点儿不同。
package com.enterra.customfieldsdemo; import org.hibernate.*; import org.hibernate.mapping.PersistentClass; import org.hibernate.tool.hbm2ddl.SchemaUpdate; import org.hibernate.cfg.Configuration; import com.enterra.customfieldsdemo.domain.Contact; public class HibernateUtil { private static HibernateUtil instance; private Configuration configuration; private SessionFactory sessionFactory; private Session session; public synchronized static HibernateUtil getInstance() { if (instance == null) { instance = new HibernateUtil(); } return instance; } private synchronized SessionFactory getSessionFactory() { if (sessionFactory == null) { sessionFactory = getConfiguration().buildSessionFactory(); } return sessionFactory; } public synchronized Session getCurrentSession() { if (session == null) { session = getSessionFactory().openSession(); session.setFlushMode(FlushMode.COMMIT); System.out.println("session opened."); } return session; } private synchronized Configuration getConfiguration() { if (configuration == null) { System.out.print("configuring Hibernate ... "); try { configuration = new Configuration().configure(); configuration.addClass(Contact.class); System.out.println("ok"); } catch (HibernateException e) { System.out.println("failure"); e.printStackTrace(); } } return configuration; } public void reset() { Session session = getCurrentSession(); if (session != null) { session.flush(); if (session.isOpen()) { System.out.print("closing session ... "); session.close(); System.out.println("ok"); } } SessionFactory sf = getSessionFactory(); if (sf != null) { System.out.print("closing session factory ... "); sf.close(); System.out.println("ok"); } this.configuration = null; this.sessionFactory = null; this.session = null; } public PersistentClass getClassMapping(Class entityClass){ return getConfiguration().getClassMapping(entityClass.getName()); } }
清单6-HibernateUtils类
除了平常的getCurrentSession()和getConfiguration()方法(这些方法对基于Hibernate的应用的常规操作是很必要的)之外,我们还需要实现像reset()和getClassMapping(Class entityClass)这样的方法。在getConfiguration()方法中,我们配置Hibernate、并将类Contact添加到配置中去。
reset()方法关闭所有Hibernate使用的资源、清除所有的设置:
public void reset() { Session session = getCurrentSession(); if (session != null) { session.flush(); if (session.isOpen()) { System.out.print("closing session ... "); session.close(); System.out.println("ok"); } } SessionFactory sf = getSessionFactory(); if (sf != null) { System.out.print("closing session factory ... "); sf.close(); System.out.println("ok"); } this.configuration = null; this.sessionFactory = null; this.session = null; }
清单7-reset()方法
getClassMapping(Class entityClass)方法返回PersistentClass对象,该对象包含相关实体映射的全部信息。特别地,对PersistentClass对象的处理允许在运行时修改实体类的属性设置。
public PersistentClass getClassMapping(Class entityClass){ return getConfiguration().getClassMapping(entityClass.getName()); }
清单8-getClassMapping(Class entityClass)方法
处理映射
一旦我们有了可用的业务实体类(Contact)和与Hibernate交互的主类,我们就能开始工作了。我们能创建、保存Contact类的实例。甚至可以在Map对象customProperties里面放置一些数据,但是需要注意的是存储在Map对象customProperties里面的数据并不会被保存到数据库里。
为了保存数据,我们需要让这个机制能在类里面创建自定义字段,并且要让Hibernate知道该如何处理它们。
为了实现对类映射的处理,我们需要创建一些接口。叫它CustomizableEntityManager吧。名字应该表现出该接口管理业务实体及其内容、属性的意图:
package com.enterra.customfieldsdemo; import org.hibernate.mapping.Component; public interface CustomizableEntityManager { public static String CUSTOM_COMPONENT_NAME = "customProperties"; void addCustomField(String name); void removeCustomField(String name); Component getCustomProperties(); Class getEntityClass(); }
清单9-CustomizableEntityManager接口
接口中重要的方法是void addCustomField(String name)和void removeCustomField(String name)。它们将分别在相应类的映射里创建、删除我们的自定义字段。
下面是实现该接口的情况:
package com.enterra.customfieldsdemo; import org.hibernate.cfg.Configuration; import org.hibernate.mapping.*; import java.util.Iterator; public class CustomizableEntityManagerImpl implements CustomizableEntityManager { private Component customProperties; private Class entityClass; public CustomizableEntityManagerImpl(Class entityClass) { this.entityClass = entityClass; } public Class getEntityClass() { return entityClass; } public Component getCustomProperties() { if (customProperties == null) { Property property = getPersistentClass().getProperty(CUSTOM_COMPONENT_NAME); customProperties = (Component) property.getValue(); } return customProperties; } public void addCustomField(String name) { SimpleValue simpleValue = new SimpleValue(); simpleValue.addColumn(new Column("fld_" + name)); simpleValue.setTypeName(String.class.getName()); PersistentClass persistentClass = getPersistentClass(); simpleValue.setTable(persistentClass.getTable()); Property property = new Property(); property.setName(name); property.setValue(simpleValue); getCustomProperties().addProperty(property); updateMapping(); } public void removeCustomField(String name) { Iterator propertyIterator = customProperties.getPropertyIterator(); while (propertyIterator.hasNext()) { Property property = (Property) propertyIterator.next(); if (property.getName().equals(name)) { propertyIterator.remove(); updateMapping(); return; } } } private synchronized void updateMapping() { MappingManager.updateClassMapping(this); HibernateUtil.getInstance().reset(); // updateDBSchema(); } private PersistentClass getPersistentClass() { return HibernateUtil.getInstance().getClassMapping(this.entityClass); } }
清单10-接口CustomizableEntityManager的实现
首先需要指出的是,在构造CustomizableEntityManager时,我们要指定管理器操作的业务实体类。该业务实体类作为参数传递给CustomizableEntityManager的构造函数:
private Class entityClass; public CustomizableEntityManagerImpl(Class entityClass) { this.entityClass = entityClass; } public Class getEntityClass() { return entityClass; }
清单11-CustomizableEntityManagerImpl构造函数
现在我们应该对void addCustomField(String name)方法的实现更感兴趣:
public void addCustomField(String name) { SimpleValue simpleValue = new SimpleValue(); simpleValue.addColumn(new Column("fld_" + name)); simpleValue.setTypeName(String.class.getName()); PersistentClass persistentClass = getPersistentClass(); simpleValue.setTable(persistentClass.getTable()); Property property = new Property(); property.setName(name); property.setValue(simpleValue); getCustomProperties().addProperty(property); updateMapping(); }
清单12-创建自定义字段
正如我们从实现中看到的一样,Hibernate在处理持久化对象的属性及其在数据库中的表示方面提供了更多的选择。下面分步讲解该方法的要素:
1)创建一个SimpleValue类对象,它指明了自定义字段的值如何被存储到字段和表所在的数据库中:
SimpleValue simpleValue = new SimpleValue(); simpleValue.addColumn(new Column("fld_" + name)); simpleValue.setTypeName(String.class.getName()); PersistentClass persistentClass = getPersistentClass(); simpleValue.setTable(persistentClass.getTable());
清单13-表创建新列
2)给持久化对象创建一个属性(property),并将动态组件添加进去,注意,这是我们为了这个目的已经计划好的:
Property property = new Property() property.setName(name) property.setValue(simpleValue) getCustomProperties().addProperty(property)
清单14-创建对象属性
3)最后应该让应用修改xml文件,并更新Hibernate配置。这个可以由updateMapping()方法来完成;
阐明上面代码中另外两个get方法的用途还是很有必要的。第一个方法是getCustomProperties():
public Component getCustomProperties() { if (customProperties == null) { Property property = getPersistentClass().getProperty(CUSTOM_COMPONENT_NAME); customProperties = (Component) property.getValue(); } return customProperties; }
清单15-获取组件CustomProperties
该方法找到并返回与业务实体映射中标签相对应的组件(Component)对象。
第二个方法是updateMapping():
private synchronized void updateMapping() { MappingManager.updateClassMapping(this); HibernateUtil.getInstance().reset(); // updateDBSchema(); }
清单16-updateMapping()方法
该方法负责存储更新后的持久化类映射,并且更新Hibernate的配置状态,以进一步使改变生效。
顺便,我们回过头来看看Hibernate配置中的语句:
<property name="hibernate.hbm2ddl.auto">update</property>
如果缺少该配置,我们就必须使用Hibernate工具类来执行数据库schema的更新。然而使用该设置让我们避免了那么做。
保存映射
运行时对映射的修改不会将自身保存到相应的xml映射文件中,为了使变化在应用下次的执行中活化,我们需要手动将变化保存到对应的映射文件中去。
我们使用MappingManager类来完成这件工作,该类的主要目的是将指定的业务实体的映射保存到其xml映射文件中去:
package com.enterra.customfieldsdemo; import com.enterra.customfieldsdemo.domain.CustomizableEntity; import org.hibernate.Session; import org.hibernate.mapping.Column; import org.hibernate.mapping.Property; import org.hibernate.type.Type; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import java.util.Iterator; public class MappingManager { public static void updateClassMapping(CustomizableEntityManager entityManager) { try { Session session = HibernateUtil.getInstance().getCurrentSession(); Class entityClass = entityManager.getEntityClass(); String file = entityClass.getResource(entityClass.getSimpleName() + ".hbm.xml").getPath(); Document document = XMLUtil.loadDocument(file); NodeList componentTags = document.getElementsByTagName("dynamic-component"); Node node = componentTags.item(0); XMLUtil.removeChildren(node); Iterator propertyIterator = entityManager.getCustomProperties().getPropertyIterator(); while (propertyIterator.hasNext()) { Property property = (Property) propertyIterator.next(); Element element = createPropertyElement(document, property); node.appendChild(element); } XMLUtil.saveDocument(document, file); } catch (Exception e) { e.printStackTrace(); } } private static Element createPropertyElement(Document document, Property property) { Element element = document.createElement("property"); Type type = property.getType(); element.setAttribute("name", property.getName()); element.setAttribute("column", ((Column) property.getColumnIterator().next()).getName()); element.setAttribute("type", type.getReturnedClass().getName()); element.setAttribute("not-null", String.valueOf(false)); return element; } }
清单17-更新持久化类映射的工具类
该类一一执行了下面的操作:
- 对于指定的业务实体,定义其xml映射的位置,并加载到DOM Document对象中,以供进一步操作;
- 查找到Document对象中的元素。我们将在这里存储自定义字段和我们所做的内容变化;
- 将该元素内嵌套的所有元素都删除;
- 对于负责自定义字段存储的组件所包含的任意持久化属性,我们都创建一个特定的document元素,并根据相应的属性为元素定义属性;
- 保存这个新建的映射文件。
虽然我们这里用了XMLUtil类(正如从代码中看到的一样)来处理XML,但是一般而言,可以换成任何一种方式来实现,不过XMLUtil已经足以加载并保存xml文件。
我们的实现如下面的清单所示:
package com.enterra.customfieldsdemo; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.w3c.dom.Document; import org.xml.sax.SAXException; import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.DocumentBuilder; import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerFactory; import javax.xml.transform.Transformer; import javax.xml.transform.OutputKeys; import javax.xml.transform.stream.StreamResult; import javax.xml.transform.dom.DOMSource; import java.io.IOException; import java.io.FileOutputStream; public class XMLUtil { public static void removeChildren(Node node) { NodeList childNodes = node.getChildNodes(); int length = childNodes.getLength(); for (int i = length - 1; i > -1; i--) node.removeChild(childNodes.item(i)); } public static Document loadDocument(String file) throws ParserConfigurationException, SAXException, IOException { DocumentBuilderFactory factory =DocumentBuilderFactory.newInstance(); DocumentBuilder builder = factory.newDocumentBuilder(); return builder.parse(file); } public static void saveDocument(Document dom, String file) throws TransformerException, IOException { TransformerFactory tf = TransformerFactory.newInstance(); Transformer transformer = tf.newTransformer(); transformer.setOutputProperty(OutputKeys.DOCTYPE_PUBLIC, dom.getDoctype().getPublicId()); transformer.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM, dom.getDoctype().getSystemId()); DOMSource source = new DOMSource(dom); StreamResult result = new StreamResult(); FileOutputStream outputStream = new FileOutputStream(file); result.setOutputStream(outputStream); transformer.transform(source, result); outputStream.flush(); outputStream.close(); } }
清单18-XML处理工具类
测试
我们有了所有必需的运行代码, 现在可以编写测试代码来看看一切到底是怎样工作的。第一个测试创建自定义字段“email”,创建并保存Contact类的实例,并给它定义“email”属性。
首先让我们看一下数据库表tbl_contact,它包括两个字段:fld_id和fld_name。代码如下:
package com.enterra.customfieldsdemo.test; import com.enterra.customfieldsdemo.HibernateUtil; import com.enterra.customfieldsdemo.CustomizableEntityManager; import com.enterra.customfieldsdemo.CustomizableEntityManagerImpl; import com.enterra.customfieldsdemo.domain.Contact; import org.hibernate.Session; import org.hibernate.Transaction; import java.io.Serializable; public class TestCustomEntities { private static final String TEST_FIELD_NAME = "email"; private static final String TEST_VALUE = "test@test.com"; public static void main(String[] args) { HibernateUtil.getInstance().getCurrentSession(); CustomizableEntityManager contactEntityManager = new CustomizableEntityManagerImpl(Contact.class); contactEntityManager.addCustomField(TEST_FIELD_NAME); Session session = HibernateUtil.getInstance().getCurrentSession(); Transaction tx = session.beginTransaction(); try { Contact contact = new Contact(); contact.setName("Contact Name 1"); contact.setValueOfCustomField(TEST_FIELD_NAME, TEST_VALUE); Serializable id = session.save(contact); tx.commit(); contact = (Contact) session.get(Contact.class, id); Object value = contact.getValueOfCustomField(TEST_FIELD_NAME); System.out.println("value = " + value); } catch (Exception e) { tx.rollback(); System.out.println("e = " + e); } } }
清单19-测试创建自定义字段
这个类的main方法负责执行下面的工作:
- 创建Contact类的CustomizableEntityManager;
- 创建名为“email”的自定义字段;
- 在事务中,我们创建一个新的Contact对象,并设置自定义字段的值为“test@test.com”;
- 保存Contact;
- 获取自定义字段“email”的值。
我们可以看到执行的结果如下:
configuring Hibernate ... ok session opened. closing session ... ok closing session factory ... ok configuring Hibernate ... ok session opened. Hibernate: insert into tbl_contact (fld_name, fld_email) values (?, ?) value = test@test.com
清单20-测试结果
在数据库里,可以看到如下所示的记录:
+--------+---------------------+----------------------+ | fld_id | fld_name | fld_email | +--------+---------------------+----------------------+ | 1 | Contact Name 1 | test@test.com | +--------+---------------------+----------------------+
清单21-DB结果
正如看到的那样,新的字段在运行时被创建,其值也被成功保存。
第二个测试使用新创建的字段来查询数据库:
package com.enterra.customfieldsdemo.test; import com.enterra.customfieldsdemo.HibernateUtil; import com.enterra.customfieldsdemo.CustomizableEntityManager; import com.enterra.customfieldsdemo.domain.Contact; import org.hibernate.Session; import org.hibernate.Criteria; import org.hibernate.criterion.Restrictions; import java.util.List; public class TestQueryCustomFields { public static void main(String[] args) { Session session = HibernateUtil.getInstance().getCurrentSession(); Criteria criteria = session.createCriteria(Contact.class); criteria.add(Restrictions.eq(CustomizableEntityManager.CUSTOM_COMPONENT_NAME + ".email", "test@test.com")); List list = criteria.list(); System.out.println("list.size() = " + list.size()); } }
清单22-测试自定义字段查询
Execution result: configuring Hibernate ... ok session opened. Hibernate: select this_.fld_id as fld1_0_0_, this_.fld_name as fld2_0_0_, this_.fld_email as fld3_0_0_ from tbl_contact this_ where this_.fld_email=? list.size() = 1
清单23-查询结果
正如看到的,使用我们的方法创建的自定义字段能够很容易地参与到数据库查询中。
进一步改善
很显然,我们上面提到的实现相当简单。它并没有反映出该功能在实际实现中会遇到的各种情况。但是它还是说明了在建议的技术平台上解决方案的大体工作机制。
另外明显的是,该需求还可以使用其它办法(比如代码生成)来实现,这些办法也许会在其它文章中介绍。
这个实现仅支持String类型的自定义字段,但是,基于该方法的实际应用(Enterra CRM)中, 已经实现了对所有原始类型、对象类型(链接到业务对象)以及集合字段的完全支持。
为了在用户界面支持自定义字段,已经实现了针对自定义字段的元描述符系统,该系统使用了用户界面生成系统。但是生成器的机制是另外一篇文章的主题。
结论
最后,Enterra CRM团队创建、验证并在实践中应用了基于ORM平台Hibernate的开放对象模型架构,它满足了客户在运行时不需要对应用源代码做任何改动、就可以按照最终用户的实际需求设置应用的需求。
译注一:Hibernate EntityManager实现了JPA、对象生命周期法则、以及JSR 220 (EJB 3.0) 定义的查询选项。
译注二:Hibernate Annotations提供了JDK 5.0代码标注的功能,从而替代XML元数据。
查看英文原文:Using Hibernate to Support Custom Domain Object Fields
发表评论
-
[收藏]maven pom.xml详解
2014-06-07 13:47 447<project xmlns="http: ... -
[转]log4j详解与实战
2014-05-16 23:32 532log4j是一个非常强大的log记录软件,下面我们就来看 ... -
Spring RMI
2014-05-12 18:12 493使用Spring的RMI支持,你可以通过RMI基础设施 ... -
[转]23种设计模式全解析-关系模式
2014-05-05 23:40 1008C、关系模式(11种) 先来张图,看看这11中模式的关 ... -
[转]23种设计模式全解析-结构模式
2014-05-05 23:38 492B、结构模式(7种) ... -
[转]23种设计模式全解析-创建模式
2014-05-05 23:37 484A、创建模式(5种) 从这一块开始,我们详细介绍Java ... -
[转]23种设计模式全解析-概要
2014-05-05 23:33 353一、设计模式的分类 ... -
[转]11 款用于优化、分析源代码的Java工具
2011-08-02 17:54 724本文将提供一些工具,帮助你优化代码以及检查源代码中的潜在问题。 ... -
[转]Stack Exchange 的架构
2011-07-05 09:18 748近日,Stack Exchange系统管理员blog上发布了一 ... -
[转]Facebook 的系统架构
2011-07-05 09:15 610来源:http://www.quora.com/What-is ... -
<转>架构师书单
2011-07-04 23:18 0作者:江南白衣,原文 ... -
大型网站架构演变和知识体系
2009-03-28 21:09 678之前也有一些介绍大型 ... -
从LiveJournal后台发展看大规模网站性能优化方法
2009-03-28 21:07 723于敦德 2006-3-16 一、LiveJournal发展历 ...
相关推荐
sql server+java项目之科帮网计算机配件报价系统源代码
有java环境就可以运行起来 ,zip里包含源码+论文+PPT, 系统设计与功能: 文档详细描述了系统的后台管理功能,包括系统管理模块、新闻资讯管理模块、公告管理模块、社区影院管理模块、会员上传下载管理模块以及留言管理模块。 系统管理模块:允许管理员重新设置密码,记录登录日志,确保系统安全。 新闻资讯管理模块:实现新闻资讯的添加、删除、修改,确保主页新闻部分始终显示最新的文章。 公告管理模块:类似于新闻资讯管理,但专注于主页公告的后台管理。 社区影院管理模块:管理所有视频的添加、删除、修改,包括影片名、导演、主演、片长等信息。 会员上传下载管理模块:审核与删除会员上传的文件。 留言管理模块:回复与删除所有留言,确保系统内的留言得到及时处理。 环境说明: 开发语言:Java 框架:ssm,mybatis JDK版本:JDK1.8 数据库:mysql 5.7及以上 数据库工具:Navicat11及以上 开发软件:eclipse/idea Maven包:Maven3.3及以上
zip里包含源码+论文+PPT,有java环境就可以运行起来 ,功能说明: 文档开篇阐述了随着计算机技术、通信技术和网络技术的快速发展,智慧社区门户网站的建设成为了可能,并被视为21世纪信息产业的主要发展方向之一 强调了网络信息管理技术、数字化处理技术和数字式信息资源建设在国际竞争中的重要性。 指出了智慧社区门户网站系统的编程语言为Java,数据库为MYSQL,并实现了新闻资讯、社区共享、在线影院等功能。 系统设计与功能: 文档详细描述了系统的后台管理功能,包括系统管理模块、新闻资讯管理模块、公告管理模块、社区影院管理模块、会员上传下载管理模块以及留言管理模块。 系统管理模块:允许管理员重新设置密码,记录登录日志,确保系统安全。 新闻资讯管理模块:实现新闻资讯的添加、删除、修改,确保主页新闻部分始终显示最新的文章。 公告管理模块:类似于新闻资讯管理,但专注于主页公告的后台管理。 社区影院管理模块:管理所有视频的添加、删除、修改,包括影片名、导演、主演、片长等信息。 会员上传下载管理模块:审核与删除会员上传的文件。 留言管理模块:回复与删除所有留言,确保系统内的留言得到及时处理。
内容概要:本文档详细介绍了LinkLab实验的五个阶段,涵盖了ELF文件的组成、符号表的理解、代码节与重定位位置的修改等内容。每个阶段都有具体的实验要求和步骤,帮助学生理解链接的基本概念和链接过程中涉及的各项技术细节。 适合人群:计算机科学专业的本科生,特别是正在修读《计算机系统基础》课程的学生。 使用场景及目标:① 通过实际操作加深对链接过程和ELF文件的理解;② 掌握使用readelf、objdump和hexedit等工具的技巧;③ 实现特定输出以验证实验结果。 阅读建议:实验过程中的每个阶段都有明确的目标和提示,学生应按照步骤逐步操作,并结合反汇编代码和二进制编辑工具进行实践。在完成每个阶段的实验后,应及时记录实验结果和遇到的问题,以便于总结和反思。
【资源说明】 基于关键词的历时百度搜索指数自动采集资料齐全+详细文档+高分项目+源码.zip 【备注】 1、该项目是个人高分项目源码,已获导师指导认可通过,答辩评审分达到95分 2、该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的,请放心下载使用! 3、本项目适合计算机相关专业(人工智能、通信工程、自动化、电子信息、物联网等)的在校学生、老师或者企业员工下载使用,也可作为毕业设计、课程设计、作业、项目初期立项演示等,当然也适合小白学习进阶。 4、如果基础还行,可以在此代码基础上进行修改,以实现其他功能,也可直接用于毕设、课设、作业等。 欢迎下载,沟通交流,互相学习,共同进步!
第一次发文的小白,解释的不好,各位大佬勿怪哦
免费下载:Hilma af Klint a Biography (Julia Voss)_tFy2T.zip
屏幕截图 2024-12-21 172527
2024级涉外护理7班马天爱劳动实践总结1.docx
IndexOutOfBoundsException(解决方案)
有java环境就可以运行起来 ,zip里包含源码+论文+PPT, 系统设计与功能: 文档详细描述了系统的后台管理功能,包括系统管理模块、新闻资讯管理模块、公告管理模块、社区影院管理模块、会员上传下载管理模块以及留言管理模块。 系统管理模块:允许管理员重新设置密码,记录登录日志,确保系统安全。 新闻资讯管理模块:实现新闻资讯的添加、删除、修改,确保主页新闻部分始终显示最新的文章。 公告管理模块:类似于新闻资讯管理,但专注于主页公告的后台管理。 社区影院管理模块:管理所有视频的添加、删除、修改,包括影片名、导演、主演、片长等信息。 会员上传下载管理模块:审核与删除会员上传的文件。 留言管理模块:回复与删除所有留言,确保系统内的留言得到及时处理。 环境说明: 开发语言:Java 框架:ssm,mybatis JDK版本:JDK1.8 数据库:mysql 5.7及以上 数据库工具:Navicat11及以上 开发软件:eclipse/idea Maven包:Maven3.3及以上
有java环境就可以运行起来 ,zip里包含源码+论文+PPT, 系统设计与功能: 文档详细描述了系统的后台管理功能,包括系统管理模块、新闻资讯管理模块、公告管理模块、社区影院管理模块、会员上传下载管理模块以及留言管理模块。 系统管理模块:允许管理员重新设置密码,记录登录日志,确保系统安全。 新闻资讯管理模块:实现新闻资讯的添加、删除、修改,确保主页新闻部分始终显示最新的文章。 公告管理模块:类似于新闻资讯管理,但专注于主页公告的后台管理。 社区影院管理模块:管理所有视频的添加、删除、修改,包括影片名、导演、主演、片长等信息。 会员上传下载管理模块:审核与删除会员上传的文件。 留言管理模块:回复与删除所有留言,确保系统内的留言得到及时处理。 环境说明: 开发语言:Java 框架:ssm,mybatis JDK版本:JDK1.8 数据库:mysql 5.7及以上 数据库工具:Navicat11及以上 开发软件:eclipse/idea Maven包:Maven3.3及以上
zip里包含源码+论文+PPT,有java环境就可以运行起来 ,功能说明: 文档开篇阐述了随着计算机技术、通信技术和网络技术的快速发展,智慧社区门户网站的建设成为了可能,并被视为21世纪信息产业的主要发展方向之一 强调了网络信息管理技术、数字化处理技术和数字式信息资源建设在国际竞争中的重要性。 指出了智慧社区门户网站系统的编程语言为Java,数据库为MYSQL,并实现了新闻资讯、社区共享、在线影院等功能。 系统设计与功能: 文档详细描述了系统的后台管理功能,包括系统管理模块、新闻资讯管理模块、公告管理模块、社区影院管理模块、会员上传下载管理模块以及留言管理模块。 系统管理模块:允许管理员重新设置密码,记录登录日志,确保系统安全。 新闻资讯管理模块:实现新闻资讯的添加、删除、修改,确保主页新闻部分始终显示最新的文章。 公告管理模块:类似于新闻资讯管理,但专注于主页公告的后台管理。 社区影院管理模块:管理所有视频的添加、删除、修改,包括影片名、导演、主演、片长等信息。 会员上传下载管理模块:审核与删除会员上传的文件。 留言管理模块:回复与删除所有留言,确保系统内的留言得到及时处理。
DevExpressVCLProductDemos-24.2.3.exe
欢迎下载
有java环境就可以运行起来 ,zip里包含源码+论文+PPT, 系统设计与功能: 文档详细描述了系统的后台管理功能,包括系统管理模块、新闻资讯管理模块、公告管理模块、社区影院管理模块、会员上传下载管理模块以及留言管理模块。 系统管理模块:允许管理员重新设置密码,记录登录日志,确保系统安全。 新闻资讯管理模块:实现新闻资讯的添加、删除、修改,确保主页新闻部分始终显示最新的文章。 公告管理模块:类似于新闻资讯管理,但专注于主页公告的后台管理。 社区影院管理模块:管理所有视频的添加、删除、修改,包括影片名、导演、主演、片长等信息。 会员上传下载管理模块:审核与删除会员上传的文件。 留言管理模块:回复与删除所有留言,确保系统内的留言得到及时处理。 环境说明: 开发语言:Java 框架:ssm,mybatis JDK版本:JDK1.8 数据库:mysql 5.7及以上 数据库工具:Navicat11及以上 开发软件:eclipse/idea Maven包:Maven3.3及以上
资源描述: 机型代码:haotian 1-----工程固件可以用于修改参数 开启diag端口。可以用于修复tee损坏以及修复底层分区。 2-----此固件是完整官方。不是第三方打包。请知悉 3-----此固件可以解锁bl后fast模式刷写。也可以底层深刷。也可以编程器写入 4-----请会用此固件 了解工程固件常识以及会用的朋友下载。 5-----个别高版本深刷需要授权才可以刷入。需要自己会刷写。 6------资源有可复制性。下载后不支持退。请考虑清楚在下载哦 工程资源常识可以参考博文:https://blog.csdn.net/u011283906/article/details/141815378 了解基本
1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于计算机科学与技术等相关专业,更为适合;
预览地址:https://blog.csdn.net/qq_42431718/article/details/144633992 html+css网页设计 美食 蛋糕美食7个页面
有java环境就可以运行起来 ,zip里包含源码+论文+PPT, 系统设计与功能: 文档详细描述了系统的后台管理功能,包括系统管理模块、新闻资讯管理模块、公告管理模块、社区影院管理模块、会员上传下载管理模块以及留言管理模块。 系统管理模块:允许管理员重新设置密码,记录登录日志,确保系统安全。 新闻资讯管理模块:实现新闻资讯的添加、删除、修改,确保主页新闻部分始终显示最新的文章。 公告管理模块:类似于新闻资讯管理,但专注于主页公告的后台管理。 社区影院管理模块:管理所有视频的添加、删除、修改,包括影片名、导演、主演、片长等信息。 会员上传下载管理模块:审核与删除会员上传的文件。 留言管理模块:回复与删除所有留言,确保系统内的留言得到及时处理。 环境说明: 开发语言:Java 框架:ssm,mybatis JDK版本:JDK1.8 数据库:mysql 5.7及以上 数据库工具:Navicat11及以上 开发软件:eclipse/idea Maven包:Maven3.3及以上