美文网首页
Java注解

Java注解

作者: 请叫我平爷 | 来源:发表于2022-02-23 14:43 被阅读0次

    作用在代码

    • @Verride:检查该方法是否是重写父类方法,如果发现其父类,或者是引用的接口中并没有该方法时,会报错
    • @Deprecated: 标记过时的方法
    • @SuppressWarnings:指示编译器忽略注解中声明的警告

    作用在其他注解的注解---元注解

    • @Retention: 标识这个注解怎么保存,是只在代码中,还是编入class文件中,或者是在运行时可以通过反射访问
    • @Documented: 标记这些注解是否包含在用户文档中
    • @Target:标记这个注解应该是哪种 Java 成员
    • @Inherited:标记这个注解是继承于哪个注解类(默认 注解并没有继承于任何子类)

    自定义注解

    • @interface 声明一个注解 public @interface 注解名{定义内容}
    • 每一个方法实际上是声明一个配置参数
    • 方法的名称就是参数的名称
    • 返回值类型就是参数的类型,只有 基本类型、Class、String、enum
    • default声明参数的默认值,空字符串、0作为默认值
    • 只有一个参数,一般用value,可以省略不写
    • 注解元素必须要有值

    限流

    pom.xml

            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-aop</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-data-redis</artifactId>
            </dependency>
            <dependency>
                <groupId>com.google.guava</groupId>
                <artifactId>guava</artifactId>
                <version>21.0</version>
            </dependency>
            <dependency>
                <groupId>org.apache.commons</groupId>
                <artifactId>commons-lang3</artifactId>
            </dependency>
    

    application.properties

    spring.redis.host=localhost
    spring.redis.port=6379
    server.port=8001
    

    limit

    import java.lang.annotation.Documented;
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Inherited;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    /**
     * 限流
     */
    @Target({ElementType.METHOD, ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Inherited
    @Documented
    public @interface Limit {
        /**
         * 资源的名字
         *
         * @return String
         */
        String name() default "";
    
        /**
         * 资源的key
         *
         * @return String
         */
        String key() default "";
    
        /**
         * Key的prefix
         *
         * @return String
         */
        String prefix() default "";
    
        /**
         * 给定的时间段
         * 单位秒
         *
         * @return int
         */
        int period() default 60;
    
        /**
         * 最多的访问限制次数
         *
         * @return int
         */
        int count() default 5;
    
        /**
         * 类型
         *
         * @return LimitType
         */
        LimitType limitType() default LimitType.CUSTOMER;
    
    
        /**
         * 异常信息内容
         *
         * @return String
         */
        String desc() default "访问失败,访问限流";
    
    
        enum LimitType {
            /**
             * 自定义key
             */
            CUSTOMER,
            /**
             * 根据请求者IP
             */
            IP;
        }
    }
    

    LimitInterceptor

    import com.example.limit02.exception.DefaultException;
    import com.google.common.collect.ImmutableList;
    import org.apache.commons.lang3.StringUtils;
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.Around;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.reflect.MethodSignature;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.data.redis.core.script.DefaultRedisScript;
    import org.springframework.data.redis.core.script.RedisScript;
    import org.springframework.web.context.request.RequestContextHolder;
    import org.springframework.web.context.request.ServletRequestAttributes;
    
    import javax.servlet.http.HttpServletRequest;
    import java.io.Serializable;
    import java.lang.reflect.Method;
    
    
    @Aspect
    @Configuration
    public class LimitInterceptor {
    
        private static final Logger logger = LoggerFactory.getLogger(LimitInterceptor.class);
    
        private final RedisTemplate<String, Serializable> limitRedisTemplate;
    
        @Autowired
        public LimitInterceptor(RedisTemplate<String, Serializable> limitRedisTemplate) {
            this.limitRedisTemplate = limitRedisTemplate;
        }
    
    
        @Around("execution(public * *(..)) && @annotation(com.example.limit02.config.Limit)")
        public Object interceptor(ProceedingJoinPoint pjp) {
            MethodSignature signature = (MethodSignature) pjp.getSignature();
            Method method = signature.getMethod();
            Limit limitAnnotation = method.getAnnotation(Limit.class);
            Limit.LimitType limitType = limitAnnotation.limitType();
            String name = limitAnnotation.name();
            String key;
            int limitPeriod = limitAnnotation.period();
            int limitCount = limitAnnotation.count();
            switch (limitType) {
                case IP:
                    key = getIpAddress();
                    break;
                case CUSTOMER:
                    key = limitAnnotation.key();
                    break;
                default:
                    key = StringUtils.upperCase(method.getName());
            }
            ImmutableList<String> keys = ImmutableList.of(StringUtils.join(limitAnnotation.prefix(), key));
            try {
                String luaScript = buildLuaScript();
                RedisScript<Number> redisScript = new DefaultRedisScript<>(luaScript, Number.class);
                Number count = limitRedisTemplate.execute(redisScript, keys, limitCount, limitPeriod);
                logger.info("Access try count is {} for name={} and key = {}", count, name, key);
                if (count != null && count.intValue() <= limitCount) {
                    return pjp.proceed();
                } else {
                    if (StringUtils.isBlank(limitAnnotation.desc())) {
                        throw new RuntimeException("请求失败,因为限流了");
                    }
                    else {
                        throw new RuntimeException(limitAnnotation.desc());
                    }
                }
            } catch (Throwable e) {
                if (e instanceof RuntimeException) {
                    throw new RuntimeException(e.getLocalizedMessage());
                }
                throw new RuntimeException("server exception");
            }
        }
    
        /**
         * 限流 脚本
         *
         * @return lua脚本
         */
        public String buildLuaScript() {
            StringBuilder lua = new StringBuilder();
            lua.append("local c");
            lua.append("\nc = redis.call('get',KEYS[1])");
            // 调用不超过最大值,则直接返回
            lua.append("\nif c and tonumber(c) > tonumber(ARGV[1]) then");
            lua.append("\nreturn c;");
            lua.append("\nend");
            // 执行计算器自加
            lua.append("\nc = redis.call('incr',KEYS[1])");
            lua.append("\nif tonumber(c) == 1 then");
            // 从第一次调用开始限流,设置对应键值的过期
            lua.append("\nredis.call('expire',KEYS[1],ARGV[2])");
            lua.append("\nend");
            lua.append("\nreturn c;");
            return lua.toString();
        }
    
        private static final String UNKNOWN = "unknown";
    
        /**
         * 获取IP地址
         * @return
         */
        public String getIpAddress() {
            HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
            String ip = request.getHeader("x-forwarded-for");
            if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
                ip = request.getHeader("Proxy-Client-IP");
            }
            if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
                ip = request.getHeader("WL-Proxy-Client-IP");
            }
            if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
                ip = request.getRemoteAddr();
            }
            return ip;
        }
    }
    

    配置redis:RedisConfig

    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
    import org.springframework.data.redis.serializer.StringRedisSerializer;
    
    import java.io.Serializable;
    
    @Configuration
    public class RedisConfig {
    
        @Bean
        public RedisTemplate<String, Serializable> limitRedisTemplate(LettuceConnectionFactory redisConnectionFactory) {
            RedisTemplate<String, Serializable> template = new RedisTemplate<>();
            template.setKeySerializer(new StringRedisSerializer());
            template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
            template.setConnectionFactory(redisConnectionFactory);
            return template;
        }
    
    }
    

    controller

    import com.example.limit02.config.Limit;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    import java.util.concurrent.atomic.AtomicInteger;
    
    @RestController
    public class LimitTestController {
    
        private static final AtomicInteger ATOMIC_INTEGER = new AtomicInteger();
    
        @Limit
        @GetMapping("/test")
        public int testLimiter() {
            // 意味着100S内最多可以访问10次
            return ATOMIC_INTEGER.incrementAndGet();
        }
    
    }
    

    用法

    1. @Limit
    2. @Limit(key = "testLimiter",limitType = Limit.LimitType.CUSTOMER,desc = "testLimiter自定义限流异常",count = 10,period = 100)

    缓存

    使用@ExtCacheable 注解即可让某个方法在一定的时间内不查数据库

    pom.xml

                    <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
            </dependency>
            <dependency>
                <groupId>org.mybatis.spring.boot</groupId>
                <artifactId>mybatis-spring-boot-starter</artifactId>
                <version>2.1.1</version>
            </dependency>
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-jdbc</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-aop</artifactId>
            </dependency>
            <dependency>
                <groupId>org.apache.commons</groupId>
                <artifactId>commons-lang3</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-data-redis</artifactId>
            </dependency>
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>fastjson</artifactId>
                <version>1.2.70</version>
            </dependency>   
    

    ExtCacheable

    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    @Target({ ElementType.METHOD })
    @Retention(RetentionPolicy.RUNTIME)
    public @interface ExtCacheable {
    
        /**
         * 有配置默认值,可不填
         * 默认规则:类名.方法名:请求参数
         * @return
         */
        String key() default "";
        
        int expireTime() default 60 * 1;//分钟
    }
    

    配置类CacheableAspect

    import com.example.cache03.annotation.ExtCacheable;
    import lombok.extern.slf4j.Slf4j;
    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.aspectj.lang.reflect.MethodSignature;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    
    import java.lang.reflect.Method;
    import java.util.ArrayList;
    import java.util.List;
    
    @Component
    @Aspect
    @Slf4j
    public class CacheableAspect {
    
        @Autowired
        private RedisUtil redisUtil;
    
        @Pointcut("@annotation(com.example.cache03.annotation.ExtCacheable)")
        public void annotationPointcut() {
        }
    
        @Around("annotationPointcut()")
        public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
            // 获得当前访问的class
            Class<?> className = joinPoint.getTarget().getClass();
            // 获得访问的方法名
            String methodName = joinPoint.getSignature().getName();
            // 得到方法的参数的类型
            Class<?>[] argClass = ((MethodSignature) joinPoint.getSignature()).getParameterTypes();
            Object[] args = joinPoint.getArgs();
            String key = "";
            int expireTime = 1800;
            try {
                // 得到访问的方法对象
                Method method = className.getMethod(methodName, argClass);
                method.setAccessible(true);
                // 判断是否存在@ExtCacheable注解
                if (method.isAnnotationPresent(ExtCacheable.class)) {
                    ExtCacheable annotation = method.getAnnotation(ExtCacheable.class);
                    key = getRedisKey(className,methodName,args,annotation);
                    expireTime = getExpireTime(annotation);
                }
            } catch (Exception e) {
                throw new RuntimeException("redis缓存注解参数异常", e);
            }
            // 获取缓存是否存在
            boolean hasKey = redisUtil.hasKey(key);
            if (hasKey) {
                return redisUtil.get(key);
            } else {
                //执行原方法(java反射执行method获取结果)
                Object res = joinPoint.proceed();
                //设置缓存
                redisUtil.set(key, res);
                //设置过期时间
                redisUtil.expire(key, expireTime);
                return res;
            }
        }
    
        private int getExpireTime(ExtCacheable annotation) {
            return annotation.expireTime();
        }
    
        private String getRedisKey(Class className,String methodName, Object[] args, ExtCacheable annotation) {
            String primalKey = annotation.key();
            if (primalKey==null || primalKey.length()==0){
                primalKey = className.getSimpleName() + "." +methodName+":";
                for (Object o : args){
                    primalKey = primalKey+ o;
                }
                log.info("-----------primalKey:{}",primalKey);
                return primalKey;
            }
            //获取#p0...集合
            List<String> keyList = getKeyParsList(primalKey);
            for (String keyName : keyList) {
                int keyIndex = Integer.parseInt(keyName.toLowerCase().replace("#p", ""));
                Object parValue = args[keyIndex];
                primalKey = primalKey.replace(keyName, String.valueOf(parValue));
            }
            return primalKey.replace("+","").replace("'","");
        }
    
        // 获取key中#p0中的参数名称
        private static List<String> getKeyParsList(String key) {
            List<String> ListPar = new ArrayList<>();
            if (key.indexOf("#") >= 0) {
                int plusIndex = key.substring(key.indexOf("#")).indexOf("+");
                int indexNext = 0;
                String parName = "";
                int indexPre = key.indexOf("#");
                if(plusIndex>0){
                    indexNext = key.indexOf("#") + key.substring(key.indexOf("#")).indexOf("+");
                    parName = key.substring(indexPre, indexNext);
                }else{
                    parName = key.substring(indexPre);
                }
                ListPar.add(parName.trim());
                key = key.substring(indexNext + 1);
                if (key.indexOf("#") >= 0) {
                    ListPar.addAll(getKeyParsList(key));
                }
            }
            return ListPar;
        }
    }
    

    相关文章

      网友评论

          本文标题:Java注解

          本文链接:https://www.haomeiwen.com/subject/jarylrtx.html