服务热线:13616026886

技术文档 欢迎使用技术文档,我们为你提供从新手到专业开发者的所有资源,你也可以通过它日益精进

位置:首页 > 技术文档 > JAVA > 新手入门 > 基础入门 > 查看文档

基于控件构架开发访问javamail的控件

  beehive只是提供了三种访问企业资源的系统控件,现实情况下我们需要访问更多类型的企业资源,所以我们需要自己来开发符合自己需要的控件。在这篇文章里,作者将介绍如何基于控件架构开发访问javamail资源的控件,简化对javamail资源的访问。

  从前面的文章中,我们已经学习了beehive中提供的三种访问企业资源的控件:jdbc控件、ejb控件和jms控件,而这也是beehive中目前已经提供的全部系统控件。然而j2ee标准中提供的企业资源类型远不止这三种,开发者选择等待beehive开发组提供更多的控件显然是不明智的,我们必须自己动手来解决控件的开发。

  本文中就将给大家介绍如何基于控件架构提供的api来开发自己的控件,我们选择开发者经常需要访问的企业资源――javamail作为目标资源,按照控件的命名规则,我们暂且叫做javamail控件吧。

  要完成一个控件的设计,通常需要完成如下的工作:

  1. 确定控件要完成的功能。
  2. 分析要完成控件的功能,确定没有使用控件之前我们通常需要提供哪些参数,这些参数是否可能组合成集合使用,参数是否必须提供,参数的类型等。
  3. 根据分析结果,设计和实现对应于这些参数的注释,这些注释能够完整地体现第二步中分析的结果。
  4. 定义和实现控件公共类

      根据控件的使用情况,确定控件公共接口类中需要对外提供的接口方法,要确定这些接口方法可能会比较困难,因为有些时候控件设计者可能难于确定是否为控件使用者提供低级接口以便使用者能够深入的控制控件的行为。

  5. 提供控件实现类

  控件实现类通常被设计成可扩展(实现org.apache.beehive.controls.api.bean.extensible接口)的,这样方便使用者扩展该实现类。控件实现类中最主要的方法是invoke(method m,object[] args)方法,这个方法的参数 m 代表控件使用者调用的业务方法,而 args 数组则对应着控件使用者调用该业务方法时提供的调用参数,控件实现类需要根据使用者提供的注释和调用业务时提供的参数完成控件主体功能的实现。

  下面的章节中,我们将详细地讲解如果依照上面的步骤来完成javamail控件的设计和开发。本文中所有例子的源代码可以在通过资源下载区中的连接完成下载。

控件功能定义

  javamail控件的开发目标是完成访问smtp服务器发送邮件的封装,提供足够的注释满足开发者在发送邮件时需要设置的参数,同时提供邮件发送的实现方法使开发者在使用控件后无需编写访问smtp服务器、设置邮件发送者/接收者等参数、发送邮件的代码,而只需要将精力集中到业务逻辑上。

需要提供的注释

  在开发控件之前,我们需要确定向开发者提供哪些注释才能够满足他们定制控件的实际需求,因此我们首先要分析开发者使用这些控件时通常需要提供哪些参数。要访问smtp服务器发送邮件,开发需要提供的参数分为两类:

  1.目标smtp服务器的参数

  目标smtp服务器的参数包括目标smtp服务器的地址,访问smtp服务器是否需要提供安全信息以及访问smtp服务器所需要的用户名和密码,其中目标smtp服务器的地址是必须的。我们需要提供的参数和相关要求如表1所示

表1 smtp服务器参数表

1.	package org.vivianj.beehive.controls.examples.javamail;2.	3.	import java.lang.annotation.elementtype;4.	import java.lang.annotation.inherited;5.	import java.lang.annotation.retention;6.	import java.lang.annotation.retentionpolicy;7.	import java.lang.annotation.target;8.	9.	import org.apache.beehive.controls.api.bean.annotationconstraints;10.	import org.apache.beehive.controls.api.bean.annotationmembertypes;11.	import org.apache.beehive.controls.api.bean.controlinterface;12.	import org.apache.beehive.controls.api.properties.propertyset;13.	14.	/**15.	 * javamailcontrol 是javamail控件的公共接口类16.	 */17.	@controlinterface18.	public interface javamailcontrol {19.	20.		/**21.		 * 获取邮件发送过程中产生的最后一个违例22.		 * @return	返回邮件发送过程中产生的最后一个违例23.		 */24.		public throwable getmailexception();25.	26.		/**27.		 * 类级别的注释,用于注释javamail控件中目标smtp服务器的28.	       相关信息29.		 */30.		@propertyset(prefix = “smtpserver”)31.	    /* 使用@ inherited注释表示该注释可以被自动继承 */32.		@inherited33.	    /* 使用allowexternaloverride表示定义的接口可以被覆盖 */34.		@annotationconstraints.allowexternaloverride35.		@retention(retentionpolicy.runtime)36.	    /* 使用type和fireld表示该注释能够作用于类、接口37.	       以及类成员变量 */38.		@target( { elementtype.type, elementtype.field })39.		public @interface smtpserver {40.	41.	42.			/**43.			 * smtp邮件发送服务器的地址,这个属性是必须设置的44.			 */45.			string serveraddress();46.	47.			/**48.			 * 使用该smtp服务器发送邮件是否需要安全认证 
49. * true - 需要安全认证
50. * false - 不需要安全认证51. */52. @annotationmembertypes.optional53. boolean authorizationrequired() default false;54. 55. /**56. * 访问smtp邮件服务器时使用的用户名57. */58. @annotationmembertypes.optional59. string principal() default "";60. 61. /**62. * 访问smtp邮件服务器时使用的密码63. */64. @annotationmembertypes.optional65. string credentials() default "";66. }67. 68. /**69. * 70. * message 用于注释继承类中的业务方法,描述被发送邮件的信息71. */72. @propertyset(prefix = "message")73. @inherited74. @annotationconstraints.allowexternaloverride75. @retention(retentionpolicy.runtime)76. @target( { elementtype.method })77. public @interface message {78. 79. /**80. * 邮件的发送者81. */82. string from();83. 84. /**85. * 邮件的接收者,可以使用xxx@xx.com,xxx1@xx.com,...的86. 形式传递多个接收者87. */88. string to();89. 90. /**91. * 邮件抄送的接收者,可以使用 xxx@xx.com,92. xxx1@xx.com,...的形式传递多个抄送的接收者93. */94. @annotationmembertypes.optional95. string cc() default "";96. 97. /**98. * 邮件暗送的接收者,可以使用xxx@xx.com,99. xxx1@xx.com,...的形式传递多个暗送的接收者100. */101. @annotationmembertypes.optional102. string bcc() default "";103. 104. /**105. * 邮件的主题106. */107. @annotationmembertypes.optional108. string subject() default "";109. 110. /**111. * 被发送邮件的类型,默认使用text/plain112. */113. @annotationmembertypes.optional114. string contenttype() 115. default "text/plain;charset=utf-8";116. 117. 118. /**119. * 邮件附件120. */121. @annotationmembertypes.optional122. string attachment() default "";123. }124. }

  从清单1中提供的源代码中我们注意到,smtpserver接口的authorizationrequired、principal、credentials属性之前使用了@annotationmembertypes.optional注释。应用了@annotationmembertypes.optional注释属性表示用户使用过程中可以不为这些属性提供内容。没有提供@annotationmembertypes.optional注释的属性(比如serveraddress)被视为必须设置的属性,控件使用者使用该注释时必须提供对应的声明,否则将无法通过编译。

 

控件实现类

   javamail控件要完成邮件发送的工作,必须要完成获取目标smtp服务器参数、设置smtp服务器参数、获取用户业务方法调用中提供的注释和参数、发送邮件等工作,其中获取目标smtp服务器参数、设置smtp服务器参数的动作通常只需要一次完成就行了,我们设计成在控件初始化的时候完成,反映到javamail控件的实现类中,我们可以将这部分工作在oncreate方法中完成。

  在控件实现类中,最主要的方法就是invoke(method m,object[] args),这个方法中的两个参数为我们提供了用于调用业务方法时提供的注释内容和调用参数,邮件发送的工作也被设计成在这个方法中完成。

  javamail控件实现类继承了javamail控件的接口类,同时实现了org.apache.beehive.controls.api.bean.extensible接口,这样javamail控件的使用者就可以直接继承该控件。清单2中显示了javamail实现类的完整源代码如下,源代码中在每一个关键点都提供了详细的说明,帮助大家理解javamail实现类的实现原理,请大家重点关注其中的oncreate和invoke两个方法。

  清单2 src/org/vivianj/beehive/controls/examples/javamail/

  javamailcontrolimpl.java

1.	package org.vivianj.beehive.controls.examples.javamail;2.	3.	import java.lang.reflect.method;4.	import java.util.properties;5.	6.	import javax.activation.datahandler;7.	import javax.activation.filedatasource;8.	import javax.mail.address;9.	import javax.mail.bodypart;10.	import javax.mail.multipart;11.	import javax.mail.session;12.	import javax.mail.transport;13.	import javax.mail.internet.internetaddress;14.	import javax.mail.internet.mimebodypart;15.	import javax.mail.internet.mimemessage;16.	import javax.mail.internet.mimemultipart;17.	18.	import org.apache.beehive.controls.api.bean.controlimplementation;19.	import org.apache.beehive.controls.api.bean.extensible;20.	import org.apache.beehive.controls.api.context.context;21.	import org.apache.beehive.controls.api.context.controlbeancontext;22.	import org.apache.beehive.controls.api.context.resourcecontext;23.	import org.apache.beehive.controls.api.context.controlbeancontext.lifecycle;24.	import org.apache.beehive.controls.api.events.eventhandler;25.	26.	/**27.	 * javamailcontrolimpl 用于封装访问smtp邮件服务器发送邮件的操作28.	 */29.	@controlimplementation30.	public class javamailcontrolimpl implements javamailcontrol, extensible,31.			java.io.serializable {32.	33.		static final long serialversionuid = 1l;34.	35.		@context36.		controlbeancontext context;37.	38.		@context39.		resourcecontext resourcecontext;40.	41.		private throwable lastexception;42.	43.		private session session;44.	45.		private properties props;46.	47.		private string serveraddress;48.	49.		private boolean authorizationrequired;50.	51.		private string principal;52.	53.		private string credentials;54.	55.		private mimemessage mimemessage;56.	57.		private multipart multipart;58.	59.		@eventhandler(field = “context”, 60.	        eventset = lifecycle.class, eventname = “oncreate”)61.		public void oncreate() {62.			smtpserver server = (smtpserver) context63.					.getcontrolpropertyset(smtpserver.class);64.			/* 获取smtp服务器的地址 */65.			serveraddress = server.serveraddress();66.			/* 目标smtp服务器发送邮件时是否需要安全认证 */67.			authorizationrequired = 68.	             server.authorizationrequired();69.			/* 访问目标smtp服务器时的用户名 */70.			principal = server.principal();71.			/* 访问目标smtp服务器时的密码 */72.			credentials = server.credentials();73.	74.			if (props == null)75.				props = system.getproperties();76.	77.			/* 初始化smtp服务器地址 */78.			props.put(“mail.smtp.host”, serveraddress);79.			/* 初始化访问目标smtp服务器时是否需要提供安全信息 */80.			setauthorizationrequired(authorizationrequired);81.		}82.	83.		/**84.		 * 返回邮件发送过程中的最后一个违例对象85.		 */86.		public throwable getmailexception() {87.	88.			return lastexception;89.		}90.	91.		/**92.		 * 根据用户提供的注释和调用方法时传入的参数完成邮件发送的93.	       动作94.		 * 95.		 * @param m96.		 *            被调用的业务方法97.		 * @param args98.		 *            调用业务方法时传递的参数99.		 */100.		public object invoke(method m, object[] args) 101.	        throws throwable {102.			message message = (message) context.103.	             getmethodpropertyset(m,message.class);104.			/* 获取邮件的接收者 */105.			string to = message.to();106.			/* 获取邮件的发送者 */107.			string from = message.from();108.			/* 获取邮件抄送的接收者 */109.			string cc = message.cc();110.			/* 获取邮件暗送的接收者 */111.			string bcc = message.bcc();112.			/* 获取邮件格式 */113.			string contenttype = message.contenttype();114.			/* 获取邮件附件 */115.			string attachment = message.attachment();116.			/* 获取邮件主题 */117.			string subject = message.subject();118.			/* 获取邮件内容 */119.			object body = null;120.			/* 如果业务方法没有提供发送方法,抛出违例信息 */121.			if (args.length < 1) {122.				body = ““;123.				throw new illegalargumentexception(124.					“at most one parameter may be defined as the “125.	                 + ”body of the mail message”);126.			} else127.				body = args[0];128.			/* 创建邮件对象 */129.			createmimemessage();130.			/* 设置邮件对象的发送者 */131.			setfrom(from);132.			/* 设置邮件对象的接收者 */133.			setto(to);134.			/* 设置邮件对象的抄送接收者 */135.			setcc(cc);136.			/* 设置邮件对象的暗送接收者 */137.			setbcc(bcc);138.			/* 设置邮件主题 */139.			setsubject(subject);140.			/* 设置邮件内容和格式 */141.			setbody(body, contenttype);142.			/* 设置邮件附件 */143.			setattachment(attachment);144.	145.			/* 发送邮件 */146.			sendmail();147.			return null;148.		}149.	150.		/**151.		 * 设置访问smtp服务器时是否需要提供152.		 * 153.		 * @param need154.		 *            访问smtp服务器时是否需要提供安全信息.155.	                  true - 需要 ,false - 不需要156.		 */157.		private void setauthorizationrequired(boolean need) {158.			if (props == null)159.				props = system.getproperties();160.	161.			if (need) {162.				props.put(“mail.smtp.auth”, “true”);163.			} else {164.				props.put(“mail.smtp.auth”, “false”);165.			}166.		}167.	168.		/**169.		 * 创建用于发送的邮件对象170.		 */171.		private void createmimemessage() throws exception {172.			try {173.				/* 获得邮件发送上下文 */174.				session = session.getdefaultinstance(props, null);175.			} catch (exception e) {176.				e.printstacktrace();177.				lastexception = e;178.				throw e;179.			}180.			try {181.				/* 创建邮件发送消息对象 */182.				mimemessage = new mimemessage(session);183.				/* 创建邮件发送对象 */184.				multipart = new mimemultipart();185.			} catch (exception e) {186.				e.printstacktrace();187.				lastexception = e;188.				throw e;189.			}190.		}191.	192.		/**193.		 * 设置被发送邮件的内容194.		 * 195.		 * @param body196.		 *            被发送邮件的内容197.		 * @param contenttype198.		 *            被发送邮件的类型199.		 */200.		public void setbody(object body, string contenttype) 201.	        throws exception {202.			try {203.				bodypart bodypart = new mimebodypart();204.				bodypart.setcontent(body, contenttype);205.				multipart.addbodypart(bodypart);206.	207.			} catch (exception e) {208.				e.printstacktrace();209.				lastexception = e;210.				throw e;211.			}212.		}213.	214.		/**215.		 * 设置被发送邮件的主题216.		 * 217.		 * @param subject218.		 *            被发送邮件的主题219.		 */220.		public void setsubject(string subject) throws exception {221.			try {222.				mimemessage.setsubject(subject);223.	224.			} catch (exception e) {225.				e.printstacktrace();226.				lastexception = e;227.				throw e;228.			}229.		}230.	231.		/**232.		 * 为被发送邮件增加附件233.		 * 234.		 * @param attachment235.		 *            附件的文件路径236.		 */237.		public void setattachment(string attachment) 238.	        throws exception {239.	240.			try {241.				bodypart bp = new mimebodypart();242.				filedatasource fileds = 243.	                 new filedatasource(attachment);244.				bp.setdatahandler(new datahandler(fileds));245.				bp.setfilename(fileds.getname());246.	247.				multipart.addbodypart(bp);248.			} catch (exception e) {249.				e.printstacktrace();250.				lastexception = e;251.				throw e;252.			}253.		}254.	255.		/**256.		 * 设置邮件的发送者257.		 * 258.		 * @param from259.		 *            邮件的发送者email地址260.		 */261.		public void setfrom(string from) throws exception {262.			try {263.				mimemessage.setfrom(new internetaddress(from));264.			} catch (exception e) {265.				e.printstacktrace();266.				lastexception = e;267.				throw e;268.			}269.		}270.	271.		/**272.		 * 设置邮件的接收者273.		 * 274.		 * @param to275.		 *            邮件的接收者字符串276.		 */277.		public void setto(string to) throws exception {278.			try {279.				mimemessage.setrecipients(280.	                javax.mail.message.recipienttype.to,281.					internetaddress.parse(to));282.			} catch (exception e) {283.				e.printstacktrace();284.				lastexception = e;285.				throw e;286.			}287.	288.		}289.	290.		/**291.		 * 设置邮件抄送的接收者292.		 * 293.		 * @param cc294.		 *            邮件抄送的接收者字符串295.		 */296.		public void setcc(string cc) throws exception {297.			try {298.				mimemessage.setrecipients(299.	                javax.mail.message.recipienttype.cc,300.					(address[]) internetaddress.parse(cc));301.			} catch (exception e) {302.				e.printstacktrace();303.				lastexception = e;304.				throw e;305.			}306.		}307.	308.		/**309.		 * 设置邮件暗送的接收者310.		 * 311.		 * @param bcc312.		 *            邮件暗送接收者字符串313.		 */314.		public void setbcc(string bcc) throws exception {315.			try {316.				mimemessage.setrecipients(317.	                javax.mail.message.recipienttype.bcc,318.					(address[]) internetaddress.parse(bcc));319.			} catch (exception e) {320.				e.printstacktrace();321.				lastexception = e;322.				throw e;323.			}324.		}325.	326.		/**327.		 * 发送邮件328.		 * 329.		 */330.		private void sendmail() throws exception {331.			try {332.				/* 设置邮件发送消息对象 */333.				mimemessage.setcontent(multipart);334.				/* 保存邮件发送消息对象 */335.				mimemessage.savechanges();336.	337.				/* 获取邮件发送上下文 */338.				session mailsession = 339.	                session.getinstance(props, null);340.				341.			/* 根据smtp服务器对于安全的不同要求连接smtp服务器 */342.		      transport transport = 343.	              mailsession.gettransport(“smtp”);344.				if (authorizationrequired)345.					transport.connect((string) props.get(346.	                 “mail.smtp.host”),principal, credentials);347.				else348.					transport.connect((string) props.get(349.	                “mail.smtp.host”), ““, ““);350.	351.				352.				/* 发送邮件给所有接收者 */353.			address[] tos = mimemessage354.		    .getrecipients(javax.mail.message.recipienttype.to);355.			if (tos != null && tos.length > 0) {356.					system.out.println(“tos.length = “ 357.	               + tos.length);358.					transport.sendmessage(mimemessage, tos);359.				}360.				/* 发送邮件给所有抄收的接收者 */361.				address[] ccs = mimemessage.getrecipients(362.	                       javax.mail.message.recipienttype.cc);363.	364.				if (ccs != null && ccs.length > 0) {365.					transport.sendmessage(mimemessage, ccs);366.					system.out.println(“ccs.length = “ 367.	                    + ccs.length);368.				}369.				/* 发送邮件给所有暗送的接收者 */370.				address[] bccs = mimemessage.getrecipients(371.	                   javax.mail.message.recipienttype.bcc);372.	373.				if (bccs != null && bccs.length > 0) {374.					transport.sendmessage(mimemessage, bccs);375.					system.out.println(“bccs.length = “ 376.	                     + bccs.length);377.				}378.	379.				/* 断开与smtp服务器的连接 */380.				transport.close();381.	382.			} catch (exception e) {383.				e.printstacktrace();384.				lastexception = e;385.				throw e;386.			}387.		}388.	}

  经过上面的这些步骤以后,javamail控件的主体部分就已经全部完成了,我们可以将它编译、打包成jar文件,在另外的应用中使用它。下面的内容将介绍如何使用javamail控件完成javamail资源访问。

 

使用javamail控件访问javamail资源

   javamail控件本身并不能直接用于发送邮件,控件使用者必须继承该控件,提供对应的注释后才能够完成邮件发送的工作,本章节中将给大家讲解如何继承javamail控件来完成邮件发送的工作。

1.使用javamail控件连接到需要提供安全认证的smtp服务器

   网易(nease)公司是我个人比较喜欢的提供免费邮箱服务的网络公司。网易(nease)公司提供的免费邮箱对应的smtp服务器(smtp.163.com)需要提供安全认证才能够发送邮件。

  我们将编写一段代码,调用已经完成的javamail控件,访问需要提供安全信息的网易公司的smtp服务器--smtp.163.com。为了简化例子,我们假设仅仅需要通过这段代码将邮件发送给唯一的一个接收者,不需要暗送或者抄送给其他人,要发送的邮件也没有附件。

  清单3 中显示了符合要求的一个例子。

  清单3 src/org/vivianj/beehive/controls/examples/

  neasejavamailcontrol.java

1.	package org.vivianj.beehive.controls.examples;2.	3.	import org.apache.beehive.controls.api.bean.controlextension;4.	import org.vivianj.beehive.controls.examples.javamail.javamailcontrol;5.	6.	/**7.	 * neasejavamailcontrol 用于继承javamail控件,完成邮件发送的
8. * 功能,使用smtp.163.com邮件服务器
9. * 使用该smtp服务器需要提供安全信息10. */11. @controlextension12. @javamailcontrol.smtpserver(serveraddress = "smtp.163.com", 13. authorizationrequired = true, principal = "principal", 14. credentials = "credentials")15. public interface neasejavamailcontrol 16. extends javamailcontrol {17. 18. /**19. * 完成发送邮件的功能
20. * 邮件发送者的邮箱是principal@163.com
21. * 邮件接收者的邮箱是principal@163.com
22. * 邮件主题是hello
23. * 邮件的内容由控件使用者在调用时使用参数body传入
24. * 25. * @param body26. * 邮件的内容27. */28. @javamailcontrol.message(from = "principal@163.com", 29. to = "principal@163.com", subject = "hello")30. public void sendmail(string body);31. }
2.使用java控件连接到不需要提供安全认证的smtp服务器

  www.vivianj.org是我创建的唯j族组织负责维护的一个网站,它同时能够为唯j族组织的部分用户提供smtp服务,它所使用的smtp服务器(mail.vivianj.org)在发送邮件时不需要用户提供安全认证信息。

  我们将编写一段代码,调用已经完成的javamail控件,连接到不需要提供安全信息的smtp服务器―mail.vivianj.org后发送邮件。为了简化例子,我们假设仅仅需要通过这段代码将邮件发送给唯一的一个接收者,不需要暗送或者抄送给其他人,要发送的邮件也没有附件。

  清单4 中显示了符合要求的一个例子。

  清单4 src/org/vivianj/beehive/controls/examples/

  vivianjjavamailcontrol.java

1.	package org.vivianj.beehive.controls.examples;2.	3.	import org.apache.beehive.controls.api.bean.controlextension;4.	import org.vivianj.beehive.controls.examples.javamail.javamailcontrol;5.	6.	/**7.	 * vivianjjavamailcontrol 用于继承javamail控件
8. * 实现通过mail.vivianj.org邮件服务器发送邮件的功能
9. * 使用该smtp服务器不需要提供安全信息10. */11. @controlextension12. @javamailcontrol.smtpserver(13. serveraddress = "mail.vivianj.org", 14. authorizationrequired = false, principal = "username", 15. credentials = "password")16. public interface vivianjjavamailcontrol 17. extends javamailcontrol {18. 19. /**20. * 完成发送邮件的功能
21. * 邮件发送者的邮箱是principal@vivianj.org
22. * 邮件接收者的邮箱是principal@163.com
23. * 邮件主题是hello
24. * 邮件的内容由控件使用者在调用时使用参数body传入
25. * 26. * @param body27. * 邮件的内容28. */29. @javamailcontrol.message(from = "principal@vivianj.org", 30. to = "principal@163.com", subject = "hello")31. public void sendmail(string body);}

结束语

  beehive1.0发布的时候,只是提供了三种系统控件:jdbc控件、ejb控件和jms控件,分别用于访问jdbc数据源、ejb和jms。然而实际应用情况下,我们通常需要访问更多类型的企业资源,所以我们必须要根据实际情况编写符合实际要求的控件。

  本文中,作者选择j2ee中常见的javamail资源作为例子,详细地介绍了如何基于控件架构分析、设计、实现和访问javamail控件的过程。读者可以根据上面的步骤,结合自己的实际需求,编写更多的控件,简化企业应用开发。

  参考资源

  beehive在线资源:

  http://beehive.apache.org/documentation.html

  下载资源

  本文中例子下载地址:

  jmscontrolexamples.zip

基于控件构架开发访问javamail的控件(图一)
 作者简介
基于控件构架开发访问javamail的控件(图二)
肖菁
肖菁 是唯j族(www.vivianj.org)创始人,bea 杭州user group负责人,自由撰稿人,开源项目buildfiledesigner(buildfiledesign.sourceforge.net)和v-security(v-security.sourceforge.net)创始人。

扫描关注微信公众号