欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Spring框架之AOP详解

程序员文章站 2022-04-25 21:28:04
...

AOP 概述

在OOP(面向对象编程)中,正是这种分散在各处的与对象核心功能无关的代码(横向切面代码)的存在,使得模块的复用复用难度增加。AOP则将封装好的对象剖开,找出其中的对多个对象产生影响的公共行为,并将其封装为一个可复用的模块,这个模块倍命名为切面(Aspect),切面将那些与业务无关,却被业务模块共同调用的逻辑提取并封装起来,减少了系统中的重复代码,降低了模块之间的耦合度,同时提高的系统的可维护性。

AOP的实现策略

(1)Java SE动态代理: 使用动态代理可以为一个或多个接口在运行期动态生成实现对象,生成的对象中实现接口的方法时可以添加增强代码,从而实现AOP。缺点是只能针对接口进行代理,另外由于动态代理是通过反射实现的,有时可能要考虑反射调用的开销。 (2)字节码生成(CGLib 动态代理) 动态字节码生成技术是指在运行时动态生成指定类的一个子类对象,并覆盖其中特定方法,覆盖方法时可以添加增强代码,从而实现AOP。其常用工具是cglib。 (3)定制的类加载器 当需要对类的所有对象都添加增强,动态代理和字节码生成本质上都需要动态构造代理对象,即最终被增强的对象是由AOP框架生成,不是开发者new出来的。解决的办法就是实现自定义的类加载器,在一个类被加载时对其进行增强。JBoss就是采用这种方式实现AOP功能。 (4)代码生成 利用工具在已有代码基础上生成新的代码,其中可以添加任何横切代码来实现AOP。 (5)语言扩展 可以对构造方法和属性的赋值操作进行增强,AspectJ是采用这种方式实现AOP的一个常见Java语言扩展。

AOP术语

1)连接点(Joinpoint)

程序执行的某个特定位置:如类开始初始化前、类初始化后、类某个方法调用前、调用后、方法抛出异常后。一个类或一段程序代码拥有一些具有边界性质的特定点,这些点中的特定点就称为“连接点”。Spring仅支持方法的连接点,即仅能在方法调用前、方法调用后、方法抛出异常时以及方法调用前后这些程序执行点织入增强。连接点由两个信息确定:第一是用方法表示的程序执行点;第二是用相对点表示的方位

2)切点(Pointcut)

每个程序类都拥有多个连接点,如一个拥有两个方法的类,这两个方法都是连接点,即连接点是程序类中客观存在的事物。AOP通过“切点”定位特定的连接点。连接点相当于数据库中的记录,而切点相当于查询条件。切点和连接点不是一对一的关系,一个切点可以匹配多个连接点。在Spring中,切点通过org.springframework.aop.Pointcut接口进行描述,它使用类和方法作为连接点的查询条件,Spring AOP的规则解析引擎负责切点所设定的查询条件,找到对应的连接点。其实确切地说,不能称之为查询连接点,因为连接点是方法执行前、执行后等包括方位信息的具体程序执行点,而切点只定位到某个方法上,所以如果希望定位到具体连接点上,还需要提供方位信息。

3)增强(Advice)

增强是织入到目标类连接点上的一段程序代码,在Spring中,增强除用于描述一段程序代码外,还拥有另一个和连接点相关的信息,这便是执行点的方位。结合执行点方位信息和切点信息,我们就可以找到特定的连接点。

4)目标对象(Target)

增强逻辑的织入目标类。如果没有AOP,目标业务类需要自己实现所有逻辑,而在AOP的帮助下,目标业务类只实现那些非横切逻辑的程序逻辑,而性能监视和事务管理等这些横切逻辑则可以使用AOP动态织入到特定的连接点上。

5)引介(Introduction)

引介是一种特殊的增强,它为类添加一些属性和方法。这样,即使一个业务类原本没有实现某个接口,通过AOP的引介功能,我们可以动态地为该业务类添加接口的实现逻辑,让业务类成为这个接口的实现类。

6)织入(Weaving)

织入是将增强添加对目标类具体连接点上的过程。AOP像一台织布机,将目标类、增强或引介通过AOP这台织布机天衣无缝地编织到一起。根据不同的实现技术,AOP有三种织入的方式:

a、编译期织入,这要求使用特殊的Java编译器。

b、类装载期织入,这要求使用特殊的类装载器。

c、动态代理织入,在运行期为目标类添加增强生成子类的方式。

Spring采用动态代理织入,而AspectJ采用编译期织入和类装载期织入。

7)代理(Proxy)

一个类被AOP织入增强后,就产出了一个结果类,它是融合了原类和增强逻辑的代理类。根据不同的代理方式,代理类既可能是和原类具有相同接口的类,也可能就是原类的子类,所以我们可以采用调用原类相同的方式调用代理类。

8)切面(Aspect)

切面由切点和增强(引介)组成,它既包括了横切逻辑的定义,也包括了连接点的定义,Spring AOP就是负责实施切面的框架,它将切面所定义的横切逻辑织入到切面所指定的连接点中。

案例分析

代理模式是常用的Java设计模式,按照代理模式的创建时期分为两种代理模式。

静态代理模式:

由程序员创建或特定工具自动生成源代码,再对其编译。在程序运行前,代理类的.class文件就已经存在了。

动态代理模式:

在程序运行时,运用反射机制动态创建而成,动态代理分为jdk动态代理和CGLIB动态代理 。

应用案例

静态代理

//entity实体类
public class Employee{
    
}
//dao接口
public interface EmployeeDAO{
    void update();
    void save();
}
//dao实现类
public class EmployeeDAOImpl implements IEmployeeDAO {
    public void save(Employee emp) {
        System.out.println("保存员工");
    }
    public void update(Employee emp) {
        System.out.println("修改员工");
    }
}
//模拟事物
//模拟事务管理器:
public class TransactionManager {
​
    public void begin() {
        System.out.println("开启事务");
    }
​
    public void commit() {
        System.out.println("提交事务");
    }
​
    public void rollback() {
        System.out.println("回滚事务");
    }
}
//静态代理类
//静态代理类
public class EmployeeServiceProxy implements IEmployeeService {
    private IEmployeeService target;//真实对象/委托对象
​
    private TransactionManager txManager;//事务管理器
​
    public void setTarget(IEmployeeService target) {
        this.target = target;
    }
​
    public void setTxManager(TransactionManager txManager) {
        this.txManager = txManager;
    }
​
    public void save(Employee emp) {
        txManager.begin();
        try {
            target.save(emp);
            txManager.commit();
        } catch (Exception e) {
            e.printStackTrace();
            txManager.rollback();
        }
    }
​
    public void update(Employee emp) {
        txManager.begin();
        try {
            target.update(emp);
            txManager.commit();
        } catch (Exception e) {
            e.printStackTrace();
            txManager.rollback();
        }
    }
}
//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:p="http://www.springframework.org/schema/p" 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 http://www.springframework.org/schema/context/spring-context-4.3.xsd">
​
    <bean id="employeeDAO" class="cn.wolfcode.dao.impl.EmployeeDAOImpl" />
​
    <bean id="transactionManager" class="cn.wolfcode.tx.TransactionManager" />
    
    <!-- 代理对象 -->
    <bean id="employeeServiceProxy" class="cn.wolfcode.proxy.EmployeeServiceProxy">
        <property name="txManager" ref="transactionManager" />
        <!-- 配置委托对象 -->
        <property name="target">
            <bean class="cn.wolfcode.service.EmployeeServiceImpl">
                <!-- 委托对象依赖dao -->
                <property name="dao" ref="employeeDAO" />
            </bean>
        </property>
    </bean>
</beans>
​
//App测试
package cn.wolfcode;
​
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import cn.wolfcode.domain.Employee;
import cn.wolfcode.service.IEmployeeService;
​
@SpringJUnitConfig
public class App {
​
    @Autowired
    private IEmployeeService service;
    @Test
    void testSave() throws Exception {
        System.out.println(service.getClass());//查看对象的真实类型
        service.save(new Employee());
    }
    @Test
    void testUpdate() throws Exception {
        service.update(new Employee());
    }
}
​

测试结果

class com.sun.proxy.$Proxy19
开启事务
保存员工
保存成功
提交事务

jdk动态代理

注意:jdk动态代理是基于接口实现

//entity实体类
public class Employee{
}

 

//dao接口
public interface IEmployeeDAO {
​
    void save(Employee emp);
​
    void update(Employee emp);
}
//dao实现类
public class EmployeeDAOImpl implements IEmployeeDAO {
    public void save(Employee emp) {
        System.out.println("保存员工");
    }
    public void update(Employee emp) {
        System.out.println("修改员工");
    }
}
//service接口
public interface IEmployeeService {
    void save(Employee emp);
​
    void update(Employee emp);
​
    void delete(Long id);
    
    List<Employee> listAll();
}
//service实现类
public class EmployeeServiceImpl implements IEmployeeService {
​
    private IEmployeeDAO dao;
​
    public void setDao(IEmployeeDAO dao) {
        this.dao = dao;
    }
​
    public void save(Employee emp) {
        dao.save(emp);
        System.out.println("保存成功");
    }
​
    public void update(Employee emp) {
        dao.update(emp);
        //测试增强是否遇到异常能回滚
        throw new RuntimeException("故意错误的");
    }
​
    public void delete(Long id) {
        System.out.println("删除操作");
    }
​
    public List<Employee> listAll() {
        System.out.println("查询所有");
        return new ArrayList<>();
    }
}
//模式事物类
//模拟事务管理器:
public class TransactionManager {
​
    public void begin() {
        System.out.println("开启事务");
    }
​
    public void commit() {
        System.out.println("提交事务");
    }
    public void rollback() {
        System.out.println("回滚事务");
    }
}
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
​
@SuppressWarnings("all")
//事务的增强操作
public class TransactionManagerAdvice implements java.lang.reflect.InvocationHandler {
​
    private Object target;//真实对象(对谁做增强)
    private TransactionManager txManager;//事务管理器(模拟)
​
    public void setTxManager(TransactionManager txManager) {
        this.txManager = txManager;
    }
​
    public void setTarget(Object target) {
        this.target = target;
    }
​
    //ClassLoader loader:类加载器 
    //Class<?>[] interfaces:得到全部的接口 
    //InvocationHandler h:得到InvocationHandler接口的子类实例
    //创建一个代理对象
    public <T> T getProxyObject() {
        return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(), //类加载器,一般跟上真实对象的类加载器
                target.getClass().getInterfaces(), //真实对象所实现的接口(JDK动态代理必须要求真实对象有接口)
                this);//如何做事务增强的对象 //要绑定接口(这是一个缺陷,cglib弥补了这一缺陷)
    }
    
    //参数说明: 
    //Object proxy:指被代理的对象。 
    //Method method:要调用的方法 
    //Object[] args:方法调用时所需要的参数
    //如何为真实对象的方法做增强的具体操作
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (method.getName().startsWith("get") || method.getName().startsWith("list")) {
            return method.invoke(target, args);//放行
        }
        
        Object ret = null;
        txManager.begin();
        try {
            //---------------------------------------------------------------
            ret = method.invoke(target, args);//调用真实对象的方法
            //---------------------------------------------------------------
            txManager.commit();
        } catch (Exception e) {
            e.printStackTrace();
            txManager.rollback();
        }
        return ret;
    }
}
<?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:p="http://www.springframework.org/schema/p" 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 http://www.springframework.org/schema/context/spring-context-4.3.xsd">
    <!-- 配置dao -->
    <bean id="employeeDAO" class="cn.wolfcode.dao.impl.EmployeeDAOImpl" />
​
    <!-- 配置事物管理器 -->
    <bean id="transactionManager" class="cn.wolfcode.tx.TransactionManager" />
​
    <!-- 配置service -->
    <bean id="employeeService" class="cn.wolfcode.service.impl.EmployeeServiceImpl">
    <!-- 依赖注入dao -->
        <property name="dao" ref="employeeDAO" />
    </bean>
    <!-- 配置一个事务增强的类 -->
    <bean id="transactionManagerAdvice" class="cn.wolfcode.tx.TransactionManagerAdvice">
            <property name="target" ref="employeeService"/>
            <property name="txManager" ref="transactionManager"/>
    </bean>
</beans>
​
​
//测试类
​
//使用Spring5提供的测试
@SpringJUnitConfig
public class App {
​
    @Autowired
    private TransactionManagerAdvice advice;
​
    //代理对象:com.sun.proxy.$Proxy19
    @Test //junit测试
    void testSave() throws Exception {
        //获取代理对象
        IEmployeeService proxy = advice.getProxyObject();
        System.out.println(proxy.getClass());
        
        proxy.save(new Employee());
    }
​
    @Test
    void testUpdate() throws Exception {
        //获取代理对象
        IEmployeeService proxy = advice.getProxyObject();
        proxy.update(new Employee());
    }
}

测试结果

class com.sun.proxy.$Proxy19
开启事务
保存员工
保存成功
提交事务

 

CGLIB动态代理

JDK的动态代理机制只能代理实现了接口的类,而不能实现接口的类就不能实现JDK的动态代理,cglib是针对类来实现代理的,他的原理是对指定的目标类生成一个子类,并覆盖其中方法实现增强,但因为采用的是继承,所以不能对final修饰的类进行代理

//创建需要被代理的类
public class EmployeeDAOImpl {
​
    public void save() {
        System.out.println("保存员工");
    }
    public void update() {
        System.out.println("修改员工");
    }
}
//模拟事务管理器:
public class TransactionManager {
​
    public void begin() {
        System.out.println("开启事务");
    }
​
    public void commit() {
        System.out.println("提交事务");
    }
​
    public void rollback() {
        System.out.println("回滚事务");
    }
}
//创建CGLIB动态代理类
package com.iarchie.cglib.advice;
​
import java.lang.reflect.Method;
​
import org.springframework.cglib.proxy.Enhancer;
//实现Spring - InvocationHandler接口
import org.springframework.cglib.proxy.InvocationHandler; 
​
public class MyAdvice implements InvocationHandler {
​
    private Object target;
​
    private TransactionManager tManager;
​
    public void setTarget(Object target) {
        this.target = target;
    }
​
    public void settManager(TransactionManager tManager) {
        this.tManager = tManager;
    }
​
    // 创建代理对象
    public <T> T getObjectProxy() {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(target.getClass());// 继承哪个类去做增加
        enhancer.setCallback(this); // 设置增加的对象
        return (T) enhancer.create();// 创建代理对象
​
    }
​
    // 为真实对象的真实方法做具体的增强
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object object = null;
        // 开启事物
        tManager.begin();
        try {
            object = method.invoke(target, args);// 调用真实对象的方法
            // 提交事物
            tManager.commit();
        } catch (Exception e) {
            e.printStackTrace();
            // 回滚事物
            tManager.rollback();
        }
        return object;
    }
​
}
<?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:p="http://www.springframework.org/schema/p" 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 http://www.springframework.org/schema/context/spring-context-4.3.xsd">
    <!-- 配置dao -->
    <bean id="employeeDAO"
        class="com.iarchie.cglib.dao.impl.EmployeeDAOImpl" />
​
    <!-- 配置事物管理器 -->
    <bean id="transactionManager"
        class="com.iarchie.cglib.advice.TransactionManager" />
​
    <!-- 配置一个事务增强的类 -->
    <bean id="myAdvice" class="com.iarchie.cglib.advice.MyAdvice">
        <property name="target" ref="employeeDAO" />
        <property name="tManager" ref="transactionManager" />
    </bean>
</beans>
//测试
package com.iarchie.cglib;
​
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
​
import com.iarchie.cglib.advice.MyAdvice;
import com.iarchie.cglib.dao.impl.EmployeeDAOImpl;
​
@SpringJUnitConfig
public class App {
​
    @Autowired
    private MyAdvice advice;
    
    //JDK代理对象:com.sun.proxy.$Proxy19
    //CGLIB代理对象:com.iarchie.cglib.dao.impl.EmployeeDAOImpl$$EnhancerByCGLIB$$765a98d8
    @Test
    void testSave() throws Exception {
        EmployeeDAOImpl proxy = advice.getObjectProxy();
        proxy.save();
        System.out.println(proxy.getClass());
    }
}

测试结果

开启事务
保存员工
提交事务
class com.iarchie.cglib.dao.impl.EmployeeDAOImpl$$EnhancerByCGLIB$$765a98d8

jdk动态代理和CGLIB动态代理的区别

java动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。

而cglib动态代理是利用asm开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。

1、如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP 2、如果目标对象实现了接口,可以强制使用CGLIB实现AOP

3、如果目标对象没有实现了接口,必须采用CGLIB库,spring会自动在JDK动态代理和CGLIB之间转换

如何强制使用CGLIB实现AOP? (1)添加CGLIB库,SPRING_HOME/cglib/*.jar (2)在spring配置文件中加入<aop:aspectj-autoproxy proxy-target-class="true"/>

JDK动态代理和CGLIB字节码生成的区别? (1)JDK动态代理只能对实现了接口的类生成代理,而不能针对类 (2)CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法 因为是继承,所以该类或方法最好不要声明成final

相关标签: SpringAOP