Spring AOP 面向切面编程

在这里插入图片描述

Spring AOP

面向切面编程,目的是将那些与业务无关的,但是却为业务模块公用的(如日志记录)那些操作封装起来,达到减少系统重复代码,降低系统耦合度的效果。

应用场景:

  • 日志记录
  • 权限控制
  • 事务管理

底层实现原理

  • JDK动态代理——前提是类要实现接口
  • CGLIB代理

Spring AOP和AspectJ的关系

  • 两者都是为了实现AOP,AOP是基于动态代理,而AspectJ是基于字节码的操作
  • AOP是运行时增强
  • AspectJ是编译时增强

如果切面不多,两者并没有太大的性能差异
如果切面多,AspectJ会快很多


AOP中的一些概念

通知

在方法执行的什么时候做什么

  • 前置通知——方法执行前要执行的操作
  • 后置通知——方法执行后要执行的操作
  • 返回通知——方法执行成功后要执行的操作
  • 异常通知——方法抛出异常后要执行的操作
  • 环绕通知——方法执行前后要执行自定义操作
连接点

可以是调用方法时,也可以是抛出异常后,总是方法的前前后后,都是连接点

切入点

如果一个类有10个方法,那么就有10个切入点

切面

通知+切入点的结合,通俗点说就是 通知说明什么时候做+做什么,切入点说明在哪里干

织入

把切面加入到对象,并创建出代理对象的过程。(由 Spring 来完成)



举例

为了更方便说明AOP的概念,这里举一个生活中的例子
在这里插入图片描述
可以看到,包租公的主要业务是签合同,收租,那么这就足够了,紫色框起来的部分都是重复的事,交给中介商就好了。
这就是AOP的思想:核心业务代码和关注点代码分离

实际代码

不能光说不练,下面来感受下实际代码

基于XMl开发的Demo

1.导入依赖

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.0.7.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.5</version>
        </dependency>
    </dependencies>

2.在pojo包下创建一个Landlord类(地主≈包租公)

package jay.pojo;

import org.springframework.stereotype.Component;

/**
 * @author Jaychan
 * @date 2020/7/10
 * @description TODO
 */

@Component("landlord")
public class Landlord {

 	  public void service(){
        //仅仅实现了核心的业务功能
        System.out.println("签合同");
        System.out.println("收房租");
        
    }
}

3.在aspect包下创建Broker类(中介商)

package jay.aspect;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;



@Component
@Aspect
class Broker {

	//告诉spring要拦截的方法
	//并自定义了一个切点,以减少表达式的出现次数,避免代码冗余
    @Pointcut("execution(* jay.pojo.Landlord.service())")
    public void lService() {
    }

    @Before("lService()")
    public void before() {
        System.out.println("带租客看房");
        System.out.println("谈价格");
    }

    @After("lService()")
    public void after() {
        System.out.println("交钥匙");
    }
}

4.在applicationContext.xml中配置自动注入,告诉ioc容器去哪里扫描这两个bean

<?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/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

    <context:component-scan base-package="aspect" />
    <context:component-scan base-package="pojo" />

    <aop:aspectj-autoproxy/>
</beans>

5.编写测试类

package test;

import org.springframework.context.support.ClassPathXmlApplicationContext;
import pojo.Landlord;

/**
 * @author Jaychan
 * @date 2020/7/10
 * @description TODO
 */
public class TestSpring {

    public static void main(String[] args) {

        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");

        Landlord landlord = context.getBean("landlord", Landlord.class);
        
        landlord.service();

    }

}

6.运行结果

七月 10, 2020 3:19:52 下午 org.springframework.context.support.AbstractApplicationContext prepareRefresh
信息: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@23ab930d: startup date [Fri Jul 10 15:19:52 CST 2020]; root of context hierarchy
七月 10, 2020 3:19:52 下午 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
信息: Loading XML bean definitions from class path resource [applicationContext.xml]
带租客看房
谈价格
签合同
收房租
交钥匙

Process finished with exit code 0




基于注解开发的Demo


第一步:选择连接点

Spring是方法级别的AOP框架,可以具体到某个类的某个方法作为连接点。另一种说法是:选择那个类的那个方法来进行功能增强。

@Component("landlord")
public class Landlord {


    public void service(){
        //仅仅实现了核心的业务功能
        System.out.println("签合同");
        System.out.println("收房租");
    }

}

我们就选Landlord的service()方法来作为连接点



第二步:创建切面

选了好了连接点,就可以创建切面了。我们可以把切面理解成一个拦截器,当程序运行到连接点的时候,就会被拦截下来,在开头执行一个初始化方法,在结尾也加入一个销毁的方法。
在Spring中,只要使用**@Aspect**注解一个类,那么SpringIoc容器就会认为这是一个切面了,同时也要记得使用 @component 标签,加到Spring的Ioc容器里。

package jay.aspect;


import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;



@Component
@Aspect
class Broker {

    @Pointcut("execution(* jay.pojo.Landlord.service())")
    public void lService() {
    }

    @Before("lService()")
    public void before() {
        System.out.println("带租客看房");
        System.out.println("谈价格");
    }

    @After("lService()")
    public void after() {
        System.out.println("交钥匙");
    }
}


第三步:定义切点

在上面的注解中定义了 execution 的正则表达式,Spring 通过这个正则表达式判断具体要拦截的是哪一个类的哪一个方法:

execution(* jay.pojo.Landlord.service())

依次对这个表达式作出分析:

execution:代表执行方法的时候会触发

* :代表任意返回类型的方法
jay.pojo.Landlord:代表类的全限定名
service():被拦截的方法名称



第四步:编写aopconfig配置类

该类主要作用是
使用JDK动态代理
告诉SpringIoc容器该扫描那个包下的类

@Configuration
@ComponentScan("jay")
@EnableAspectJAutoProxy   //(proxyTargetClass = true) 
						 //如果为true 就会使用cglib实现
public class AopConfig {
}


第五步:编写测试类

package jay;

import jay.config.AopConfig;
import jay.pojo.Landlord;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

/**
 * @author Jaychan
 * @date 2020/7/10
 * @description TODO
 */
public class TestSpring {

    public static void main(String[] args) {

        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AopConfig.class);

        Landlord bean = context.getBean(Landlord.class);

        bean.service();

    }
}

输出结果

七月 10, 2020 3:26:13 下午 org.springframework.context.support.AbstractApplicationContext prepareRefresh
信息: Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@2d38eb89: startup date [Fri Jul 10 15:26:13 CST 2020]; root of context hierarchy
带租客看房
谈价格
签合同
收房租
交钥匙

Process finished with exit code 0
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值