我们在开发过程中经常使用到自定义注解来实现在一些类或者方法执行过程中切面,统一实现某些业务操作。例如自定义注解
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.Map;
@Documented
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface CtgEncrypt {
/** 参数类型 */
Class<?> paramType() default Map.class;
/** 加密类型 (AES/RSA) */
String securityType() default "AES";
/**返回参数Request /响应参数Response */
String reType() default "";
}
注解一般开门见山,说我要干一个什么事情。使用@interface来修饰。例如上面这个注解就是用来对方法上的参数进行加密的。
@Target({ElementType.METHOD,ElementType.TYPE})
这个代码作用的METHOD(方法)上
@Retention(RetentionPolicy.RUNTIME)
这个代码代码运行时执行操作。
自定义的注解需要实现它功能才能用,不是注解了注解本身就有这个功能,没那么强大。
第二步实现注解功能。
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import com.alibaba.fastjson.JSONObject;
import cn.ctg.common.response.ResponseCode;
import cn.ctg.common.response.ResponseData;
import cn.ctg.common.util.UserTokenUtils;
import cn.ctg.common.util.XssUtils;
import cn.hutool.json.JSONUtil;
/**
* 切面,实现注解
*/
@Aspect
@Component
public class SecurityAspect {
@Value("${keys.aeskey:-1}")
private String AES_KEY;
@Value("${keys.jwtkey:-1}")
private String JWT_KEY;
@Value("${xss.url:-1}")
private String xxsUrl;
private AntPathMatcher antPathMatcher = new AntPathMatcher();
/**切面*/
@Pointcut("@annotation(cn.ctg.common.util.security.CtgDecrypt) || @annotation(cn.ctg.common.util.security.CtgEncrypt)")
public void pointCut(){ }
/**
*
* @param joinPoint
* @return
* @throws Throwable
*/
@Around("execution(* cn.ctg.*.controller.*.*(..))")
public Object doAroundHtml(ProceedingJoinPoint joinPoint) throws Throwable {
Object[] args = joinPoint.getArgs();
HttpServletRequest httpServletRequest = UserTokenUtils.getHttpServletRequest();
String requestURI = httpServletRequest.getRequestURI();
String[] split = xxsUrl.split("\\|");
if(split==null){
return joinPoint.proceed(args);
}
if(pathMatcher(Arrays.asList(split),requestURI)) {
for (int i = 0; i < args.length; i++) {
Object arg = args[i];
Map<String, Object> map = JSONUtil.parseObj(JSONObject.toJSONString(arg));
for (Map.Entry<String, Object> entry : map.entrySet()) {
if (XssUtils.isStripXSS(entry.getValue().toString())) {
ResponseData<Object> responseData = ResponseData.error(ResponseCode.XSS_CODE_ERROR);
return responseData;
}
}
}
}
return joinPoint.proceed(args);
}
/** 返回参数加密*/
@Around("pointCut()")
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
//执行方法,获取返回值
Object result = joinPoint.proceed();
String data = JSONUtil.toJsonStr(((ResponseData<?>) result).getData());
if(data.equals("{}")){
data = String.valueOf(((ResponseData<?>) result).getData());
}
/** 可以根据注解选择 加密方法 防止统一*/
((ResponseData<?>) result).setEncrypt(true);
return result;
}
// 白名单查询
private boolean pathMatcher(List<String> urlList, String requestUri) {
for (String url : urlList) {
if (antPathMatcher.match(url, requestUri)) {
return true;
}
}
return false;
}
}
代码中的这一句 @Pointcut("@annotation(cn.ctg.common.util.security.CtgDecrypt) || @annotation(cn.ctg.common.util.security.CtgEncrypt)")
就是用来实现CtgEncrypt这个注解的
再使用@Around("pointCut()")进行方法环绕。实现注解的动作。
/** 返回参数加密*/
@Around("pointCut()")
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
//执行方法,获取返回值
Object result = joinPoint.proceed();
String data = JSONUtil.toJsonStr(((ResponseData<?>) result).getData());
if(data.equals("{}")){
data = String.valueOf(((ResponseData<?>) result).getData());
}
/** 可以根据注解选择 加密方法 防止统一*/
((ResponseData<?>) result).setEncrypt(true);
return result;
}
这是我们看到一个重要类型ProceedingJoinPoint。执行当前切点的意思
这里就通过源码看看ProceedingJoinPoint能做什么东西。
/*******************************************************************************
* Copyright (c) 2005 Contributors.
* All rights reserved.
* This program and the accompanying materials are made available
* under the terms of the Eclipse Public License v1.0
* which accompanies this distribution and is available at
* http://eclipse.org/legal/epl-v10.html
*
* Contributors:
* initial implementation Alexandre Vasseur
*******************************************************************************/
package org.aspectj.lang;
import org.aspectj.runtime.internal.AroundClosure;
/**
* ProceedingJoinPoint exposes the proceed(..) method in order to support around advice in @AJ aspects
*
* @author Alexandre Vasseur
*/
public interface ProceedingJoinPoint extends JoinPoint {
/**
* The joinpoint needs to know about its closure so that proceed can delegate to closure.run().
* This internal method should not be called directly, and won't be visible to the end-user when
* packed in a jar (synthetic method).
*
* @param arc the around closure to associate with this joinpoint
*/
void set$AroundClosure(AroundClosure arc);
/**
* The joinpoint needs to know about its closure so that proceed can delegate to closure.run().
* This internal method should not be called directly, and won't be visible to the end-user when
* packed in a jar (synthetic method). This should maintain a stack of closures as multiple around
* advice with proceed are targeting a joinpoint and the stack will need to be unwound when
* exiting nested advice. Passing a non null arc indicates a push, passing null indicates a pop.
*
* @param arc the around closure to associate with this joinpoint
*/
default void stack$AroundClosure(AroundClosure arc) {
throw new UnsupportedOperationException();
}
/**
* Proceed with the next advice or target method invocation
*
* @return the result of proceeding
* @throws Throwable if the invoked proceed throws anything
*/
public Object proceed() throws Throwable;
/**
* Proceed with the next advice or target method invocation.
*
* Unlike code style, proceed(..) in annotation style places different requirements on the
* parameters passed to it. The proceed(..) call takes, in this order:
* <ul>
* <li> If 'this()' was used in the pointcut for binding, it must be passed first in proceed(..).
* <li> If 'target()' was used in the pointcut for binding, it must be passed next in proceed(..) -
* it will be the first argument to proceed(..) if this() was not used for binding.
* <li> Finally come all the arguments expected at the join point, in the order they are supplied
* at the join point. Effectively the advice signature is ignored - it doesn't matter
* if a subset of arguments were bound or the ordering was changed in the advice signature,
* the proceed(..) calls takes all of them in the right order for the join point.
* </ul>
* Since proceed(..) in this case takes an Object array, AspectJ cannot do as much
* compile time checking as it can for code style. If the rules above aren't obeyed
* then it will unfortunately manifest as a runtime error.
*
* @param args the arguments to proceed with
* @return the result of proceeding
* @throws Throwable if the invoked proceed throws anything
*/
public Object proceed(Object[] args) throws Throwable;
}
从源码上继承了JoinPoint
public Object proceed() throws Throwable;
Proceed with the next advice or target method invocation
继续执行下一个通知或目标方法调用
获取当前正在执行的对象(如果不可用则为空——例如静态上下文)
Object getThis();
获取目标对象(如果没有,则为空)
Object getTarget();
获取这个连接点上的参数
Object[] getArgs();
获取连接点上的签名。
Signature getSignature();