北京 切换校区

全国24小时免费热线

400-009-1906

Spring AOP是什么?你都拿它做什么?

时间:2019-01-17   来源:尚学堂   阅读:216
首页> Spring AOP是什么?你都拿它做什么?

  为什么会有面向切面编程(AOP)?我们知道Java是一个面向对象(OOP)的语言,但它有一些弊端,比如当我们需要为多个不具有继承关系的对象引入一个公共行为,例如日志、权限验证、事务等功能时,只能在在每个对象里引用公共行为。这样做不便于维护,而且有大量重复代码。AOP的出现弥补了OOP的这点不足。

  为了阐述清楚SpringAOP,我们从将以下方面进行讨论:

  1.代理模式

  2.静态代理原理及实践

  3.动态代理原理及实践

  4.SpringAOP原理及实战

 

  1.代理模式

  代理模式:为其他对象提供一种代理以控制对这个对象的访问。这段话比较官方,但我更倾向于用自己的语言理解:比如A对象要做一件事情,在没有代理前,自己来做;在对A代理后,由A的代理类B来做。代理其实是在原实例前后加了一层处理,这也是AOP的初级轮廓。

 

  2.静态代理原理及实践

  静态代理模式:静态代理说白了,就是在程序运行前就已经存在代理类的字节码文件、代理类和原始类的关系在运行前就已经确定。废话不多说,我们看一下代码。为了方便阅读,博主把单独的class文件合并到接口中,读者可以直接复制代码运行:

package test.staticProxy;
 
// 接口
public interface IUserDao {
    void save();
    void find();
}
 
//目标对象
class UserDao implements IUserDao{
    @Override
    public void save() {
        System.out.println("模拟:保存用户!");
    }
    @Override
    public void find() {
        System.out.println("模拟:查询用户");
    }
}
 
/**
  * 静态代理
  * 特点:
  * 2. 目标对象必须要实现接口
  * 2. 代理对象,要实现与目标对象一样的接口
 */
class UserDaoProxy implements IUserDao{
 
    // 代理对象,需要维护一个目标对象
    private IUserDao target = new UserDao();
 
    @Override
    public void save() {
        System.out.println("代理操作: 开启事务...");
        target.save();   // 执行目标对象的方法
        System.out.println("代理操作:提交事务...");
    }
 
    @Override
    public void find() {
        target.find();
    }
}

  测试结果:

  静态代理虽然保证了业务类只需关注逻辑本身,代理对象的一个接口只服务于一种类型的对象。如果要代理的方法很多,势必要为每一种方法都进行代理。再者,如果增加一个方法,除了实现类需要实现这个方法外,所有的代理类也要实现此方法。增加了代码的维护成本。那么要如何解决呢?答案是使用动态代理。

 

  3.动态代理原理及实践

  动态代理模式:动态代理类的源码是在程序运行期间,通过JVM反射等机制动态生成。代理类和委托类的关系是运行时才确定的。实例如下:

package test.dynamicProxy;
 
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
 
// 接口
public interface IUserDao {
    void save();
    void find();
}
 
//目标对象
class UserDao implements IUserDao{
 
    @Override
    public void save() {
        System.out.println("模拟: 保存用户!");
    }
 
    @Override
    public void find() {
        System.out.println("查询");
    }
}
 
/**
 * 动态代理:
 * 代理工厂,给多个目标对象生成代理对象!
 *
 */
class ProxyFactory {
 
    // 接收一个目标对象
    private Object target;
 
    public ProxyFactory(Object target) {
        this.target = target;
    }
 
    // 返回对目标对象(target)代理后的对象(proxy)
    public Object getProxyInstance() {
        Object proxy = Proxy.newProxyInstance(
            target.getClass().getClassLoader(),  // 目标对象使用的类加载器
            target.getClass().getInterfaces(),   // 目标对象实现的所有接口
            new InvocationHandler() {            // 执行代理对象方法时候触发
 
                @Override
                public Object invoke(Object proxy, Method method, Object[] args)
                        throws Throwable {
 
                    // 获取当前执行的方法的方法名
                    String methodName = method.getName();
                    // 方法返回值
                    Object result = null;
                    if ("find".equals(methodName)) {
                        // 直接调用目标对象方法
                        result = method.invoke(target, args);
                    } else {
                        System.out.println("开启事务...");
                        // 执行目标对象方法
                        result = method.invoke(target, args);
                        System.out.println("提交事务...");
                    }
                    return result;
                }
            }
        );
        return proxy;
    }
}

  测试结果如下:

IUserDao proxy = (IUserDao)new ProxyFactory(target).getProxyInstance();

  其实是JDK动态生成了一个类去实现接口,隐藏了这个过程:

class $jdkProxy implements IUserDao{}

  使用JDK生成的动态代理的前提是目标类必须有实现的接口。但这里又引入一个问题,如果某个类没有实现接口,就不能使用JDK动态代理。所以CGLIB代理就是解决这个问题的。

  CGLIB是以动态生成的子类继承目标的方式实现,在运行期动态的在内存中构建一个子类,如下:

public class UserDao{}
 
// CGLIB 是以动态生成的子类继承目标的方式实现,程序执行时,隐藏了下面的过程
public class $Cglib_Proxy_class  extends UserDao{}

  CGLIB使用的前提是目标类不能为final修饰。因为final修饰的类不能被继承。

  现在,我们可以看看AOP的定义:面向切面编程,核心原理是使用动态代理模式在方法执行前后或出现异常时加入相关逻辑。

  通过定义和前面代码我们可以发现3点:

  •  AOP是基于动态代理模式。

  •  AOP是方法级别的。

  •  AOP可以分离业务代码和关注点代码(重复代码),在执行业务代码时,动态的注入关注点代码。切面就是关注点代码形成的类。

 

  4.SpringAOP

  前文提到JDK代理和CGLIB代理两种动态代理。优秀的Spring框架把两种方式在底层都集成了进去,我们无需担心自己去实现动态生成代理。那么,Spring是如何生成代理对象的?

  1.创建容器对象的时候,根据切入点表达式拦截的类,生成代理对象。

  2.如果目标对象有实现接口,使用JDK代理。如果目标对象没有实现接口,则使用CGLIB代理。然后从容器获取代理后的对象,在运行期植入“切面”类的方法。通过查看Spring源码,我们在DefaultAopProxyFactory类中,找到这样一段话。

  简单的从字面意思看出:如果有接口,则使用JDK代理,反之使用CGLIB,这刚好印证了前文所阐述的内容。SpringAOP综合两种代理方式的使用前提有会如下结论:如果目标类没有实现接口,且class为final修饰的,则不能进行SpringAOP编程!

  知道了原理,现在我们将自己手动实现Spring的AOP:

package test.spring_aop_anno;
 
import org.aspectj.lang.ProceedingJoinPoint;
 
public interface IUserDao {
    void save();
}
 
// 用于测试 CGLIB 动态代理
class OrderDao {
    public void save() {
        //int i =1/0; 用于测试异常通知
        System.out.println("保存订单...");
    }
}
 
//用于测试 JDK 动态代理
class UserDao implements IUserDao {
    public void save() {
        //int i =1/0; 用于测试异常通知
        System.out.println("保存用户...");
    }
}
 
//切面类
class TransactionAop {
 
    public void beginTransaction() {
        System.out.println("[前置通知]  开启事务..");
    }
 
    public void commit() {
        System.out.println("[后置通知] 提交事务..");
    }
 
    public void afterReturing() {
        System.out.println("[返回后通知]");
    }
 
    public void afterThrowing() {
        System.out.println("[异常通知]");
    }
 
    public void arroud(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("[环绕前:]");
        pjp.proceed(); // 执行目标方法
        System.out.println("[环绕后:]");
    }
}

  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:context="http://www.springframework.org/schema/context"
    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/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">
 
    <!-- dao实例加入容器 -->
    <bean id="userDao" class="test.spring_aop_anno.UserDao"></bean>
 
    <!-- dao实例加入容器 -->
    <bean id="orderDao" class="test.spring_aop_anno.OrderDao"></bean>
 
    <!-- 实例化切面类 -->
    <bean id="transactionAop" class="test.spring_aop_anno.TransactionAop"></bean>
 
    <!-- Aop相关配置 -->
    <aop:config>
        <!-- 切入点表达式定义 -->
        <aop:pointcut expression="execution(* test.spring_aop_anno.*Dao.*(..))" id="transactionPointcut"/>
        <!-- 切面配置 -->
        <aop:aspect ref="transactionAop">
            <!-- 【环绕通知】 -->
            <aop:around method="arroud" pointcut-ref="transactionPointcut"/>
            <!-- 【前置通知】 在目标方法之前执行 -->
            <aop:before method="beginTransaction" pointcut-ref="transactionPointcut" />
            <!-- 【后置通知】 -->
            <aop:after method="commit" pointcut-ref="transactionPointcut"/>
            <!-- 【返回后通知】 -->
            <aop:after-returning method="afterReturing" pointcut-ref="transactionPointcut"/>
            <!-- 异常通知 -->
            <aop:after-throwing method="afterThrowing" pointcut-ref="transactionPointcut"/>
        </aop:aspect>
    </aop:config>
</beans>

  切入点表达式不在这里介绍。参考SpringAOP切入点表达式

  代码的测试结果如下:

  到这里,我们已经全部介绍完SpringAOP。回到开篇的问题,我们拿它做什么?

  1.Spring声明式事务管理配置:请参考博主的另一篇文章:分布式系统架构实战demo:SSM+Dubbo

  2.Controller层的参数校验:参考SpringAOP拦截Controller做参数校验

  3.使用SpringAOP实现MySQL数据库读写分离案例分析

  4.在执行方法前,判断是否具有权限

  5.对部分函数的调用进行日志记录:监控部分重要函数,若抛出指定的异常,可以以短信或邮件方式通知相关人员。

  6.信息过滤,页面转发等等功能

 

  SpringAOP还能做什么,实现什么魔幻功能,就在于我们每一个平凡而又睿智的程序猿!

相关资讯

  • 北京校区
  • 山西校区
  • 郑州校区
  • 武汉校区
  • 四川校区
  • 长沙校区
  • 深圳校区
  • 上海校区
  • 广州校区
  • 保定招生办

北京海淀区校区(总部):北京市海淀区西三旗街道建材城西路中腾建华商务大厦东侧二层尚学堂
北京京南校区:北京亦庄经济开发区科创十四街6号院1号楼 赛蒂国际工业园
咨询电话:400-009-1906 / 010-56233821
面授课程: JavaEE培训大数据就业班培训大数据云计算周末班培训零基础大数据连读班培训大数据云计算高手班培训人工智能周末班培训人工智能+Python全栈培训H5+PHP全栈工程师培训

山西学区地址:山西省晋中市榆次区大学城大学生活广场万科商业A1座702

郑州学区地址:河南电子商务产业园6号楼4层407
咨询电话:0371-55177956

武汉学区地址:湖北省武汉市江夏区江夏大道26号 宏信悦谷创业园4楼
咨询电话:027-87989193

四川学区地址:成都市高新区锦晖西一街99号布鲁明顿大厦2栋1003室
咨询电话:028-65176856 / 13880900114

网址:http://www.cssxt.com/
咨询电话:0731-83072091

深圳校区地址:深圳市宝安区航城街道航城大道航城创新创业园A4栋210(固戍地铁站C出口)
咨询电话:0755-23061965 / 18898413781

上海尚学堂松江校区地址:上海市松江区荣乐东路2369弄45号绿地伯顿大厦2层
咨询电话:021-67690939

广州校区地址:广州市天河区元岗横路31号慧通产业广场B区B1栋6楼尚学堂(地铁3号线或6号线到“天河客运站”D出口,右拐直走约800米)
咨询电话:020-2989 6995

保定招生办公室

地址:河北省保定市竞秀区朝阳南大街777号鸿悦国际1101室

电话:15132423123

Copyright 2006-2019 北京尚学堂科技有限公司  京ICP备13018289号-19  京公网安备11010802015183  
媒体联系:18610174079 闫老师  

Java基础班,免费试学三周