SpringBoot series - write their own starter

Original Address: https://www.xncoding.com/2017/07/22/spring/sb-starter.html

Preface:

Spring Boot composed of numerous Starter, Starter family members over version is also increasing. In traditional Maven project usually some layer components split into modules to manage, so interdependent multiplexing, in Spring Boot project we can create a custom Spring Boot Starter to achieve this purpose.

Starter can be considered a service - allows developers to use a feature does not need to deal with various dependencies of attention, it does not require specific configuration information from Spring Boot automatically by Bean class found in the classpath needs, and weaving corresponding Bean. For chestnuts, there is a spring-boot-starter-jdbc the starter, so we only need to introduce the DataSource bean with @Autowired in BookPubApplication can, Spring Boot automatically creates an instance of DataSource.

This article deals with a simple example to demonstrate how to prepare their starter.

This example is the self-proclaimed revolution payment features easy-pay-spring-boot-starter

Of course, the official name of the spring-boot-stater- {name}, then their names stater defined {name} -spring-boot-stater

First, add the Maven Dependencies

The first step of course is to create a maven project, add automatic dependent SpringBoot of:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

    <groupId>com.zuoyan.spring.boot</groupId>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>easy-pay-spring-boot-starter</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.9.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
    </properties>

    

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-autoconfigure</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>

        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>servlet-api</artifactId>
            <version>2.5</version>
            <scope>provided</scope>
        </dependency>

    </dependencies>

    

</project>

The above servlet-api project is due to the need to use the HttpRequest need to use this dependence, it is dependent on the rest of the auto-configuration needed

Note where spring-boot-configuration-processorthe action is generated at compile time spring-configuration-metadata.json, this document primarily for use IDE for prompt use. As intellij idea, when this configuration attribute jar configuration application.yml, you can ctlr + left mouse button, you will jump to the IDE configure this attribute class.

Here that naming the next artifactId, Spring Official Starter usually named spring-boot-starter-{name}as spring-boot-starter-web.

Spring Starter official unofficial name should follow the recommended {name}-spring-boot-starterformat.

Second, write attribute configuration class

That is application.properties we usually configured in SpringBoot or is application.yml configuration file attributes, and then use to us by injecting into the project

package com.zuoyan.springboot.easypay.bean;

import org.springframework.boot.context.properties.ConfigurationProperties;

/**
 * 
 * 易支付的配置文件
 *  通过application.properties  配置文件配置
 * 1.partner:商户ID
 * 2.key :分配给商户的密钥
 * 
 * @author 左岩
 *
 */

@ConfigurationProperties(value = "spring.easy.pay")
public class Alipay_config {

    //商户ID
    private String partner = "your ID";
    //商户Key
    private String key = "your key"; 
    //签名方式不用更改
    private String sign_type = "MD5";
    //字符编码格式,目前支持GBK或 utf-8
    private String input_charset = "utf-8";
    //访问模式,根据自己的服务器是否支持ssl访问,若支持请选择https;若不支持请选择http
    private String transport = "http";
    //支付API地址
    private String apiurl = "http://pay.hackwl.cn/";
    //支付成功返回通知的url
    private String notify_url = "http://www.xxxxx.com/notifyurl";
    //支付成功后需要跳转的页面地址
    private String return_url = "http://www.xxxxxx.com/returnurl";


    public String getPartner() {
        return partner;
    }

    public void setPartner(String partner) {
        this.partner = partner;
    }

    public String getKey() {
        return key;
    }

    public void setKey(String key) {
        this.key = key;
    }

    public String getSign_type() {
        return sign_type;
    }

    public void setSign_type(String sign_type) {
        this.sign_type = sign_type;
    }

    public String getInput_charset() {
        return input_charset;
    }

    public void setInput_charset(String input_charset) {
        this.input_charset = input_charset;
    }

    public String getTransport() {
        return transport;
    }

    public void setTransport(String transport) {
        this.transport = transport;
    }

    public String getApiurl() {
        return apiurl;
    }

    public void setApiurl(String apiurl) {
        this.apiurl = apiurl;
    }

    public String getNotify_url() {
        return notify_url;
    }

    public void setNotify_url(String notify_url) {
        this.notify_url = notify_url;
    }

    public String getReturn_url() {
        return return_url;
    }

    public void setReturn_url(String return_url) {
        this.return_url = return_url;
    }

}

We do not Tucao my name, mainly when he started writing this feature class, the change is based on PHP, there is no Java was just getting started naming convention of good habits, then there are too many references, do not bother to replace up, so use it. That some payments need to use this parameter inside the main configuration example: merchant key, merchant security key, paid a successful jump url, url successful payment notification

Preparation of business class (personal understanding Popular, is the need for this kind of place where property configuration)

package com.zuoyan.springboot.easypay.bean;

import com.zuoyan.springboot.easypay.function.EpayCoreFunction;
import com.zuoyan.springboot.easypay.function.EpayMD5Function;

import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.TreeMap;


/**
 * 类名:EpaySubmit 
 * 功能:易支付请求接口提交类
 * 详细:构造易支付接口表单HTML文本,获取远程HTTP数据
 * 
 * @author 左岩
 *
 */
public class EpaySubmit {

    private Alipay_config alipay_config;
    private String alipay_gateway_new;

    //设置EpaySubmit 配置
    public void setAlipay_config(Alipay_config alipay_config) {
        this.alipay_config = alipay_config;
    }

    public EpaySubmit(Alipay_config alipay_config) {
        this.alipay_config = alipay_config;
        this.alipay_gateway_new = alipay_config.getApiurl()+"submit.php?";
    }
    
    public TreeMap<String,String> buildRequestPara(HashMap<String, String> parameter) throws Exception
    {
        //除去待签名参数数组中的空值和签名参数
        HashMap<String, String> para_filter = EpayCoreFunction.paraFilter(parameter);
        //对签名参数数组排序
        TreeMap<String, String> para_sort = EpayCoreFunction.argSort(para_filter);
        //生成签名结果
        String mysign = this.buildRequestMysign(para_sort);
        //签名结果与签名方式加入请求提交参数数组中
        para_sort.put("sign", mysign);
        para_sort.put("sign_type",alipay_config.getSign_type().toUpperCase());
        return para_sort;
        
    }
    
    public String buildRequestMysign(TreeMap<String, String> para_sort) throws Exception
    {
        //把数组所有元素,按照“参数=参数值”的模式用“&”字符拼接成字符串
        String prestr = EpayCoreFunction.createLinkstring(para_sort);
        String mysign = EpayMD5Function.md5Sign(prestr.trim(),alipay_config.getKey());
        return mysign;
        
    }
    
    /**
     * 建立请求,以表单Html的形式构造
     * @param parameter  请求参数数组
     * @return
     * @throws NoSuchAlgorithmException 
     */
    public String buildRequestForm(HashMap<String, String> parameter) throws Exception
    {
        TreeMap<String,String> para = this.buildRequestPara(parameter);
        String method = "";
        String sHtml = "<form id='alipaysubmit' name='alipaysubmit' action='"+this.alipay_gateway_new+"_input_charset="+this.alipay_config.getInput_charset().toLowerCase().trim()+"' method='"+method+"'>";
        //遍历处理过后的 Parameter,拼接字符串
        for(String key : para.keySet())
        {
             sHtml+= "<input type='hidden' name='"+key+"' value='"+para.get(key)+"'/>";
        }
        String button_name="页面正在跳转,请稍后!";
        //submit按钮请不要含有name属性
        sHtml+="<input type='submit' value='"+button_name+"'></form>";
        //设置js事件然后自动提交
        sHtml+="<script>document.forms['alipaysubmit'].submit();</script>";
        //将拼装好的js返回
        return sHtml;
    }
    
}

Payment success notification is returned categories:

package com.zuoyan.springboot.easypay.function;

import com.zuoyan.springboot.easypay.bean.Alipay_config;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;
import java.util.regex.Pattern;

/**
 *
 * 类名:EpayNotify 功能:彩虹易支付通知处理类 详细:处理易支付接口通知返回
 * 
 * @author 左岩
 *
 */
public class AlipayNotify {

    private Alipay_config alipay_config;
    private String http_verify_url;

    public void setAlipay_config(Alipay_config alipay_config) {
        this.alipay_config = alipay_config;
    }


    public AlipayNotify(Alipay_config alipay_config) {
        this.alipay_config = alipay_config;
        this.http_verify_url = alipay_config.getApiurl() + "api.php?";
    }


    public AlipayNotify(){}

    /**
     * 针对notify_url验证消息是否是支付宝发出的合法消息
     *
     * @param request
     * @param response
     * @return 验证结果
     * @throws Exception
     */
    public boolean verifyNotify(HttpServletRequest request, HttpServletResponse response) throws Exception {
        return veryfy(request, response);

    }


    /**
     * 针对return_url 验证消息是否是支付宝发出的和合法消息
     *
     * @param request
     * @param response
     * @return 验证结果
     * @throws Exception
     */
    public boolean verifyReturn(HttpServletRequest request, HttpServletResponse response) throws Exception {

        return veryfy(request, response);
    }

    /**
     * 从Request请求参数中 获取请求参数的 map
     *
     * @param request
     * @return
     * @throws Exception
     */
    public static HashMap<String, String> getGetMap(HttpServletRequest request) throws Exception {

        Map<String, String[]> requestMap = request.getParameterMap();
        HashMap<String, String> returnMap = new HashMap<String, String>();

        for (String key : requestMap.keySet()) {
            returnMap.put(key, new String(request.getParameter(key).getBytes("ISO8859-1"), "UTF-8"));
            //测试获取的字符
        }

        return returnMap;

    }

    /**
     * 功能:获取返回时的签名验证结果
     *
     * @param para_temp 通知返回来的参数数组
     * @param sign      返回的签名结果
     * @return 签名验证结果
     * @throws Exception
     */
    public boolean getSignVeryfy(HashMap<String, String> para_temp, String sign) throws Exception {
        // 出去签名数组中参数数组中的空值和签名参数
        HashMap<String, String> paraFilter = EpayCoreFunction.paraFilter(para_temp);
        // 对待签名参数数组排序
        TreeMap<String, String> para_sort = EpayCoreFunction.argSort(paraFilter);
        // 把数组所有元素,按照 “参数=参数值”的模式用"&"字符拼接成字符串
        String prestr = EpayCoreFunction.createLinkstring(para_sort);

        System.out.println("测试创建的字符串为:" + prestr);

        boolean isSgin = false;

        isSgin = EpayMD5Function.md5Verify(prestr, sign, this.alipay_config.getKey());

        return isSgin;

    }


    /**
     * 功能描述: <br>
     * 〈抽取公用方法〉
     * @Param: No such property: code for class: Script1
     * @Return: boolean
     * @Author: Administrator
     * @Date: 2019/10/24 15:34
     *
     */
    public boolean veryfy(HttpServletRequest request, HttpServletResponse response) {

        try {
            // 判断GET来的数组是否为空
            if (request.getParameterMap().isEmpty()) {
                return false;
            } else {
                // 获取Request请求中所带的参数,并将这些个参数封装成一个map
                HashMap<String, String> para_temp = getGetMap(request);
                // 这个获取Map,也就是Request返回的签名参数
                String sign = para_temp.get("sign");
                // 生成签名结果
                boolean isSign = getSignVeryfy(para_temp, sign);
                // 获取支付宝远程服务器ATN结果 (验证是否是支付宝发来的消息)
                String responseTxt = "true";
                // 验证
                // responseTxt的结果不是true,与服务器的设置问题、合作身份者ID、notify_id 一分钟失效有关
                // isSign 的结果不是true,与安全校验码、请求时的参数格式(如:带自定义参数等)、编码格式有关

                // Java中的正则匹配
                String regex = ".*(?i)true$";
                if (Pattern.matches(regex, responseTxt) && isSign) {
                    return true;
                } else {
                    return false;
                }

            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        //后来添加的 可能会成为问题代码
        return  false;
    }

}

There are above System.out.println (), I hope you tap Tucao, this also was not used to log, now is not in the habit, but fortunately, was feeling more than the value of the print out debugging convenient, then no change

Third, the top priority to: automatic configuration class

package com.zuoyan.springboot.easypay;

import com.zuoyan.springboot.easypay.bean.Alipay_config;
import com.zuoyan.springboot.easypay.bean.EpaySubmit;
import com.zuoyan.springboot.easypay.function.AlipayNotify;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @ProjectName: EasyPayBootStarter
 * @Package: PACKAGE_NAME
 * @ClassName: EasyPayAutoConfiguration
 * @Author: ZuoYanCoder
 * @Description: 声明Starter自动配置类
 * @Date: 2019/10/24 15:00
 * @Version: 1.0
 */

@Configuration
@ConditionalOnClass({AlipayNotify.class,EpaySubmit.class})
@EnableConfigurationProperties(Alipay_config.class)
public class EasyPayAutoConfiguration {

    private final Alipay_config alipay_config;

    @Autowired
    public EasyPayAutoConfiguration(Alipay_config alipay_config)
    {
        this.alipay_config = alipay_config;
    }

    @Bean
    //当容器中没有这个Bean的时候才创建这个Bean
    @ConditionalOnMissingBean(AlipayNotify.class)
    public AlipayNotify alipayNotify(){
        AlipayNotify alipayNotify = new AlipayNotify(alipay_config);
        return alipayNotify;
    }

    @Bean
    @ConditionalOnMissingBean(EpaySubmit.class)
    public EpaySubmit epaySubmit(){
        EpaySubmit epaySubmit = new EpaySubmit(alipay_config);
        return epaySubmit;
    }

}

This is to application.properties configuration file is the property gets out, and then configure the other two class configuration EpaySubmit, AlipayNotify

Under explain a few relevant comments and Starter used:

1. @ConditionalOnClass,当classpath下发现该类的情况下进行自动配置。
2. @ConditionalOnMissingBean,当Spring Context中不存在该Bean时。
3. @ConditionalOnProperty(prefix = "example.service",value = "enabled",havingValue = "true"),当配置文件中example.service.enabled=true时。

Fourth, add spring.factories

The final step, in resources/META-INF/the next to create spring.factoriesa file, the contents of reference for the following:

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.zuoyan.springboot.easypay.EasyPayAutoConfiguration

If there are multiple classes autoconfiguration, can be separated by commas newline.

OK, get away, run mvn:installpackage installation, a Spring Boot Starter will develop complete.

V. Summary

Starter summarized under the principle of:

  1. Spring Boot when you start scanning project depends JAR package, look for include spring.factoriesJAR package file
  2. The spring.factoriesload class configuration AutoConfigure
  3. The @Conditionalconditions of the annotation, and the automatic configuration Bean injection Spring Context

Guess you like

Origin www.cnblogs.com/kangxinxin/p/11881191.html