AOP学习:一篇够用(零配置纯注解开发)

一、AOP核心思想:将业务代码和具体功能做分离。

实现方式:代理模式实现

二、代理模式

2.1.静态代理:

静态代理是设计模式中的一种代理模式,通过在代理类中封装被代理对象,间接控制对被代理对象的访问。以下是静态代理的简单示例:

        2.1.1. 定义一个接口(被代理对象):

public interface Subject {
    void doSomething();
}

        2.1.2. 创建一个实现了接口的类(被代理对象):

public class RealSubject implements Subject {
    @Override
    public void doSomething() {
        System.out.println("RealSubject is doing something...");
    }
}

        2.1.3. 创建代理类,实现相同的接口:

public class ProxySubject implements Subject {
    private RealSubject realSubject;

    public ProxySubject(RealSubject realSubject) {
        this.realSubject = realSubject;
    }

    @Override
    public void doSomething() {
        System.out.println("ProxySubject doSomething is intercepted...");
        realSubject.doSomething();
        System.out.println("ProxySubject doSomething is finished...");
    }
}

        2.1.4. 使用代理类:

public class Main {
    public static void main(String[] args) {
        RealSubject realSubject = new RealSubject(); // 创建被代理对象
        ProxySubject proxySubject = new ProxySubject(realSubject); // 创建代理对象
        proxySubject.doSomething(); // 调用代理对象的方法,实际上会执行代理逻辑
    }
}

在上述示例中,RealSubject 是被代理对象,它实现了 Subject 接口。ProxySubject 是代理类,它也实现了 Subject 接口,并在代理的 doSomething() 方法中添加了额外的逻辑。当我们调用代理对象的 doSomething() 方法时,实际上会先执行代理逻辑,再调用被代理对象的 doSomething() 方法,最后执行代理逻辑的后续操作。

静态代理的特点是代理类必须在编译时就已经存在,并且每一个被代理类都需要一个对应的代理类。虽然静态代理相对简单,但代理类的数量会随着被代理类的增加而增加,增加了代码的维护成本。另外,静态代理只能代理特定接口或类,无法应对接口或类的变化。为了更灵活地处理代理对象,可以使用动态代理。

2.2.动态代理:

动态代理是在运行时生成代理对象的一种代理方式,无需预先定义代理类。Java提供了java.lang.reflect包中的ProxyInvocationHandler接口来实现动态代理。以下是使用动态代理的简单示例:

        2.2.1. 定义一个接口(被代理对象):

public interface Subject {
    void doSomething();
}

        2.2.2. 创建一个实现了接口的类(被代理对象):

public class RealSubject implements Subject {
    @Override
    public void doSomething() {
        System.out.println("RealSubject is doing something...");
    }
}

        2.2.3. 创建一个实现了

InvocationHandler接口的动态代理类:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class DynamicProxy implements InvocationHandler {
    private Object realSubject;

    public DynamicProxy(Object realSubject) {
        this.realSubject = realSubject;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("DynamicProxy doSomething is intercepted...");
        Object result = method.invoke(realSubject, args);
        System.out.println("DynamicProxy doSomething is finished...");
        return result;
    }
}

        2.2.4. 使用动态代理:

import java.lang.reflect.Proxy;

public class Main {
    public static void main(String[] args) {
        RealSubject realSubject = new RealSubject(); // 创建被代理对象
        DynamicProxy dynamicProxy = new DynamicProxy(realSubject); // 创建动态代理对象

        Subject proxyInstance = (Subject) Proxy.newProxyInstance(
                realSubject.getClass().getClassLoader(),
                realSubject.getClass().getInterfaces(),
                dynamicProxy //实现invocationHandle接口的类对象
        );

        proxyInstance.doSomething(); // 调用动态代理对象的方法,实际上会执行代理逻辑
    }
}

在上述示例中,DynamicProxy 实现了 InvocationHandler 接口,并在 invoke() 方法中添加了额外的逻辑。当我们使用 Proxy.newProxyInstance() 方法创建代理对象时,需要传入被代理对象的类加载器、接口数组和代理对象(DynamicProxy)。

动态代理的特点是可以代理任意接口或类,并且在运行时生成代理对象,无需预先定义代理类。这样,我们可以根据实际需要来动态生成代理对象,更加灵活。动态代理常用于框架和库中,用于实现横切关注点(如事务管理、日志记录等)的插入。

三、AOP(底层使用了动态代理)

3.1.概述:

概念:面向切面编程

实现方式:通过预编译方式和运行期动态代理的方式实现

作用:在不修改源代码的情况下,给程序动态统一添加额外功能的一种技术。

3.2.专业术语:

>1.横切关注点:方法中抽取出来的非核心业务

>2.通知(增强):通俗的说,就是想要增强的功能,例如:安全,事务,日志

每一个横切关注点上要做的事情需要写一个方法来实现,这样的方法就叫通知方法。

  • 前置通知:被代理目标方法执行前。
  • 返回通知:被代理的目标方法成功结束后。
  • 异常通知:被代理的目标方法异常结束后执行。
  • 后置通知:被代理的目标方法最终结束后。
  • 环绕通知:使用tyr...catch...finally结构围绕被代理的目标方法,包括上面四种通知的所有位置。

>3.切面:封装通知方法的类

>4.连接点:允许使用通知的地方

四、动态代理分类

  1. JDK动态代理(代理对象和目标对象实现同一个接口)

  2. CGlib动态代理(没借口,代理对象继承被代理对象)

区别:有接口的情况用的是JDK的动态代理;没有接口则用cglib动态代理

*注意:是不是实现类接口就一定是JDK动态代理吗?

不是的,涉及到实现了Bean生命周期的回调接口是不会使用JDK动态代理的,其次,接口中的方法数一定要大于0。

源码如下:

实现初始化、销毁、关闭接口都不会执行JDK动态代理。

总结:接口必须是有效的

五、AOP代码实现(零配置文件)

5.1. 引入依赖

<!--IOC基础包-->
<dependency>    
<groupId>org.springframework</groupId>    
<artifactId>spring-context</artifactId>    
<version>5.3.18</version>
</dependency>
<!--第三方面向切面编程的组件包-->
<dependency>    
<groupId>org.aspectj</groupId>    
<artifactId>aspectjweaver</artifactId>    
<version>1.9.4</version>
</dependency>
<!--单元测试-->
<dependency>    
<groupId>junit</groupId>    
<artifactId>junit</artifactId>    
<version>4.12</version>    
<scope>test</scope>
</dependency>

5.2. aop各种通知类型的顺序:

前置通知->目标方法->后置通知->(有异常则是异常通知)->返回通知

环绕通知包含了所有通知的位置

5.3. 代码实现

项目模块如下:

5.3.1. aopservie包下接口及实现类

public interface UserService {
    void add();    
    String update(Long id, String name);    
    void delete(Long id);}
@Service
public class UserServiceImpl implements UserService{
    @Override    
    public void add() {
        System.out.println("添加方法");    }

    @Override    
    public String update(Long id, String name) {
        System.out.println("修改方法");        
        return "success";    
        }
        
    @Override
    public void delete(Long id) {
        System.out.println("删除方法");    }
}

5.3.2. config配置

/** 
* 如果项目中使用的是Spring Boot,那么AOP默认是启用的,不需要使用@EnableAspectJAutoProxy注解 */
@Configuration
@ComponentScan(basePackages = "com.aopdemo")
@EnableAspectJAutoProxy
public class AopConfig {
}

5.3.3. 切面类

package com.aopdemo.config;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import java.util.Arrays;

/** * 切面类 */
@Component
@Aspectpublic class AopFactory {

    /**     
    * 前置通知     
    *     
    * @Before(value = "切入点表达式")     
    * 切入点表达式:execution(访问修饰符 返回值 包名.类/接口名.方法名(参数) throw 异常名)     
    * 访问修饰符和返回值:可以用一个*表示全部     
    * 参数:可以用..表示任意     
    */    
    @Before("execution(* com.aopdemo.aopservice.UserServiceImpl.update(..))")
    public void before() {
        System.out.println("aop before");    
        }

    /**     
    * 后置通知     
    */    
    @After("execution(* com.aopdemo.aopservice.UserServiceImpl.update(..))")
    public void after(JoinPoint joinPoint) {
        // 获取方法名        
        String name = joinPoint.getSignature().getName();        
        System.out.println("aop after,方法名为:" + name);    }

    /**     
    * 返回通知     
    * @param joinPoint     
    * @param res 目标方法(被增强的方法)的返回值     
    */    
    @AfterReturning(value = "execution(* com.aopdemo.aopservice.UserServiceImpl.update(..))", returning = "res")
    public void afterReturning(JoinPoint joinPoint, Object res) {
        // 获取方法名        
        Object[] args = joinPoint.getArgs();        
        String string = res.toString();        
        System.out.println("afterReturning,参数为:" + Arrays.toString(args));        
        System.out.println("afterReturning,返回值为:" + string);    
        }

    /**     
    * 异常通知     
    * @param joinPoint     
    * @param e     
    */    
    @AfterThrowing(value = "execution(* com.aopdemo.aopservice.UserServiceImpl.update(..))", throwing = "e")
    public void afterThrowing(JoinPoint joinPoint, Exception e) {
        // 获取方法名        
        String name = joinPoint.getSignature().getName();        
        System.out.println("aop afterThrowing,方法名为:" + name);        
        System.out.println("aop afterThrowing,异常为:" + e);    
        }

    /**     
    * Around通知     
    */    
    @Around(value = "execution(* com.aopdemo.aopservice.UserServiceImpl.delete(..))")
    public Object afterThrowing(ProceedingJoinPoint proceedingJoinPoint) {
        // 获取方法名        
        System.out.println("目标方法前执行");        
        Object result = new Object();        
        try {
            result = proceedingJoinPoint.proceed();            
            System.out.println("目标方法之后执行");        
            } catch (Throwable e) {
            System.out.println("目标方法抛异常后执行");        
            } finally {
            System.out.println("目标方法执行完毕后执行");        
            }
        return result;    
        }

    /**     
    * 重用切入点表达式:可省略写切入点表达式     
    */    
    @Pointcut("execution(* com.aopdemo.aopservice.UserServiceImpl.add(..))")
    public void point() {}

    @Before(value = "point()")
    public void testAdd() {
        System.out.println("aop before");    
        }
}

5.3.4 测试类

public class test {
    @Test    
    public void test() {
        ApplicationContext applicationContext
                = new AnnotationConfigApplicationContext(AopConfig.class);        
                UserService bean = applicationContext.getBean(UserService.class);
                //        bean.add();
                //        System.out.println("====================================");
                //        bean.update(123L, "samY");        
                bean.delete(2L);    
                }
}

六、切面类的优先级

相同方法上存在多个切面,切面优先级控制切面的内外嵌套顺序

  • 优先级高的切面:外面
  • 优先级低的切面:里面

使用@Order注解可以控制切面的优先级

  • @Order(较小的数):优先级高
  • @Order(较大的数):优先级低
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值