Spring下基于注解的参数校验

本文介绍了一种基于Spring AOP实现的参数校验方法,通过自定义XML解析及注解,实现对业务方法参数的有效性和正确性校验。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

        前段时间,为了校验接口数据方便,实现了一个简易的参数校验功能。通过参数上加注解并且配置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等组件注册到工厂中。







评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值