1. 程式人生 > >Spring下基於註解的引數校驗

Spring下基於註解的引數校驗

        前段時間,為了校驗介面資料方便,實現了一個簡易的引數校驗功能。通過引數上加註解並且配置spring aop實現的。

大概配置如下:

public Object ArroundParamCheck(ProceedingJoinPoint pj) throws Throwable{
		if(null == pj){
			log.info("傳入的物件是空的!!!!!!!!!!");
		}
		log.info("引數校驗開始================");
		Object[] obs = pj.getArgs();
		Response_CM resp = paramCheckService.paramCheck(obs);
		log.info("引數校驗結束==============" + resp.toString());
		if(!CodeMsgConstant.CODE_000000.equals(resp.getRespCode())){
			MethodSignature methodSg = (MethodSignature) pj.getSignature();
			Class<?> returnType = methodSg.getReturnType();
			return paramCheckService.getCheckResult(returnType, resp);
		}
		Object pjResult = pj.proceed();
		return pjResult;
		
	}
<bean id="paramCheckAop" class="com.huateng.iccs.pre.corp.aop.ParamCheckAop"></bean> 
 <aop:config>
    	<aop:pointcut expression="execution(* com.huateng.iccs.pre.corp.service.impl.ScfContractServiceImpl.*(..))" 
    	id="scfContractPc"/>
    	<aop:pointcut expression="execution(* com.huateng.iccs.pre.corp.service.impl.ScfCustomerServiceImpl.*(..))" 
    	id="scfCustomerPc"/>
    	<aop:pointcut expression="execution(* com.huateng.iccs.pre.corp.service.impl.ClientInformServiceImpl.*(..))" 
    	id="clientInformPc"/>
    	<aop:aspect id = "paramCheck" ref ="paramCheckAop">
    		<aop:around pointcut-ref="scfContractPc" method="ArroundParamCheck"/>
    		<aop:around pointcut-ref="scfCustomerPc" method="ArroundParamCheck"/>
    		<aop:around pointcut-ref="clientInformPc" method="ArroundParamCheck"/>
    	</aop:aspect>
    </aop:config> 

但是引入的系統時候需要顯性的設定一個環繞增強,並且要在xml中配置切面切點。

        使用過@Transactional的註解的應該都知道,只需要在方法或者類上標註@Transactional註解,就可以開啟spring事務。使用起來比較便捷。為了作者練手,並且使用方便,於是便想引數校驗做成類似模式。

1、spring下自定義xml解析的實現

    使用@Transactional需要在spring xml下配置<tx:annotation-driven />來開啟註解事務。同樣這種方式的校驗也是從自定義xml解析開始。  

    這裡的xml解析很簡單

  • 編寫對應的xsd

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<xsd:schema xmlns="http://www.component.org/schema/avd"
		xmlns:xsd="http://www.w3.org/2001/XMLSchema"
		xmlns:beans="http://www.springframework.org/schema/beans"
		xmlns:tool="http://www.springframework.org/schema/tool"
		targetNamespace="http://www.component.org/schema/avd"
		elementFormDefault="qualified"
		attributeFormDefault="unqualified">
	<xsd:import namespace="http://www.springframework.org/schema/beans"></xsd:import>
	<xsd:element name="annotation-driven"></xsd:element>
</xsd:schema>

    這裡的xsd編寫也很簡單,因為只是一個標記,表示開啟自動引數解析,只需要配置一個element,不需要額外的配置。

<xsd:element name="annotation-driven"></xsd:element>
  • 編寫對應的NamespaceHandlerSupport和BeanDefinitionParser。
固定寫法。AvdNamespaceHandler繼承NamespaceHandlerSupport 重寫init方法。AvdAnnotationDrivenBeanDefinitionParse實現BeanDefinitionParse重寫parse方法。
public class AvdNamespaceHandler extends NamespaceHandlerSupport{

	@Override
	public void init() {
		registerBeanDefinitionParser("annotation-driven", new AvdAnnotationDrivenBeanDefinitionParser());
	}

}
public class AvdAnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser{

	public static final String AVD_ADVISOR_BEAN_NAME = "com.fosun.component.validate.interceptor.avdAdvisor";
	@Override
	public BeanDefinition parse(Element element, ParserContext parserContext) {
		configureAutoProxyCreator(element,parserContext);
		return null;
	}
	
	private void configureAutoProxyCreator(Element element, ParserContext parserContext){
		/**
		 * 如果必要開啟自動建立代理物件
		 * 此方法可以將targetClass委託給spring讓spring為符合切點的目標類自動建立代理物件
		 */
		AopNamespaceUtils.registerAutoProxyCreatorIfNecessary(parserContext, element);
		String avdAdvisorName = AVD_ADVISOR_BEAN_NAME;
		if(!parserContext.getRegistry().containsBeanDefinition(avdAdvisorName)){
			Object eleSource = parserContext.extractSource(element);
			RootBeanDefinition sourceDef = new RootBeanDefinition(AnnotationAvdAttributeSource.class);
			sourceDef.setSource(eleSource);
			sourceDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
			String sourceName = parserContext.getReaderContext().registerWithGeneratedName(sourceDef);
			
			RootBeanDefinition interceptorDef = new RootBeanDefinition(AutoAvdInterceptor.class);
			interceptorDef.setSource(eleSource);
			interceptorDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
			String interceptorName = parserContext.getReaderContext().registerWithGeneratedName(interceptorDef);
			
			
			RootBeanDefinition advisorDef = new RootBeanDefinition(BeanFactoryAutoAvdDefineAdvisor.class);
			advisorDef.setSource(eleSource);
			advisorDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
			advisorDef.getPropertyValues().add("avdAttributeSource", new RuntimeBeanReference(sourceName));
			advisorDef.getPropertyValues().add("adviceBeanName", interceptorName);
			parserContext.getRegistry().registerBeanDefinition(avdAdvisorName, advisorDef);
			
			CompositeComponentDefinition compositeDef = new CompositeComponentDefinition(element.getTagName(), eleSource);
			compositeDef.addNestedComponent(new BeanComponentDefinition(sourceDef, sourceName));
			compositeDef.addNestedComponent(new BeanComponentDefinition(interceptorDef, interceptorName));
			compositeDef.addNestedComponent(new BeanComponentDefinition(advisorDef, avdAdvisorName));
			parserContext.registerComponent(compositeDef);
		}
		
	}

}
  • 配置spring.handlers和spring.schmas

兩個檔案的目錄格式如上圖所示(嚴格按照)spring.handlers
http\://www.component.org/schema/avd = com.fosun.component.validate.config.AvdNamespaceHandler
spring.schemas
http\://www.component.org/schema/avd.xsd = com/fosun/component/validate/config/component-avd-1.0.xsd
  • pom檔案配置
        作者使用maven打jar,需要在pom檔案下做如下配置,不然打的jar中不包含spring.handlers、spring.schemas這兩個檔案
	<build><plugins>
		<plugin>
			<artifactId>maven-jar-plugin</artifactId>
			<configuration>					
				<classesDirectory>target/classes/</classesDirectory>
				<archive>
					<addMavenDescriptor>false</addMavenDescriptor>
				</archive>
			</configuration>
		</plugin>
	</plugins></build>
  • spring配置檔案中使用

如上圖在配置檔案中增加
xmlns:avd="http://www.component.org/schema/avd"

http://www.component.org/schema/avd http://www.component.org/schema/avd.xsd

<avd:annotation-driven/>

2、Spring aop實現

    同樣,如同普通的aop實現一樣,這種方式也需要配置增強、切點和切面。同時,需要宣告一個類似@Transactional的註解,用來標記在方法或者類上來表示該方法或者類開啟校驗。作者在這裡定義了@AutoAvd註解
  • 切點類

繼承StaticMethodMatcherPointcut表示這是一個靜態切點。這個方法的主要目的就是匹配目標方法或者類是否標記了@AutoAvd註解。

public abstract class AutoAvdDefinePointcut extends StaticMethodMatcherPointcut{

	@Override
	public boolean matches(Method method, Class<?> targetClass) {
		AvdAttributeSource aad = getAvdAttributeSource();
		return (aad == null || aad.getAutoAvdDefine(method, targetClass) != null);
	}
	
	public abstract AvdAttributeSource getAvdAttributeSource();

}
  • 增強類

實現MethodInterceptor介面,標識這是一個環繞增強。

public class AutoAvdInterceptor implements MethodInterceptor{
	
	private final Logger log = LoggerFactory.getLogger(getClass());
	
	@Autowired
	ParamCheckService paramCheckService;

	@Override
	public Object invoke(MethodInvocation invocation) throws Throwable {
		// TODO Auto-generated method stub
//		Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);
		Object[] obs = invocation.getArguments();
		log.info("引數校驗開始================");
		Response_CM resp = paramCheckService.paramCheck(obs);
		log.info("引數校驗結束==============" + resp.toString());
		if(!CodeMsgConstant.CODE_000000.equals(resp.getRespCode())){
			Method method = invocation.getMethod();
			Class<?> returnType = method.getReturnType();
			return paramCheckService.getCheckResult(returnType, resp);
		}
		return invocation.proceed();
	}

}
  • advisor

繼承AbstractBeanFactoryPointcutAdvisor重寫getPointcut()方法。

BeanFactoryAutoAvdDefineAdvisor中的AvdAttributeSource用來解析@AutoAvd註解的內容,

public class BeanFactoryAutoAvdDefineAdvisor extends AbstractBeanFactoryPointcutAdvisor{
	
	private AvdAttributeSource avdAttributeSource;
	
	private AutoAvdDefinePointcut aadPointcut = new AutoAvdDefinePointcut() {
		@Override
		public AvdAttributeSource getAvdAttributeSource() {
			return avdAttributeSource;
		}
	};

	public void setAvdAttributeSource(AvdAttributeSource avdAttributeSource) {
		this.avdAttributeSource = avdAttributeSource;
	}

	@Override
	public Pointcut getPointcut() {
		return this.aadPointcut;
	}

}

AnnotationAvdAttributeSource實現AvdAttributeSource來實現由@AutoAvd表示的內容。 

回顧切點的matches()方法,利用了AvdAttributeSource的getAutoAvdDefine()方法來具體判讀是否匹配切點。

public class AnnotationAvdAttributeSource implements AvdAttributeSource{
	private AutoAvdAnnotationParser annotationParser;
	
	public AnnotationAvdAttributeSource(){
		this.annotationParser = new SpringAutoAvdAnnotationParser();
	}

	@Override
	public AutoAvdDefine getAutoAvdDefine(Method method, Class<?> targetClass) {
		/**
		 * 先這麼寫
		 */
		return computerAutoAvdDefine(method,targetClass);
	}
	
	private AutoAvdDefine computerAutoAvdDefine(Method method, Class<?> targetClass){
		/**
		 * 不支援非public方法
		 */
		if ( !Modifier.isPublic(method.getModifiers())) {
			return null;
		}

		Class<?> userClass = ClassUtils.getUserClass(targetClass);
		Method specificMethod = ClassUtils.getMostSpecificMethod(method, userClass);
		specificMethod = BridgeMethodResolver.findBridgedMethod(specificMethod);

		AutoAvdDefine aad = findAutoAvdAnnotationDefine(specificMethod);
		if (aad != null) {
			return aad;
		}

		aad = findAutoAvdAnnotationDefine(specificMethod.getDeclaringClass());
		if (aad != null) {
			return aad;
		}

		if (specificMethod != method) {
			aad = findAutoAvdAnnotationDefine(method);
			if (aad != null) {
				return aad;
			}
			return findAutoAvdAnnotationDefine(method.getDeclaringClass());
		}
		return null;
	}
	
	private AutoAvdDefine findAutoAvdAnnotationDefine(Method method){
		return determineAutoAvdDefine(method);
	}
	private AutoAvdDefine findAutoAvdAnnotationDefine(Class<?> targetClass){
		return determineAutoAvdDefine(targetClass);
	}
	private AutoAvdDefine determineAutoAvdDefine(AnnotatedElement ae){
		return this.annotationParser.parseAutoAvdAnnotation(ae);
	}
}

關鍵

        在第二步中,我們只是定義了切點,環繞增強,切面等。還未介紹如何將這些交給spring託管。並且為符合切點的物件生成代理物件。
private void configureAutoProxyCreator(Element element, ParserContext parserContext){
		/**
		 * 如果必要開啟自動建立代理物件
		 * 此方法可以將targetClass委託給spring讓spring為符合切點的目標類自動建立代理物件
		 */
		AopNamespaceUtils.registerAutoProxyCreatorIfNecessary(parserContext, element);
		String avdAdvisorName = AVD_ADVISOR_BEAN_NAME;
		if(!parserContext.getRegistry().containsBeanDefinition(avdAdvisorName)){
			Object eleSource = parserContext.extractSource(element);
			RootBeanDefinition sourceDef = new RootBeanDefinition(AnnotationAvdAttributeSource.class);
			sourceDef.setSource(eleSource);
			sourceDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
			String sourceName = parserContext.getReaderContext().registerWithGeneratedName(sourceDef);
			
			RootBeanDefinition interceptorDef = new RootBeanDefinition(AutoAvdInterceptor.class);
			interceptorDef.setSource(eleSource);
			interceptorDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
			String interceptorName = parserContext.getReaderContext().registerWithGeneratedName(interceptorDef);
			
			
			RootBeanDefinition advisorDef = new RootBeanDefinition(BeanFactoryAutoAvdDefineAdvisor.class);
			advisorDef.setSource(eleSource);
			advisorDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
			advisorDef.getPropertyValues().add("avdAttributeSource", new RuntimeBeanReference(sourceName));/**<property name="name" ref=ref/>*/
			advisorDef.getPropertyValues().add("adviceBeanName", interceptorName);/**<property name="name" value="value"/>*/
			parserContext.getRegistry().registerBeanDefinition(avdAdvisorName, advisorDef);
			
			CompositeComponentDefinition compositeDef = new CompositeComponentDefinition(element.getTagName(), eleSource);
			compositeDef.addNestedComponent(new BeanComponentDefinition(sourceDef, sourceName));
			compositeDef.addNestedComponent(new BeanComponentDefinition(interceptorDef, interceptorName));
			compositeDef.addNestedComponent(new BeanComponentDefinition(advisorDef, avdAdvisorName));
			parserContext.registerComponent(compositeDef);
		}
		
	}


        回顧AvdAnnotationDrivenBeanDefinitionParser的configureAutoProxyCreator()方法。在這裡將自動校驗的切點增強等邏輯交給spring託管並生成代理物件。        方法開始呼叫AopNamespaceUtils.registerAutoProxyCreatorIfNecessary(parserContext, element);。熟悉spring aop的都清楚,在使用spring aop時,一般都需要在xml中配置<aop:aspectj-autoproxy proxy-target-class="true" />;。spring這對這個進行解析的時候最終就會呼叫AopNamespaceUtils.registerAutoProxyCreatorIfNecessary(parserContext, element);方法。目的是為符合切點的容器中的bean自動建立代理物件。接下來就是將BeanFactoryAutoAvdDefineAdvisor等元件註冊到工廠中。






相關推薦

Spring基於註解引數

        前段時間,為了校驗介面資料方便,實現了一個簡易的引數校驗功能。通過引數上加註解並且配置spring aop實現的。大概配置如下:public Object ArroundParamCheck(ProceedingJoinPoint pj) throws Thr

更加靈活的引數Spring-boot自定義引數註解

上文[測試開發專題:如何在spring-boot中進行引數校驗](https://www.immortalp.com/articles/2020/05/15/1589509696197.html),我們討論瞭如何使用@Min、@Max等註解進行引數校驗,主要是針對基本資料型別和級聯物件進行引數校驗的演示,但是

關於怎麼解決從ajax傳入的json引數注入到Controller的接收物件 以及如何在Spring Boot專案使用引數

<html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <tit

Spring Boot專案使用引數

開發web專案有時候我們需要對controller層傳過來的引數進行一些基本的校驗,比如非空,非null,整數值的範圍,字串的個數,日期,郵箱等等。最常見的就是我們直接寫程式碼校驗,這樣以後比較繁瑣,而且不夠靈活。 Bean Validation 1.0(JSR-303

如何在 Spring/Spring Boot 中做引數?你需要了解的都在這裡!

本文為作者原創,如需轉載請在文首著名地址,公眾號轉載請申請開白。 springboot-guide : 適合新手入門以及有經驗的開發人員查閱的 Spring Boot 教程(業餘時間維護中,歡迎一起維護)。 資料的校驗的重要性就不用說了,即使在前端對資料進行校驗的情況下,我們還是要對傳入後端的資料再進行一

測試開發專題:如何在spring-boot中進行引數

上文我們討論了spring-boot如何去獲取前端傳遞過來的引數,那傳遞過來總不能直接使用,需要對這些引數進行校驗,符合程式的要求才會進行下一步的處理,所以本篇文章我們主要討論spring-boot中如何進行引數校驗。 ### lombok使用介紹 在介紹引數校驗之前,先來了解一下lombok的使用,因為

手把手寫一個基於Spring Boot框架引數元件(JSR-303)

前言           之前參與的新開放平臺研發的過程中,由於不同的介面需要對不同的入參進行校驗,這就涉及到通用引數的校驗封裝,如果不進行封裝,那麼寫出來的校驗程式碼將會風格不統一、校驗工具類不一致、維護風險高等其它因素,於是我對其公共的校驗做了一個封裝,達到了通過註解的方式即可實現引數統一校驗。 遇到的問

spring註解引數

一般入參我們都會轉為vo物件。那麼直接在物件的屬性上註解即可。 其實spring用的是hibernate的validator. 步驟 1.配置spring.xml <mvc:annotation-driven /> 2.配置

Spring -- 通過攔截器使用註解方式引數

public class OpenHandlerInterceptor extends HandlerInterceptorAdapter { private static final Logger LOGGER = Logger.getLogger(OpenHandlerInterceptor.c

基於註解引數框架

近期由於工作的需要,寫了一個簡易的引數校驗框架,雖然市場上有common-validator 和hibernate-validator兩個開源的,但是有些情景他們是無法滿足的,比如引數欄位之間的依賴關係,這在專案中是極其常見的。他們僅僅提供了對欄位的簡單的格式校驗。另外這兩種

Spring Validation-用註解代替程式碼引數

# Spring Validation ## 概念 在原先的編碼中,我們如果要驗證前端傳遞的引數,一般是在接受到傳遞過來的引數後,手動在程式碼中做 if-else 判斷,這種編碼方式會帶來大量冗餘程式碼,十分的不優雅。 因此,推出了用註解的方式,來代替手動判斷的方式,讓編碼更加的簡潔。 ## 使用方式

Spring基礎系列-引數

原創作品,可以轉載,但是請標註出處地址:https://www.cnblogs.com/V1haoge/p/9953744.html Spring中使用引數校驗 概述 ​ JSR 303中提出了Bean Validation,表示JavaBean的校驗,Hibernate Validation是其具體實

SpringMVC自定義註解進行引數

在我的另一篇部落格中(SpringMVC),學習瞭如何使用Spring MVC結合Hibernate的校驗框架validation(它和hibernate沒有任何關係)對引數進行校驗。在實際專案中,引數的校驗邏輯可能比較複雜,這時我們可以自定義註解來實現引數校驗,下面是一個簡單的例子。 po

SpringMVC自定義註解進行引數(以列舉值是否合法為例)

pom引入springMVC依賴,以springboot專案為例 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-

Spring Boot MVC 引數

文章目錄 Spring Boot Validate 常用註解 使用說明 基本使用 實現分組校驗 處理校驗結果 自定義校驗註解 配置校驗提示資訊 手動進行校驗

Spring Boot 使用hibernate validator進行引數

demo 實體類 public class UserBean { @NotBlank(message="使用者名稱不能為空") private String userName; @NotBlank(message="年齡不能為空") @P

自定義註解完成引數

在現在的專案開發中,經常會用到註解,比如Spring的@Autowired,SpringMVC的@Controller,@RequestMapping等等,大部分人只知道用,不知道這些註解的怎麼發揮作用。有沒有想過手動寫一個註解,完成引數校驗呢? 簡介 註解(

Spring基於註解Spring MVC(

上一篇文章《Spring:基於註解的Spring MVC(上)》,講了Spring MVC環境搭建、@RequestMapping以及引數繫結,這是Spring MVC中最基礎也是最重要的內容,本篇文章繼續講講Spring MVC中其餘的知識點,先從Model開始。 Mod

Spring請求引數

SpringMVC支援的資料校驗是JSR303的標準,通過在bean的屬性上打上@NotNull、@Max等進行驗證。JSR303提供有很多annotation介面,而SpringMVC對於這些驗證是使用hibernate的實現,所以我們需要新增hibernate的一個validator包: 依賴引用 com

java註解方式引數

1、註解類NotEmpty.java空值校驗 package com.cmbc.umm.core.common.annotation; import java.lang.annotation.Documented; import java.lang.an