存档2月 2020

Pointcut 切面

<!--未使用Springcloud时使用AOP增加下面的引用-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
        </dependency>

切面描述

Advice(通知、切面): 某个连接点所采用的处理逻辑,也就是向连接点注入的代码, AOP在特定的切入点上执行的增强处理。
@Before: 标识一个前置增强方法,相当于BeforeAdvice的功能.
@After: final增强,不管是抛出异常或者正常退出都会执行.
@AfterReturning: 后置增强,似于AfterReturningAdvice, 方法正常退出时执行.
@AfterThrowing: 异常抛出增强,相当于ThrowsAdvice.
@Around: 环绕增强,相当于MethodInterceptor.
JointPoint(连接点):程序运行中的某个阶段点,比如方法的调用、异常的抛出等。
Pointcut(切入点): JoinPoint的集合,是程序中需要注入Advice的位置的集合,指明Advice要在什么样的条件下才能被触发,在程序中主要体现为书写切入点表达式。
Advisor(增强): 是PointCut和Advice的综合体,完整描述了一个advice将会在pointcut所定义的位置被触发。
@Aspect(切面): 通常是一个类的注解,里面可以定义切入点和通知
AOP Proxy:AOP框架创建的对象,代理就是目标对象的加强。Spring中的AOP代理可以使JDK动态代理,也可以是CGLIB代理,前者基于接口,后者基于子类。

JoinPoint常用的方法:

Object[] getArgs:返回目标方法的参数
Signature getSignature:返回目标方法的签名
Object getTarget:返回被织入增强处理的目标对象
Object getThis:返回AOP框架为目标对象生成的代理对象
当使用@Around处理时,需要将第一个参数定义为ProceedingJoinPoint类型,该类是JoinPoint的子类。

优先级

Spring提供了如下两种解决方案指定不同切面类里的增强处理的优先级:
1. 让切面类实现org.springframework.core.Ordered接口:实现该接口的int getOrder()方法,该方法返回值越小,优先级越高
2. 直接使用@Order注解来修饰一个切面类:使用这个注解时可以配置一个int类型的value属性,该属性值越小,优先级越高
3. 同一个切面类里的两个相同类型的增强处理在同一个连接点被织入时,Spring AOP将以随机的顺序来织入这两个增强处理,没有办法指定它们的织入顺序。即使给这两个 advice 添加了 @Order 这个注解,也不行!

表达式

Wildcard
*: 匹配任意数量的字符
+:匹配制定数量的类及其子类
..:一般用于匹配任意数量的子包或参数
详细示例见后面的例子
Operators

&&:与操作符
||:或操作符
!:非操作符
Designators

1. within()

//匹配productService类中的所有方法
@pointcut("within(com.sample.service.productService)")
public void matchType()

//匹配sample包及其子包下所有类的方法
@pointcut("within(com.sample..*)")
public void matchPackage()
2. 匹配对象(this, target, bean)

this(AType) means all join points where this instanceof AType is true. So this means that in your case once the call reaches any method of Service this instanceof Service will be true.

this(Atype)意思是连接所有this.instanceof(AType) == true的点,所以这意味着,this.instanceof(Service) 为真的Service实例中的,所有方法被调用时。

target(AType) means all join points where anObject instanceof AType . If you are calling a method on an object and that object is an instanceof Service, that will be a valid joinpoint.

target(AType)意思是连接所有anObject.instanceof(AType)。如果你调用一个Object中的一个方法,且这个object是Service的实例,则这是一个合法的切点。
To summarize a different way - this(AType) is from a receivers perspective, and target(AType) is from a callers perspective.

总结:this(AType)同接受者方面描述,target(AType)则从调用者方面描述。
this
@pointcut("this(com.sample.demoDao)")
public void thisDemo()
target
@pointcut("target(com.sample.demoDao)")
public void targetDemo()
bean
//匹配所有以Service结尾的bean里面的方法
@pointcut("bean(*Service)")
public void beanDemo
3. 参数匹配

bean
//匹配所有以find开头,且只有一个Long类型参数的方法
@pointcut("execution(* *..find*(Long))")
public void argDemo1()

//匹配所有以find开头,且第一个参数类型为Long的方法
@pointcut("execution(* *..find*(Long, ..))")
public void argDemo2()
arg
@pointcut("arg(Long)")
public void argDemo3()

@pointcut("arg(Long, ..)")
public void argDemo4()
4. 匹配注解

@annotation
//匹配注解有AdminOnly注解的方法
@pointcut("@annotation(com.sample.security.AdminOnly)")
public void demo1()
@within
//匹配标注有admin的类中的方法,要求RetentionPolicy级别为CLASS
@pointcut("@within(com.sample.annotation.admin)")
public void demo2()
@target
//注解标注有Repository的类中的方法,要求RetentionPolicy级别为RUNTIME
@pointcut("target(org.springframework.stereotype.Repository)")
public void demo3()
@args
//匹配传入参数的类标注有Repository注解的方法
@pointcut("args(org.springframework.stereotype.Repository)")
public void demo3()
5. execution()

格式:

execution(<修饰符模式>? <返回类型模式> <方法名模式>(<参数模式>) <异常模式>?)
标注❓的项目表示着可以省略
execution(
    modifier-pattern?  //修饰符
    ret-type-pattern  //返回类型
    declaring-type-pattern?  //方法模式
    name-pattern(param-pattern)  //参数模式
    throws-pattern?  //异常模式
)

/*
整个表达式可以分为五个部分:

 1、execution(): 表达式主体。

 2、第一个*号:表示返回类型,*号表示所有的类型。

 3、包名:表示需要拦截的包名,后面的两个句点表示当前包和当前包的所有子包,com.sample.service.impl包、子孙包下所有类的方法。

 4、第二个*号:表示类名,*号表示所有的类。

 5、*(..):最后这个星号表示方法名,*号表示所有的方法,后面括弧里面表示方法的参数,两个句点表示任何参数。
*/
@pointcut("execution(* com.sample.service.impl..*.*(..))")

Mysql Error

ERROR 1071 (42000): Specified key was too long; max key length is 767 bytes 解决方案:

1:启用系统变量innodb_large_prefix
注意:光有这个系统变量开启是不够的。必须满足下面几个条件:
1:系统变量innodb_large_prefix为ON
2:系统变量innodb_file_format为Barracuda
3:ROW_FORMAT为DYNAMIC或COMPRESSED

mysql> show variables like '%innodb_large_prefix%';
mysql> set global innodb_large_prefix=on;

show variables like '%innodb_file_format%';
set global innodb_file_format=Barracuda;

对脚本进行修改,添加ROW_FORMAT=DYNAMIC
ALTER TABLE TEST ROW_FORMAT=DYNAMIC;
create table test (........) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;

ERROR 1146 (42S02): Table ‘库名.表名’ doesn’t exist

  1. 首先退出mysql exit
  2. 在命令行界面输入:
mysql_upgrade -uroot -p --force
  1. 然后重启mysql即可
    windows重启mysql命令是:
    net stop mysql #关闭mysql
    net start mysql #开启mysql

access denied

vim /etc/my.cnf文件;
[mysqld]
skip-grant-tables
flush privileges;
set password for ‘root’@‘localhost’=password(‘Admin123@qwe’);

sudo systemctl restart mysqld

时间错误

mysql –u root –p
确认时区
show variables like '%time_zone%';
set global time_zone = '+8:00';  ##修改mysql全局时区为北京时间,即我们所在的东8区
flush privileges;  #立即生效

vim /etc/mysql/my.cnf
##在[mysqld]区域中加上
default-time_zone = '+8:00'

修改jdbc url连接数据库参数 添加serverTimezone=Asia/Shanghai参数

拦截器 Interceptor

实现一个自己拦截器只需要两个步骤即可:

  1. 新建一个类实现HandlerInterceptor接口;
    重写我们需要的方法,如上的三个方法并不是必须的,视自己的业务需求而定。
public class MyInterceptor implements HandlerInterceptor {
    private Logger logger = LoggerFactory.getLogger(MyInterceptor.class);
    /**
     * 预处理回调方法,实现处理器的预处理(如检查登陆),第三个参数为响应的处理器,自定义Controller
     * 返回值:true表示继续流程(如调用下一个拦截器或处理器);
   *       false表示流程中断(如登录检查失败),不会继续调用其他的拦截器或处理器,此时我们需要通过response来产生响应;
   */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        logger.info("preHandle");
        // 返回false会阻止调用链的继续进行
        return true;
    }
    /**
     * 后处理回调方法,实现处理器的后处理(但在渲染视图之前),此时我们可以通过modelAndView(模型和视图对象)对模型数据进行处理或对视图进行处理,modelAndView也可能为null。
   */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        logger.info("postHandle");
    }
    /**
    * 整个请求处理完毕回调方法,即在视图渲染完毕时回调,如性能监控中我们可以在此记录结束时间并输出消耗时间,还可以进行一些资源清理,类似于try-catch-finally中的finall
   * 但仅调用处理器执行链中preHandle返回true的拦截器的afterCompletion。
   */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        logger.info("afterCompletion");
    }
}
  1. 在实现了自己的拦截器后还不能马上使用,还需要在Spring中注册与配置,我们新建一个配置类,将上面我们实现的拦截器进行注册,此时拦截器就能生效了。
@Configuration
@EnableWebMvc
public class InterceptorConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new MyInterceptor());
    }
}

多拦截器的处理过程

当我们需要多个拦截器的时候,每个拦截器的方法处理顺序如下:
Interceptor1:preHandle > Interceptor2:preHandle > Interceptor2:postHandle > Interceptor1:postHandle > Interceptor2:afterCompletion > Interceptor1:afterCompletion
为什么会有这样的执行结果呢?其实这和HandlerInterceptor中三个方法的执行时机有关系:

preHandle:在Controller处理之前做拦截进行处理,因此越是声明在前的拦截器,其preHandle方法就越先得到执行。

postHandle:在Controller处理之后,视图渲染返回之前进行处理,有点像括号嵌套的机制,左括号越是在前,其对应的右括号越是在后。

(PS:这里的“返回之前”只是针对视图渲染逻辑的处理,如果是Rest的接口,那么调用会在Controller处理之后直接返回,不会等到postHandle处理完之后才返回)

afterCompletion:在所有处理完成后用于对资源进行回收进行处理,因此,所有的afterCompletion都会在所有的preHandle和postHandle之后才会执行,且和postHandle一样,按照右括号规则执行。

对特定请求地址的拦截

我们可以在拦截器中定义对那些URL需要拦截,而哪些URL是不需要做任何处理的。

@Configuration
@EnableWebMvc
public class InterceptorConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 对所有的请求都要拦截,但是“/getInt”不在拦截的范围之内
        registry.addInterceptor(new MyInterceptor())
                .addPathPatterns("/**").excludePathPatterns("/getInt");
    }
}

拦截器适配器HandlerInterceptorAdapter

有时候我们可能只需要实现三个回调方法中的某一个,如果实现HandlerInterceptor接口的话,三个方法必须实现,不管你需不需要,此时spring提供了一个HandlerInterceptorAdapter适配器(一种适配器设计模式的实现),允许我们只实现需要的回调方法。

异常处理 Exception

spring mvc提供三种异常处理器:
DefaultHandlerExceptionResolver, ResponseStatusExceptionResolver, ExceptionHandlerExceptionResolver

为了统一异常处理,我们可以自定义一个HandlerExceptionResolver,所有的异常均由自定义的异常处理, 由此我们可以重写DispatcherServlet类的
processHandlerException()方法:

  1. @ExceptionHandler
    使用@ExceptionHandler注解作用在方法上面,参数是具体的异常类型。一旦系统抛出这种类型的异常时,会引导到该方法来处理。但是它的缺陷很明显,处理异常的方法和出错的方法(或者异常最终抛出来的地方)必须在同一个controller,不能全局控制。

  2. @ControllerAdvice + @ExceptionHandler
    使用@ControllerAdvice 和@ExceptionHandler 可以全局控制异常,使业务逻辑和异常处理分隔开。

自定异常
@Data
public class UserNotExitsException extends RuntimeException{
    private Object id;
    public UserNotExitsException(Object id){
        super("用户不存在");
        this.id = id;
    }
}
全局控制异常
@ControllerAdvice
@Slf4j
public class ControllerExceptionHandler {

    /**
     * 用户不存在
     * 待处理异常的类 UserNotExitsException
     * @return 将返回的信息转为JSON,同时返回HTTP状态码500
     */
    @ExceptionHandler(UserNotExitsException.class)
    @ResponseBody
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public JsonResult handleUserNotExitException(UserNotExitsException ex){
        log.info("handleUserNotExitException:"+String.valueOf(ex.getId()));
        return JsonResult.error(ex.getMessage(),ex.getId());
    }
}

优先级

既然在SpringMVC中有两种处理异常的方式,那么就存在一个优先级的问题:

当发生异常的时候,SpringMVC会如下处理:

(1)SpringMVC会先从配置文件找异常解析器HandlerExceptionResolver

(2)如果找到了异常异常解析器,那么接下来就会判断该异常解析器能否处理当前发生的异常

(3)如果可以处理的话,那么就进行处理,然后给前台返回对应的异常视图

(4)如果没有找到对应的异常解析器或者是找到的异常解析器不能处理当前的异常的时候,就看当前的Controller中有没有提供对应的异常处理器,如果提供了就由Controller自己进行处理并返回对应的视图

(5)如果配置文件里面没有定义对应的异常解析器,而当前Controller中也没有定义的话,就看有没有全局ControllerAdvice提供的全局异常处理器,如果没有那么该异常就会被抛出来。

k8s卸载清理

k8s 节点删除

kubectl drain <node name> --delete-local-data --force --ignore-daemonsets
kubectl delete node <node name>
kubeadm reset

若需要重新加入 ,则再次执行 kubeadm init or kubeadm join

清理 Pods

kubectl delete node --all
rm -r /var/etcd/backups/* 删除备份。

卸载清理K8S

kubeadm reset -f
modprobe -r ipip
lsmod
rm -rf ~/.kube/
rm -rf /etc/kubernetes/
rm -rf /etc/systemd/system/kubelet.service.d
rm -rf /etc/systemd/system/kubelet.service
rm -rf /usr/bin/kube*
rm -rf /etc/cni
rm -rf /opt/cni
rm -rf /var/lib/etcd
rm -rf /var/etcd