Spring框架之AOP详解

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

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Coding工匠

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值