【Java之AOP】

AOP

面向切面编程 Aspect Oriented Programming

切面:切入点+通知

给容器中的组件进行增强,AOP其实做的事情就是将委托类组件替换为代理组件,取出的组件就是代理组件。AOP做的是更精细的增强,并不是所有的组件里的方法都做增强,而是做筛选,开发人员提供筛选条件。

切入点 → 筛选容器中的组件中要被增强的方法

通知 → 做什么样的增强

编程术语

Target:目标类、委托类、被代理类

Proxy:代理类

PointCut:切入点

Advice:通知

Aspect:切面

JoinPoint:连接点,增强过程中的点。通过JoinPoint可以拿到一些参数(委托类对象、代理类对象、方法、参数)

Weaver:织入

案例

案例1使用工厂方法注册代理

FactoryBean

@Component
public class ServiceFactoryBean implements FactoryBean<UserService> {
    @Qualifier("userServiceImpl") 
    @Autowired
    UserService userService; //维护一个成员变量,用于增强
    @Override
    public UserService getObject() throws Exception {
        Object proxy = Enhancer.create(UserServiceImpl.class, new InvocationHandler() {
            @Override
            public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
                System.out.println("使用FactoryBean的动态代理进行增强");
                method.invoke(userService, objects);
                return null;
            }
        });
        return (UserService) proxy;
    }

    @Override
    public Class<?> getObjectType() {
        return UserService.class;
    }
}

这里一定要注意,因为我们的动态代理也会实现委托类的接口,所以在取出委托类的组件时要按照类型+id来取值,因为此时实现了接口,那就相当于有了两个这个类型的组件

@Autowired
@Qualifier("serviceFactoryBean")
UserService userServiceProxy;

 @Test
 public void test3(){
     userServiceProxy.demo();
 }

SpringAOP

Spring提供了一个生成动态代理对象的ProxyFactoryBean

通过getObject方法来生成代理对象,那么需要指定委托类对象,也需要指定增强

通知要实现接口 → MethodInterceptor → invoke

@FunctionalInterface
public interface MethodInterceptor extends Interceptor {
    Object invoke(MethodInvocation var1) throws Throwable;
}

实现接口在导包的时候,一定要注意导的是org.aopalliance下的包

@Component
public class UserServiceInter implements MethodInterceptor {
    @Override
    public Object invoke(MethodInvocation methodInvocation) throws Throwable {
        System.out.println("AOP的动态代理");
        Object proceed = methodInvocation.proceed(); //执行委托类的方法
        System.out.println("AOP的动态代理");
        return proceed;
    }
}

通知需要做的事情类似于InvocationHandler

1.委托类方法的执行

2.增强

通过配置文件来注册AOP的对象,target表示的是委托类,interceptorNames表示的就是通知的组件名

<!--ProxyFactoryBean已经编译好了-->
<bean id="userServiceProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
    <!--委托类组件-->
    <property name="target" ref="userServiceImpl"/>
    <!--interceptor是什么,长什么样 → 通知-->
    <property name="interceptorNames" value="customInterceptor"/>
</bean>

这里在取出的时候也要注意动态代理会实现委托类的接口,就会多产生一个该类型的实例,就不能使用取出类型的方法取出。

只有是在容器中注册一个代理类才会出现这种情况,如果没有在容器中注册,就不会。

AspectJ

Pointcut

切入点表达式→匹配的方法→判断方法是否在增强范围内→筛选方法进行增强

execution

大的范围指定,指定满足特征的方法

表达式的规范:execution(修饰符 返回值 包名、类名、方法名(形参))

例如:public void com.cskaoyan.service.UserService.sayHello();

1.能否省略

2.能否通配

3.特殊的用法

修饰符

可以省略→直接省略不写就代表任意的修饰符

返回值

不能省略,但可以使用通配符*来表示返回值可以是任意值

如果是Pojo类,那就需要写一个全限定类名,这里面也可以用通配符

例如:com.cskaoyan.bean.User→com.caskaoyan.*.User

包名、类名、方法名

只能省略一部分:包名的第一级和方法名不能省略(头尾不能省略),中间的任意一部分都可以省略

使用…进行省略,这里可以先写出完整的,然后中间的部分删去不写即可,只要用了…就表示省略,如果有多级的,那么也只需要使用一次…

public void com.cskaoyan.service.UserServiceImpl.sayHello();

中间加黑的部分都可以省略

例如:

execution(void com…service.UserServiceImpl.sayHello())

execution( void com.cskaoyan…UserServiceImpl.sayHello()😉

execution( void com.cskaoyan…sayHello()😉

可以使用通配符*,任意位置都可以使用

  • execution(void *.cskaoyan…sayHello())
  • execution(void com.cskaoyan…say*())
形参

public void com.cskaoyan.service.UserServiceImpl.sayHello()

可以省略不写→表示无参方法

可以使用通配符

*表示任意类型的单个字符

…表示任意参数(类型任意,数量任意)

同样的,如果是pojo类作为形参,那么需要写全限定类名

增强service层的任意方法:

execution(* com.cskaoyan.service…*(…))

前面的第一个…表示的是省略中间的包,*表示的任意方法名,方法参数的…表示的是任意参数

使用

需要引入依赖:aspectjweaver

<!--aspectjweaver → groupid为org.aspectj-->
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.7</version>
</dependency>

引入aop的schema约束

<aop:config>
    <aop:pointcut id="mypointcut" expression="execution(* com.cskaoyan.service..*(..))"/>
</aop:config>

@annotation

指定要增强的具体的方法,注解的形式

@annotation(自定义的注解的全限定类名)

首先需要自定义一个注解,自定义的注解写在容器中的组件的哪个方法上,哪个方法就被划入了增强的范围

@Target(ElementType.METHOD)  //注解可以出现在方法上
@Retention(RetentionPolicy.RUNTIME) //注解生效时间:runtime运行时
public @interface CountTime {
}
<aop:pointcut id="mypointcut2" expression="@annotation(com.cskaoyan.anno.CountTime)"/>

Advisor

Advisor:pointcut + advice (实现MethodInterceptor接口)

<aop:config>
    <aop:pointcut id="myPointcut" expression="execution(* com.fh.service.*(..))"/>
    <aop:pointcut id="myPointcut2" expression="@annotation(com.fh.anno.CountTime)"/>

    <aop:advisor advice-ref="countTimeAdvice" pointcut-ref="myPointcut2"/>
</aop:config>
@Component
public class CountTimeAdvice implements MethodInterceptor {
    @Override
    public Object invoke(MethodInvocation methodInvocation) throws Throwable {
        long start = System.currentTimeMillis();
        Object proceed = methodInvocation.proceed();
        long end = System.currentTimeMillis();
        System.out.println(methodInvocation.getMethod().getName() + "执行时间:" + (end - start));
        return proceed;
    }
}

​ pointcut属性:切入点,可以直接写execution表达式,也可以用注解

​ advisor:advice-ref表示的是通知的全限定类名,pointcut-ref表示指定的切入点

进行增强后,取出的组件就是增 强后的代理组件,动态织入将委托类替换为代理类;此外,也需要看代理的对象是否在容器中注册,只有注册了才会起冲突。AspectJ中的没有注册,采取的是动态织入的方式。

Aspect

Aspect = pointcut + advice(提供好一些包含时间属性的通知)

具体的增强还是需要自己来实现

时间属性:相对于委托类方法的执行时间,是一个相对时间,只需要关注相对委托类的时间就行,不需要关注通知之间的时间。

  • Before :在委托类方法之前执行
  • Around:一部分在之前,一部分在之后,类似于InvocationHandler,会使用invoke来调用委托类的方法
  • After:在委托类方法执行之后执行,类似于finally,不管发生什么情况,对应的方法一定会执行
  • AfterThrowing:在抛出异常后执行,类似于catch
  • AfterRetruning:在委托方法执行之后执行,可以获得执行的return的结果,如果抛出异常则不会执行

1.如何将通知和方法结合

将方法写在切面类中,作为通知方法

2.切面组件

1.注册组件

2.标记切面组件

@Component
public class CustomAspect {

    //before通知方法,方法名任意
    public void mybefore() {
        System.out.println("Before通知:正道的光1.0");
    }

    //after通知方法,方法名任意
    public void myafter() {
        System.out.println("After通知:照在大地上1.0");
    }

    //around通知方法 → 类似于InvocationHandler的invoke、类似于MethodInterceptor的invoke
    //要包含委托类方法的执行和增强两部分
    //around方法的返回值作为代理对象的执行结果 → 返回值为Object类型
    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.out.println("around的前半部分");

        //委托类方法的执行 → ProceedingJoinPoint
        Object proceed = proceedingJoinPoint.proceed();//执行的是委托类的方法

        //增强
        System.out.println("around的后半部分");

        return proceed + " niupi";
    }

    //afterReturning可以获得委托类方法的执行结果,意味着能够接收到这个值 → 形参中以Object接收
    //形参名自己定义
    public void afterReturning(Object returnValue) {
        System.out.println("afterReturning通知:" + returnValue);
    }

    //afterThrowing通知 抛出异常的时候执行 → 可以获得你抛出的异常对象
    //使用Exception或Throwable来接收
    public void afterThrowing(Throwable e) {
        System.out.println("afterThrowing通知:" + e.getMessage());
    }

}
<aop:aspect ref="customAspect"/>

如何将通知和切入点结合

使用配置文件:

<aop:config>
    <aop:pointcut id="mypointcut" expression="execution(* com.cskaoyan.service..*(..))"/>
    <aop:pointcut id="mypointcut2" expression="@annotation(com.cskaoyan.anno.CountTime)"/>
    <aop:aspect ref="customAspect">
        <!--子标签-->
        <!--指定方法为什么通知方法、通知是啥切入点-->
        <!--pointcut属性:直接写切入点表达式-->
        <!--pointcut-ref属性:引用了切入点的id-->
        <aop:before method="mybefore" pointcut-ref="mypointcut"/>
        <aop:after method="myafter" pointcut-ref="mypointcut"/>
        <aop:around method="around" pointcut-ref="mypointcut"/>
        <!--returning属性:after-returning通知方法中哪一个Object类型的形参来接收委托类方法的执行结果-->
        <aop:after-returning method="afterReturning" pointcut-ref="mypointcut" returning="returnValue"/>
        <!--throwing属性:afterThrowing通知方法中哪一个Exception或Throwable类型的形参来接收委托类方法抛出的异常-->
        <aop:after-throwing method="afterThrowing" pointcut-ref="mypointcut" throwing="e"/>
    </aop:aspect>
</aop:config>

如果委托类方法抛出异常

Around通知的后半部分和AfterReturning通知执行不到

After通知是可以执行到的,它类似于finally

共有的属性:method、pointcut(-ref)

特有属性:afterReturning → returning、AfterThrowing → throwing ,让通知方法的形参能够接收到一些值

aop:pointcut的作用范围

aop:pointcut标签可以写在aop:aspect标签里,这时候就表示的是局部变量

<aop:config>
    <!--全局变量:整个aop:config标签里都可以使用-->
    <aop:pointcut id="mypointcut" expression="execution(* com.cskaoyan.service..*(..))"/>
    <aop:aspect ref="customAspect">
        <!--局部变量:只能在当前aop:aspect标签里使用-->
        <aop:pointcut id="mypointcut3" expression="execution(* com.cskaoyan.service..*(..))"/>
    </aop:aspect>
</aop:config>

JoinPoint连接点

通过连接点,可以获得增强过程中的一些值:委托类对象、代理对象、方法、

提供这些值可以让业务更加灵活

写在Before通知或者Around通知的形参中

public interface ProceedingJoinPoint extends JoinPoint {
    
    //意味着委托类方法的参数不做变化
    Object proceed() throws Throwable;
	//意味着委托类方法传入你的参数
    Object proceed(Object[] var1) throws Throwable;
}
在around通知中,可以直接使用ProceedingJoinPoint的实例来获得对应的值

before通知里的使用

//before通知方法,方法名任意
public void mybefore(JoinPoint joinPoint) {
    System.out.println("Before通知:正道的光1.0");
    Object proxy = joinPoint.getThis();   //proxy代理对象
    Object target = joinPoint.getTarget();//target委托类对象

    //拿到方法
    Signature signature = joinPoint.getSignature();
    String name = signature.getName();  //Signature的name就是方法名

    //委托类方法执行的参数
    Object[] args = joinPoint.getArgs();

}

around通知里的使用

public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
    //比如说,我们将传给委托类方法的参数拿到
    Object[] args = proceedingJoinPoint.getArgs();
    //如果执行的是sayHello5我们做一个个性化的处理
    if ("sayHello5".equals(proceedingJoinPoint.getSignature().getName())) {
        //个性化的处理
        //原来传入的参数是songge → 现在给改一下 改成songge and ligenli
        args[0] += " and ligenli";

    }


    System.out.println("around的前半部分");

    //委托类方法的执行 → ProceedingJoinPoint
    //Object proceed = proceedingJoinPoint.proceed();//执行的是委托类的方法的参数
    Object proceed = proceedingJoinPoint.proceed(args);//执行的是委托类的方法,参数被修改


    //增强
    System.out.println("around的后半部分");

    return proceed + " niupi";
}

通过around可以对参数进行修改,更加灵活了,返回值就是最终的方法执行的结果

注解使用aspectj

需要做的事

  1. aop:pointcut标签配置了切入点以及对应的切入点表达式
  2. 指定组件为切面组件
  3. 指定切面组件中的方法为一个什么样的通知方法
  4. 将方法和切入点绑定起来
打开AspectJ的注解开关
<aop:aspectj-autoproxy/>
注册切面类

在组件上增加 一个@Aspect注解,将当前组件标记为切面组件;注意@Component不能省

@Aspect
@Component
public class CustomAspect {
    
}
切入点配置

以方法的形式存在:体现出切入点的id和表达式

  • id:方法名作为切入点的id
  • 表达式:@Pointcut的value属性
@Pointcut("execution(* com.cskaoyan.service..*(..))")
public void mypointcut() {//只需要这个方法的方法名
}
指定方法为什么通知方法

在切面类中的方法上增加上通知注解:@Before、@After、@Around、@AfterReturning、@AfterThrowing

将方法与切入点绑定

利用通知注解的value属性

  1. 直接写切入点表达式
  2. 引用切入点对应的方法
//@Before("execution(* com.cskaoyan.service..*(..))") //直接写切入点表达式
@Before("mypointcut()")  //引用方法名时要有一对括号
public void mybefore(JoinPoint joinPoint) {

}
//afterReturning可以获得委托类方法的执行结果,意味着能够接收到这个值 → 形参中以Object接收
//形参名自己定义
@AfterReturning(value = "mypointcut()",returning = "returnValue")
public void afterReturning(Object returnValue) {
    System.out.println("afterReturning通知:" + returnValue);
}

//afterThrowing通知 抛出异常的时候执行 → 可以获得你抛出的异常对象
//使用Exception或Throwable来接收
@AfterThrowing(value = "mypointcut()",throwing = "e")
public void afterThrowing(Throwable e) {
    System.out.println("afterThrowing通知:" + e.getMessage());
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值