`
ahuaxuan
  • 浏览: 633328 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

java邮件:在简单和复杂之间的方案

阅读更多
/**
*作者:张荣华(ahuaxuan)
*2007-07-11
*转载请注明出处及作者
*/

Javamail,论坛上由已经有很多的讨论,但是俺觉得还是不够完整,不完整不是说讲的不细致,而是指不全面,而是缺少high level的全面论述,所以俺来补充一下。

这篇文章的名字起得很古怪(估计还有人暗地里说文章名字取得如何如何,文章实质却是水货等等了,先不忙下结论,各位看官接着往下看便知),叫简单和复杂之间,为什么要取这么个奇怪的名字,搞得人一头雾水,其实我想要表达的意思是这样的,之前坛子上有很多人讨论过如何使用javamail(包括spring对其的封装),也有人讨论过如何通过jms发送emal,一个是简单的api介绍,一个是比较复杂的异步方案,但是试问除了简单使用其api难道就只能使用jms来进行异步发送了吗,我们可以再找到一种介于这两者之间的方案,就是concurrent(我的建议是在普通的web应用中邮件发送不需要用jms,但是最好也不要使用同步发送,所以普通的web应该使用concurrent来进行异步邮件发送应该是比较好的选择)。

在普通的web应用中,发送邮件应该只能算小任务,而使用jms来发送邮件有点杀鸡用牛刀的味道,那么如果能建立一个线程池来管理这些小线程并重复使用他们,应该来说是一个简单有效的方案,我们可以使用concurrent包中的Executors来建立线程池,Executors是一个工厂,也是一个工具类,我把它的api的介绍简单的翻译了一下(如果翻译有误请大家不要吝啬手中的砖头)

方  法 说  明
newCachedThreadPool() 创建一个包含新线程的线程池,池中线程的数量需要预先指定,该线程池会复用之前创建的线程(前提是该线程还是有效线程)。如果你的要执行的任务是短生命周期的任务的话,使用这种池提高性能是很具代表性的。这个方法有一个重载
newFixedThreadPool() 创建一个线程池以复用指定数量的线程,如果当所有线程都是活动状态时(指这些线程都在运行),那么新的任务将会等待,知道有空余的线程。如果有任何一个线程因为在运行中发生错误而终结(非正常shutdown),那么如果有新的任务要并发处理,concurrent就会创建一个新的线程放入池中。
newSingleThreadExecutor() 创建一个使用单工作线程的executor,
newScheduledThreadPool() 可调度的线程池,池中的线程可以在某一时间延迟之后执行,也可以周期性执行
newSingleThreadScheduledExecutor() 单一可调度的线程


上面我重点解释了newFixedThreadPool(),因为我们将使用newFixedThreadPool方法来创建一个线程池,这个线程池中存放的线程就是我们用来发送邮件的。代码如下:
/**
 * 由spring管理的线程池类,返回的ExecutorService就是给我们来执行线程的
*如果不交给spring管理也是可以的,可以使用单例模式来实现同样功能,但是poolSize   *要hardcode了
 * @author 张荣华(ahuaxuan)
* @version $Id$
 */
public class EasyMailExecutorPool implements InitializingBean {

	//线程池大小,spring配置文件中配置
	private int poolSize;
	private ExecutorService service;

	public ExecutorService getService() {
		return service;
	}

	public int getPoolSize() {
		return poolSize;
	}

	public void setPoolSize(int poolSize) {
		this.poolSize = poolSize;
	}

	/**
	 * 在 bean 被初始化成功之后初始化线程池大小
	 */
	public void afterPropertiesSet() throws Exception {
		service = Executors.newFixedThreadPool(poolSize);
	}
}



这样我们就初始化了线程池的大小,接下来就是如何使用这个线程池中的线程了,我们看看MailService是如何来使用线程池中的线程的,这个类中的代码我已经作了详细的解释

/**
 * 用来发送 mail 的 service, 其中有一个内部类专门用来供线程使用
 * @author 张荣华(ahuaxuan)
 * @since 2007-7-11
 * @version $Id$
 */
public class EasyMailServieImpl implements EasyMailService{
	private static transient Log logger = LogFactory.getLog(EasyMailServieImpl.class); 
	
	//注入MailSender
	private JavaMailSender javaMailSender;
	
	//注入线程池
	private EasyMailExecutorPool easyMailExecutorPool;
	
	//设置发件人
	private String from;
	
	public void setEasyMailExecutorPool(EasyMailExecutorPool easyMailExecutorPool) {
		this.easyMailExecutorPool = easyMailExecutorPool;
	}

	public void setJavaMailSender(JavaMailSender javaMailSender) {
		this.javaMailSender = javaMailSender;
	}
	
	public void setFrom(String from) {
		this.from = from;
	}

	/**
	 * 简单的邮件发送接口,感兴趣的同学可以在这个基础上继续添加
	 * @param to
	 * @param subject
	 * @param text
	 */
	public void sendMessage(EmailEntity email){
		if (null == email) {
			if (logger.isDebugEnabled()) {
				logger.debug("something you need to tell here");
			}
			return;
		}
		SimpleMailMessage simpleMailMessage = new SimpleMailMessage();
		
		simpleMailMessage.setTo(email.getTo());
		simpleMailMessage.setSubject(email.getSubject());
		simpleMailMessage.setText(email.getText());
		simpleMailMessage.setFrom(from);
		
		easyMailExecutorPool.getService().execute(new MailRunner(simpleMailMessage));
	}
	
	/**
	 * 发送复杂格式邮件的接口,可以添加附件,图片,等等,但是需要修改这个方法,
	 * 如何做到添加附件和图片论坛上有例子了,需要的同学搜一下,
	 * 事实上这里的text参数最好是来自于模板,用模板生成html页面,然后交给javamail去发送,
	 * 如何使用模板来生成html见 {@link http://www.iteye.com/topic/71430 }
	 * 
	 * @param to
	 * @param subject
	 * @param text
	 * @throws MessagingException
	 */
	public void sendMimeMessage(EmailEntity email) throws MessagingException {
		if (null == email) {
			if (logger.isDebugEnabled()) {
				logger.debug("something you need to tell here");
			}
			return;
		}
		MimeMessage message = javaMailSender.createMimeMessage();
		MimeMessageHelper helper = new MimeMessageHelper(message);
		
		helper.setTo(email.getTo());
		helper.setFrom(from);
		helper.setSubject(email.getSubject());
		
		this.addAttachmentOrImg(helper, email.getAttachment(), true);
		this.addAttachmentOrImg(helper, email.getImg(), false);
		
		//这里的text是html格式的, 可以使用模板引擎来生成html模板, velocity或者freemarker都可以做到
		helper.setText(email.getText(),true);
		
		easyMailExecutorPool.getService().execute(new MailRunner(message));
	}
	
	/**
	 * 添加附件或者是图片
	 * @param helper
	 * @param map
	 * @param isAttachment
	 * @throws MessagingException
	 */
	private void addAttachmentOrImg(MimeMessageHelper helper, Map map, boolean isAttachment) throws MessagingException {
		for (Iterator it = map.entrySet().iterator(); it.hasNext();) {
			Map.Entry entry = (Map.Entry) it.next();
			String key = (String) entry.getKey();
			String value = (String) entry.getValue();
			if (StringUtils.isNotBlank(key) && StringUtils.isNotBlank(value)) {
				FileSystemResource file = new FileSystemResource(new File(value));
				if (!file.exists()) continue;
				if (isAttachment) {
					helper.addAttachment(key, file);
				} else {
					helper.addInline(key, file);
				}
			}
		}
	}
	
	/**
	 * 用来发送邮件的 Runnable, 该类是一个内部类,之所以使用内部类,而没有使用嵌套类(静态内部类),
	 * 是因为内部类可以之间得到 service 的 javaMailSender
	 * 每次发送邮件都会从线程池中取一个线程, 然后进行发邮件操作
	 * @author ahuaxuan
	 */
	private class MailRunner implements Runnable {
		SimpleMailMessage simpleMailMessage;
		MimeMessage mimeMessage; 
		
		/**
		 * 构造简单文本邮件
		 * @param simpleMailMessage
		 */
		public MailRunner(SimpleMailMessage simpleMailMessage) {
			if (mimeMessage == null) {
				this.simpleMailMessage = simpleMailMessage;
			}
		}
		
		/**
		 * 构造复杂邮件,可以添加附近,图片,等等
		 * @param mimeMessage
		 */
		public MailRunner(MimeMessage mimeMessage) {
			if (simpleMailMessage == null) {
				this.mimeMessage = mimeMessage;
			}
		}
		
		/**
		 * 该方法将在线程池中的线程中执行
		 */
		public void run() {
			try {
				if (simpleMailMessage != null) {
					javaMailSender.send(this.simpleMailMessage);
				} else if (mimeMessage != null) {
					javaMailSender.send(this.mimeMessage);
				}
            	
            } catch (Exception e) {
            	if (logger.isDebugEnabled()) {
            		logger.debug("logger something here", e);
            	}
            }     
		}
	}
}


MailService中的EmailEntity是对邮件的抽象(我只使用了失血模型,事实上我们也可以让这个EmailEntity来实现Runnable接口,这样Service中的内部类就可以去掉了,同时service中的大部分代码就要搬到EmailEntity及其父类里了,大家更倾向于怎么做呢?),代码如下:

/**
 * 该类是对邮件的抽象,邮件有哪些属性,这个类就有哪些属性 显然这个只是一个例子,
 * 这个例子中附带mimemessage发送所需的附件或者图片(如果有的话)
 * 需要使用的同学自己扩展
 * 
 * @author 张荣华(ahuaxuan)
* @version $Id$
 */
public class EmailEntity {

	String to;

	String subject;

	String text;

	//邮件附件
	Map<String, String> attachment = new HashMap<String, String>();

	//邮件图片
	Map<String, String> img = new HashMap<String, String>();
	//这里省去大段的getter和setter方法
}

接下来就是在spring的配置文件中配置这些类了,我相信对熟悉spring的人来说这不是什么大问题:

<beans>
    <bean id="javaMailSender" class="org.springframework.mail.javamail.JavaMailSenderImpl" autowire="byName">
        <property name="host" value="${mail.host}"/>
        <property name="username" value="${mail.username}"/>
        <property name="password" value="${mail.password}"/>
    </bean>

    <bean id="easyMailExecutorPool" class="org.zhangronghua.easymail.EasyMailExecutorPool" autowire="byName">
    	<property name="poolSize">
    		<value>5</value>
    	</property>
    </bean>
    
    <bean id="easyMailService" class="org.zhangronghua.easymail.EasyMailServieImpl" autowire="byName">
    	<property name="from" value="${mail.default.from}"/>
    </bean>
</beans>


经过这么一番折腾之后,一个邮件发送的雏形就完成了,接着需要什么样的邮件发送功能就可以随意往MailService里添加内容了, 而如果需要用模板来生成html格式的邮件真的需要看http://www.iteye.com/topic/71430这个贴了,无论你是想用velocity还是想用freemarker来做模板引擎,这个贴中的例子都是可以直接拿来使用的

总结,如果自己起线程来发送邮件是一个非常危险的事情,如果并发一高(比如超过20),服务器估计就快撑不住了,而如果使用jms来异步发送邮件,学习的曲线高,成本也高,我不建议为了一个小小的邮件发送就在项目中导入jms(之所以这样说是因为还有很多项目就是基于webservice的,那么使用jms来调度webservice是一个不错的选择),所以使用线程池来实现这个异步的功能既安全又简单,这个例子是开源的,大家可以在自己的项目中随意修改,随意封装。

要注意的是,concurrent在jdk5.0以上版本中才有,如果你使用的是1.4的jdk需要单独下载concurrent包。

作者:张荣华,未经作者同意不得随意转载!


分享到:
评论
18 楼 cfyme 2012-07-04  
public void generateMimeMailMessage(JavaMailSender javaMailSender, ExecutorService executorService) throws MessagingException { 
        super.javaMailSender = javaMailSender; 
        super.executorService = executorService; 
         
        MimeMessage message = javaMailSender.createMimeMessage(); 
        MimeMessageHelper helper = new MimeMessageHelper(message); 

有一个问题:
在上面这个方法中 我觉得最下面两行要改成
mimeMessage = javaMailSender.createMimeMessage();  
MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true, "utf-8");

不然父类中的mimeMessage永远是null
17 楼 cfyme 2012-07-04  
看了您的分享,学习了很多,有个问题,请问我按你重构后的 去运行,怎么也执行不到父类中RUN方法,不知道哪里出问题了,能把源代码发我一份吗,cfyme@163.com
16 楼 llin96 2007-12-15  
作者已经写的很完整很详细了, 如果你不熟练spring就先去看看文档吧
15 楼 liuxinsudi 2007-10-11  
能否把整个源代码让我拜读一下? wangfujunemail@163.com
14 楼 liuxinsudi 2007-10-11  
能否把整个源代码让我拜读一下? wangfujunemail@163.com
13 楼 harrybit 2007-09-06  
楼主能把完整的代码挂上来下载就更好了
12 楼 ahuaxuan 2007-08-24  
maserkinger 写道
方法挺好,不过介绍得比较简单了.
而且按这么配置的话
easyMailExecutorPool.getService().execute(new MailRunner(simpleMailMessage));
会出现空指针错误,建议楼主放个无错误的用法上来!

稍微思考一下就知道你的问题处在easyMailExecutorPool没有注入到EasyMailServieImpl,或者easyMailExecutorPool的 public void afterPropertiesSet() throws Exception {  
        service = Executors.newFixedThreadPool(poolSize);  
    }  
方法没有正确执行,稍微debug一下就知道了,我的代码都是经过测试的
11 楼 maserkinger 2007-08-22  
方法挺好,不过介绍得比较简单了.
而且按这么配置的话
easyMailExecutorPool.getService().execute(new MailRunner(simpleMailMessage));
会出现空指针错误,建议楼主放个无错误的用法上来!
10 楼 sskhnje 2007-08-02  
谢谢!
真是个好人.
能发一份源码给我吗?
谢谢啊!

sskhnje@mail.ynu.edu.cn
9 楼 downpour 2007-07-24  
自己实现一个的成本也不小,不如扔到JMS里面,配一下就ok了。

不过这个池不错,可以成为范本了。简单耐用啊。
8 楼 baibai326 2007-07-24  
恩,谢谢啦。
呵呵,大侠就是大侠,还这么谦虚,我是没有先飞的笨鸟。o(∩_∩)o...
7 楼 ahuaxuan 2007-07-24  
baibai326 写道
楼主请教一个问题,
如果有在run()中
synchronized(this.simpleMailMessage){
  javaMailSender.send(this.simpleMailMessage);
}

如果并发有很多,以你的经验大概会出现什么情况啊?

,想知道我的想法跟大侠之间差距有多少,所以问一问,谢谢回答,呵呵。

这里没有同步的问题,不需要synchronized,因为EmailEntity是一个实体类,每次请求都会创建一个呀,所以即使是并发,只要服务器有命这里就不会出现什么问题

ps:我不是什么大侠,只是刚起飞的菜鸟,呵呵
6 楼 baibai326 2007-07-24  
楼主请教一个问题,
如果有在run()中
synchronized(this.simpleMailMessage){
  javaMailSender.send(this.simpleMailMessage);
}

如果并发有很多,以你的经验大概会出现什么情况啊?

,想知道我的想法跟大侠之间差距有多少,所以问一问,谢谢回答,呵呵。
5 楼 maxima 2007-07-19  
concurrent 使程序员不用关注系线程的管理,实现异步处理轻松不少.
4 楼 ahuaxuan 2007-07-13  
jianfeng008cn 写道
jms concurrent jmx 这几种方案并不冲突,无所谓简单复杂吧

你说得对,这几种方案不冲突,所谓得简单和复杂只是针对使用而言。


popi 写道
有些标题党的感觉,
题名改为《用复杂方式解释简单内容》比较合适。
关键词:耗时操作,线程池,与spring无关

谢谢你的提醒,本人也比较反对标题党,可是我并不觉得文章的名字有什么不妥,本来就是在简单和复杂之间的选择。而用《复杂方式解释简单内容》在我眼里似乎更加标题党一些,但是本文的题目也确实没有能涵盖住内容,除了线程池,耗时操作,接触到设计模式,域模型等,这一点相信你也看出来了,人人都能写出能工作的代码,本文的一个目的就是想说明写代码不是只要能工作就可以了的。
spring是基础,就不用说了


3 楼 popi 2007-07-13  
有些标题党的感觉,
题名改为《用复杂方式解释简单内容》比较合适。
关键词:耗时操作,线程池,与spring无关
2 楼 jianfeng008cn 2007-07-13  
jms concurrent jmx 这几种方案并不冲突,无所谓简单复杂吧
1 楼 ahuaxuan 2007-07-12  
昨天发的帖子说错了一个概念:上面例子中其实使用的是失血模型,我说成了贫血模型,在此更正一下,经过思考,我觉得对这个邮件的抽象用真正的贫血模型是比较合适的,也比失血模型更加的oo,而且代码我也已经重构过了,重构完之后发现最后的结果是整个就是一strategy+adapter,最后service中的方法变成了这个德行(service中的方法变成了策略的调用者)
public void sendMimeMessage(EmailEntity email) throws MessagingException {
		email.setFrom(from);
		email.generateMimeMailMessage(javaMailSender, easyMailExecutorPool.getService());
		email.send();
	}


方法都跑EmailEntity和其父类中去了,但是我没有办法在Entity中注入需要的service,只要作为参数传了进去,从整体上来看,整个easymail变得更加得面向对象了。这让我体会到贫血模型确实要比失血模型更oo

我们看看EmailEntity中得代码:

/**
 * 该类是对邮件的抽象,邮件有哪些属性,这个类就有哪些属性 显然这个只是一个例子,
 * 这个例子中附带mimemessage发送所需的附件或者图片(如果有的话)
 * 需要使用的同学自己扩展
 * 
 * @author 张荣华(ahuaxuan)
 * @since 2007-7-11
 * @version $Id$
 */
public class EmailEntity extends EmailRunner{
	
	/**
	 * 构造简单文本邮件
	 * @param simpleMailMessage
	 */
	public void generateSimpleMailMessage(JavaMailSender javaMailSender, ExecutorService executorService) {
		super.javaMailSender = javaMailSender;
		super.executorService = executorService;
		simpleMailMessage = new SimpleMailMessage();
		
		simpleMailMessage.setTo(to);
		simpleMailMessage.setSubject(subject);
		simpleMailMessage.setText(text);
		simpleMailMessage.setFrom(from);
	}
	

	/**
	 * 构造复杂邮件,可以添加附近,图片,等等
	 * @param mimeMessage
	 * @throws MessagingException 
	 */
	public void generateMimeMailMessage(JavaMailSender javaMailSender, ExecutorService executorService) throws MessagingException {
		super.javaMailSender = javaMailSender;
		super.executorService = executorService;
		
		MimeMessage message = javaMailSender.createMimeMessage();
		MimeMessageHelper helper = new MimeMessageHelper(message);
		
		helper.setTo(to);
		helper.setFrom(from);
		helper.setSubject(subject);
		
		super.addAttachmentOrImg(helper, attachment, true);
		super.addAttachmentOrImg(helper, img, false);
		
		//这里的text是html格式的, 可以使用模板引擎来生成html模板, velocity或者freemarker都可以做到
		helper.setText(text);
	}


	/**
	 * 发送邮件方法, 在这个方法调用之前必须调用 generateMimeMailMessage 或者 generateSimpleMailMessage,
	 * @see EasyMailServiceImpl#sendMimeMessage(EmailEntity)
	 * @see EasyMailServiceImpl#sendMessage(EmailEntity)
	 */
	public void send() {
		if(super.javaMailSender != null && super.executorService != null){
			super.executorService.execute(this);
		}
	}
}


再看看其父类中方法,这个就是executor要执行得代码了:

/**
	 * 该方法将在线程池中的线程中执行
	 */
	public void run() {
		try {
			if (simpleMailMessage != null) {
				javaMailSender.send(this.simpleMailMessage);
			} else if (mimeMessage != null) {
				javaMailSender.send(this.mimeMessage);
			}

		} catch (Exception e) {
			if (logger.isDebugEnabled()) {
				logger.debug("logger something here", e);
			}
		}
	}

相关推荐

    Java mail邮件开发

    张孝祥的java mail开发教程 Java Mail API的开发是SUN为Java开发者提供公用API框架的持续努力的良好例证。...乍看起来,Java Mail API所拥有的类总数以及类之间的关系可能让人误解为要花费漫长的学习时间。

    复杂邮件程序完整Java源码.doc

    复杂邮件程序完整Java源码.doc

    java 面试题 总结

    JAVA平台提供了两个类:String和StringBuffer,它们可以储存和操作字符串,即包含多个字符的字符数据。这个String类提供了数值不可改变的字符串。而这个StringBuffer类提供的字符串进行修改。当你知道字符数据要改变...

    在Java中轻松实现数据结构和算法:在Java中轻松实现数据结构和算法

    对于任何计算机科学专业人士而言,它都是一本便捷的指南,《 Java中的数据结构和算法变得容易:数据结构和算法难题》是解决与数据结构和算法有关的各种复杂问题的解决方案库。 计算机科学行业的那些读者可以将其...

    Javamail 1.3收发邮件开发教程.pdf

    Java Mail API 的开发是SUN 为Java 开发者提供公用API 框架的持续努力的良好例证。提倡公用框 架,反对...实际上,一旦正式开 始使用,你就会发现该API 不失为在应用程序中加入健壮的邮件/通讯支持的简单工具。

    Java数据库编程宝典3

    7.2 在较复杂的WHRER子句中使用运算符 7.2.1 DISTINCT运算符 7.2.2 TOP运算符 7.2.3 比较运算符 7.2.4 CHAR和VARCHAR运算符 7.2.5 逻辑运算符 7.2.6 算术运算符 7.2.7 混合运算符:IN和BETWEEN 7.2.8 集合...

    javaOA办公系统模块设计方案.pdf

    javaOA办公系统模块设计⽅案 1.模型管理 :web在线流程设计器、预览流程xml、导出xml、部署流程 2.流程管理 :导⼊导出流程资源⽂件、查看流程图、根据流程实例反射出流程模型、激活挂起 、⾃由跳转 3.运⾏中流程:...

    超级有影响力霸气的Java面试题大全文档

     JAVA平台提供了两个类:String和StringBuffer,它们可以储存和操作字符串,即包含多个字符的字符数据。这个String类提供了数值不可改变的字符串。而这个StringBuffer类提供的字符串进行修改。当你知道字符数据要...

    jsr80 java 访问 usb

    数据传输发生在主机和 USB 设备上特定的 端点(endpoint) 之间,主机与端点之间的数据链接称为 管道(pipe)。 一个给定的 USB 设备可以有许多个端点,主机与设备之间数据管道的数量与该设备上端点的数量相同。一个管道...

    java大型在线考试培训系统

    下载地址在附件 试题模块(章节结构) 无限自定义层次结构 模块内管理试题 模块试题信息统计 批量移动试题 试题/题型 单选、多选、填空、判断、问答、文件、综合7大类基础题型 无限变通设置完型填空、阅读理解、...

    java讲师笔试题-EDAF05-labs-public:EDAF05算法、数据结构和复杂性的实验,在LTH举行

    教师java笔试题EDAF05-labs EDAF05 实验室,在 2018-19 年第 4 研究期间在 LTH 举行。 讲师和课程召集人是 Jonas Skeppstedt。 此版本的实验室作业由 Lars Åström 编写和维护。 以前的版本是由 Thore Husfeldt 和 ...

    协议:Java协议库

    Java协议库提供了用于收集消息,调试信息和警告的通用解决方案。 总览 较大的应用程序通常需要适当的反馈给调用者以进行复杂的操作。 假设用户要保存许多订单。 业务逻辑将验证输入的数据,根据通用系统设置计算...

    Java数据库编程宝典2

    7.2 在较复杂的WHRER子句中使用运算符 7.2.1 DISTINCT运算符 7.2.2 TOP运算符 7.2.3 比较运算符 7.2.4 CHAR和VARCHAR运算符 7.2.5 逻辑运算符 7.2.6 算术运算符 7.2.7 混合运算符:IN和BETWEEN 7.2.8 集合...

    Java数据库编程宝典4

    7.2 在较复杂的WHRER子句中使用运算符 7.2.1 DISTINCT运算符 7.2.2 TOP运算符 7.2.3 比较运算符 7.2.4 CHAR和VARCHAR运算符 7.2.5 逻辑运算符 7.2.6 算术运算符 7.2.7 混合运算符:IN和BETWEEN 7.2.8 集合...

    java弹出网站源码寻找-secure-email:下一代安全电子邮件项目概述

    这些问题很难解决,而且很难解决,因为没有快速的技术解决方案:问题在于用户体验、现实世界基础设施和安全性之间的复杂交互。 尽管尚未就如何最好地解决任何这些问题达成共识,但本报告中列出的项目的多样性反映了...

    Java数据库编程宝典1

    7.2 在较复杂的WHRER子句中使用运算符 7.2.1 DISTINCT运算符 7.2.2 TOP运算符 7.2.3 比较运算符 7.2.4 CHAR和VARCHAR运算符 7.2.5 逻辑运算符 7.2.6 算术运算符 7.2.7 混合运算符:IN和BETWEEN 7.2.8 集合...

    计算机软件方案建议书

    软件方案建议书 电子书台包含电子文档管理系统,全文检索系统,FTP服务器,OA系统,电子签章系统,红城教育网站,邮件系统等多个子系统,该平台业务复杂,功能强大,设计到教育领域的多种业务,是一个组合性平台。...

    OpenCms 8.0.2.zip

    OpenCms基于JAVA和XML语言技术,因此它适合完全融入到现有的系统内部。OpenCms可以非常好的运行在一个完全的开源环境中(例如:Linux、Apache、Tomcat、MySQL). 当然,也可以很好的运行于商业环境下(例如:Windows...

Global site tag (gtag.js) - Google Analytics