AOP的介绍

本文详细介绍了AOP(面向切面编程)的概念,包括静态代理和动态代理(如JDK代理和CGLIB代理)的实现原理、优缺点,以及Spring框架中的应用。此外,还探讨了切面、通知、织入和增强等关键概念,以及AOP在实际项目中的应用实例。
摘要由CSDN通过智能技术生成

AOP:是一种编程思想,面向切面编程,是面向对象编程的补充。

面向切面编程:是实现在不修改源代码的情况下给程序动态同一添加额外功能的一种技术,在Spring框架中,AOP通过代理技术实现。它通过拦截方法调用来实现关注点的添加,从而达到代码重用和系统复用性的目的。

代理模式:

代理模式是一种比较好的设计模式,我们使用代理对象来代替对真实对象的访问,这样就可以在不修改原目标对象的前提下,提供额外的功能操作,扩展目标对象的功能。

被代理的对象可以是远程对象、创建开销大的对象或需要安全控制的对象。

代理模式主要涉及三个角色:

Subject: 抽象角色, 声明真实对象和代理对象的共同接口.

Proxy: 代理角色, 它是真实角色的封装, 其内部持有真实角色的引用, 并且提供了和真实角色一样的 接口, 因此程序中可以通过代理角色来操作真实的角色, 并且还可以附带其他额外的操作.

RealSubject: 真实角色, 代理角色所代表的真实对象, 是我们最终要引用的对象

静态代理: 

//共同接口(subject,抽象角色)
public interface Singer {
    public void sing();

}

//目标类实现接口
public class GuoFuCheng implements Singer{
    @Override
    public void sing() {
        System.out.println("郭富城唱了几首歌...............");
    }
}

//代理类实现接口,并完成目标类的增强
public class Broker implements Singer{
//设置属性
    private Singer singer;
//将要目标类作为参数传进来赋给属性,意思是代理哪一个歌手
    public Broker(Singer singer){
        this.singer = singer;
    }
    @Override
    public void sing() {
        System.out.println("彩排");
        singer.sing();
        System.out.println("收工了,庆祝!");
    }
}

//测试类
public class TestProxy {
    public static void main(String[] args) {
        //创建目标类对像
        GuoFuCheng g = new GuoFuCheng();
        //创建代理对象,并且将目标对象传进代理类中
        Broker broker = new Broker(g);
        //代理对象调用方法
        broker.sing();
    }
}

注意:代理对象和目标对象实现一样的接口

优缺点

优点:可以在不修改目标对象的前提下扩展目标对象的功能。

缺点:

冗余。由于代理对象要实现与目标对象一致的接口,会产生过多的代理类。

不易维护。一旦接口增加方法,目标对象与代理对象都要进行修改。

动态代理(jdk代理或者接口代理)

动态代理利用了JDK API,动态地在内存中构建代理对象,从而实现对目标对象的代理功能。动态代理 又被称为JDK代理或接口代理。

注意:动态代理对象不需要实现接口,但是要求目标对象必须实现接口,否则不能使用动态代理。

JDK中生成代理对象主要涉及的类有:

动态代理利用了JDK API,动态地在内存中构建代理对象,从而实现对目标对象的代理功能。动态代理 又被称为JDK代理或接口代理。

注意:动态代理对象不需要实现接口,但是要求目标对象必须实现接口,否则不能使用动态代理。

JDK中生成代理对象主要涉及的类有:

使用动态代理的步骤:

  1. 实现 InvocationHandler 接口, 并在 invoke 中调用真实对象的对应方法

  2. 通过 Proxy.newProxyInstance 静态方法获取一个代理对象

//接口
public interface Singer {
    public void sing();
}

//目标类实现接口
public class LiuDeiHua implements Singer{
    @Override
    public void sing() {
        System.out.println("刘德华唱了一首歌!");
    }
}

//创建代理工厂
public class SingerProxyFactory implements InvocationHandler {
    //维护一个目标对象
    private Object target;

    public SingerProxyFactory(Object target) {
        this.target = target;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("刘德华登场!");
        //执行目标对象方法,args为参数,returnValue为方法返回值
        Object returnValue = method.invoke(target, args);
        System.out.println("刘德华演唱完毕!");
        return returnValue;
    }
}

//测试类
public class TestDynamicProxy {
    public static void main(String[] args) {
        //目标对象
        Singer target = new LiuDeiHua();
        InvocationHandler invocationHandler = new SingerProxyFactory(target);
        //创建代理对象
        Singer instance = (Singer) Proxy.newProxyInstance(Singer.class.getClassLoader(), new Class<?>[]{Singer.class}, invocationHandler);
        instance.sing();
    }
}

动态代理和静态代理的区别:

1.代理对象生成时机:

静态代理在编译时就已经实现,编译完成后代理类是一个实际的class文件

动态代理是在运行时动态生成的,即编译完成后没有实际的class文件,而是在运行时动态生成类字 节码,并加载到JVM中

2.代理对象数量

静态代理:每一个被代理类都需要对应一个静态代理类。

动态代理:通过代理工厂在运行时生成的代理类可以用于代理多个不同的被代理对象。

3.代码维护性:

静态代理:由于每个被代理类都需要对应一个静态代理类,当被代理类发生变化时,静态代理类也需要相应修改,维护成本较高。

动态代理:由于代理类的生成是动态的,对被代理类的变化更加灵活,不需要修改代理类的代码,维护成本较低。

4.功能拓展性

静态代理:需要手动为每个代理类编写具体的逻辑,扩展新的功能时需要修改代理类的源代码。

动态代理:通过使用AOP(面向切面编程)的方式,可以在运行时动态地为被代理类添加额外的功能,无需修改代理类的源代码。

CGlib代理(可以在内存中动态创建对象,不需要实现接口,也属于动态代理的范畴)

JDK动态代理和CGLIB代理都是实现Spring框架中AOP的代理方式,它们的实现原理和应用场景有所不同

区别:

实现原理:

JDK动态代理是基于Java反射机制实现的,它要求目标类必须实现一个或多个接口,代理对 象在运行时动态创建,通过实现目标类接口的方式来代理目标类。

CGLIB代理则是基于ASM字节码框架 实现的,它可以代理没有实现接口的目标类。CGLIB在运行时通过动态生成目标类的子类来实现代理。

性能表现:

JDK动态代理因为需要实现目标类接口,所以它的性能相对较低,但是它的应用场景更为广 泛,适用于大多数情况下的代理需求。

CGLIB代理则因为不需要实现目标类接口,所以它的性能相对较 高,但是它不能代理final类和final方法,以及一些无法生成子类的类。

应用场景:

JDK动态代理适用于代理接口的场景,例如Spring中的事务处理、日志记录等。

CGLIB代理 适用于代理类的场景,例如Spring中的AOP切面编程等。

AOP术语

  1. 切面(Aspect): 切面是一个模块化的代码单元,它描述了在应用程序中横跨多个点执行的横切关注点。切面可以理解为比面向对象程序设计中的类更高层次的模块化结构。

  2. 连接点(Join point): 连接点是程序执行过程中能够插入切面的点。例如,在 Java 应用程序中,连接点可能是一个方法调用或异常的抛出。

  3. 通知(Advice): 通知是在连接点上执行的代码,通知包括了在连接点执行之前、之后或中间执行的代码。

  4. 切入点(Pointcut): 切入点指定了一个或多个连接点,切面可以针对这些连接点提供通知。

  5. 目标对象(Target object): 目标对象是即将被代理的对象实例。通知和切面并不直接作用于目标对象,而是通过代理对象间接作用于目标对象。

  6. 织入(Weaving): 织入是将切面集成到程序执行流程中的过程,可以在编译期、类加载期或运行期进行。

  7. 引入(Introduction): 引入是一种特殊的通知类型,它允许为现有的类添加新的方法或属性。

  8. 代理(Proxy): 代理是一个对象,它可以替代目标对象,并通过将切面织入到连接点上来提供额外的功能。

  9. 增强(Enhancement): 增强是指在执行过程中为目标对象添加额外的行为或改变其内部状态的能力。

理解

切面的含义:包含了重复的代码逻辑,比如处理事务、安全、日志记录等。这些逻辑可能会在一个系统中的多个模块或方法中重复出现,导致代码冗余和可维护性低下,通过将这些逻辑抽取成切面,我们可以将关注点从具体的业务逻辑中分离出来,避免代码重复。同时,切面提供了一种可插拔的机制,使得我们可以在不修改原始代码的情况下,动态地将额外的行为织入到目标对象的方法中,实现对目标对象的增强或修改。

拦截的含义:找到需要被增强的方法作为切入点,并通过切入点将需要增强的方法织入目标对象中。

AOP的通知分类

  1. 前置通知(Before Advice):在目标方法执行之前执行的通知。可以在前置通知中进行一些准备操作,比如参数验证、权限检查等。

  2. 后置通知(After Advice):在目标方法执行之后执行的通知。无论目标方法是否发生异常,后置通知都会被执行。可以用于释放资源、记录日志等操作。类似于finally

  3. 返回通知(After Returning Advice):在目标方法成功执行并返回结果后执行的通知。可以获取目标方法的返回值,并进行相应的处理。类似于catch

  4. 异常通知(After Throwing Advice):在目标方法抛出异常后执行的通知。可以捕获目标方法抛出的异常,进行异常处理或者记录日志等操作。

  5. 环绕通知(Around Advice):是最为强大和灵活的通知类型。环绕通知包围目标方法的执行,在方法调用前后都可以执行自定义的逻辑。可以决定是否继续执行目标方法,可以修改目标方法的参数和返回值。

AOP的两种实现

注解:

//创建User类
@Component
public class User {
       public void add(){
       int i = 12 / 0;
       System.out.println("User--add");
      }
}

//创建代理类(增强类)
@Component
@Aspect
public class UserProxy {
// 相同切入点抽取
       @Pointcut(value = "execution(* com.hqyj.gyq.aopDemo.User.add(..))")
        public void pointDemo() {
}
// 前置通知, @Before注解表示作为前置通知 void 方法名(参数列表)
       @Before(value = "pointDemo()") //使用相同切入点
        public void before(){
        System.out.println("before()方法");
}
        @AfterReturning(value = "execution(* com.hqyj.gyq.aopDemo.User.add(..))")
         public void afterReturning(){
         System.out.println("afterReturning()方法");
}
// 方法执行之后执行(可以理解异常中的finally,不管目标方法是否正常执行,after修饰的方法都
会执行)
        @After(value = "execution(* com.hqyj.gyq.aopDemo.User.add(..))")
         public void after(){
         System.out.println("after()方法");
}
// 异常通知
         @AfterThrowing(value = "execution(* com.hqyj.gyq.aopDemo.User.add(..))")
          public void afterThrowing(){
          System.out.println("afterThrowing()方法");
}
// 环绕通知:方法之前和之后都会执行
          @Around(value = "execution(* com.hqyj.gyq.aopDemo.User.add(..))")
           public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable
{
           System.out.println("环绕之前");
// 被增强的方法执行
           proceedingJoinPoint.proceed();
           System.out.println("环绕之后");
}
}

//创建测试类
public class AopTest {
       public static void main(String[] args) {
              ApplicationContext ac = new
              ClassPathXmlApplicationContext("applicationContext.xml");
              User user = ac.getBean(User.class);
              user.add();
           }
}

//在配置文件中开启扫描
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="包路径">
</context:component-scan>
<!--开启Aspect生成代理对象
proxy-target-class:
false(默认值):默认使用jdk动态代理,不满足则切换为CGLIB代理
true:使用CGLIB代理
-->
<aop:aspectj-autoproxy proxy-target-class="false"></aop:aspectj-autoproxy>
</beans>

xml方式

//创建book类
public class Book {
       public void buy(){
              System.out.println("Book.buy()");
     }
}

//创建代理类(增强类)
public class BookProxy {
       public void a(){
              System.out.println("BookProxy.before(a)");
    }
}

//配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<bean id="book" class="com.hqyj.gyq.aopDemo.Book"></bean>
<bean id="bookProxy" class="com.hqyj.gyq.aopDemo.BookProxy"></bean>
<!--aop配置-->
<aop:config>
<!-- 配置切入点(被增强的方法) -->
<aop:pointcut id="p"expression="execution(*com.hqyj.gyq.aopDemo.Book.buy(..))"/>
<aop:aspect ref="bookProxy">
<aop:before method="a" pointcut-ref="p"></aop:before>
</aop:aspect>
</aop:config>
</beans>

//测试类
public class AopTest {
       public static void main(String[] args) {
              ApplicationContext ac = new
              ClassPathXmlApplicationContext("applicationContext.xml");
              Book b = (Book) ac.getBean("book");
              b.buy();
                  }
}
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值