Spring AOP学习笔记

学习视频:8001 Spring AOP概述_哔哩哔哩_bilibili8006 基于注解的AOP实现_哔哩哔哩_bilibili

目录

1.AOP概述

Spring AOP术语

连接点(Joinpoint)

切入点(Pointcut)

通知/增强处理(Advice)

切面(Aspect)

目标对象(Target)

织入(Weaving)

代理(Proxy)

引介(Introduction)

2.Spring AOP的实现机制

JDK动态代理 

CGlib代理

3.基于XML的AOP实现

配置切面

配置切入点

Spring AOP切入点表达式的基本格式

配置通知

        在Spring的配置文件中,使用元素配置了5种常用通知,分别为前置通知、后置通知、环绕通知、返回通知和异常通知。

4.基于注解的AOP实现


1.AOP概述

        AOP的全称是Aspect Oriented Programming,即面向切面编程。和OOP不同,AOP主张将程序中相同的业务逻辑进行横向隔离,并将重复的业务逻辑抽取到一个独立的模块中,以达到提高程序可重用性和开发效率的目的。        

        在传统的业务处理代码中,通常都会进行事务处理、日志记录等操作。虽然使用OOP可以通过组合或者继承的方式来达到代码的重用,但如果要实现某个功能(如日志记录),同样的代码仍然会分散到各个方法中。

未使用AOP的面向切面编程案例

          例如,订单系统中有添加订单信息、更新订单信息和删除订单信息3个方法,这3个方法中都包含事务管理业务代码,订单系统的逻辑如图所示。


Spring AOP术语

         AOP并不是一个新的概念,AOP中涉及很多术语,如切面、连接点、切入点、通知/增强处理、目标对象、织入、代理和引介等,下面针对AOP的常用术语进行简单介绍。

 

连接点(Joinpoint)

         连接点是程序执行过程中某个特定的节点,例如,某方法调用时或处理异常时。在Spring AOP中,一个连接点通常是一个方法的执行。        

所以UserService中的四个方法都能叫做连接点


切入点(Pointcut)

        当某个连接点满足预先指定的条件时,AOP就能够定位到这个连接点,在连接点处插入切面,该连接点也就变成了切入点

要对save和delete方法做增强,所以切入点是这两个方法


通知/增强处理(Advice)

          通知/增强处理就是插入的切面程序代码。可以将通知/增强处理理解为切面中的方法,它是切面的具体实现

要做什么样的增强,这里是做日志输出的增强,所以Advice是printLog()方法


切面(Aspect)

         切面是指关注点形成的类(关注点是指类中重复的代码),通常是指封装的、用于横向插入系统的功能类(如事务管理、日志记录等)。在实际开发中,该类被Spring容器识别为切面,需要在配置文件中通过<bean>元素指定。

这里比如输出日志到底是在save之前输出还是之后输出,配置这个关系的文件就是切面


目标对象(Target)

          目标对象是指被插入切面的方法,即包含主业务逻辑的类对象。或者说是被一个或者多个切面所通知的对象。

比如UserService对象就是目标对象,它的save是要增强的方法


织入(Weaving)

        将切面代码插入到目标对象上,从而生成代理对象的过程。织入可以在编译时,类加载时和运行时完成。在编译时进行织入就是静态代理,而在运行时进行织入则是动态代理。

比如这些关系给到Spring底层如何做的这个就叫织入


代理(Proxy)

        将通知应用到目标对象之后,程序动态创建的通知对象,就称为代理。代理类既可能是和原类具有相同接口的类,也可能是原类的子类,可以采用调用原类相同的方式调用代理类。

比如给这个save方法增强后生成的对象


引介(Introduction)

        引介是一种特殊的通知,它可为目标对象添加一些属性和方法。这样,即使一个业务类原本没有实现某一个接口,通过AOP的引介功能,也可以动态地为该业务类添加接口的实现逻辑,让业务类成为这个接口的实现类。        


2.Spring AOP的实现机制

Spring AOP的默认代理方式

        默认情况下,Spring AOP使用JDK动态代理,JDK动态代理是通过java.lang.reflect.Proxy 类实现的,可以调用Proxy类的newProxyInstance()方法创建代理对象。JDK动态代理可以实现无侵入式的代码扩展,并且可以在不修改源代码的情况下,增强某些方法。

JDK动态代理 

代码实现

public interface UserDao {

    public void saveUser();

    public void deleteUser();

}
public class UserDaoImpl implements UserDao{
    @Override
    public void saveUser() {
        System.out.println("保存用户信息");
    }

    @Override
    public void deleteUser() {
        System.out.println("删除用户");
    }
}
public class MyAspect {

        public void check_permission()
        {
            System.out.println("模拟检查权限...");
        }

        public void log()
        {
            System.out.println("模拟日志输入出...");
        }
}

public class MyProxy implements InvocationHandler {
    private UserDao userDao;
    public  Object createProxy(UserDao userDao) {
        this.userDao = userDao;
        //创建jdk的动态代理对象需要3个参数 ,类加载器,被代理对象实现的接口,处理器(增强功能的具体实现)
        ClassLoader classLoader = MyProxy.class.getClassLoader();
        Class<?>[] interfaces = userDao.getClass().getInterfaces();
        //创建动态代理对象
       Object proxy= Proxy.newProxyInstance(classLoader, interfaces, this);
        return  proxy;

    }
    /**
     *
     * @param proxy 代理对象本身
     *
     * @param method 将要增强的方法

     * @param args  方法的参数
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{
        //创建增强类对象
        MyAspect myAspect=new MyAspect();
        myAspect.check_permission();

        //保证原有功能的执行
         Object object=method.invoke(userDao,args);

        //日志增强
        myAspect.log();


        return object;

    }


}
public class JdkProxyTest {

    public static void main(String[] args) {
            MyProxy myProxy=new MyProxy();
            UserDao userDao=new UserDaoImpl();
             UserDao  proxyUserDao=(UserDao)myProxy.createProxy(userDao);
             proxyUserDao.saveUser();
             proxyUserDao.deleteUser();
    }

}


CGlib代理

JDK与CGLib动态代理的比较

         JDK动态代理存在缺陷,它只能为接口创建代理对象,当需要为类创建代理对象时,就需要使用CGLib(Code Generation Library)动态代理,CGLib动态代理不要求目标类实现接口,它采用底层的字节码技术,通过继承的方式动态创建代理对象。Spring的核心包已经集成了CGLib所需要的包,所以开发中不需要另外导入JAR包。


public class UserDao2 {

    public void saveUser()
    {
        System.out.println("userDao2中的保存用户");
    }
    public void deleteUser()
    {
        System.out.println("userDao2中的删除用户");
    }



}
import com.it.demo01.MyAspect;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class MyCglibProxy implements MethodInterceptor {

    public Object createProxy(Object target) {
        //被代理对象的类
        Enhancer enhancer = new Enhancer();
        //被代理对象的类
        enhancer.setSuperclass(target.getClass());
        //增强的具体代码实现
        enhancer.setCallback(this);
        return enhancer.create();
    }




    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        MyAspect myAspect = new MyAspect();
        //增强的功能
        myAspect.check_permission();

        //保证原有功能的执行
        methodProxy.invokeSuper(o, objects);

        //增强的功能
        myAspect.log();

        return null;


    }
    
    
}
public class CglibTest {
    public static void main(String[] args) {
        MyCglibProxy cglibProxy=new MyCglibProxy();
        //被代理对象
        UserDao2 userDao2=new UserDao2();

     UserDao2 userDao21=(UserDao2)cglibProxy.createProxy(userDao2);
        userDao21.saveUser();
        userDao21.deleteUser();


    }


}


3.基于XML的AOP实现

使用AOP代理对象的好处

        因为Spring AOP中的代理对象由IoC容器自动生成,所以开发者无须过多关注代理对象生成的过程,只需选择连接点、创建切面、定义切点并在XML文件中添加配置信息即可。 Spring提供了一系列配置Spring AOP的XML元素。

配置Spring AOP的XML元素

配置切面

         在Spring的配置文件中,配置切面使用的是<aop:aspect>元素,该元素会将一个已定义好的Spring Bean转换成切面Bean,因此,在使用<aop:aspect>元素之前,要在配置文件中先定义一个普通的Spring Bean。Spring Bean定义完成后,通过<aop:aspect>元素的ref属性即可引用该Bean。配置<aop:aspect>元素时,通常会指定idref两个属性。

<aop:aspect>元素的id属性和ref属性的描述

配置切入点

        在Spring的配置文件中,切入点是通过<aop:pointcut>元素来定义的。当<aop:pointcut>元素作为<aop:config>元素的子元素定义时,表示该切入点是全局的,它可被多个切面共享;当<aop:pointcut>元素作为<aop:aspect>元素的子元素时,表示该切入点只对当前切面有效。定义<aop:pointcut>元素时,通常会指定id、expression属性。

<aop:pointcut>元素的id属性和expression属性描述

Spring AOP切入点表达式的基本格式

        execution(modifiers-pattern?ret-type-pattern

        declaring-type-pattern?

        name-pattern(param-pattern) throws-pattern?)

execution表达式各部分参数说明  

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

配置通知

        在Spring的配置文件中,使用<aop:aspect>元素配置了5种常用通知,分别为前置通知、后置通知、环绕通知、返回通知和异常通知


 <aop:aspect>元素的常用属性


代码实现

public interface UserDao {
    public void save();

    public void delete();

    public void findById();

    public void update();



}
public class UserDaoImpl implements   UserDao{


    @Override
    public void save() {
        System.out.println("保存用户信息");

    }

    @Override
    public void delete() {
        System.out.println("删除用户信息");
    }

    @Override
    public void findById() {
        System.out.println("根据id查询用户信息");
    }

    @Override
    public void update() {
        System.out.println("更新用户信息");
    }
}
public class XmlAdvice {

    /**
     *
     * @param joinPoint 内部封装了切入点的相关信息
     */
    public void before(JoinPoint joinPoint)
    {
        System.out.println("前置通知");
        System.out.println("目标类:"+joinPoint.getTarget());
        System.out.println("切入点的方法名:"+joinPoint.getSignature().getName());


    }


}
    <context:component-scan base-package="com.it"></context:component-scan>

    <bean id="userDao" class="com.it.demo03.UserDaoImpl"></bean>
    <!--增强类所在的对象,切面类对象-->
    <bean id="xmlAdvice" class="com.it.demo03.XmlAdvice"></bean>
   <!-- aop配置根标签,声明开始aop配置-->
    <!--在切面中配置增强和切入点的关系-->
    <aop:config >
       <!-- 全局的切入点-->
        <aop:pointcut id="pointcut" expression="execution(* com.it.demo03.UserDaoImpl.*(..))"></aop:pointcut>
        <!--配置切面-->
        <aop:aspect ref="xmlAdvice">
           <!-- 配置关系-->
            <aop:before method="before" pointcut-ref="pointcut"></aop:before>

        </aop:aspect>

    </aop:config>


public class XmlAopTest {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
       UserDao userDao=(UserDao)context.getBean("userDao");
        userDao.delete();
        userDao.findById();
        userDao.save();
        userDao.update();




    }
}


    public void after(JoinPoint joinPoint)
    {
        System.out.println("后置通知");

    }

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

 

这个时候在save()方法里搞个int i=1/0 测试的时候save方法会抛异常但是还是会执行,但是save后面的update方法没法执行了


  <aop:after-returning method="after_returning" pointcut-ref="pointcut"></aop:after-returning>

这个是返回通知,返回值结束才会执行(如果出行异常,不会执行) 

 这个的话就是异常通知——出现异常才会执行


环绕通知

 public Object around(ProceedingJoinPoint proceedingJoinPoint)
    {
        System.out.println("自定义环绕通知1111,在切入点之前执行");
        //执行原有功能
        Object res= null;
        try {
            res = proceedingJoinPoint.proceed();
        } catch (Throwable e) {
           e.printStackTrace();
        }


        System.out.println("自定义环绕通知2222,在切入点之后执行");
        //返回原有方法的返回值
        return res;

    }
   <aop:around method="around" pointcut-ref="pointcut"></aop:around>


4.基于注解的AOP实现

Spring AOP的注解

public interface UserDao {
    public void save();

    public void delete();

    public void findById();

    public void update();



}
public class UserDaoImpl implements UserDao {


    @Override
    public void save() {
        System.out.println("保存用户信息");

    }

    @Override
    public void delete() {
        System.out.println("删除用户信息");
    }

    @Override
    public void findById() {
        System.out.println("根据id查询用户信息");
    }

    @Override
    public void update() {
        System.out.println("更新用户信息");
    }
}

 

@Aspect
public class AnnoAdvice {
    @Pointcut("execution(* com.it.demo04.UserDaoImpl.*(..))")
    public void pointcut()  {


    }


    /**
     *
     * @param joinPoint 内部封装了切入点的相关信息
     */
    @Before("pointcut()")
    public void before(JoinPoint joinPoint)
    {
        System.out.println("前置通知2");
        System.out.println("目标类:"+joinPoint.getTarget());
        System.out.println("切入点的方法名:"+joinPoint.getSignature().getName());


    }
    @After("pointcut()")
    public void after(JoinPoint joinPoint)
    {
        System.out.println("后置通知2");

    }
    @Around("pointcut()")
    public Object around(ProceedingJoinPoint proceedingJoinPoint)
    {
        System.out.println("自定义环绕通知1111,在切入点之前执行2");
        //执行原有功能
        Object res= null;
        try {
            res = proceedingJoinPoint.proceed();
        } catch (Throwable e) {
           e.printStackTrace();
        }


        System.out.println("自定义环绕通知2222,在切入点之后执行2");
        //返回原有方法的返回值
        return res;

    }
        @AfterReturning("pointcut()")
        public void after_returning()
        {
            System.out.println("返回通知,返回值结束才会执行(如果出现异常,不会执行2)");
        }
        @AfterThrowing("pointcut()")
        public void after_throwing()
        {
            System.out.println("异常通知,(出现异常才会执行2)");
        }



}
public class AnnotationAopTest {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
       UserDao userDao=(UserDao)context.getBean("userDao2");
        userDao.delete();
        userDao.findById();
        userDao.save();
        userDao.update();
        
    }
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小吴有想法

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值