KotlinSpringProblem

1、错误日志:

Error: lateinit property xxx has not been initialized


问题出现在引入 spring-aop, 定义 controller 层的日志切面之后
错误如下:
lateinit property redisClusterClient has not been initialized;

2、原因

AOP只能对publicprovide 生效,如果你的方法限制是private,那么service注入就为空,在springboot 中默认使用的是cglib来代理操作对象,首先,私有方法是不会出现在代理类中,这也就是为什么代理对象无法对private操作的根本原因;

  1. jdk是代理接口,私有方法必然不会存在在接口里,所以就不会被拦截到;
  2. cglib是子类,private的方法照样不会出现在子类里,也不能被拦截。

Controller中的方法不可设置私有属性

kotlin方法和类默认的访问修饰 是 public
但是仍旧注入bean为null, 为什么呢?

方法默认为public final, 应更改为public open, 这里涉及到 代理相关的问题

📌Kotlin-代理
从编译后的文件可以看出
image.png

1、场景描述:

两次请求如下:
image.png

image.png

2、源码:

  1. package com.example.demo.component;
  2. import lombok.extern.slf4j.Slf4j;
  3. import org.aspectj.lang.JoinPoint;
  4. import org.aspectj.lang.Signature;
  5. import org.aspectj.lang.annotation.Aspect;
  6. import org.aspectj.lang.annotation.Before;
  7. import org.aspectj.lang.annotation.Pointcut;
  8. import org.aspectj.lang.reflect.MethodSignature;
  9. import org.springframework.context.annotation.ComponentScan;
  10. import org.springframework.context.annotation.EnableAspectJAutoProxy;
  11. import org.springframework.stereotype.Component;
  12. import org.springframework.web.context.request.RequestContextHolder;
  13. import org.springframework.web.context.request.ServletRequestAttributes;
  14. import javax.servlet.http.HttpServletRequest;
  15. import java.util.Arrays;
  16. /**
  17. * aop-接口请求参数日志记录
  18. *
  19. * @author yongxiang.jiang
  20. * @date 2019/12/31 11:26
  21. */
  22. @Slf4j
  23. @Aspect
  24. @Component
  25. @ComponentScan
  26. @EnableAspectJAutoProxy
  27. public class LogAopComponent {
  28. @Pointcut("execution(public * com.example.demo.controller..*(..))")
  29. public void logController() {
  30. }
  31. @Before("logController()")
  32. public void doBeforeEnterController(JoinPoint joinPoint) throws ClassNotFoundException {
  33. if (joinPoint == null) {
  34. return;
  35. }
  36. // 接收到请求,记录请求内容
  37. ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
  38. assert attributes != null;
  39. HttpServletRequest request = attributes.getRequest();
  40. String classType = joinPoint.getTarget().getClass().getName();
  41. Class<?> clazz = Class.forName(classType);
  42. Signature signature = joinPoint.getSignature();
  43. MethodSignature methodSignature = (MethodSignature) signature;
  44. // 记录下请求内容
  45. log.info("REQUEST_URL : {}", request.getRequestURL().toString());
  46. log.info("PARAMS_NAME : {}", Arrays.toString(methodSignature.getParameterNames()));
  47. log.info("PARAMS : {}", Arrays.toString(joinPoint.getArgs()));
  48. }
  49. }
package com.example.demo.component;

import com.example.demo.util.GsonUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.support.atomic.RedisAtomicLong;
import org.springframework.stereotype.Service;

import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;

/**
 * @author yongxiang.jiang
 * @version v1.0
 * @since 2020/8/12 10:25
 */
@Service
public class RedisClusterClient {

    final RedisTemplate<String, Object> redisTemplate;
    /**
     * 使用StringRedisSerializer序列化
     */
    final RedisTemplate<String, Object> stringRedisTemplate;

    @Autowired
    public RedisClusterClient(RedisTemplate<String, Object> redisTemplate, @Qualifier("stringValueSerializedRedisTemplate") RedisTemplate<String, Object> stringRedisTemplate) {
        this.redisTemplate = redisTemplate;
        this.stringRedisTemplate = stringRedisTemplate;
    }

    /**
     * 存储对象
     *
     * @param key
     * @param object
     * @return
     */
    public boolean set(final String key, final Object object) {
        redisTemplate.opsForValue().set(key, GsonUtil.toJson(object));
        return true;
    }

    /**
     * 清除特定前缀key
     *
     * @param pattern jksh.*
     * @return
     */
    public boolean flushDb(String pattern) {
        return redisTemplate.delete(pattern);
    }

    /**
     * 存储对象 并设置过期时间
     *
     * @param key
     * @param value
     * @param expire
     * @return
     */
    public boolean set(String key, Object value, long expire) {
        boolean result = false;
        redisTemplate.opsForValue().set(key, value);
        if (expire != -1) {
            result = redisTemplate.expire(key, expire, TimeUnit.SECONDS);
        }
        return result;
    }

    /**
     * 取对象
     *
     * @param key
     * @return
     */
    public <T> T get(final String key, Class<T> clazz) {
        String json = String.valueOf(redisTemplate.opsForValue().get(key));
        return GsonUtil.fromJson(json, clazz);
    }

    public Object get(final String key) {
        return redisTemplate.opsForValue().get(key);
    }

    /**
     * 删除对象
     *
     * @param key
     */
    public void del(final String key) {
        redisTemplate.delete(key);
    }

    /**
     * 是否存在key
     *
     * @param key
     * @return
     */
    public boolean hasKey(final String key) {
        return redisTemplate.hasKey(key);
    }

    /**
     * 重命名
     *
     * @param oKey
     * @param nKey
     */
    public void rename(final String oKey, final String nKey) {
        redisTemplate.rename(oKey, nKey);
    }

    /**
     * 过期key
     *
     * @param key
     * @param timeout
     * @param unit
     */
    public void expire(String key, long timeout, TimeUnit unit) {
        redisTemplate.expire(key, timeout, unit);
    }

    /**
     * redis 计数器
     *
     * @param key
     * @param delta 设置一个递增KEY 如果返回值>1 说明是delta时间内多次请求
     * @return
     */
    public Long increment(String key, long delta) {
        return stringRedisTemplate.opsForValue().increment(key, delta);
    }

    /**
     * list 存储数据
     *
     * @param key
     * @param value
     */
    public boolean lPush(String key, Object value) {
        redisTemplate.opsForList().leftPush(key, value);
        return true;
    }

    /**
     * 删除区间以外的数据
     *
     * @param key
     * @return
     */
    public boolean ltrim(String key, long start, long end) {
        redisTemplate.opsForList().trim(key, start, end);
        return true;
    }

    /**
     * 获取List列表
     *
     * @param key
     * @return List<String>
     */
    public List lrange(String key) {
        List range = redisTemplate.opsForList().range(key, 0, -1);
        return range;
    }

    /**
     * 获取list数据
     *
     * @param key
     * @param start 起始值
     * @param end   结束值
     */
    public List lrange(String key, long start, long end) {
        List range = redisTemplate.opsForList().range(key, start, end);
        return range;
    }

    /**
     * set 中添加数据
     *
     * @param key
     * @param value
     */
    public boolean sAdd(String key, Object value) {
        redisTemplate.opsForSet().add(key, value);
        return true;
    }

    /**
     * zset 添加元素
     *
     * @param key
     * @param value
     */
    public boolean zsetAdd(String key, Object value, double score) {
        Boolean add = redisTemplate.opsForZSet().add(key, GsonUtil.toJson(value), score);
        return add;
    }

    /**
     * 根据score删除元素
     *
     * @param key
     * @param max
     * @param min
     * @return
     */
    public boolean removeByScore(String key, double max, double min) {
        Long aLong = redisTemplate.opsForZSet().removeRangeByScore(key, max, min);
        return aLong > 0;
    }

    /**
     * zset 删除元素
     *
     * @param key
     * @param value
     * @return
     */
    public boolean zsetRemove(String key, Object value) {
        return redisTemplate.opsForZSet().remove(key, GsonUtil.toJson(value)) > 0;
    }


    /**
     * 基于redis的分布式锁
     *
     * @param key
     * @param timeout 单位/毫秒
     * @return
     */
    public boolean tryLock(String key, long timeout) {
        Boolean setnx = redisTemplate.opsForValue().setIfAbsent(key, "setnx");
        if (setnx) {
            expire(key, timeout, TimeUnit.MILLISECONDS);
        }
        return setnx;
    }

    /**
     * 获取key的过期时间
     * <p>
     * 返回值为-1时 此键值没有设置过期日期
     * 返回值为-2时 不存在此键
     *
     * @param key
     * @param timeUnit
     * @return
     */
    public long getExpire(String key, TimeUnit timeUnit) {
        return redisTemplate.opsForValue().getOperations().getExpire(key, timeUnit);
    }

    /**
     * 设置redisKey
     *
     * @param key      key
     * @param value    值
     * @param expire   过期时间
     * @param timeUnit 单位(默认:秒)
     */
    public boolean set(String key, Object value, long expire, TimeUnit timeUnit) {
        if (timeUnit == null) {
            timeUnit = TimeUnit.SECONDS;
        }
        boolean result = false;
        redisTemplate.opsForValue().set(key, value);
        if (expire != -1) {
            result = redisTemplate.expire(key, expire, timeUnit);
        }
        return result;
    }

    /**
     * 自增1(原子操作)
     *
     * @param key    key
     * @param expire 过期时间(默认单位:秒),<0 无失效时间
     */
    public Long getIncr(String key, long expire, TimeUnit timeUnit) {
        if (timeUnit == null) {
            timeUnit = TimeUnit.SECONDS;
        }
        RedisAtomicLong entityIdCounter = new RedisAtomicLong(key, redisTemplate.getConnectionFactory());
        long increment = entityIdCounter.incrementAndGet();
        if (increment == 1 && expire > 0) {
            entityIdCounter.expire(expire, timeUnit);
        }
        return increment;
    }

    /**
     * hash-添加元素
     *
     * @param key     键
     * @param hashKey hash键
     * @param value   值
     */
    public void putInMap(String key, Object hashKey, Object value) {

        redisTemplate.opsForHash().put(key, hashKey, value);
    }

    /**
     * hash-获取元素
     *
     * @param key     键
     * @param hashKey hash键
     */
    public <T> T getFromMap(String key, Object hashKey) {
        return (T) redisTemplate.opsForHash().get(key, hashKey);
    }

    /**
     * hash-是否存在key
     *
     * @param key     键
     * @param hashKey hash键
     * @return boolean
     */
    public boolean hasKeyInMap(String key, Object hashKey) {
        return redisTemplate.opsForHash().hasKey(key, hashKey);
    }

    /**
     * hash-删除元素
     *
     * @param key      键
     * @param hashKeys hash键
     * @return boolean
     */
    public boolean deleteInMap(String key, Object hashKeys) {
        Long delete = redisTemplate.opsForHash().delete(key, hashKeys);
        return delete > 0;
    }

    /**
     * 模糊匹配key
     *
     * @param keys key
     * @return set
     */
    public Set<String> keys(String keys) {
        return redisTemplate.keys(keys);
    }

    /**
     * Set-将一个或多个成员添加到集合
     *
     * @param key    键
     * @param values 值
     * @return long
     */
    public long addForSet(String key, Object... values) {
        return redisTemplate.opsForSet().add(key, values);
    }

    /**
     * Set-从集合中删除一个或多个成员
     *
     * @param key    key
     * @param values values
     * @return boolean
     */
    public long removeForSet(String key, Object... values) {
        return redisTemplate.opsForSet().remove(key, values);
    }

    /**
     * Set-判断确定给定值是否是集合的成员
     *
     * @param key   key
     * @param value value
     * @return boolean
     */
    public boolean isMemberForSet(String key, Object value) {
        return redisTemplate.opsForSet().isMember(key, value);
    }

    /**
     * Set-获取集合中的成员数
     *
     * @param key 键
     * @return long
     */
    public long cardForSet(String key) {
        return redisTemplate.opsForSet().size(key);
    }

    /**
     * Set-减去多个集合
     * [集合<key>减去<collection>的集合, 即集合<key>中的值为交集]
     *
     * @param key        key
     * @param collection 集合
     * @return set
     */
    public Set<Object> diffForSet(String key, Collection<String> collection) {
        return redisTemplate.opsForSet().difference(key, collection);
    }


    /**
     * Set-获取集合中所有元素
     *
     * @param key key
     * @return set
     */
    public Set<Object> membersForSet(String key) {
        return redisTemplate.opsForSet().members(key);
    }

    /**
     * 设置过期时间
     *
     * @param key      key
     * @param timeout  时长
     * @param timeUnit 单位[时,分,秒...]
     * @return boolean
     */
    public boolean setExpire(String key, long timeout, TimeUnit timeUnit) {
        return redisTemplate.expire(key, timeout, timeUnit);
    }

}
package com.example.demo.controller

import com.example.demo.component.RedisClusterClient
import com.example.demo.model.User
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController


/**
 *
 * @author yongxiang.jiang
 * @version v1.0
 * @since 2020/8/27 9:34
 */
@RestController
@RequestMapping("/kt")
open class KotlinController {

    @Autowired
    lateinit var redisClusterClient: RedisClusterClient

    @GetMapping("/get/{key}")
    open fun get(@PathVariable key: String): String {
        return if (redisClusterClient.hasKey(key))
            redisClusterClient.get(key) as String
        else
            "key not exist!"
    }

    @GetMapping("/add/{key}/{name}")
    fun add(@PathVariable key: String, @PathVariable name: String): String {
        redisClusterClient.set(key, User(name, 21))
        return "ok"
    }

}

3、延申

image.png
final表示最终的意思,它修饰的类是不能被继承的;
final修饰的方法能被继承(Math类里就有),但是不能够被重写
其实关系并不复杂,需要记住:
final可用于声明属性、方法和类,分别表示属性不可变,方法不可重写,类不可继承。 当然final修饰的方法是可以被重载的。

4、重写和重载

1、重写(Override)

重写是子类对父类的允许访问的方法的实现过程进行重新编写, 返回值和形参都不能改变。即外壳不变,核心重写!
重写的好处在于子类可以根据需要,定义特定于自己的行为。 也就是说子类能够根据需要实现父类的方法。
重写方法不能抛出新的检查异常或者比被重写方法申明更加宽泛的异常。例如: 父类的一个方法申明了一个检查异常 IOException,但是在重写这个方法的时候不能抛出 Exception 异常,因为 Exception 是 IOException 的父类,只能抛出 IOException 的子类异常。
在面向对象原则里,重写意味着可以重写任何现有方法。

2、重载(Overload):

重载(overloading) 是在一个类里面,方法名字相同,而参数不同。返回类型可以相同也可以不同。

每个重载的方法(或者构造函数)都必须有一个独一无二的参数类型列表。
最常用的地方就是构造器的重载。
重载规则:

  • 被重载的方法必须改变参数列表(参数个数或类型不一样);
  • 被重载的方法可以改变返回类型;
  • 被重载的方法可以改变访问修饰符;
  • 被重载的方法可以声明新的或更广的检查异常;
  • 方法能够在同一个类中或者在一个子类中被重载。
  • 无法以返回值类型作为重载函数的区分标准。

3、未解决问题:

  1. 为什么final修饰的方法在注入时bean为null, 而open修饰的方法则无此问题?

仅修饰符 open 和 final 之分 (一起编译测试接口, final 修饰的方法在通过注入的 bean 去调用方法时, 该 bean 为 null, 导致 NPE)