JDK、CGLIB动态代理,SpringAOP详解。

本文详细介绍了Spring AOP的概念、应用场景和实现方式,包括XML配置和注解方式。通过例子展示了如何在UserService的save方法执行前插入事务管理操作,如开启事务、提交事务等。同时,解释了切入点表达式、通知类型以及动态代理(JDK和CGLIB)的工作原理。
摘要由CSDN通过智能技术生成

SpringAOP

介绍

aop:面向切面编程,跟上篇的IOC一样它也不是一种技术而是一种思想,解决:在不破坏源代码的情况下,实现对业务方法的增强.可以减少重复代码,提高代码重用性,让我们开发者只关心核心业务逻辑的代码

常见的应用场景:

  • 性能测试
  • 事务管理
  • 日志输出

aop思想底层实现技术:JDK、CGLIB
根据是否有接口选择使用其中一种技术.
相关术语

* target:目标对象
* proxy:代理对象
* joinPoint:连接点,目标对象的所有方法
* pointcut:切点,需要被增强的方法
* advice:通知 (增强的方法)
* aspect:切面, 切点+通知(声明对那些方法在什么时候执行那些操作)
* weaving:织入,动词,将通知织入到切点的过程

在这里插入图片描述

AOP开发(xml方式)

需求:在执行UserService中save方法之前,执行通知类中的beforeBeginTransaction方法
步骤分析:

  1. 创建一个项目,导入jar包
    • spring-context,spring-aop(spring5.1版本中,导入springcontext的已经把aopjar包导入)
    • aspectweaver(Aspectj第三方的aop框架)
    • spring-test和junit
  2. 编写UserService及其实现类,提供save和findAll方法
  3. 编写TransactionManager通知类
    • 编写beforeBeginTransaction方法 打印一句话
  4. 编写spring的核心配置i文件
    • 将userService交个spring管理
    • 将通知类交给spring管理
    • aop配置 在目标对象的某个方法执行的什么时候动态的执行通知类中的指定的操作

代码实现:
pom.xml:

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

UserServiceImpl:

public class UserServiceImpl implements UserService {
    @Override
    public void save() {
        System.out.println("执行了save方法");
    }

    @Override
    public void findAll() {
        System.out.println("执行了findAll方法");
    }
}

TransactionManager:

//通知类
public class TransactionManager {
    public void beforeBeginTransaction(){
        System.out.println("开启事务");
    }
}

applicationContext.xml:
格式为:

<aop:config>
    <aop:aspect ref="指向通知类">
        <aop:通知类型 method="通知类中方法名" pointcut="切入点表达式"/>
        ...
    </aop:aspect>
</aop:config>
<?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">

    <!--配置service-->
    <bean id="userService" class="com.llz.service.impl.UserServiceImpl"/>

    <!--配置通知类-->
    <bean id="transactionManager" class="com.llz.aop.TransactionManager"/>

    <!--aop配置-->
    <aop:config>
        <!--配置切面 在切入点执行的什么时候执行通知类中的那个方法-->
        <aop:aspect ref="transactionManager">
        	<!-- 在save方法执行之前执行通知类中的beforeBeginTransaction方法-->
            <aop:before method="beforeBeginTransaction" pointcut="execution(public void com.llz.service.impl.UserServiceImpl.save())"/>
        </aop:aspect>
    </aop:config>
</beans>

测试类:

@ContextConfiguration("classpath:applicationContext.xml")
@RunWith(SpringJUnit4ClassRunner.class)
public class TestAOP {
    @Autowired
    UserService userService;
    @Test
    public void testHello(){
        userService.findAll();
        System.out.println("-----");
        userService.save();
    }
}

切入点(execution)表达式

作用:定位要增强的方法

execution([修饰符] 返回值 包名.类名.方法名(参数类型列表))
例:
execution(void com.llz.service.impl.AccountServiceImpl.transfer(String,String,int))
execution(public void com.llz.service.impl.UserServiceImpl.save())
提示: 表达式中可以使用 * 和 …

  • :匹配任意字符
    … :出现在包中和方法中
    包: 表示当前包及其子包
    方法:表示参数任意

例如:

  • void com.llz.service.impl.UserServiceImpl.save(User) :指定类中指定方法,参数和返回值都是指定
  • * com.llz.service.impl.UserServiceImpl.*() : 指定类中所有无参且返回值任意的方法
  • * com.llz.service.impl.UserServiceImpl.*(…) : 指定类中所有的方法
  • * com.llz.service.impl.*.*(…) : 指定包下的所有类中的任意方法
  • * com.llz.service…*.*(…) : 指定包及其子包下所有类中的任意方法
  • * com.llz.service…*.find*(…) : 指定包及其子包下所有类中以find开头的方法

2.通知类型

before : 前置通知,在切点执行之前 执行
after-returning : 后置通知,在切点执行成功之后 执行
after-throwing : 异常通知,在切点执行出现异常之后 执行
after : 最终通知,在切点执行之后,无论成功与否,都会 执行
around : 环绕通知,以上通知的任意组合

note:
1.后置通知和异常通知不会同时出现。
2.开发中一般使用环绕通知,若添加通知类型超过了一个,请使用环绕通知。且一定注意环绕通知一定需要加上返回值一般用*代替。

通知切面类中方法:
ProceedingJoinPoint 正在执行的切入点(方法)

//通知类
public class TransactionManager {

    public void beforeBeginTransaction(){
        System.out.println("开启事务");
    }

    public void afterReturningCommit(){
        System.out.println("提交事务");
    }

    public void afterThrowingRollbak(){
        System.out.println("回滚事务");
    }

    public void afterRelease(){
        System.out.println("释放资源");
    }
    
    //ProceedingJoinPoint 正在执行的切入点(方法)
    public void aroundMethod(ProceedingJoinPoint pjp) throws Throwable {
        try {
            System.out.println("开启事务");
            //目标方法执行
            pjp.proceed();
            System.out.println("提交事务");
        } catch (Throwable e) {
            e.printStackTrace();
            System.out.println("回滚事务");
            throw e;
        } finally {
            System.out.println("释放资源");
        }
    }
}

applicationContext.xml:

<!--aop配置-->
<aop:config>
    <!--配置切面 在切入点执行的什么时候执行通知类中的那个方法-->
    <aop:aspect ref="transactionManager">
        <aop:before method="beforeBeginTransaction" pointcut="execution(public void com.llz.service.impl.UserServiceImpl.save())"/>
        <aop:after-returning method="afterReturningCommit" pointcut="execution(* com.llz.service.impl.*.save(..))"/>
        <aop:after-throwing method="afterThrowingRollbak" pointcut="execution(* com.llz.service.impl.*.save(..))"/>
        <aop:after method="afterRelease" pointcut="execution(* com.llz.service.impl.*.save(..))"/>
    </aop:aspect>

    <!--环绕通知-->
    <aop:aspect ref="transactionManager">
        <aop:around method="aroundMethod" pointcut="execution(* com.llz.service.impl.*.find*(..))"/>
    </aop:aspect>
</aop:config>

执行结果:
在这里插入图片描述

3.抽取切入点表达式

<aop: pointcut id=“切入点名字” expression=“切点表达式”>

在这里插入图片描述

基于注解的AOP开发

1.注解+xml方式

需求:在执行某个service中save方法的时候,添加环绕通知
步骤分析:

  1. 准备好service代码(目标类和切面类)
  2. 目标类和通知切面类都使用注解加入spring管理
  3. 修改通知切面类
    • 在类上再添加一个注解 @Aspect ,用来声明自己是一个切面类
    • 在方法上通过通知注解声明切入点
  4. 在spring核心配置文件中
    • 组件扫描
    • aop自动代理(aop的注解支持)
      代码实现:
      applicationContext.xml:
  <!--组件扫描-->
    <context:component-scan base-package="cn.it"/>
    <!--aop注解支持-->
    <aop:aspectj-autoproxy/>

通知类代码:
在这里插入图片描述

通知类型注解

@Before
@AfterReturning
@AfterThrowing
@After
@Around
注意:aop的注解有个小bug,使用aop注解的时候务必使用环绕通知即可.

3.抽取切入点

步骤分析及代码实现:

 /**
     * 在切面中声明一个方法,一般用public修饰,返回值为void,无参数
     * 使用@Pointcut 声明 切入点表达式
     * 在通知注解上使用 方法名() 来引用切入点表达式即可
     */
    @Pointcut("execution(* cn.it.service.impl.*.find*(..))")
    public void pc_find(){}

    @Before("pc_find()")
    public void beforeBeginTransaction(){
        System.out.println("开启事务");
    }

    @AfterReturning("pc_find()")
    public void afterReturningCommit(){
        System.out.println("提交事务");
    }

4.纯注解

编写一个spring配置类,替代beans.xml

  • 添加组件扫描注解
  • 添加aop自动代理注解(注解支持)
    • @EnableApectJAutoProxy

在这里插入图片描述

因为springAOP底层逻辑是动态代理,接着聊聊动态代理。我之前的文章也写过JDK的动态代理,有兴趣可以翻看之前的文章。

jdk 动态代理

public class TestJDKProxy {
    @Test
    public void test1(){
        //创建被代理对象(目标对象)
        UserService userService = new UserServiceImpl();
        //使用jdk动态代理创建代理对象
        UserService userServiceProxy = (UserService) Proxy.newProxyInstance(
                TestJDKProxy.class.getClassLoader(),
                new Class[]{UserService.class},
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                       //只需要对save方法进行增强,
                        if ("save".equals(method.getName())) {
                            Object result = null;
                            try {
                                System.out.println("开启事务");
                                //目标方法执行
                                result = method.invoke(userService, args);
                                System.out.println("提交事务");
                            } catch (Exception e) {
                                e.printStackTrace();
                                System.out.println("回滚事务");
                            } finally {
                                System.out.println("释放资源");
                            }
                            return result;
                        }
                        //其他方法执行原来的操作
                        return method.invoke(userService, args);
                    }
                }
        );
        userServiceProxy.findAll();
        System.out.println("------");
        userServiceProxy.save();
    }
}

CGLIB动态代理

使用的时候就需要导入cglib的jar包(spring内置了)

public class TestCGLIB {
    @Test
    public void testProxy(){
        //创建被代理对象
        OrderService orderService = new OrderService();
        //使用CGLIB技术创建service的代理对象
        OrderService orderServiceProxy = (OrderService) Enhancer.create(OrderService.class, new MethodInterceptor() {
            @Override
            public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {

                //只需要对save方法进行增强,
                if ("save".equals(method.getName())) {
                    Object result = null;
                    try {
                        System.out.println("开启事务");
                        //目标方法执行
                        result = method.invoke(orderService, args);
                        System.out.println("提交事务");
                    } catch (Exception e) {
                        e.printStackTrace();
                        System.out.println("回滚事务");
                    } finally {
                        System.out.println("释放资源");
                    }
                    return result;
                }

                //其他方法执行原来的操作
                return method.invoke(orderService, args);
            }
        });
        orderServiceProxy.findAll();
        System.out.println("----");
        orderServiceProxy.save();
    }
}

结果:
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值