Spring AOP
面向切面编程,目的是将那些与业务无关的,但是却为业务模块公用的(如日志记录)那些操作封装起来,达到减少系统重复代码,降低系统耦合度的效果。
应用场景:
底层实现原理
Spring 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