您的当前位置:首页正文

02:spring之AOP

2024-11-29 来源:个人技术集锦

一:AOP 简介

1:AOP的概念

AOP,Aspect Oriented Programming,面向切面编程,是对面向对象编程OOP的升华。OOP是纵向对一个事物的抽象,一个对象包括静态的属性信息,包括动态的方法信息等。而AOP是横向的对不同事物的抽象,属性与属性、方法与方法、对象与对象都可以组成一个切面,而用这种思维去设计编程的方式叫做面向切面编程。

2:AOP思想的实现方案

动态代理技术,在运行期间,对目标对象的方法进行增强,代理对象同名方法内可以执行原有逻辑的同时嵌入执行其他增强逻辑或其他对象的方法。

3:

学习BeanPostProcessor时,在BeanPostProcessor的after方法中使用动态代理对Bean进行了增强,实际存储到单例池singleObjects中的不是当前目标对象本身,而是当前目标对象的代理对象Proxy,这样在调用目标对象方法时,实际调用的是代理对象Proxy的同名方法,起到了目标方法前后都进行增强的功能,对该方式进行一下优化,将增强的方法提取出去到一个增强类中,且只对com.zgs.service.impl包下的任何类的任何方法进行增强。

  • SpringConfig
@Configuration
@ComponentScan(basePackages = "com.zgs")
public class SpringConfig {
}
  • ApplicationContextTest
public class ApplicationContextTest {
    public static void main(String[] args) {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
//        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
        UserService userService = applicationContext.getBean(UserService.class);
        userService.show1();
    }
}
  • MyAdvice
@Component
public class MyAdvice {
    public void before() {
        System.out.println("MyAdvice的before方法执行.....");
    }

    public void after() {
        System.out.println("MyAdvice的after方法执行.....");
    }
}
  • MockAopBeanPostProcessor
@Component
public class MockAopBeanPostProcessor implements BeanPostProcessor, ApplicationContextAware {
    private ApplicationContext applicationContext;

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if (bean.getClass().getPackage().getName().equals("com.zgs.service.impl")) {
            //对bean进行动态代理增强-生成bean的代理类
            Object proxyBean = Proxy.newProxyInstance(bean.getClass().getClassLoader(),
                    bean.getClass().getInterfaces(),
                    (Object proxy, Method method, Object[] args) -> {
                        //拦截目标方法,调用目标方法,并做增强
                        //前置增强
                        MyAdvice myAdvice = applicationContext.getBean(MyAdvice.class);
                        myAdvice.before();
                        //调用目标方法
                        Object obj = method.invoke(bean, args);
                        //调用后置增强
                        myAdvice.after();
                        return obj;
                    });
            return proxyBean;
        }
        return bean;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}
  • UserService
public interface UserService {
    void show1();
    void show2();
}
  • UserServiceImpl
@Component
public class UserServiceImpl implements UserService {
    @Override
    public void show1() {
        System.out.println("UserServiceImpl的show1方法执行");
    }

    @Override
    public void show2() {
        System.out.println("UserServiceImpl的show2方法执行");
    }
}
  • applicationContext.xml
<?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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       https://www.springframework.org/schema/context/spring-context.xsd">


    <bean id="userService" class="com.zgs.service.impl.UserServiceImpl"></bean>
    <bean id="userDao" class="com.zgs.dao.impl.UserDaoImpl"></bean>
    <bean id="myAdvice" class="com.zgs.advice.MyAdvice"></bean>
    <bean id="mockAopBeanPostProcessor" class="com.zgs.post.MockAopBeanPostProcessor"></bean>
</beans>
  • 输出结果

4:AOP相关概念

二:基于xml配置的AOP

1:xml方式AOP快速入门

1.1:AOP基础代码问题

  • 被增强的包名在代码写死了
  • 通知对象的方法在代码中写死了

1.2:通过配置文件的方式去解决上述问题

  • 配置哪些包、哪些类、哪些方法需要被增强
  • 配置目标方法要被哪些通知方法所增强,在目标方法执行之前还是之后执行增强

1.3:xml方式配置AOP的步骤:

  • 导入AOP相关坐标;
<dependency>
   <groupId>org.aspectj</groupId>
   <artifactId>aspectjweaver</artifactId>
   <version>1.9.6</version>
</dependency>
  • 准备目标类、准备增强类,并配置给Spring管理;
  • 配置切点表达式(哪些方法被增强);
  • 配置织入(切点被哪些通知方法增强,是前置增强还是后置增强)。

1.4:代码

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


    <!--目标类-->
    <bean id="userService" class="com.zgs.service.impl.UserServiceImpl"></bean>

    <!--通知类-->
    <bean id="myAdvice" class="com.zgs.advice.MyAdvice"></bean>

    <!--aop配置-->
    <aop:config>
        <!--配置切点表达式 目的知道那些方法需要被增强-->
        <aop:pointcut id="myPointCut" expression="execution(void com.zgs.service.impl.UserServiceImpl.show1())"/>
        <aop:pointcut id="myPointCut2" expression="execution(void com.zgs.service.impl.UserServiceImpl.show2())"/>
        <!--配置织入 目的知道那些方法需要被增强-->
        <aop:aspect ref="myAdvice">
            <aop:before method="beforeAdvice" pointcut-ref="myPointCut"/>
            <aop:after method="afterAdvice" pointcut-ref="myPointCut"/>
            <aop:before method="beforeAdvice" pointcut-ref="myPointCut2"/>
            <aop:after method="afterAdvice" pointcut="execution(void com.zgs.service.impl.UserServiceImpl.show2())"/>
        </aop:aspect>
    </aop:config>

<!--    <bean id="userDao" class="com.zgs.dao.impl.UserDaoImpl"></bean>-->
<!--    <bean id="mockAopBeanPostProcessor" class="com.zgs.post.MockAopBeanPostProcessor"></bean>-->
</beans>
  • ApplicationContextTest
public class ApplicationContextTest {
    public static void main(String[] args) {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
//        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
        UserService userService = applicationContext.getBean(UserService.class);
        userService.show1();
        userService.show2();
    }
}
  • MyAdvice
@Component
public class MyAdvice {
    public void beforeAdvice() {
        System.out.println("MyAdvice的before增强方法执行.....");
    }

    public void afterAdvice() {
        System.out.println("MyAdvice的after增强方法执行.....");
    }
}
  • 结果

三:xml方式AOP配置详解

1.1:AOP详细配置的细节:

  • 切点表达式的配置方式
  • 切点表达式的配置语法
  • 通知的类型
  • AOP的配置的两种方式

1.2:两种切点表达式的配置方式

直接将切点表达式配置在通知上,

也可以将切点表达式抽取到外面,在通知上进行引用

1.3:切点表达式语法

切点表达式是配置要对哪些连接点(哪些类的哪些方法)进行通知的增强,语法如下:
execution([访问修饰符]返回值类型 包名.类名.方法名(参数))
其中
  • 访问修饰符可以省略不写;
  • 返回值类型、某一级包名、类名、方法名 可以使用 * 表示任意;
  • 包名与类名之间使用单点 . 表示该包下的类,使用双点 .. 表示该包及其子包下的类;
  • 参数列表可以使用两个点 .. 表示任意参数。

1.3.1:切点表达式例子

//表示访问修饰符为public、无返回值、在com.zgs.aop包下的TargetImpl类的无参方法show
execution(public void com.zgs.aop.TargetImpl.show())
//表述com.zgs.aop包下的TargetImpl类的任意方法
execution(* com.zgs.aop.TargetImpl.*(..))
//表示com.zgs.aop包下的任意类的任意方法
execution(* com.zgs.aop.*.*(..))
//表示com.zgs.aop包及其子包下的任意类的任意方法
execution(* com.zgs.aop..*.*(..))
//表示任意包中的任意类的任意方法
execution(* *..*.*(..))

1.4:AspectJ的通知五种类型

通知方法在被调用时,Spring可以为其传递一些必要的参数

1.4.1:JoinPoint 对象

public void 通知方法名称(JoinPoint joinPoint){
//获得目标方法的参数
System.out.println(joinPoint.getArgs());
//获得目标对象
System.out.println(joinPoint.getTarget());
//获得精确的切点表达式信息
System.out.println(joinPoint.getStaticPart());
}

1.4.2:ProceedingJoinPoint对象

public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println(joinPoint.getArgs());//获得目标方法的参数
System.out.println(joinPoint.getTarget());//获得目标对象
System.out.println(joinPoint.getStaticPart());//获得精确的切点表达式信息
Object result = joinPoint.proceed();//执行目标方法
return result;//返回目标方法返回值
}

1.4.3:Throwable对象

public void afterThrowing(JoinPoint joinPoint,Throwable th){
//获得异常信息
System.out.println("异常对象是:"+th+"异常信息是:"+th.getMessage());
}

1.4.4:五种通知类型的代码演示

ApplicationContextTest
public class ApplicationContextTest {
    public static void main(String[] args) {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
//        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
        UserService userService = applicationContext.getBean(UserService.class);
        userService.show1();
        System.out.println("========================================");
        userService.show2();
    }
}
UserServiceImpl
@Component
public class UserServiceImpl implements UserService {
    @Override
    public void show1() {
        System.out.println("UserServiceImpl的show1方法执行");
    }

    @Override
    public void show2() {
        System.out.println("UserServiceImpl的show2方法执行");
        //模拟抛出异常
        int i = 1 / 0;
    }
}
MyAdvice
@Component
public class MyAdvice {
//    public void beforeAdvice() {
//        System.out.println("前置通知---MyAdvice的before增强方法执行.....");
//    }
    public void beforeAdvice(JoinPoint joinPoint) {
        System.out.println("当前目标对象---"+joinPoint.getTarget());
        System.out.println("当前目标方法---"+joinPoint.getSignature().getName());
        System.out.println("当前表达式---"+joinPoint.getStaticPart());
        System.out.println("前置通知带参数---MyAdvice的before增强方法执行.....");
    }


    public void afterAdvice() {
        System.out.println("最终通知---MyAdvice的after增强方法执行.....");
    }

    public void afterReturnAdvice() {
        System.out.println("后置通知---MyAdvice的afterReturn增强方法执行.....");
    }

    public void aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("环绕前通知---MyAdvice的around增强方法执行.....");
        //执行目标方法
        Object proceed = joinPoint.proceed();
        System.out.println("环绕后通知---MyAdvice的around增强方法执行.....");

    }

    public void afterThrowingAdvice(Throwable throwable) {
        System.out.println("当前异常---"+throwable);
        System.out.println("异常通知---MyAdvice的afterThrowingAdvice增强方法执行.....");
    }
}

applicationContext.xml

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


    <!--目标类-->
    <bean id="userService" class="com.zgs.service.impl.UserServiceImpl"></bean>

    <!--通知类-->
    <bean id="myAdvice" class="com.zgs.advice.MyAdvice"></bean>

    <!--aop配置-->
    <aop:config>
        <!--配置切点表达式 目的知道那些方法需要被增强-->
<!--        <aop:pointcut id="myPointCut" expression="execution(void com.zgs.service.impl.UserServiceImpl.show1())"/>-->
<!--        <aop:pointcut id="myPointCut2" expression="execution(void com.zgs.service.impl.UserServiceImpl.show2())"/>-->
        <aop:pointcut id="myPointCut2" expression="execution(* com.zgs.service.impl.*.*(..))"/>
        <!--配置织入 目的知道那些方法需要被增强-->
        <aop:aspect ref="myAdvice">
<!--            <aop:before method="beforeAdvice" pointcut-ref="myPointCut"/>-->
<!--            <aop:after method="afterAdvice" pointcut-ref="myPointCut"/>-->
            <aop:before method="beforeAdvice" pointcut-ref="myPointCut2"/>
<!--            <aop:after-returning method="afterReturnAdvice" pointcut-ref="myPointCut2"/>-->
<!--            <aop:around method="aroundAdvice" pointcut-ref="myPointCut2"/>-->
            <aop:after-throwing method="afterThrowingAdvice" pointcut-ref="myPointCut2" throwing="throwable"/>
            <aop:after method="afterAdvice" pointcut-ref="myPointCut2"/>
        </aop:aspect>
    </aop:config>

<!--    <bean id="userDao" class="com.zgs.dao.impl.UserDaoImpl"></bean>-->
<!--    <bean id="mockAopBeanPostProcessor" class="com.zgs.post.MockAopBeanPostProcessor"></bean>-->
</beans>

1.5:使用Advisor配置切面

AOP的另一种配置方式,该方式需要通知类实现Advice的子功能接口
Advice的子功能接口

1.5.1:代码演示如下

ApplicationContextTest
public class ApplicationContextTest {
    public static void main(String[] args) {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
//        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
        UserService userService = applicationContext.getBean(UserService.class);
        userService.show1();
        System.out.println("========================================");
        userService.show2();
    }
}
UserServiceImpl
@Component
public class UserServiceImpl implements UserService {
    @Override
    public void show1() {
        System.out.println("UserServiceImpl的show1方法执行");
    }

    @Override
    public void show2() {
        System.out.println("UserServiceImpl的show2方法执行");
        //模拟抛出异常
    }
}
MyAdvice2
@Component
public class MyAdvice2 implements MethodBeforeAdvice, AfterReturningAdvice {

    @Override
    public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
        System.out.println("最终通知。。。。。。。。。。。");
    }

    @Override
    public void before(Method method, Object[] args, Object target) throws Throwable {
        System.out.println("前置通知。。。。。。。。。。。");
    }
}
MyAdvice3
@Component
public class MyAdvice3 implements MethodInterceptor {

    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        System.out.println("环绕前通知。。。。。。。。。。。。。。。。。。");
        Object invoke = invocation.getMethod().invoke(invocation.getThis(), invocation.getArguments());
        System.out.println("环绕后通知。。。。。。。。。。。。。。。。。。");
        return invoke;
    }
}

applicationContext.xml

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


    <!--目标类-->
    <bean id="userService" class="com.zgs.service.impl.UserServiceImpl"></bean>

    <!--通知类-->
    <bean id="myAdvice" class="com.zgs.advice.MyAdvice"></bean>
    <!--通知类-->
    <bean id="myAdvice2" class="com.zgs.advice.MyAdvice2"></bean>
    <!--通知类-->
    <bean id="myAdvice3" class="com.zgs.advice.MyAdvice3"></bean>

    <!--aop配置-->
    <aop:config>
        <!--配置切点表达式 目的知道那些方法需要被增强-->
        <!--        <aop:pointcut id="myPointCut" expression="execution(void com.zgs.service.impl.UserServiceImpl.show1())"/>-->
        <!--        <aop:pointcut id="myPointCut2" expression="execution(void com.zgs.service.impl.UserServiceImpl.show2())"/>-->
        <aop:pointcut id="myPointCut2" expression="execution(* com.zgs.service.impl.*.*(..))"/>
        <!--配置织入 目的知道那些方法需要被增强-->
<!--        <aop:aspect ref="myAdvice">-->
<!--            &lt;!&ndash;            <aop:before method="beforeAdvice" pointcut-ref="myPointCut"/>&ndash;&gt;-->
<!--            &lt;!&ndash;            <aop:after method="afterAdvice" pointcut-ref="myPointCut"/>&ndash;&gt;-->
<!--            &lt;!&ndash;            <aop:before method="beforeAdvice" pointcut-ref="myPointCut2"/>&ndash;&gt;-->
<!--            &lt;!&ndash;            <aop:after-returning method="afterReturnAdvice" pointcut-ref="myPointCut2"/>&ndash;&gt;-->
<!--            &lt;!&ndash;            <aop:around method="aroundAdvice" pointcut-ref="myPointCut2"/>&ndash;&gt;-->
<!--            &lt;!&ndash;            <aop:after-throwing method="afterThrowingAdvice" pointcut-ref="myPointCut2" throwing="throwable"/>&ndash;&gt;-->
<!--            &lt;!&ndash;            <aop:after method="afterAdvice" pointcut-ref="myPointCut2"/>&ndash;&gt;-->
<!--        </aop:aspect>-->
        <aop:advisor advice-ref="myAdvice2" pointcut-ref="myPointCut2"/>
        <aop:advisor advice-ref="myAdvice3" pointcut-ref="myPointCut2"/>
    </aop:config>

    <!--    <bean id="userDao" class="com.zgs.dao.impl.UserDaoImpl"></bean>-->
    <!--    <bean id="mockAopBeanPostProcessor" class="com.zgs.post.MockAopBeanPostProcessor"></bean>-->
</beans>

输出结果

1.5.2:使用aspect和advisor配置区别:

  • 配置语法不同:
  • 通知类的定义要求不同,advisor 需要的通知类需要实现Advice的子功能接口:
  • 可配置的切面数量不同:
  • 使用场景不同:
  1. 如果通知类型多、允许随意搭配情况下可以使用aspect进行配置;
  2. 如果通知类型单一、且通知类中通知方法一次性都会使用到的情况下可以使用advisor进行配置;
  3. 在通知类型已经固定,不用人为指定通知类型时,可以使用advisor进行配置。

1.6:xml方式AOP原理解析-processon流程图

  • 通过xml方式配置AOP时,我们引入了AOP的命名空间,根据讲解的,要去找spring-aop包下的META-INF,在去 找spring.handlers文件中找到对应的handler。
http\://www.springframework.org/schema/aop=org.springframework.aop.config.AopNamespaceHandler
  • 最终加载的是 AopNamespaceHandler,该Handler的init方法中注册了config标签对应的解析器。

  • 以ConfigBeanDefinitionParser作为入口进行源码剖析,最终会注册一个AspectJAwareAdvisorAutoProxyCreator 进入到Spring容器中

  • AspectJAwareAdvisorAutoProxyCreator 的上上级父类AbstractAutoProxyCreator中的 postProcessAfterInitialization方法。
  • 通过断点方式观察,当bean是匹配切点表达式时this.wrapIfNecessary(bean,beanName,cacheKey)返回的是 一个JDKDynamicAopProxy
  • 动态代理的实现的选择,在调用getProxy() 方法时,我们可选用的 AopProxy接口有两个实现类,如上图,这两种 都是动态生成代理对象的方式,一种就是基于JDK的,一种是基于Cglib的

详情见流程图。

1.6.1:jdk动态代理与cglib动态代理

1.6.2:强制使用cglib

applicationContext.xml
<aop:config proxy-target-class="true">
        <!--配置切点表达式 目的知道那些方法需要被增强-->
        <!--        <aop:pointcut id="myPointCut" expression="execution(void com.zgs.service.impl.UserServiceImpl.show1())"/>-->
        <!--        <aop:pointcut id="myPointCut2" expression="execution(void com.zgs.service.impl.UserServiceImpl.show2())"/>-->
        <aop:pointcut id="myPointCut2" expression="execution(* com.zgs.service.impl.*.*(..))"/>
        <!--配置织入 目的知道那些方法需要被增强-->
        <aop:aspect ref="myAdvice">
            <!--            <aop:before method="beforeAdvice" pointcut-ref="myPointCut"/>-->
            <!--            <aop:after method="afterAdvice" pointcut-ref="myPointCut"/>-->
                        <aop:before method="beforeAdvice" pointcut-ref="myPointCut2"/>
                        <aop:after-returning method="afterReturnAdvice" pointcut-ref="myPointCut2"/>
                        <aop:around method="aroundAdvice" pointcut-ref="myPointCut2"/>
                        <aop:after-throwing method="afterThrowingAdvice" pointcut-ref="myPointCut2" throwing="throwable"/>
                        <aop:after method="afterAdvice" pointcut-ref="myPointCut2"/>
        </aop:aspect>
<!--        <aop:advisor advice-ref="myAdvice2" pointcut-ref="myPointCut2"/>-->
<!--        <aop:advisor advice-ref="myAdvice3" pointcut-ref="myPointCut2"/>-->
    </aop:config>

输出结果

1.6.3:模拟Cglib基于超类的动态代理代码

CGlibTest

public class CGlibTest {
    public static void main(String[] args) {
        //目标对象
        Target target = new Target();
        //通知对象
        MyAdvice4 myAdvice4 = new MyAdvice4();
        //编写cglib代码
        Enhancer enhancer = new Enhancer();
        //设置父类
        enhancer.setSuperclass(Target.class);
        //设置回调
        enhancer.setCallback(new MethodInterceptor() {
            @Override
            //相当于jdk的proxy的invkoe方法
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                myAdvice4.before();
                Object result = methodProxy.invoke(target, objects);
                myAdvice4.after();
                return result;
            }
        });
        //生成代理对象
        Target proxy = (Target) enhancer.create();
        proxy.show();
    }
}
Target
public class Target {
    public void show(){
        System.out.println("Target.show()");
    }
}
MyAdvice4
@Component
public class MyAdvice4 {
    public void before() {
        System.out.println("前置增强");
    }
    public void after() {
        System.out.println("后置增强");
    }
}

输出结果

显示全文