虽然目前相对较火的支付方式是微信支付,但是本人更喜欢支付宝支付,有积分拿,还可以部分提现免手续费,每月还有信用卡还款免手续费额度,捐步数,蚂蚁森林等等,扯远了,总之,对我来说,微信用于沟通,支付宝是支付主力。
以下是支付宝集成到服务器的经验总结
使用支付功能,首先是进行注册等等,然后获得相应的 pid 和 appid 以及公钥和私钥;
支付宝小程序开发又有对应的 id 和 公钥私钥对;
申请支付宝开发的具体步骤略,懒得贴图,毕竟还要打码,哈哈
为了快速集成开发,引入了支付宝封装的jar包
alipay-sdk-java-{version}.jar
alipay-trade-sdk-{version}.jar;
之后就可以开始 briskly 的 coding 啦
若是jar包问题,可以私发
// 支付宝支付工具类
public final class AlipayHelper {
private AlipayHelper() { }
private static AlipayTradeService tradeService;
static {
Configs.init("alipay-config.properties");
/// 初始化
tradeService = new AlipayTradeServiceImpl.ClientBuilder().build();
}
public static AlipayTradeService getTradeService() {
return tradeService();
}
}
@Controller
@RequestMapping(value = { "/requestOf/alipay" })
public class AlipayController {
/**
* /// 获取支付宝用户ID ///
* @param code 用户授权之后获取到的
*/
@ResponseBody
@RequestMapping(value = { "/attain/user_id/{code}" })
public Map<String,Object> attainUserId(@PathVariable String code) {
// Map自动转换为Json
Map<String,Object> dataMap = new HashMap<>(8,0.1f);
try {
AlipayClient alipayClient = new DefaultAlipayClient(
"https://openapi.alipay.com/gateway.do", "支付宝小程序APPID",
"私钥字符串","json","UTF-8","公钥字符串","RSA2");
// OAuthToken 验证请求封装类
AlipaySystemOauthTokenRequest request
= new AlipaySystemOauthTokenRequest();
request.setGrantType("authorization_code"); // 验证
request.setCode(code);
// OAuthToken 验证响应封装类
AlipaySystemOauthTokenResponse response
= alipayClient.execute(request);
if(response.isSuccess()){
String userId = response.getUserId();
dataMap.put("user_id", userId);
} else {
dataMap.put("err_msg", response.getCode()
+ "-" + response.getMsg() + "-"
+ + response.getSubCode() + "-"
+ + response.getSubMsg());
}
} catch (AlipayApiException e) {
dataMap.put("err_msg", e.getMessage());
}
return dataMap;
}
}
@Controller
@RequestMapping(value = { "/requestOf/alipay" })
public class AlipayController {
/**
* 获取调起支付的参数
*/
@ResponseBody
@RequestMapping(value = { "/pre/callFor/payment" })
public Map<String,Object> preCallForPayment(String account,Double fee) {
// Map自动转换为Json
Map<String,Object> dataMap = new HashMap<>(8,0.1f);
try {
AlipayTradeAppPayModel model = new AlipayTradeAppPayModel();
model.setBody( "自定义字符串,如 缴费支付" );
model.setSubject( "自定义字符串,如 202号楼6单元501室" );
model.setOutTradeNo("自定义的交易单号,如 202101011010101234567890");
model.setTimeoutExpress( "10m" );
model.setTotalAmount( fee ); // 需支付金额:元
model.setProductCode( "产品代码,如 ELECTRICITY_PAYMENT" );
AlipayClient alipayClient = new DefaultAlipayClient(
"https://openapi.alipay.com/gateway.do", "支付宝小程序APPID",
"私钥字符串","json","UTF-8","公钥字符串","RSA2");
// 当前调用接口名称:alipay.trade.app.pay
AlipayTradeAppPayRequest alipayRequest
= new AlipayTradeAppPayRequest();
alipayRequest.setBizModel( model );
alipayRequest.setNotifyUrl( // 回调通知的URL,有调用策略,稍后会提
"https://***.com/requestOf/alipay/pre/callFor/callback" );
// 这里使用的是sdkExecute
AlipayTradeAppPayResponse response
= alipayClient.sdkExecute(alipayRequest);
/// 返回值 ///
dataMap.put( "signed_string", new String[] { } );
dataMap.put( "response_body", response.getBody() );
dataMap.put( "sign_type", "RSA2" );
dataMap.put( "body", "" );
dataMap.put( "subject", "" );
dataMap.put( "out_trade_no", "" );
dataMap.put( "timeout_express", "10m" );
dataMap.put( "total_amount", fee );
} catch (AlipayApiException e) {
dataMap.put("err_msg", e.getMessage());
}
return dataMap;
}
/**
* /// 支付宝给服务器发送的请求(回调) <br/>
* https://docs.open.alipay.com/194/103296/ <br/>
* 程序执行完后必须打印输出“success”(不包含引号)。<br/>
* 如果商户反馈给支付宝的字符不是success这7个字符,
* 支付宝服务器会不断重发通知,直到超过24小时22分钟。<br/>
* 一般情况下,25小时以内完成8次通知(
* 通知的间隔频率一般是:4m,10m,10m,1h,2h,6h,15h);<br/>
* <br/>
* <br/>
* 自有APP主动查询订单是否完成支付,也可以调用当前方法,可以
* 多传递参数来判断是支付宝回调还是APP查询状态
*/
@RequestMapping(value = { "/pre/callFor/callback" })
public void preCallForCallback(HttpServletRequest request,
HttpServletResponse response) throws Exception {
// 订单号
String out_trade_no = request.getParameter("out_trade_no");
// 二次验证订单状态
AlipayTradeQueryRequestBuilder builder
// Builder 典型的流式编程,setXxx() 返回 调用对象this 而非 void
= new AlipayTradeQueryRequestBuilder().setOutTradeNo( out_trade_no );
// 订单结果
AlipayF2FQueryResult result
= AlipayHelper.getTradeService().queryTradeResult(builder);
if (result.isTradeSuccess()) {
/// 取出支付宝用户ID和交易号 ///
String openid = result.getResponse().getBuyerUserId();
String transaction_id = result.getResponse().getTradeNo();
// TODO 更多逻辑操作等待
// 告知支付宝我也处理成功,无需再次调用
response.getWriter().write("success");
} else {
// TODO 未成功支付的相应操作
}
}
}
@Controller
@RequestMapping(value = { "/requestOf/alipay" })
public class AlipayController {
/**
* 获取调起支付的参数
* @param aliuId ***获取到的支付宝用户ID,参考之前的示例***
*/
@ResponseBody
@RequestMapping(value = { "/pre/callFor/miniProgram" })
public Map<String,Object> preCallForMiniProgram(
String account,Double fee,String aliuId) {
// Map自动转换为Json
Map<String,Object> dataMap = new HashMap<>(8,0.1f);
AlipayClient alipayClient = new DefaultAlipayClient(
"https://openapi.alipay.com/gateway.do", "支付宝小程序APPID",
"私钥字符串","json","UTF-8","公钥字符串","RSA2");
AlipayTradePayModel model = new AlipayTradePayModel();
model.setSubject( "电费充值" );
model.setOutTradeNo( "自定义订单号" );
model.setTimeoutExpress( "2m" );
model.setTotalAmount( fee );
model.setBuyerId( aliuId );
AlipayTradeCreateRequest alipayRequest = new AlipayTradeCreateRequest();
alipayRequest.setBizModel(model);
// 支付完成回调,参考之前的示例
alipayRequest.setNotifyUrl( "https://***.com/***" );
AlipayTradeCreateResponse alipayResponse
= alipayClient.execute(alipayRequest);
if (alipayResponse.isSuccess()) {
dataMap.put("tradeNo", alipayResponse.getTradeNo());
dataMap.put("outTradeNo", "自定义订单号");
dataMap.put("totalAmount", fee);
dataMap.put("subject", "电费充值");
} else {
dataMap.put("err_msg", "请重试");
}
return dataMap;
}
}
@Controller
@RequestMapping(value = { "/requestOf/alipay" })
public class AlipayController {
/**
* 支付宝当面付二维码
*/
@ResponseBody
@RequestMapping(value = { "/face2face/payment" })
public Map<String,Object> faceToFacePayment(
String account,Double fee) {
// Map自动转换为Json
Map<String,Object> dataMap = new HashMap<>(8,0.1f);
// 预创建订单参数封装
AlipayTradePrecreateRequestBuilder builder
= new AlipayTradePrecreateRequestBuilder()
.setOutTradeNo("自定义订单号")
.setSubject("话费充值")
.setTotalAmount(fee )
.setBody("话费充值")
.setStoreId("CHARGE_STATION_"+fee)
.setTimeoutExpress("2m")
// 支付结果回调,见之前示例
.setNotifyUrl("https://***.com/***");
AlipayF2FPrecreateResult result
= AlipayUtil.getTrade().tradePrecreate(builder);
if ( result.isTradeSuccess() ) {
// 二维码内容,生成二维码后,使用支付宝扫码即可支付
dataMap.put("qrText", result.getResponse().getQrCode());
} else {
dataMap.put("err_msg", "请重试");
}
return dataMap;
}
}
@Controller
@RequestMapping(value = { "/requestOf/alipay" })
public class AlipayController {
/**
* 对已支付的订单进行退款
*/
@ResponseBody
@RequestMapping(value = { "/refundWith/{out_trade_no}" })
public Map<String,Object> refund(@PathVariable String out_trade_no) {
// Map自动转换为Json
Map<String,Object> dataMap = new HashMap<>(8,0.1f);
try {
/// 自定义方法,根据单号获取金额,或者请求参数中传递过来也可
String yuan = invokeXxx(out_trade_no);
///
AlipayTradeRefundRequestBuilder builder
= new AlipayTradeRefundRequestBuilder()
.setOutTradeNo( out_trade_no )
.setRefundAmount( yuan )
.setOutRequestNo( out_trade_no )
.setRefundReason( "退款" ); // 退款原因,支付宝订单详情是可以看到的
AlipayF2FRefundResult result
= AlipayHelper.getTradeService().tradeRefund(builder);
if ( result.isTradeSuccess() ) {
// TODO 退款成功,请注意查收
dataMap.put("suc_msg", "退款成功,请注意查收");
} else {
// TODO 请重试
dataMap.put("err_msg", "请重试");
}
} catch (AlipayApiException e) {
dataMap.put("err_msg", e.getMessage());
}
return dataMap;
}
}
@Controller
@RequestMapping(value = { "/requestOf/alipay" })
public class AlipayController {
/**
* 生成二维码图片流
*/
@ResponseBody
@RequestMapping(value = { "/view/qr" })
public Map<String,Object> viewQr(
String qrText,HttpServletResponse response) {
Map<EncodeHintType, Object> hints = new HashMap<>();
hints.put(EncodeHintType.ERROR_CORRECTION,ErrorCorrectionLevel.L);
hints.put(EncodeHintType.CHARACTER_SET, "utf-8");
hints.put(EncodeHintType.MARGIN, 1);
BitMatrix bitMatrix
= new MultiFormatWriter()
.encode(qrText,BarcodeFormat.QR_CODE, 300, 300, hints);
OutputStream output = response.getOutputStream();
MatrixToImageWriter.writeToStream(bitMatrix,"png",output);
output.flush();
output.close();
}
}
着急的小伙伴可以跳过路过这一部分
com.alipay.demo.trade.config.Configs配置文件加载类,借助接口org.apache.commons.configuration.Configuration的子类org.apache.commons.configuration.PropertiesConfiguration进行配置文件读取;
public class Configs {
private static Log log = LogFactory.getLog(Configs.class);
private static Configuration configs;
// 根据文件名读取配置文件,文件后缀名必须为.properties
public synchronized static void init(String filePath) {
if (configs != null) {
return;
}
try {
// **** 此处进行配置文件读取 ****
configs = new PropertiesConfiguration(filePath);
} catch (ConfigurationException e) {
e.printStackTrace();
}
if (configs == null) {
throw new IllegalStateException(
"can`t find file by path:" + filePath);
}
// ****
log.info("配置文件名: " + filePath);
log.info(description());
}
}
/**
* This is the "classic" Properties loader which loads the values from
* a single or multiple files (which can be chained with "include =".
* 这是一个典型的属性加载器,从一个或多个文件中加载属性配置
* 多文件的形式是:文件中存在 key=include 且 value=another.properties
* 即include作为键,另一个配置文件作为值
* All given path references are either absolute or relative to the
* file name supplied in the constructor.
* 所有给定的路径引用要么是绝对路径 要么是 相对于构造器的提供的参数的相对路径
* <p>
* In this class, empty PropertyConfigurations can be built, properties
* added and later saved. include statements are (obviously) not supported
* if you don't construct a PropertyConfiguration from a file.
* 这个类里面,空的属性配置对象也可以进行构建编译,允许属性稍后再进行设置。不过,显而易见的是,
* 由于没有使用有参数的构造器,所以在当前配置中包含另一个配置文件是不被支持的,因为没有路径啊。
*
* <p>The properties file syntax is explained here, basically it follows
* the syntax of the stream parsed by {@link java.util.Properties#load} and
* adds several useful extensions:
* 现在开始解释属性文件的语法,基本上是遵守 Properties.load 方法所规定的语法并添加了一些扩展
*
* <ul>
* <li>
* Each property has the syntax <code>key <separator> value</code>. The
* separators accepted are {@code '='}, {@code ':'} and any white
* space character. Examples:
* 每一个属性的书写均以 键 分隔符 值。可被接受的分隔符有 = : 和空白字符。示例如下:
* <pre>
* key1 = value1
* key2 : value2
* key3 value3</pre>
* </li>
* <li>
* The <i>key</i> may use any character, separators must be escaped:
* 属性的 键 可以使用任意字符,不过若包含指定的分隔符,则必须要转义设置下,防止被当作分隔符处理。
* 如,若 键为 key:foo ,则需要在配置文件中使用 \: ,否则会存在异义。
* <pre>
* key\:foo = bar</pre>
* </li>
* <li>
* <i>value</i> may be separated on different lines if a backslash
* is placed at the end of the line that continues below.
* 这里需要注意的是,若在行的末尾处有一个 \ 则表示当前行被分隔为若干行,
* 直到读取到行尾不包含 \ 的某处。
* </li>
* <li>
* <i>value</i> can contain <em>value delimiters</em> and will then be interpreted
* as a list of tokens. Default value delimiter is the comma ','. So the
* following property definition
* 值对应的内容可以用分隔符,以此来表示值为集合型数据。默认的值分隔符是逗号。例如正面这个属性的定义
* <pre>
* key = This property, has multiple, values
* </pre>
* will result in a property with three values.
* 会导致这个属性具有三个值,分别为 "This property", "has multiple", "values"
* You can change the value
* delimiter using the {@link AbstractConfiguration#setListDelimiter(char)}
* method. Setting the delimiter to 0 will disable value splitting completely.
* 可以通过调用父类方法 setListDelimiter(char) 设置值分隔符,设置为0则值不会被分隔。
* </li>
* <li>
* Commas in each token are escaped placing a backslash right before
* the comma.
* 如果值的确需要包含逗号不作分隔之用途,则每一个逗号之前都需要加一个 \ 来转义。
* </li>
* <li>
* If a <i>key</i> is used more than once, the values are appended
* like if they were on the same line separated with commas.
* 如果一个 键 出现多次,则对就的值会被作为集合赋予 键 ,效用和 值分隔符 效用相同。
* <em>Note</em>:
* 注意:
* When the configuration file is written back to disk the associated
* {@link PropertiesConfigurationLayout} object (see below) will
* try to preserve as much of the original format as possible,
* 当配置文件要写回到磁盘时,则使用的配置类对象一起工作的 PropertiesConfigurationLayout 对象
* 会试着尽可能的保留原有格式。如:
* i.e. properties
* with multiple values defined on a single line will also be written back on
* a single line, and multiple occurrences of a single key will be written on
* multiple lines.
* 一行多个值的仍旧会被保存为一行多个值,同样的,若一个key出现多行,则仍会被保存多行。
* If the {@code addProperty()} method was called
* multiple times for adding multiple values to a property, these properties
* will per default be written on multiple lines in the output file, too.
* 如果多次调用 addProperty() 方法给某属性增加多个值,则这些属性也会被保存为多行。
* Some options of the {@code PropertiesConfigurationLayout} class have
* influence on that behavior.
* PropertiesConfigurationLayout类中的一些选项会影响到这个保存规则。
* </li>
* <li>
* Blank lines and lines starting with character '#' or '!' are skipped.
* 任意行以 # 或 ! 开头的均会被跳过,不会被读取。
* </li>
* <li>
* If a property is named "include" (or whatever is defined by
* setInclude() and getInclude() and the value of that property is
* the full path to a file on disk, that file will be included into
* the configuration.
* 如果一个属性命名为 include 或者通过 setInclude() 方法设置的对应的值,并且对应的属性值
* 是一个磁盘上的全路径,则这个文件会被加载读取,其中包含的属性会被包含进来。
* You can also pull in files relative to the parent
* configuration file. So if you have something like the following:
* 当然,也可以使用 相对于当前配置的文件 的相对路径,若配置如下,
*
* include = additional.properties
*
* Then "additional.properties" is expected to be in the same
* directory as the parent configuration file.
* 则这个 additional.properties 应该存在于和 当前配置文件 相同的文件夹内。
*
* The properties in the included file are added to the parent configuration,
* they do not replace existing properties with the same key.
* 子配置文件中的属性会被添加到主配置项中,若有同名的key也不会替换主配置项的属性。
* (对应的key变为集合型数据,类似于一个key多行的情形。此处有待验证)
* </li>
* </ul>
*
* <p>Here is an example of a valid extended properties file:
* 以下是一个符合规则的属性配置文件
*
* <p><pre>
* # lines starting with # are comments
* “#”开头的被当作注释
*
* # This is the simplest property
* key = value
* 最简单的 键 = 值 形式的属性
*
* # A long property may be separated on multiple lines
* longvalue = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa \
* aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
* 被分隔为多行的值,行尾以 \ 结束
*
* # This is a property with many tokens
* tokens_on_a_line = first token, second token
* 值分隔符的应用,值为集合型
*
* # This sequence generates exactly the same result
* tokens_on_multiple_lines = first token
* tokens_on_multiple_lines = second token
* 出现多行的同名key,同样被解析为集合型
*
* # commas may be escaped in tokens
* commas.escaped = Hi\, what'up?
* 在值分隔符前加入 \ 进行转义,免于被认为是多个值
*
* # properties can reference other properties
* base.prop = /base
* first.prop = ${base.prop}/first
* second.prop = ${first.prop}/second
* 属性之间相互引用 (后者引用前者)
* </pre>
*
* <p>A {@code PropertiesConfiguration} object is associated with an
* instance of the {@link PropertiesConfigurationLayout} class,
* which is responsible for storing the layout of the parsed properties file
* (i.e. empty lines, comments, and such things).
* 当前类对象会关联一个 PropertiesConfigurationLayout 对象,它负责存储配置文件,保留其格式
*
* The {@code getLayout()}
* method can be used to obtain this layout object. With {@code setLayout()}
* a new layout object can be set. This should be done before a properties file
* was loaded.
* 可以通过 getLayout() 方法获取这个对象,也可以通 setLayout() 设置一个新的对象。
* 设置这个对象的操作需在属性配置文件加载之前调用。
*
* <p><em>Note:</em>Configuration objects of this type can be read concurrently
* by multiple threads. However if one of these threads modifies the object,
* synchronization has to be performed manually.
* 注意,虽然这个配置对象可以被多个线程同步读取,但是
* 若是其中一个线程修改的属性值,则需要代码调用来实现属性同步
*
*/
public class PropertiesConfiguration extends AbstractFileConfiguration
{
/** Constant for the supported comment characters.*/
/** 支持的注释符常量 */
static final String COMMENT_CHARS = "#!";
/** Constant for the default properties separator.*/
/** 默认的属性分隔符常量 */
static final String DEFAULT_SEPARATOR = " = ";
/** The list of possible key/value separators */
/** 可能使用到的 键值 分隔符集合 */
private static final char[] SEPARATORS = new char[] {'=', ':'};
/** Stores the layout object.*/
/** 模板对象,最终用于读取和写回到文件 */
private PropertiesConfigurationLayout layout;
/**
* Creates and loads the extended properties from the specified file.
* 从指定文件创建并加载扩展属性
* The specified file can contain "include = " properties which then
* are loaded and merged into the properties.
* 指定的文件若包含 "include = another.properties" 的属性,
* 则 another.properties 配置文件中的数据会被加载并放置到同一个属性集中
*/
public PropertiesConfiguration(String fileName)
throws ConfigurationException {
super(fileName); /**** 关注此方法 ****/
}
}
/**
* <p>Partial implementation of the {@code FileConfiguration} interface.
* 此类部分实现了 FileConfiguration 接口
* Developers of file based configuration may want to extend this class,
* the two methods left to implement are {@link FileConfiguration#load(Reader)}
* and {@link FileConfiguration#save(Writer)}.</p>
* 使用基于文件配置的开发者可能会扩展这个类,若是如此,load(Reader)和save(Writer)方法需要去实现
* <p>This base class already implements a couple of ways to specify the location
* of the file this configuration is based on.
* 这个基类已经实现几种方式用于指定配置文件的路径
* The following possibilities
* exist:
* 这些可行的方式有:
* <ul><li>URLs: With the method {@code setURL()} a full URL to the
* configuration source can be specified. This is the most flexible way. Note
* that the {@code save()} methods support only <em>file:</em> URLs.</li>
* 指定URL的方式,通过setURL()来指定配置文件全路径。这是一个可最大扩展的方式。
* 需要注意的是,save()方法保存配置文件时,只支持file:开头的URL。
* <li>Files: The {@code setFile()} method allows to specify the
* configuration source as a file. This can be either a relative or an
* absolute file. In the former case the file is resolved based on the current
* directory.</li>
* 指定File对象的方式,通过setFile()来指向配置文件。可以传一个相对路径或者绝对路径。
* 如果是前一种的相对路径,则是基于当前路径来解析。
* <li>As file paths in string form: With the {@code setPath()} method a
* full path to a configuration file can be provided as a string.</li>
* 还有一种是文件路径字符串,通过setPath()设置一个文件路径字符串。
* <li>Separated as base path and file name: This is the native form in which
* the location is stored. The base path is a string defining either a local
* directory or a URL. It can be set using the {@code setBasePath()}
* method. The file name, non surprisingly, defines the name of the configuration
* file.</li>
* 设置字符串路径,这个路径由基础路径和文件名组成,这也是本地文件存储的方式。
* 基础路径是一个文件夹或者是URL,可以通过setBasePath()来设置。
* 文件名的话,一点也不出乎意料,就是一个配置文件的名字。
* </ul></p>
* <p>The configuration source to be loaded can be specified using one of the
* methods described above. Then the parameterless {@code load()} method can be
* called.
* 被加载的配置源可以通过以上几种方式指定。之后load()方法就能被调用了。
* Alternatively, one of the {@code load()} methods can be used which is
* passed the source directly.
* 或者,可以直接使用load()的重载方法在调用时直接指定配置源。
* These methods typically do not change the
* internally stored file; however, if the configuration is not yet associated
* with a configuration source, the first call to one of the {@code load()}
* methods sets the base path and the source URL.
* 调用load()的重载方法不会改变文件内容,不过,
* 如果调用load()时没有指定配置源,则第一次load()调用时,设置基础路径和源URL。
* This fact has to be taken
* into account when calling {@code load()} multiple times with different file
* paths.</p>
* 这是要考虑到使用不过文件路径调用load()的情形。
* <p>Note that the {@code load()} methods do not wipe out the configuration's
* content before the new configuration file is loaded. Thus it is very easy to
* construct a union configuration by simply loading multiple configuration
* files, e.g.</p>
* 注意,在新的配置文件被装载之前,load()方法不会清除配置文件的内容(需要调用clear方法,正面有提)
* 所以,可以通过简单的调用几次load()方法来加载多个配置文件,来组成一个配置文件联合体。
* <p><pre>
* config.load(configFile1);
* config.load(configFile2);
* </pre></p>
* <p>After executing this code fragment, the resulting configuration will
* contain both the properties of configFile1 and configFile2.
* 执行完上面两句代码,则最终的配置对象会包含configFile1和configFile2中的配置项。
* On the other
* hand, if the current configuration file is to be reloaded, {@code clear()}
* should be called first. Otherwise the properties are doubled. This behavior
* is analogous to the behavior of the {@code load(InputStream)} method
* in {@code java.util.Properties}.</p>
* 另一方面呢,如果要重新加载配置文件,需要先调用clear()方法,否则属性会有重复的,因为
* 之前读取到的不会被删除(上面有提到)。这和Properties类的load(InputStream)方法类似。
*/
public abstract class AbstractFileConfiguration
extends BaseConfiguration
implements FileConfiguration, FileSystemBased
{
/** Holds a reference to the reloading strategy.*/
/** 配置文件重载策略 */
protected ReloadingStrategy strategy;
/** A lock object for protecting reload operations.*/
/** 重载锁定 */
protected Object reloadLock = new Lock("AbstractFileConfiguration");
public AbstractFileConfiguration()
{
initReloadingStrategy(); /** 设置重载策略 */
......
}
private void initReloadingStrategy()
{
// InvariantReloadingStrategy: A strategy that
// never triggers a reloading.
// 一个永远不会触发重新加载配置的策略 ,这......
setReloadingStrategy(new InvariantReloadingStrategy());
}
public AbstractFileConfiguration(String fileName)
throws ConfigurationException
{
this();
// store the file name
setFileName(fileName);
// load the file
load(); /**** 这个是重点
兜兜转转兜兜转转,最终调用的是 PropertiesConfiguration 里的
PropertiesConfigurationLayout对象 的 load(Reader) 方法
****/
}
}
com.alipay.demo.trade.service.AlipayTradeService支付宝调用接口类,主要实现子类为com.alipay.demo.trade.service.impl.AlipayTradeServiceImpl,接口定义如下
public interface AlipayTradeService {
// 当面付2.0流程支付
public AlipayF2FPayResult tradePay(
AlipayTradePayRequestBuilder builder);
// 当面付2.0消费查询
public AlipayF2FQueryResult queryTradeResult(
AlipayTradeQueryRequestBuilder builder);
// 当面付2.0消费退款
public AlipayF2FRefundResult tradeRefund(
AlipayTradeRefundRequestBuilder builder);
// 当面付2.0预下单(生成二维码)
public AlipayF2FPrecreateResult tradePrecreate(
AlipayTradePrecreateRequestBuilder builder);
}
接口提供了四个方法,方便使用。观察参数 *Builder,典型的 建造者(Builder) 模式,
并运用流式编程中的 setXxx() 方法返回调用对象this 而不是常见的void。
AlipayTradePayRequestBuilder
AlipayTradeQueryRequestBuilder
AlipayTradeRefundRequestBuilder
AlipayTradePrecreateRequestBuilder
《未完待续》
以上是工作过程中应用过的,做了些许修改,如果书写错误或者其它想让本人帮忙调用的接口,可以留言私信。总之,大家一起交流提高吧