Bootstrap

Spring框架的核心组件——Aop

一 、AOP

1、什么是AOP

(1)面向切面编程(Aspect Oriented Programming,AOP),利用AOP 可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

(2)通俗描述:不通过修改源代码方式,在主干功能里面添加新功能

2、AOP的类型
  • 静态代理:代码间的耦合性降低,但是如果代理的类过多,代码量将会大大增加。(直接定义一个代理类,使用set方法或构造方法将目标类传入代理类,然后再对方法进行扩展,其中代理类和目标类都实现同一个接口)——参考博客:静态代理
  • 动态代理:一个动态代理类代理的是一个接口,一般对应一类业务,可以代理多个类,只要实现了同一个接口即可。
3、AOP(底层原理)

AOP 底层使用动态代理

(1)第一种有接口情况,使用JDK 动态代理
  • 被代理类必须实现接口,基于InvocationHandler、Proxy生成代理类。
  • JDK 动态代理主要涉及到java.lang.reflect 包中的:Proxy 类和InvocationHandler接口。JDK动态代理通过实现该接口定义横切逻辑,并通过反射机制调用目标类的代码,动态将横切逻辑和业务逻辑编制在一起。 Proxy 利用InvocationHandler 动态创建一个符合某一接口的实例,生成目标类的代理对象。
(2)第二种没有接口情况,使用CGLIB 动态代理
  • 代理类继承被代理类,被代理类不需要实现接口。
  • CGLib底层采用ASM字节码生成框架,使用字节码技术生成代理类,比使用Java反射效率要高。唯一需要注意的是,CGLib不能对声明为final的方法进行代理,因为CGLib原理是动态生成被代理类的子类。

参考博客:JDK和cglib实现

(3)spring两种代理方式的优缺点

1)若目标对象实现了若干接口,spring使用JDK的java.lang.reflect.Proxy类代理。

  • 优点:因为有接口,所以使系统更加松耦合
  • 缺点:为每一个目标类创建接口

2)若目标对象没有实现任何接口,spring使用CGLIB库生成目标对象的子类。

  • 优点:因为代理类与目标类是继承关系,所以不需要有接口的存在。CGLib底层采用ASM字节码生成框架,使用字节码技术生成代理类,比使用Java反射效率要高,相比于JDK的实现方式性能更好。

  • 缺点:因为没有使用接口,所以系统的耦合性没有使用JDK的动态代理好。

参考博客:字节码增强和Spring AOP原理

注意:
Spring中默认的策略是如果目标类是接口,则使用JDK 动态代理技术,否则使用Cglib 来生成代理。

4、AOP(术语)
(1)AOP中的名词

连接点(JointPoint):目标对象中所有可以增强的方法叫做连接点。

切入点(PointCut):目标对象中将要被增强的的方法。

通知(Advice):实际需要添加的逻辑部分。

关注点:是应用中一个模块的行为,一个关注点可能会被定义成一个我们想实现的一个功能。

横切关注点:部分关注点横切程序代码中的数个模块,即在多个模块中都有出现,它们即被称作横切关注点。如日志 , 安全 , 缓存 , 事务等等 ,这些功能往往横跨系统中的每个业务模块。

切面(Aspect): 对横切性关注点的抽象,即为切面。一个切面就是一个类。类里面的方法就是一个通知。

目标(Target):被通知对象。

代理(Proxy):将通知应用于目标对象后创建的对象。

Weavy(织入): 将通知应用到连接点的过程。

在这里插入图片描述
横切关注点,即哪些方法需要被拦截,执行什么样的逻辑。对关注点的抽象,即是切面。一个切面就是一个类,类里面的方法就是一个通知。有了切面和通知,就需要定义这些通知的切入点,换句话说,就是哪些方法需要被拦截,而这些被拦截的方法就是连接点,所谓的连接点就是被织入切面的方法。通知的执行就是织入的过程,而被织入这些通知的对象,就是目标对象。

(2) 五种通知(Advice)类型

Spring的AOP框架里的5种Advice(通知),在不改变原代码的情况下去添加新功能。
(1)前置通知[Before advice]:在连接点前面执行,前置通知不会影响连接点的执行,除非此处抛出异常。

(2)后置通知[After advice]:在连接点执行完成后执行,不管是正常执行完成,还是抛出异常,都会执行返回通知中的内容。

(3)环绕通知[Around advice]:环绕通知围绕在连接点前后,比如一个方法调用的前后。这是最强大的通知类型,能在方法调用前后自定义一些操作。环绕通知还需要负责决定是继续处理join point(连接点)(调用ProceedingJoinPointproceed方法)还是中断执行。

(4)正常返回通知[After returning advice]:在连接点正常执行完成后执行,如果连接点抛出异常,则不会执行。

(5)异常返回通知[After throwing advice]:在连接点抛出异常后执行。

5、AOP 的实现
(1) 使用Spring的API接口来实现 (Aspectj)

在这里插入图片描述
首先编写业务类及其接口实现类。

public interface UserService {
    void add();
    void delete();
}
public class UserServiceImpl implements UserService {
    public void add() {
        System.out.println("删除了一个用户");
    }
    public void delete() {
        System.out.println("增加了一个用户");
    }
}

然后去写增强类 , 编写一个前置增强, 一个后置增强。

import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;

public class Log implements MethodBeforeAdvice {

    public void before(Method method, Object[] objects, Object o) throws Throwable {
        System.out.println(o.getClass().getName()+"的"+method.getName()+"被执行了");

    }
}
import org.springframework.aop.AfterAdvice;
import org.springframework.aop.AfterReturningAdvice;
import java.lang.reflect.Method;

public class AfterLog implements AfterReturningAdvice {
    public void afterReturning(Object returnvalue, Method method, Object[] objects, Object o1) throws Throwable {
        System.out.println("执行了"+o1.getClass().getName()+method.getName()+"返回结果为"+returnvalue);
    }
}

最后去spring的applicationContext.xml中注册 , 并实现aop切入实现 , 注意导入aop约束.

<?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: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">

<!--方式一:aspictj织入-->
    <aop:config>
        <!--//定义切入点-->
        <aop:pointcut id=" pointcut" 
        expression="execution(* service.UserServiceImpl.*(..))"/>
        
        <!--将通知应用到切入点-->
        <aop:advisor advice-ref="log" pointcut-ref=" pointcut"/>
        <aop:advisor advice-ref="afterlog" pointcut-ref=" pointcut"/>
    </aop:config>
    
</beans>

注意:
切入点表达式
(1)切入点表达式作用:告诉对哪个类里面的哪个方法进行增强
(2)语法结构:
execution([权限修饰符] [返回类型] [类全路径] [方法名称]([参数列表]) )

测试:

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import service.UserService;

public class Mytest {

    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserService userService = 
        (UserService) context.getBean("userService");
        userService.add();
    }
}

结果:
service.UserServiceImpl的add被执行了
删除了一个用户
执行了service.UserServiceImpladd返回结果为null

直接使用Spring的API,相当于Before、after等通知方法,都是准备好了的,为我们省去了定义切面的过程,我们只需定义切入点即可,直接将通知方法织入到连接点中。

注:
当执行公共业务时,可以利用AOP将日志,安全等公共业务加入进去,这样程序员只需关注自己领域的业务即可。

(2)Aop的实现方式——自定义的方式实现

业务类保持不变,手写一个切入类作为切面。

public class DiyPointCut {
    public  void before(){
        System.out.println("===方法执行前===");
    }
    public  void after(){
        System.out.println("===方法执行后===");
    }
}

去spring的applicationContext.xml中注册

    <bean id="diy" class="DIY.DiyPointCut"/>
    
    <aop:config>
    
        	<!--定义切面-->
       		<aop:aspect ref="diy">
           
            <!--定义切入点-->
            <aop:pointcut id="point" 
            expression="execution(* service.UserServiceImpl.*(..))"/>
           
            <!--通知,此时的before都是自己定义的方法-->
            <aop:before method="before" pointcut-ref="point"/>
            <aop:after method="after" pointcut-ref="point"/>
            
            </aop:aspect>
    </aop:config>

测试:

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import service.UserService;


public class Mytest {

    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserService userService = (UserService) context.getBean("userService");
        userService.add();
    }
}

结果:
===方法执行前===
删除了一个用户
===方法执行后===

自定义的方式,可以让我们随意定义切面,以及切面里面的通知方法,比较灵活。

(3)注解的方式实现

编写一个注解实现的增强类

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

@Aspect  //标注一个切面
public class AnnotationPointCut {
    @Before("execution(* service.UserServiceImpl.*(..))")  //Before里面写入切入点
    public void before(){
        System.out.println("方法执行前");
    }

    @After("execution(* service.UserServiceImpl.*(..))")
    public void after(){
        System.out.println("方法执行后");
    }
    
    //在环绕增强中,给定一个参数,代表我们要获取处理切入的点
    @Around("execution(* service.UserServiceImpl.*(..))")
    public void around(ProceedingJoinPoint jp) throws Throwable {
        System.out.println("环绕前");
        //执行原方法
        Object proceed = jp.proceed();//ProceedingJoinPoint 连接点
        System.out.println("还绕后");
    }
}

在Spring配置文件中,注册bean,并增加支持注解的配置

    <!-- 注入注解类-->
    <bean id="annotationPointCut" class="DIY.AnnotationPointCut"/>
    
    <!--在spring 配置文件中开启生成代理对象-->
    <aop:aspectj-autoproxy/>

注:

  • <aop:aspectj-autoproxy proxy-target-class="true"/> 使用的是cglib的方式
  • <aop:aspectj-autoproxy proxy-target-class="false"/> 默认使用jdk的方式

测试:

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import service.UserService;


public class Mytest {

    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserService userService = 
        (UserService) context.getBean("userService");
        userService.add();
    }
}
结果:
环绕前
方法执行前
删除了一个用户
方法执行后
还绕后

注解方式,可以直接用注解定义切面,及通知方法,比较方便快捷一些,符合现在的编码风格。

注意上述AOP的使用必须加入依赖包:

<dependency> 
	<groupId> org.aspectj</groupId > 
	<artifactId> aspectjweaver</artifactId > 
	<version> 1.9.4</version > 
</dependency> 
6、Aop在Spring中的应用
  • 事务
  • 日志
  • 异常管理
  • 返回类型处理
  • 允许用户自定义切面

参考博客:https://blog.csdn.net/qq_33369905/article/details/105828920

;