Spring的AOP详细使用

Spring 之 AOP

  • 传统的OOP编程,多个类公用同一方法,修改时就要修改所有的文件
  • AOP采取横向抽取机制,将分散在各个方法中的重复代码提取出来,然后在程序编译或运行时再将这些提取出来的代码应用到需要执行的地方
  • AOP的本质是在一系列纵向的控制流程中,把那些相同的子流程提取成一个横向的面
  • AOP采用了横向抽取机制,而不是纵向继承,其实就是可以理解为一个动态代理类,原类的增强
  • aop的用处,权限校验、日志、性能监控
  • 动态代理 有 JDK动态代理 (只能对实现了接口的类产生代理对象 )和 Cglib动态代理 (其实就是子类实现) ,spring底层判断有没有实现接口,实现了用前者,否则用后者
  • spring的AOP是(AspectJ的xml配置,AspectJ是AOP的框架,以前Spring有自己的实现比较繁琐,所以使用了AspectJ)

基于JDK的实现

// 1 定义接口 
public interface Calc {
  int add(int i,int j);
  int sub(int i,int j);
  int mul(int i,int j);
  int div(int i,int j);
}

// 2 实现接口 
public class CalcImpl implements Calc {

  @Override
  public int add(int i, int j) {
    System.out.println("this is add !!!");
    int result = i + j;
    return result;
  }

  @Override
  public int sub(int i, int j) {
    int result = i - j;
    return result;
  }

  @Override
  public int mul(int i, int j) {
    int result = i * j;
    return result;
  }

  @Override
  public int div(int i, int j) {
    int result = i / j;
    return result;
  }

}

// 3 实现jdk代理类 
public class JDKProxy  implements InvocationHandler {
  private Calc calc ;

  public JDKProxy(Calc calc) {
    this.calc = calc;
  }
  public  Calc createProxy(){
    Calc calc1 = (Calc) Proxy.newProxyInstance(
        calc.getClass().getClassLoader(), // 类加载器
        calc.getClass().getInterfaces(),
        this
    );
    return calc1;
  }

  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

    // 判断方法名是不是add
    if("add".equals(method.getName())){
      System.out.println(new Date());
    }

    return method.invoke(calc, args);
  }
}

  // 4. 调用测试 
  public static void main(String[] args) {
    Calc calcImpl = new CalcImpl();
    Calc proxy = new JDKProxy(calcImpl).createProxy();
    proxy.add(1, 2);
  } 
  

基于CgLib的动态代理


// 1 新建类 因为CgLib可以代理普通类 
public class CalcDao {

  public void save(){
    System.out.println("save");
  }
}

// 2 实现CgLibProxy
public class CgLibProxy implements MethodInterceptor {
  private CalcDao calcDao;

  public CgLibProxy(CalcDao calcDao) {
    this.calcDao = calcDao;
  }

  public CalcDao createProxy(){
    // 创建cglib核心类对象
    Enhancer enhancer = new Enhancer();
    // 设置父类,因为cglib的实现,是基于继承实现的
    enhancer.setSuperclass(calcDao.getClass());
    // 设置回调
    enhancer.setCallback(this);
    // 创建代理对象
    CalcDao proxy  = (CalcDao) enhancer.create();
    return  proxy;
  }

  @Override
  public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy)
      throws Throwable {
    if("save".equals(method.getName())){
      System.out.println("enhancer");
    }
    return methodProxy.invokeSuper(proxy, args);
  }
}

// 3 调用测试 
  public static void main(String[] args) {
    CalcDao calcDao = new CalcDao();
    CalcDao proxy1 = new CgLibProxy(calcDao).createProxy();
    proxy1.save();
  }

正式使用Spring的AOP

1 导包
spring aop 和 spring aspects (spring 和 AspectJ整合的包 )
<!-- https://mvnrepository.com/artifact/aopalliance/aopalliance -->
<dependency>
    <groupId>aopalliance</groupId>
    <artifactId>aopalliance</artifactId>
    <version>1.0</version>
</dependency>

<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.4</version>
</dependency>

--------------------- --------------------- --------------------- ---------------------

2  创建spring的核心配置文件,导入Aop的约束:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context-3.0.xsd">
</beans>

--------------------- --------------------- --------------------- ---------------------

3. 新建目标对象 

public interface ProductDao {
  public void save();
  public void update();
  public void find();
  public void delete();
}

public class ProductDaoImpl implements ProductDao {

  @Override
  public void save() {
    System.out.println("save");
  }

  @Override
  public void update() {
    System.out.println("update");
  }

  @Override
  public void find() {
    System.out.println("find");
  }

  @Override
  public void delete() {
    System.out.println("delete");
  }
}

--------------------- --------------------- --------------------- --------------------- 
4. 新建切面类 

/**
 * 切面类
 */
public class MyAspectXML {
  public void check(){
    System.out.println("'权限校验'");
  }
}

--------------------- --------------------- --------------------- --------------------- 

5. 配置到xml中 

 <!--目标对象,被增强的对象    -->
 <bean id="productDao" class="com.starbugs.wehcat.aop.product.ProductDaoImpl" />
 <!--切面类交给spring-->
 <bean id="myAspect" class="com.starbugs.wehcat.aop.product.MyAspectXML" />

 <!--aop配置对目标类产生代理-->
 <aop:config>
     <!--expression配置哪些类的哪些方法需要增强-->
     <aop:pointcut id="pointcut1" expression="execution(* com.starbugs.wehcat.aop.product.ProductDaoImpl.save(..))" />
     <!--配置切面-->
     <aop:aspect ref="myAspect" >
         <aop:before method="check" pointcut-ref="pointcut1"/>
     </aop:aspect>
 </aop:config>

通知类型
1 前置通知 目标方法执行前
2 后置通知 可以阻止目标方法的执行
3 环绕通知 前和后
4 异常抛出通知
5 最终通知 无论代码有异常,都执行

1 xml配置
 <aop:config>
        <!--expression配置哪些类的哪些方法需要增强-->
        <aop:pointcut id="pointcut1" expression="execution(* com.starbugs.wehcat.aop.product.ProductDaoImpl.save(..))" />
        <aop:pointcut id="pointcut2" expression="execution(* com.starbugs.wehcat.aop.product.ProductDaoImpl.update(..))" />
        <aop:pointcut id="pointcut3" expression="execution(* com.starbugs.wehcat.aop.product.ProductDaoImpl.delete(..))" />
        <aop:pointcut id="pointcut4" expression="execution(* com.starbugs.wehcat.aop.product.ProductDaoImpl.find(..))" />
       <!--配置切面-->
        <aop:aspect ref="myAspect" >
            <!-- 前置信息 可以获取切入点的信息 -->
            <aop:before method="check" pointcut-ref="pointcut1"/>
            <!--后置通知 还可以获得返回值 -->
            <aop:after-returning method="writeLog" pointcut-ref="pointcut2" returning="res"/>
            <!--环绕通知-->
            <aop:around method="monitor" pointcut-ref="pointcut3" />

            <aop:after-throwing method="exe" pointcut-ref="pointcut4" throwing="ex"/>

            <aop:after method="after" pointcut-ref="pointcut4" />
        </aop:aspect>

    </aop:config>
2 增强类 
public class MyAspectXML {

  // 前置通知 joinPoint 切入点信息
  public void check(JoinPoint joinPoint){
    System.out.println("'权限校验'" + joinPoint);
  }

  // 后置通知
  public void writeLog(JoinPoint joinPoint, Object res){
    System.out.println("writeLog !!!" + joinPoint);
    System.out.println(res);
  }

  // 环绕通知
  public Object monitor(ProceedingJoinPoint joinPoint) throws Throwable {
    System.out.println("环绕通知 之前");
    Object obj = joinPoint.proceed();
    System.out.println("环绕通知 之后");
    return obj;
  }

  // 异常抛出通知
  public void exe(Throwable ex){
    System.out.println("异常抛出通知" + ex.getMessage());
  }

  public void after(){
    System.out.println("最终通知");
  }
}

spring AOP 表达式

这里有一点需要注意是表达式,可以用逻辑运算符 && || !

execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws- pattern?
modifiers-pattern:表示定义的目标方法的访问修饰符,如public、private等。 
ret-type-pattern:表示定义的目标方法的返回值类型,如void、String等。 
declaring-type-pattern:表示定义的目标方法的类路径,com.ssm.jdk.UserDaoImpl。 name-pattern:表示具体需要被代理的目标方法,如add()方法。
param-pattern:表示需要被代理的目标方法包含的参数,本章示例中目标方法参数都为空。 
throws- pattern:表示需要被代理的目标方法抛出的异常类型

// demo1 
execution(public * com.spring.service.BusinessObject.businessService(java.lang.String,..))
述切点表达式将会匹配使用public修饰,返回值为任意类型,并且是com.spring.BusinessObject类中名称为businessService的方法,方法可以有多个参数,但是第一个参数必须String类型的方法

// demo2 
execution(* com.spring.service.BusinessObject.*())
表示返回值为任意类型,在com.spring.service.BusinessObject类中,并且参数个数为零的方法

// demo3 
execution(* com.spring.service.Business*.*())
表示返回值为任意类型,在com.spring.service包中,以Business为前缀的类,并且是类中参数个数为零方法

// demo4 ..通配符,该通配符表示0个或多个项 主要用于任意参数或任意路径 
execution(* com.spring.service..*.businessService())
匹配返回值为任意类型,并且是com.spring.service包及其子包下的任意类的名称为businessService的方法,而且该方法不能有任何参数

** this和target**

  • this和target表达式中都只能指定类或者接口
  • 类A和B,A调B某方法,如切点表达式为this(B),那么A的实例将会被匹配,也即其会被使用当前切点表达式的Advice环绕;如果这里切点表达式为target(B),那么B的实例也即被匹配,其将会被使用当前切点表达式的Advice环绕
  • this匹配代理对象为指定类型的类。
  • target匹配业务对象为指定类型的类。

目标对象与代理对象

  • 如果目标对象被代理的方法是其实现的某个接口的方法,那么将会使用Jdk代理生成代理对象,此时代理对象和目标对象是两个对象,并且都实现了该接口;
  • 如果目标对象是一个普通类,那么将会使用Cglib代理生成代理对象,并且只会生成一个对象,即Cglib生成的代理类的对象

this和target使用3种情况

  1. this(interfaceA)和 target(interfaceA),效果一样
  2. this(普通类 )和 target(普通类),效果一样
  3. this(接口类) 和 target(接口类),this 代理类不是接口类的类型,所以不一样

within
within表达式的粒度为类,其参数为全路径的类名(可使用通配符)
within的语义表示匹配指定类型的类实例,这里的@within表示匹配带有指定注解的类

// 表示 com.spring.service.BusinessObject下边所有的方法 
within(com.spring.service.BusinessObject)

// 修饰一个类 
@Around("@within(com.demo.annotation.Aspect1)")

annotation
匹配使用@annotation指定注解标注的方法将会被环绕

 // 修饰一个方法 
  @Around("@annotation(com.demo.annotation.Aspect2)")

args
args表达式的作用是匹配指定参数类型和指定参数数量的方法

args(java.lang.String,..,java.lang.Integer)

// 参数是某个注解的类被匹配 
@Around("@args(com.demo.Aspect3)")

spring boot中使用

首先,导包

<dependency>  
  <groupId>org.springframework.boot</groupId>  
  <artifactId>spring-boot-starter-aop</artifactId>  
</dependency>  

然后建一个切面类即可

package com.starbugs.wehcat.aop;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;

/**
 * 日志切面
 * @author starbooks
 *
 * 1 @Aspect 作用是把当前类标识为一个切面供容器读取
 * 2 @Pointcut 建立一个切入点
 */
@Aspect
@Component
public class LogAspect {

  @Pointcut("execution(* com.starbugs.wehcat.controller.FirstController.*(..)) || execution(* com.starbugs.wehcat.controller.StudentController.*(..))")
  public void webLog(){

  }

  @Before("webLog()")
  public void deBefore(JoinPoint joinPoint) throws Throwable {
    // 接收到请求,记录请求内容
    ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
    HttpServletRequest request = attributes.getRequest();
    // 记录下请求内容
    System.out.println("URL : " + request.getRequestURL().toString());
    System.out.println("HTTP_METHOD : " + request.getMethod());
    System.out.println("IP : " + request.getRemoteAddr());
    System.out.println("CLASS_METHOD : " + joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());
    System.out.println("ARGS : " + Arrays.toString(joinPoint.getArgs()));
  }

  @AfterReturning(returning = "ret", pointcut = "webLog()")
  public void doAfterReturning(Object ret) throws Throwable {
    // 处理完请求,返回内容
    System.out.println("方法的返回值 : " + ret);
  }

  //后置异常通知

  @AfterThrowing("webLog()")
  public void throwss(JoinPoint jp){
    System.out.println("方法异常时执行.....");
  }

  //后置最终通知,final增强,不管是抛出异常或者正常退出都会执行

  @After("webLog()")
  public void after(JoinPoint jp){
    System.out.println("方法最后执行.....");
  }

  //环绕通知,环绕增强,相当于MethodInterceptor
  // @Around参数必须为ProceedingJoinPoin

  @Around("webLog()")
  public Object arround(ProceedingJoinPoint pjp) {
    System.out.println("方法环绕start.....");
    try {
      Object o =  pjp.proceed();
      System.out.println("方法环绕proceed,结果是 :" + o);
      return o;
    } catch (Throwable e) {
      e.printStackTrace();
      return null;
    }
  }
}

自定义注解

1. 注解的常用地方

1.利用注解实现AOP拦截以及操作日志记录
2.利用注解实现对前端参数的数据有效性校验。
3.利用注解结合反射获取java bean属性字段的额外信息

2.注解的定义

@interface 表示注解的定义
@target 表示该注解的作用域 有 CONSTRUCTOR, FIELD, METHOD, TYPE
@retention 表示注解类型保留时间的长短,它接收RetentionPolicy参数,可能的值有SOURCE, CLASS, 以及RUNTIME,我们常用runtime,表示在编译以及java vm都会保存,所以可以用来反射阶段获取字段的额外属性值
@Documented 表示该注解可以被javadoc等工具文档
@constraint 表示注解的约束关系,属性值validateBy这个属性是设定约束关系的实现类

package com.example.aop;  
  
import java.lang.annotation.ElementType;  
import java.lang.annotation.Retention;  
import java.lang.annotation.RetentionPolicy;  
import java.lang.annotation.Target;  
  
@Target({ElementType.METHOD, ElementType.TYPE})  
@Retention(RetentionPolicy.RUNTIME)  
public @interface UserAccess {  
    String desc() default "default";  
}  

参考资料

https://www.cnblogs.com/zhangxufeng/p/9160869.html
https://www.cnblogs.com/chenziyu/p/9547343.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值