zoukankan      html  css  js  c++  java
  • SpringBoot 系列

    原文地址: https://www.xncoding.com/2017/07/22/spring/sb-starter.html

    前言:

    Spring Boot由众多Starter组成,随着版本的推移Starter家族成员也与日俱增。在传统Maven项目中通常将一些层、组件拆分为模块来管理, 以便相互依赖复用,在Spring Boot项目中我们则可以创建自定义Spring Boot Starter来达成该目的。

    可以认为starter是一种服务——使得使用某个功能的开发者不需要关注各种依赖库的处理,不需要具体的配置信息, 由Spring Boot自动通过classpath路径下的类发现需要的Bean,并织入相应的Bean。举个栗子,spring-boot-starter-jdbc这个starter的存在, 使得我们只需要在BookPubApplication下用@Autowired引入DataSource的bean就可以,Spring Boot会自动创建DataSource的实例。

    本篇将通过一个简单的例子来演示如何编写自己的starter。

    这个例子就是自己封转的支付功能的 easy-pay-spring-boot-starter

    当然了官方的名称 spring-boot-stater-{name} ,然后自己定义的stater 名称 {name}-spring-boot-stater

    一、添加Maven 依赖

    第一步当然是创建一个 maven 工程,添加SpringBoot 的自动依赖:

    <?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>
    

    上面的servlet-api 是由于项目中有需要用到的HttpRequest 所以需要使用这个依赖,剩下的就是自动配置需要的依赖

    注意其中 spring-boot-configuration-processor 的作用是编译时生成spring-configuration-metadata.json, 此文件主要给IDE使用,用于提示使用。如在intellij idea中,当配置此jar相关配置属性在application.yml, 你可以用ctlr+鼠标左键,IDE会跳转到你配置此属性的类中。

    这里说下artifactId的命名问题,Spring 官方 Starter通常命名为spring-boot-starter-{name}spring-boot-starter-web

    Spring官方建议非官方Starter命名应遵循{name}-spring-boot-starter的格式。

    二、编写属性配置类

    ​ 也就是通常我们在 SpringBoot 中配置的application.properties 或者是 application.yml 文件中配置的属性,然后通过注入到项目中给我们使用

    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;
    	}
    
    }
    
    

    大家就不要吐槽我的命名了,主要是当初写这个功能类的时候 ,是根据PHP改的,还有就是当时刚入门Java 还没有命名规范的良好习惯,然后有太多引用了,也懒得替换了,就这样用吧。 这个里面主要配置的就是一些支付需要用到的参数 比如: 商户的key 、商户的security key 、 支付成功跳转的url 、支付成功通知的url

    编写业务类(个人理解通俗来说,就是哪里需要这个属性配置类的地方)

    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;
    	}
    	
    }
    
    

    支付成功通知返回类:

    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;
    	}
    
    }
    
    

    还有上面的 System.out.println() ,也希望大家轻点吐槽,这个也是当时不习惯使用log ,现在也是不太习惯,但是还好,当时感觉把值打印出来 调试多方便,后来就没改

    三、重中之重来了: 自动配置类

    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;
        }
    
    }
    
    

    这个就是将application.properties 配置文件中配置的属性 获取出来,然后配置另外两个配置类 EpaySubmit、 AlipayNotify

    解释下用到的几个和Starter相关的注解:

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

    四、添加spring.factories

    最后一步,在resources/META-INF/下创建spring.factories文件,内容供参考下面:

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

    如果有多个自动配置类,用逗号分隔换行即可。

    OK,完事,运行 mvn:install 打包安装,一个Spring Boot Starter便开发完成了。

    五、总结

    总结下Starter的工作原理:

    1. Spring Boot在启动时扫描项目所依赖的JAR包,寻找包含spring.factories文件的JAR包
    2. 根据spring.factories配置加载AutoConfigure类
    3. 根据 @Conditional 注解的条件,进行自动配置并将Bean注入Spring Context
  • 相关阅读:
    并发编程学习笔记之Java存储模型(十三)
    并发编程学习笔记之原子变量与非阻塞同步机制(十二)
    并发编程学习笔记之构建自定义的同步工具(十一)
    并发编程学习笔记之显示锁(十)
    并发编程学习笔记之可伸缩性(九)
    并发编程学习笔记之死锁(八)
    并发编程学习笔记之自定义配置线程池(七)
    并发编程学习笔记之取消和关闭(六)
    并发编程学习笔记之线程池(五)
    并发编程学习笔记之并发工具类(四)
  • 原文地址:https://www.cnblogs.com/kangxinxin/p/11881191.html
Copyright © 2011-2022 走看看