③Spring-AOP

目录

1. AOP

AOP 概述

AOP 术语

1.1. AOP 底层原理(扩展)

JDK 动态代理

CGlib 动态代理

1.2 JDK静态代理实现

1.3 JDK 动态代理实现

2.AOP 准备工作

2.1 AspectJ 介绍

2.2 引入 AOP 相关依赖

2.3 切入点表达式

3.AspectJ 注解实现

3.1 Spring 配置文件

3.2 创建被增强对象和增强对象

3.3 添加增强类注解和切入点表达式

3.4 设置增强类优先级

3.5 完全注解开发

4.AspectJ 配置文件实现

4.1 创建被增强对象和增强对象

4.2 Spring 配置文件


1. AOP

怎么解决日志、异常处理等公共问题?

AOP 概述

  • 定义AOP(Aspect Oriented Programming,面向切面编程),通过预编译和运行时动态代理扩展程序功能。

  • 作用利用 AOP 可以对业务逻辑的各个部分进行隔离,降低耦合性,提高程序可重用性和开发效率

  • 场景:日志记录,性能统计,安全控制,事务处理,异常处理

  • 通俗描述:不修改源代码,在主干功能中添加新功能

AOP 术语

  • 连接点(Joinpoint): 程序执行过程中明确的点,如方法的调用,或者异常的抛出。在Spring AOP中,连接点总是方法的调用。

  • 切入点(Pointcut): 可以插入增强处理的连接点。简而言之,当某个连接点满足指定要求时,该连接点将被添加增强处理,该连接点也就变成了切入点。

  • 切面(Aspect): 切面用于组织多个Advice,Advice放在切面中定义:某方面切点集合;例如:事务,日志,安全,异常等

  • 增强(通知,装置)(Advice):

    • AOP框架在特定的切入点执行的增强处理。

    • 实际增强的逻辑部分,称为通知,通知分为五种类型:

      • 前置通知:方法执行之前的处理

      • 返回通知:方法执行之后的处理

      • 环绕通知:方法执行前后的处理

      • 异常通知:方法抛出异常的处理

      • 后置通知:方法执行之后一定的处理

  • 织入:是一个动作,即把通知应用到切入点的过程(如图)

1.1. AOP 底层原理(扩展)

  • 底层原理:动态代理

    • 有接口情况JDK动态代理

    • 无接口情况CGLib动态代理

如果学习过设计模式,应该对上述两种代理方式非常了解了。没有学习过也没关系,我们接着往下看

JDK 动态代理

public interface UserDao {
    void save();
}
public class UserDaoImpl implements UserDao {
    @Override
    public void save(){
        //保存
    }
}

有接口情况:创建 UserDao 接口实现类代理对象

CGlib 动态代理

public class User {
    public void add(){
        //...
    }
}
// 原始方法:通过子类继承,重写User类方法
public class Person extends User {
    @Override
    public void add(){
        super.add();
        //增强代码逻辑
    }
}

无接口情况:创建 User 类子类代理对象


由于 Spring5 中对上述代理已经做了很好的封装,我们只需要通过最简单的方式进行配置即可

但仍然需要我们对原理有一定的认识,只有做到“知其然,知其所以然”,才能真正“以不变应万变”

1.2 JDK静态代理实现

public class StaticUserDaoProxy implements UserDao {
    private UserDao userDao = null;
    public  StaticUserDaoProxy(UserDao userDao){
        this.userDao = userDao;
    }
​
    @Override
    public void save() {
        System.out.println("开启事务");
        //调用代理目标类的方法
        userDao.save();
        System.out.println("提交事务");
    }
}

1.3 JDK 动态代理实现

实现方式使用Proxy中的方法创建代理对象

具体方法newProxyInstance()

方法参数

  • ClassLoader loader:类加载器

  • Class<?>[] interfaces:增强方法所在类实现的接口数组

  • InvocationHandler h:实现InvocationHandler接口,创建代理对象,编写增强方法

常言道:“Talking is cheap, show me the code"。话不多说,下面上代码~

1)创建 UserDao 接口和对应实现类

public interface UserDao {
    int add(int a, int b);
    String update(String id);
}
public class UserDaoImpl implements UserDao {
    @Override
    public int add(int a, int b) {
        return a + b;
    }
    @Override
    public String update(String id) {
        return id;
    }
}

2)创建 UserDao 代理对象

public class UserDaoProxy {
    private UserDao target;
    public UserDaoProxy(UserDao target) {
        this.target = target;
    }
    public UserDao newProxyInstance() {
        Class<?> targetClass = target.getClass();
        ClassLoader classLoader = targetClass.getClassLoader();
        Class<?>[] interfaces = targetClass.getInterfaces();
        return (UserDao) Proxy.newProxyInstance(classLoader, interfaces, new UserDaoInvocationHandler());
    }
    class UserDaoInvocationHandler implements InvocationHandler {
​
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            // 被代理对象方法前置逻辑
            System.out.print("method=" + method.getName() + ", args=" + Arrays.toString(args));
            // 被代理对象方法
            Object result = method.invoke(target, args);
            // 被代理对象方法后置逻辑
            System.out.println(", result=" + result);
            return result;
        }
    }
}

3)测试

UserDao target = new UserDaoImpl();
UserDaoProxy userDaoProxy = new UserDaoProxy(target);
UserDao userDao = userDaoProxy.newProxyInstance();
userDao.add(1, 2);
userDao.update("UUID1");
// method=add, args=[1, 2], result=3
// method=update, args=[UUID1], result=UUID1

2.AOP 准备工作

2.1 AspectJ 介绍

Spring 一般都是基于AspectJ实现 AOP 操作的

  • AspectJ不是 Spring 的一部分,而是一个独立的 AOP 框架

  • 一般会把AspectJ和 Spring 搭配使用,进行 AOP 操作,因为这样更加方便

基于 AspectJ 进行 AOP 操作的两种方式:

  • 基于 XML 配置文件方式实现

  • 基于注解方式实现(推荐使用)

2.2 引入 AOP 相关依赖

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
</dependency>

2.3 切入点表达式

切入点表达式的作用:知道对哪个类的哪个方法进行增强

语法结构:execution([权限修饰符][返回类型][类全路径][方法名]([参数列表]))

举例:

举例1:对com.zking.spring.dao.BookDao中的add()方法进行增强

execution(* com.zking.spring.dao.BookDao.add(..))

举例2:对com.zking.spring.dao.BookDao中的所有方法进行增强

execution(* com.zking.spring.dao.BookDao.*(..))

举例3:对com.zking.spring.dao包中所有类的所有方法进行增强

execution(* com.zking.spring.dao.*.*(..))

举例4:dao.. 代表dao包及dao包所有子包

execution(* com.zking.spring.dao..*.*(..))

3.AspectJ 注解实现

3.1 Spring 配置文件

  • 1)引入contextaop名称空间

  • 2)配置组件扫描基础包

  • 3)开启AspectJ生成代理对象

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       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="com.zking.spring"/>
​
    <!--开启AspectJ生成代理对象-->
    <aop:aspectj-autoproxy/>
</beans>

3.2 创建被增强对象和增强对象

  • 1)创建 UserService对象,并添加@Service注解

  • 2)创建 LogAspect对象,并添加@Component注解

@Service
public class BookServiceImpl implements BookService {
    @Override
    public void update() {
        System.out.println("book update...");
    }
​
    @Override
    public void save() {
        System.out.println("book save...");
    }
​
    @Override
    public void list() {
        System.out.println("book list...");
    }
}
​
@Component
public class LogAspect {
    /**
     * 前置通知
     */
    public void before() {
        System.out.println("before...");
    }
​
    /**
     * 返回通知
     */
    public void afterReturning() {
        System.out.println("afterReturning...");
    }
​
    /**
     * 后置通知
     */
    public void after() {
        System.out.println("after...");
    }
​
    /**
     * 异常通知
     */
    public void afterThrowing() {
        System.out.println("afterThrowing...");
    }
​
    /**
     * 环绕通知
     */
    public void around() {
        System.out.println("around...");
    }
}

3.3 添加增强类注解和切入点表达式

@Aspect
@Component
public class LogAspect {
    /**
     *   * com.zking.spring.service..*.*(..))
     *   * 代表权限返回类型为任意
     *   com.zking.spring.service..  service包及其子包
     *   *.* 所有类及所有方法名
     *   (..) 方法的参数任意
     */
    /**
     * 设置切点
     */
    @Pointcut("execution(* com.zking.spring.service..*.*(..))")
    public void pointcut(){
​
    }
    /**
     * 前置通知advice
     */
    @Before("pointcut()")
    public void before(){
        System.out.println("before....");
    }
​
    /**
     * 返回通知
     */
    @AfterReturning("pointcut()")
    public void afterReturning(){
        System.out.println("afterReturning....");
    }
​
    /**
     * 后置通知
     */
    @After("pointcut()")
    public void after(){
        System.out.println("after....");
    }
​
    /**
     * 异常通知
     */
    @AfterThrowing("pointcut()")
    public void afterThrowing(){
        System.out.println("afterThrowing....");
    }
​
    //环绕通知
    @Around("pointcut()")
    public void around(ProceedingJoinPoint joinPoint) throws Throwable{
        System.out.println("around before....");
        //调用代理目标的方法
        joinPoint.proceed();
        System.out.println("around after....");
    }
}

语法结构:execution([权限修饰符][返回类型][类全路径][方法名]([参数列表]))

代码测试

ApplicationContext ctx = new ClassPathXmlApplicationContext("beans1.xml");
BookService bookService = ctx.getBean("bookServiceImpl", BookService.class);
bookService.update();

结果:

around before....
before....
book update...
afterReturning....
after....
around after....

为了演示异常通知,需要修改下被增强对象中的方法,模拟一个异常

@Service
public class BookServiceImpl implements BookService {
    @Override
    public void update() {
        System.out.println("book update...");
        int k = 10 / 0; //模拟异常
    }
}

运行结果:

around before....
before....
book update...
afterThrowing....
after....

对比正常情况下,发现少了afterReturning,即后置异常和around after即环绕增强的后置处理

3.4 设置增强类优先级

如果有多个增强类对类中同一个方法进行增强,可以设置增强类的优先级,来决定哪个增强类先执行,哪个增强类后执行

使用@Order注解设置增强类的优先级,其中指定优先级数字,注解格式:@Order(数字类型值)

  • 数字类型值越小,优先级越高

  • 数字类型值越大,优先级越低

最佳实践

@Component
@Aspect
@Order(1)
public class LogAspect {
    //...
}
@Component
@Aspect
@Order(3)
public class CacheAspect {
    //...
}

3.5 完全注解开发

如果要用完全注解的方式进行开发,可以使用注解类代替 Spring 配置文件

@Configuration
@ComponentScan(value = "com.zking.spring")
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class AopConfig {
}

其中:

  • 注解@ComponentScan(value = "com.zking.spring")代替了<context:component-scan base-package="com.zking.spring"/>进行组件扫描的配置

  • 注解@EnableAspectJAutoProxy(proxyTargetClass = true)代替了<aop:aspectj-autoproxy/>开启AspectJ生成代理对象

对应关系:

注解方式配置文件方式
@ComponentScan<context:component-scan>
@EnableAspectJAutoProxy<aop:aspectj-autoproxy>

4.AspectJ 配置文件实现

4.1 创建被增强对象和增强对象

与3.4相同,并可去掉注解

4.2 Spring 配置文件

  • 1)引入aop名称空间

  • 2)配置被增强对象和增强对象创建

  • 3)配置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 https://www.springframework.org/schema/aop/spring-aop.xsd">
​
    <!--创建对象-->
    <bean id="userService" class="com.zking.spring.service.impl.UserService"></bean>
    <bean id="logAspect" class="com.zking.spring.aspect.LogAspect"></bean>
​
    <!--配置aop增强-->
    <aop:config>
        <!--配置切入点-->
        <aop:pointcut id="p" expression="execution(* com.zking.spring.service..*.*(..))"/>
        <!--配置切面-->
        <aop:aspect ref="logAspect">
            <!--前置通知-->
            <aop:before method="before" pointcut-ref="p"/>
            <!--后置通知-->
            <aop:after-returning method="afterReturning" pointcut-ref="p"/>
            <!--最终通知-->
            <aop:after method="after" pointcut-ref="p"/>
            <!--异常通知-->
            <aop:after-throwing method="afterThrowing" pointcut-ref="p"/>
            <!--环绕通知-->
            <aop:around method="around" pointcut-ref="p"/>
        </aop:aspect>
    </aop:config>
</beans>

其中,配置文件的标签与注解的对应关系如下表:

配置文件方式注解方式
<aop:pointcut>@Pointcut
<aop:aspect>@Aspect
<aop:before>@Before
<aop:after-returning>@AfterReturning
<aop:after>@After
<aop:after-throwing>@AfterThrowing
<aop:around>@Around

代码测试

ApplicationContext ctx = new ClassPathXmlApplicationContext("beans2.xml");
BookService bookService = ctx.getBean("bookServiceImpl", BookService.class);
bookService.update();

测试结果

before....
around before....
book update...
around after....
after....
AfterReturning....

                
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值