Spring AOP(切面编程)

最近在学习Spring框架,写这篇文章也算是对近段学习的一个总结,本文主要从三种代理模式写起,静态代理、动态代理和Cglib代理,然后到Spring AOP的配置及使用,包括XML方式配置和注解两种实现方式

一、代理模式

代理模式是一种设计模式,简单说即是在不改变源码的情况下,实现对目标对象的功能扩展

功能需求: 现在假设有个 UserDao 接口,有保存动作 save() 方法,UserDaoImpl 实现 UserDao接口 , 但是在执行 save() 方法的时候动态植入两句打印

1. 静态代理

静态代理方式处理的,目标对象必须实现接口,并且代理对象要实现跟目标对象一样的接口

public interface UserDao {
    public void save();
}
public class UserDaoImpl implements UserDao {

    @Override
    public void save() {
        System.out.println("执行保存动作");
    }
}
/**
 * 静态代理类
 * @author liuchao
 */
public class UserProxy implements UserDao {
    
    private UserDao _userDao;
    
    public UserProxy(UserDao userDao) {
        _userDao = userDao;
    }
    
    @Override
    public void save() {
        //静态代理方式实现开始植入
        System.out.println("开启事务");
        
        _userDao.save();
        
        //静态代理方式实现结束植入
        System.out.println("提交事务");
    }
}
import org.junit.Test;
/**
 * 测试类
 * @author liuchao
 */
public class UserTest {
    
    @Test
    public void testSave() {
        
        //1. 创建目标对象
        UserDao userDao = new UserDaoImpl();
        
        //2. 创建代理工厂对象
        UserProxy factory = new UserProxy(userDao);
        
        //3. 执行代理工厂保存方法
        factory.save();
    }
}
//控制台打印结果
开启事务
执行保存动作
提交事务
2. 动态代理

动态代理是JDK自带的,所以也叫做JDK代理,也需要目标对象实现接口,也是只能对目标对象实现接口里面的方法进行拦截。
调用Proxy类的静态方法newProxyInstance即可,该方法会返回代理类对象

static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler h )
接收的三个参数依次为:

  • ClassLoader loader:指定当前目标对象使用类加载器,写法固定
  • Class<?>[] interfaces:目标对象实现的接口的类型,写法固定
  • InvocationHandler h:事件处理接口,需传入一个实现类,一般直接使用匿名内部类
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * 动态代理工厂类
 * @author liuchao
 */
public class ProxyFactory {

    // 生成动态代理对象
    public static Object newProxyInstance(Object target) {
        // InvocationHandler
        return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(),
                new InvocationHandler() {

                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

                        System.out.println("开启事务");

                        Object result = method.invoke(target, args);

                        System.out.println("提交事务");

                        return result;
                    }
                });
    }
}
import org.junit.Test;
/**
 * 测试类
 * @author liuchao
 */
public class UserTest {
    
    @Test
    public void testSave() {
        
        UserDao userDao = new UserDaoImpl();
        
        //生成代理对象 (动态创建一个实现该接口的匿名内部类)
        UserDao proxyInstance = (UserDao)ProxyFactory.newProxyInstance(userDao);
        
        //执行保存方法
        proxyInstance.save();
    }
}
//控制台打印
开启事务
执行保存动作
提交事务
3. Cglib代理

第三方框架实现的对基本代理模式的一个拓展,该代理植入不需要目标对象实现接口,内部通过动态生成目标对象子类的方式来实现对目标对象方法拦截

前提条件:

    1. 需要引入Cglib的jar文件,由于Spring的核心包中已经包括了Cglib功能,所以也可以直接引入spring-core-4.0.6.RELEASE.jar
    1. 目标类不能为final
    1. 目标对象的方法如果为finalstatic,那么不会被拦截

所以接下来重新定义的 UserDaoImpl 类不必实现任何接口

public class UserDaoImpl {

    public void save() {
        System.out.println("执行保存动作");
    }
}

代理工厂类必须实现 MethodInterceptor 接口,因为方法拦截是通过实现该接口里面的 intercept (..) 方法。

import java.lang.reflect.Method;

import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
/**
 * Cglib代理工厂类
 * @author liuchao
 */
public class ProxyFactory implements MethodInterceptor {
    
    private Object target;
    
    public ProxyFactory(Object target) {
        this.target = target;
    }
    
    //实例化代理对象
    public Object getProxyInstance() {
        //1. 工具类
        Enhancer en = new Enhancer();
        //2. 设置父类
        en.setSuperclass(this.target.getClass());
        //3. 设置回调,会调用下面的intercept(..)方法
        en.setCallback(this);
        //4. 创建子类对象
        return en.create();
    }
    
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        
        System.out.println("开启事务");
        
        Object result = method.invoke(this.target, args);
        
        System.out.println("提交事务");
        
        return result;
    }
}
import org.junit.Test;
/**
 * 测试类
 * @author liuchao
 */
public class UserTest {
    
    @Test
    public void testSave() {
        //1. 创建目标对象
        UserDaoImpl userDao = new UserDaoImpl();
        
        //2. 生成代理对象
        UserDaoImpl proxyInstance = (UserDaoImpl) new ProxyFactory(userDao).getProxyInstance();
        
        //3. 执行保存方法
        proxyInstance.save();
        
        System.out.println("代理对象类名: " +  proxyInstance.getClass().getSimpleName());
        System.out.println("代理对象父类名: " + proxyInstance.getClass().getSuperclass().getSimpleName());
    }
}
//控制台打印
开启事务
执行保存动作
提交事务
代理对象类名: UserDaoImpl$$EnhancerByCGLIB$$5467e6b5
代理对象父类名: UserDaoImpl

二、Spring AOP编程

需要导入的jar包

//Spring核心包
    spring-core-4.0.6.RELEASE.jar
    spring-expression-4.0.6.RELEASE.jar
    spring-beans-4.0.6.RELEASE.jar
    commons-logging-1.1.3.jar
         
//后期通过注解方式实现需要
    spring-context-4.0.6.RELEASE.jar 
         
//Aop部分架包
    spring-aop-4.0.6.RELEASE.jar
    aspectjweaver-1.8.7.jar //下载地址: http://central.maven.org/maven2/org/aspectj/aspectjweaver/1.8.7/
    aopalliance-1.0.jar //下载地址: http://central.maven.org/maven2/aopalliance/aopalliance/1.0/

基本概念

    1. Pointcut(切入点):简称切点,定义匹配通知所要织入的一个或多个连接点,一般常用正则表达式定义所匹配的类和方法名称来指定这些切点(切点表达式这里不做过多累述,详情请点击https://www.cnblogs.com/domi22/p/8047929.html
    1. Aspect(切面):通常是一个类,里面可以定义切入点和通知
    1. JointPoint(连接点):应用执行过程中能够插入切面的一个点,一般是方法的调用
    1. Advice(通知):AOP在特定的切入点上执行的增强处理,有五种,分别为 前置通(Before)后置通知(After)返回通知(After-returning)异常通知(After-throwing)环绕通知(Around)
    1. AOP代理:AOP框架创建的对象,代理就是目标对象的加强。Spring中的AOP代理可以使JDK动态代理,也可以是Cglib代理,前者基于接口,后者基于子类
1. XML方式实现

为了代码清晰省略了 UserDao 和 UserDaoImpl,直接在 UserService 层进行一个简单的数据保存模拟

import org.aspectj.lang.JoinPoint;

/**
 * 自定义一个切面类
 * @author liuchao
 */
public class Aspect {

    public void beforeAction(JoinPoint joinPoint) {
        System.out.println("开启事务");

        System.out.println("开启事务, 连接点 方法名: " + joinPoint.getSignature().getName());

        Object[] args = joinPoint.getArgs();
        if (args != null) {
            for (Object object : args) {
                System.out.println("开启事务, 方法参数: " + object.toString());
            }
        }
    }

    public void afterAction(JoinPoint joinPoint) {
        System.out.println("提交事务");

        System.out.println("提交事务, 连接点 方法名: " + joinPoint.getSignature().getName());

        Object[] args = joinPoint.getArgs();
        if (args != null) {
            for (Object object : args) {
                System.out.println("提交事务, 方法参数: " + object.toString());
            }
        }
    }
}
/**
 * 业务层
 * @author liuchao
 */
public class UserService {
    
    public void save() {
        System.out.println("持久层对象调用保存方法进行数据保存");
    }
}
/**
 * 视图控制器层
 * @author liuchao
 */
public class UserAction {
    //Spring IOC注入, 通过Set方式注入
    private UserService userService;
    public void setUserService(UserService userService) {
        this.userService = userService;
    }
    
    public void saveAction() {
        userService.save();
    }
}

Spring 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:aop="http://www.springframework.org/schema/aop"
    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.xsd 
        http://www.springframework.org/schema/aop 
        http://www.springframework.org/schema/aop/spring-aop.xsd">
                
        <bean id="userService" class="com.e_aop_xml.UserService"></bean>
        
        <bean id="userAction" class="com.e_aop_xml.UserAction">
            <property name="userService" ref="userService"></property>
        </bean>
        
        <!-- 切面类 -->
        <bean id="aspect" class="com.e_aop_xml.Aspect"></bean>
        
        <!-- Aop配置 -->
        <aop:config>
            <!-- 切点定义: 定义一个切入点表达式, 指定那些方法需要进行切面编程 -->
            <aop:pointcut expression="execution(* com.e_aop_xml.UserService.*(..))" id="pt"/>
            <!-- 切面 -->
            <aop:aspect ref="aspect">
                <!-- 目标方法前置调用 -->
                <aop:before method="beforeAction" pointcut-ref="pt"/>
                <aop:after method="afterAction" pointcut-ref="pt"/>
            </aop:aspect>
        </aop:config>
        
</beans>
import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
 * 测试类
 * @author liuchao
 */
public class UserTest {
    
    @Test
    public void testSaveAction() {
        @SuppressWarnings("resource")
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("com/e_aop_xml/bean.xml");
        UserAction userAction = (UserAction) context.getBean("userAction");
        userAction.saveAction();
    }
}
//控制台输出
开启事务
开启事务, 连接点 方法名: save
持久层对象调用保存方法进行数据保存
提交事务
提交事务, 连接点 方法名: save
2. 注解方式实现
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

/**
 * 自定义一个切面类
 * @author liuchao
 */
@Component
@org.aspectj.lang.annotation.Aspect
public class Aspect {
    
    @Before("execution(* com.f_aop_anno.UserService.*(..))")
    public void beforeAction(JoinPoint joinPoint) {
        System.out.println("开启事务, 连接点 方法名: " + joinPoint.getSignature().getName());
    }
    
    //自定义一个切点, 然后引用
    @Pointcut("execution(* com.f_aop_anno.UserService.*(..))")
    public void pointCut() {}
    
    @After("pointCut()")
    public void afterAction(JoinPoint joinPoint) {
        System.out.println("提交事务, 连接点 方法名: " + joinPoint.getSignature().getName());
    }
}
import org.springframework.stereotype.Service;
/**
 * 业务层
 * @author liuchao
 */
@Service
public class UserService {
    
    public void save() {
        System.out.println("持久层对象调用保存方法进行数据保存");
    }
}
import javax.annotation.Resource;

import org.springframework.stereotype.Controller;

/**
 * 视图控制器层
 * @author liuchao
 */
@Controller(value="userAction")
public class UserAction {
    
    @Resource
    private UserService userService;
    
    public void saveAction() {
        userService.save();
    }
}

Spring 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:aop="http://www.springframework.org/schema/aop"
    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.xsd 
        http://www.springframework.org/schema/aop 
        http://www.springframework.org/schema/aop/spring-aop.xsd">
        
        <!-- 开启注解扫描 -->
        <context:component-scan base-package="com.f_aop_anno"></context:component-scan>
        
        <!-- 启用@AsjectJ支持, 需要配置下面一句: -->
        <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
 * 测试类
 * @author liuchao
 */
public class UserTest {
    
    @Test
    public void testSaveAction() {
        @SuppressWarnings("resource")
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("com/f_aop_anno/bean.xml");
        UserAction userAction = (UserAction) context.getBean("userAction");
        userAction.saveAction();
    }
}
//控制台打印
开启事务, 连接点 方法名: save
持久层对象调用保存方法进行数据保存
提交事务, 连接点 方法名: save
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,723评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,080评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,604评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,440评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,431评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,499评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,893评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,541评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,751评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,547评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,619评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,320评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,890评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,896评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,137评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,796评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,335评论 2 342