Spring框架AOP(面向切面编程)

本文是Spring框架AOP相关的知识点,欢迎阅读,学习一起进步。
初识Spring框架请参考:Spring框架基础
Spring-IOC框架请参考:IOC详解


一.AOP介绍

  • AOP(Aspect Oriented Programming)面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。

二.AOP作用

  • 利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
  • 通俗的说,就是在不修改原有业务逻辑的情况下,可以在指定的方法前后添加特定的业务逻辑。

三.AOP相关术语

在这里插入图片描述

  • 切面(Aspect):一个关注点的模块化,这个关注点可能会横切多个对象。事务管理是J2EE应用中一个关于横切关注点的很好的例子。在Spring AOP中,切面可以使用基于模式)或者基于@Aspect注解的方式来实现。
  • 连接点(Joinpoint):在程序执行过程中某个特定的点,比如某方法调用的时候或者处理异常的时候。在SpringAOP中,一个连接点总是表示一个方法的执行。
  • 通知(Advice):在切面的某个特定的连接点上执行的动作。其中包括了“around”、“before”和“after”等不同类型的通知(通知的类型将在后面部分进行讨论)。许多AOP框架(包括Spring)都是以拦截器做通知模型,并维护一个以连接点为中心的拦截器链。
  • 切入点(Pointcut):匹配连接点的断言。通知和一个切入点表达式关联,并在满足这个切入点的连接点上运行(例如,当执行某个特定名称的方法时)。切入点表达式如何和连接点匹配是AOP的核心:Spring缺省使用AspectJ切入点语法。
  • 引入(Introduction):用来给一个类型声明额外的方法或属性(也被称为连接类型声明 (inter-type declaration))。Spring允许引入新的接口(以及一个对应的实现)到任何被代理的对象。例如,你可以使用引入来使一个bean实现IsModified接口,以便简化缓存 机制。
  • 目标对象(Target Object): 被一个或者多个切面所通知的对象。也被称做被通知 (advised)对象。既然SpringAOP是通过运行时代理实现的,这个对象永远是一个被代理(proxied)对象。
  • AOP代理(AOP Proxy):AOP框架创建的对象,用来实现切面契约(例如通知方法执行等等)。在Spring中,AOP代理可以是JDK动态代理或者CGLIB代理。
  • 织入(Weaving):把切面连接到其它的应用程序类型或者对象上,并创建一个被通知的对象。这些可以在编译时(例如使用AspectJ编译器),类加载时和运行时完成。Spring和其他纯Java AOP框架一样,在运行时完成织入。

四.Spring AOP 基础知识

  • Spring的 AOP底层用到两种代理机制:
  • JDK 的动态代理:针对实现了接口的类产生代理。
  • CGlib 的动态代理:针对没有实现接口的类产生代理,应用的是底层的字节码增强的技术 生成当前类的子类对象

在这里插入图片描述

(1)JDK动态代理实现

  • 创建接口和对应实现类
public interface UserService {
 void login();
 void loginOut();
}
//实现类
public class UserServiceImpl implements  UserService {
    public void login() {
        System.out.println("login方法触发");
    }
    public void loginOut() {

        System.out.println("loginOut方法触发");
    }
} 
  • 创建动态代理类
public class PerformHandler implements InvocationHandler {
    private  Object target; //目标对象
    public  PerformHandler(Object target){
        this.target = target;
    }
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//本方法中的其他输出输入增强
        System.out.println("方法触发了");
        //执行被代理类 原方法
        Object invoke = method.invoke(target, args);
        System.out.println("执行完毕了");
        return invoke;
    }
}
  • 测试
@Test
public  void  test1(){
          //测试JDK动态代理技术
           UserService userService = new UserServiceImpl();
           PerformHandler performHandler = new PerformHandler(userService);
           userService = (UserService)
           Proxy.newProxyInstance(userService.getClass().getClassLoader(),
                userService.getClass().getInterfaces(),
                performHandler
           );
           userService.login();
         }
  • 测试结果: 在调用接口方法的前后都会添加代理类的方法!

(2) CGlib实现代理

  • 使用JDK创建代理有一个限制,它只能为接口创建代理实例.这一点可以从Proxy的接口方法newProxyInstance(ClassLoader loader,Class []interfaces,InvocarionHandler h)中看的很清楚第二个入参 interfaces就是需要代理实例实现的接口列表.
  • 对于没有通过接口定义业务方法的类,如何动态创建代理实例呢? JDK动态代理技术显然已经黔驴技穷,CGLib作为一个替代者,填补了这一空缺.
  • GCLib采用底层的字节码技术,可以为一个类创建子类,在子类中采用方法拦截的技术拦截所有父类方法的调用并顺势志入横切逻辑.

  • 创建CGLib代理器
public class CglibProxy  implements MethodInterceptor{
          private Enhancer enhancer = new Enhancer();
         
          //设置被代理对象
          public  Object getProxy(Class clazz){
            enhancer.setSuperclass(clazz);
            enhancer.setCallback(this);
            return  enhancer.create();
         }
         
          public Object intercept(Object obj, Method method,
                      Object[] objects,
                      MethodProxy methodProxy) throws Throwable {
            System.out.println("CGLig代理之前之前");
            Object invoke = methodProxy.invokeSuper(obj,objects);
            System.out.println("CGLig代理之前之后");
            return invoke;
         }
        }

测试

@Test
public  void test2(){
          //TODO CGlib实现
          CglibProxy cglibProxy = new CglibProxy();
          UserServiceImpl userService= (UserServiceImpl)
        cglibProxy.getProxy(UserServiceImpl.class);
          userService.login();
        }
测试结果: 在调用接口方法的前后都会添加代理类的方法!

在这里插入图片描述


五.Spring中基于AOP注解开发案例

  • 先创建UserService接口:
public interface UserService {
    void save();
    void delete();
    void update();
    void select();
}
  • 实现类
public class UserServiceImpl implements UserService {
    public void save() {
        System.out.println("保存用户");
    }
    public void delete() {
        System.out.println("删除用户");
    }
    public void update() {
        System.out.println("更新用户");
    }
    public void select() {
        System.out.println("查找用户");
    }
}
  • 增强类
public class MyAdivce {
    /**
     //前置通知:目标方法运行之前调用
     //后置通知(如果出现异常不会调用):在目标方法运行之后调用
     //环绕通知:在目标方法之前和之后都调用
     //异常拦截通知:如果出现异常,就会调用
     //后置通知(无论是否出现 异常都会调用):在目标方法运行之后调用
     */
//前置通知
    public void before(){
        System.out.println("这是前置通知");
    }
    //后置通知
    public void afterReturning(){
        System.out.println("这是后置通知(方法不出现异常)");
    }
    public Object around(ProceedingJoinPoint point) throws Throwable {
        System.out.println("这是环绕通知之前部分!!");
        Object object = point.proceed(); //调用目标方法
        System.out.println("这是环绕通知之后的部分!!");
        return object;
    }
    public void afterException(){
        System.out.println("异常通知!");
    }
    public void after(){
        System.out.println("这也是后置通知,就算方法发生异常也会调用!");
    }
}
  • 创建配置文件class-path路径下创建applicationContext-aop-annotation.xml
  • 配置目标对象,配置通知对象,开启注解
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns="http://www.springframework.org/schema/beans"
        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-4.2.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context-4.2.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop-4.2.xsd ">
<bean name="userService" class="com.it.spring.service.UserServiceImpl" />
<!--配置通知对象-->
<bean name="myAdvice" class="com.it.spring.aop.MyAdivce" />
<!-- 配置将增强织入目标对象 使用注解的方式-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
  • 修改增强类MyAdvice
  • 在类上添加 @Aspect 注解
  • 可以将 execution 直接定义在通知方法名上,如下:
//后置通知
@AfterReturning("execution(* com..service.*ServiceImpl.*(..))")
public void afterReturning(){
System.out.println("这是后置通知(如果出现异常不会调用)!!");
}
  • 也可以定义一个方法, 如下面的 pc() 方法,然后在通知方法上指定 @Before(“MyAdvice.pc()”)
@Pointcut("execution(* com..service.*ServiceImpl.*(..))")
public void pc(){}
//前置通知
//指定该方法是前置通知,并制定切入点
@Before("MyAdvice.pc()")
public void before(){
        System.out.println("这是前置通知!!");
        }
  • 完整定义增强类

@Aspect
public class MyAdivce {
    /**
     //前置通知:目标方法运行之前调用
     //后置通知(如果出现异常不会调用):在目标方法运行之后调用
     //环绕通知:在目标方法之前和之后都调用
     //异常拦截通知:如果出现异常,就会调用
     //后置通知(无论是否出现 异常都会调用):在目标方法运行之后调用
     */
    @Pointcut("execution(* com.itqf.spring.service.*ServiceImpl.*(..))")
    public void pc(){
    }
    //前置通知
    @Before("MyAdivce.pc()")
    public void before(){
        System.out.println("这是前置通知");
    }
    //后置通知
    @AfterReturning("execution(* com..*ServiceImpl.*(..))")
    public void afterReturning(){
        System.out.println("这是后置通知(方法不出现异常)");
    }
    public Object around(ProceedingJoinPoint point) throws Throwable {
        System.out.println("这是环绕通知之前部分!!");
        Object object = point.proceed(); //调用目标方法
        System.out.println("这是环绕通知之后的部分!!");
        return object;
    }
    public void afterException(){
        System.out.println("异常通知!");
    }
    public void after(){
        System.out.println("这也是后置通知,就算方法发生异常也会调用!");
    }
}

  • The best investment for young people is to invest in yourself
    在这里插入图片描述
  • 2020.03.26 来自辰兮的第37篇博客
发布了41 篇原创文章 · 获赞 191 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/weixin_45393094/article/details/105116401
今日推荐