項目上線少量spring代碼熱更新解決方案

場景:

公司上線的權限系統涉及的有一個功能:

  1. 使用其它平臺的認證,如果通過。我們的平臺也算認證通過。
  2. 公司同一個產品可能有多家公司在用,每家的認證平臺也不一樣,認證方式不一樣,數據格式也不一樣。
  3. 在以上兩種情況出現的時候,還要保證公司產品保持標準化,以便客戶後面升級。

這樣就比較麻煩了。按理講,這種情況就屬於客戶定製版本。升級也是按照自制版本升級。標準產品裏的新功能要去每一個定製產品裏做合併。

在這種各個客戶要各種功能的情況越來越多,標準版本要合併到各定製版本的工作量越來越大。所以思考出以下解決方案。

  1. 統一功能的接口;
  2. 爲各現場做不同的實現;
  3. 做一個配置,表明是哪個實現;
  4. 把實現類注入到Spring中,以便使用.

下面給出一個目前做出來效果比較好的具體例子:

  1. 登錄這個功能的通用接口
public interface AdLoginService {

	public boolean adLogin(String userName, String password, String url);
}
  1. 這個接口功能的不同實現類:
    a. 通用實現
@Service
public class AdLoginServiceGeneralImpl implements AdLoginService{
	
	@Autowired
	private MConfig mConfig;
	

	@Override
	public boolean adLogin(String userName, String password, String url) {
		// String adPath = String.format(config.getADPathtmpl(), username);
		String adPath = String.format(mConfig.get("sys.config.ADPathtmpl"), userName);
		Hashtable<String, String> HashEnv = new Hashtable<String, String>();
		HashEnv.put(Context.SECURITY_AUTHENTICATION, "simple");
		HashEnv.put(Context.SECURITY_PRINCIPAL, adPath);
		HashEnv.put(Context.SECURITY_CREDENTIALS, password);
		HashEnv.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
		HashEnv.put("com.sun.jndi.ldap.connect.timeout", "3000");
		// HashEnv.put(Context.PROVIDER_URL, config.getADUrl());
		HashEnv.put(Context.PROVIDER_URL, url);
		try {
			DirContext ctx = new InitialDirContext(HashEnv);
			ctx.close();
			return true;
		} catch (NamingException e) {
			e.printStackTrace();
		}
		return false;
	}
}

b. 某個現場的特殊實現:

public class AdLoginServiceTuyinImpl implements AdLoginService {
	
	private static Logger log = LoggerFactory.getLogger(AdLoginServiceTuyinImpl.class);

	@Override
	public boolean adLogin(String userName, String password, String url) {
		String envelope = createEnvelope(userName, password);
		return idVerify(url, envelope);
	}

	private static String createEnvelope(String id, String password) {
		StringBuffer buffer = new StringBuffer();
		buffer.append("<?xml version=\"1.0\" encoding=\"utf-8\"?>");
		buffer.append("<soap:Envelope xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"");
		buffer.append(
				" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">");
		buffer.append("<soap:Body>");
		buffer.append("<IdVerify xmlns=\"http://microsoft.com/taiwan/mcs\">");
		buffer.append("<id>" + id + "</id>");
		buffer.append("<password>" + password + "</password>");
		buffer.append("</IdVerify>");
		buffer.append("</soap:Body>");
		buffer.append("</soap:Envelope>");
		System.out.println(buffer.toString());
		return buffer.toString();
	}

	private boolean idVerify(String requestUrl, String outputStr) {
		try {
			Map<String, String> headers = new HashMap<>();
			headers.put("Content-Type", "text/xml;charset=UTF-8");
			headers.put("SOAPAction", "http://microsoft.com/taiwan/mcs/IdVerify");
			String rst = NetUtils.httpsPost(requestUrl, headers, outputStr.getBytes("utf-8"), 50000);
			log.debug("adlogin detail:{}", rst);
			return (StringUtils.isNotBlank(rst) && rst.contains("<StatusCode>0</StatusCode>")) ? true : false;
		} catch (Exception e) {
			e.printStackTrace();
		}
		return false;
	}
}

這樣,一個接口兩個實現就都有了。

然後,我們需要一個配置,這個配置就可以各個現場不一樣。

key doc value
sys.config.ADEnable ad login config(true-開啓, false-關閉)(默認值false) true
sys.config.ADLoginClass AD域登錄實現 net.riking.core.OAuth.service.impl.AdLoginServiceTuyinImpl
sys.config.ADUrl ad login config ldap://172.16.32.1:389

這裏只需要配置不同的實現和平臺登錄URL就可以了。

當然這樣還不行。必須在程序裏動態使用。如果實現類裏使用了Spring注入一些資源。我們就必須把這個實現類初始化爲SpringBean。

Spring注入方法:

@Component("OAuthSpringContextBeanUtil")
public class SpringContextBeanUtil implements ApplicationContextAware {

	private static ApplicationContext applicationContext;

	public void setApplicationContext(ApplicationContext applicationContext) {
		SpringContextBeanUtil.applicationContext = applicationContext;
	}

	/**
	 * @return ApplicationContext
	 */
	public static ApplicationContext getApplicationContext() {
		return applicationContext;
	}

	public static Object getBean(String name) throws BeansException {
		return applicationContext.getBean(name);
	}
	
	public static Object getBean(Class<?> clazz) {
		return applicationContext.getBean(clazz);
	}
	
	
	
	public static void addNewBean(Object bean) throws Exception {
		ConfigurableListableBeanFactory beanFactory = ((ConfigurableApplicationContext) applicationContext).getBeanFactory();
		beanFactory.registerSingleton(bean.getClass().getCanonicalName(), bean);
	}
}

根據配置注入bean:

//其它代碼
	if (!adLogin(user.getUserName(), user.getPassword())) {
		return new Resp("#{login.info.userOrPasswordError}").setCode(CodeDef.UNAUTHORIZED);
	}
//其它代碼


private boolean adLogin(String username, String password) throws Exception {
		String className = mConfig.get("sys.config.ADLoginClass");
		AdLoginService adLoginService;
		if(StringUtils.isBlank(className)) {
			adLoginService = (AdLoginService) SpringContextBeanUtil.getBean(AdLoginServiceGeneralImpl.class);
		}else {
		//以下是關鍵代碼
			Class<?> clz = Class.forName(className);
			try {
				adLoginService = (AdLoginService) SpringContextBeanUtil.getBean(clz);
			}catch(Exception e) {
				Object newInstance = clz.newInstance();
				SpringContextBeanUtil.addNewBean(newInstance);
				adLoginService = (AdLoginService) SpringContextBeanUtil.getBean(clz);
			}
			if(null == adLoginService) {
				return false;
			}
		}
		return adLoginService.adLogin(username, password, mConfig.get("sys.config.ADUrl"));
	}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章