Spring学习笔记(四)—— Spring中的AOP

一、AOP概述

  AOP(Aspect Oriented Programming),即面向切面编程,可以说是OOP(Object Oriented Programming,面向对象编程)的补充和完善。OOP引入封装、继承、多态等概念来建立一种对象层次结构,用于模拟公共行为的一个集合。不过OOP允许开发者定义纵向的关系,但并不适合定义横向的关系,例如日志功能。日志代码往往横向地散布在所有对象层次中,而与它对应的对象的核心功能毫无关系对于其他类型的代码,如安全性、异常处理和透明的持续性也都是如此,这种散布在各处的无关的代码被称为横切(cross cutting),在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用。

  AOP技术恰恰相反,它利用一种称为"横切"的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名为"Aspect",即切面。所谓"切面",简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性。

  使用"横切"技术,AOP把软件系统分为两个部分:核心关注点横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处基本相似,比如权限认证、日志、事物。AOP的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来。

  下面举个生活上的例子来帮助理解AOP是什么样的思想,先看一下传统程序的流程,比如银行系统会有一个取款流程:

   

  我们可以把方框里的流出合为一个,另外系统还会有一个查询余额的流出,我们先把这两个流程放在一起:

  接口

  这有个问题就是,有多少接口,就要copy多少次代码,这显然不符合我们的要求。好,我们提取一个公共的方法,让每个接口都来调用这个方法,这里有点切面的味道了:

  

  同样有个问题,虽然不用每次都copy代码了,但是每个接口总要调用这个方法吧。有没有想过把这个验证用户的代码提取出去,不放到主流程里去呢?这就是AOP的作用了,有了AOP,你写代码时不要把这个验证用户步骤写进去,即完全不考虑验证用户,你写完之后,在另我一个地方,写好验证用户的代码,然后告诉Spring你要把这段代码加到哪几个地方,Spring就会帮你加过去,而不要你自己Copy过去,这里还是两个地方,如果你有多个控制流呢,这个写代码的方法可以大大减少你的时间,不过AOP的目的不是这样,这只是一个“副作用”,真正目的是,你写代码的时候,事先只需考虑主流程,而不用考虑那些不重要的流程。举一个通用的例子,经常在debug的时候要打log吧,你也可以写好主要代码之后,把打log的代码写到另一个单独的地方,然后命令AOP把你的代码加过去,注意AOP不会把代码加到源文件里,但是它会正确的影响最终的机器代码。

  现在大概明白了AOP了吗,我们来理一下头绪,上面那个方框像不像个平面,你可以把它当块板子,这块板子插入一些控制流程,这块板子就可以当成是AOP中的一个切面。所以AOP的本质是在一系列纵向的控制流程中,把那些相同的子流程提取成一个横向的面,这句话应该好理解吧,我们把纵向流程画成一条直线,然把相同的部分以绿色突出,如下图左,而AOP相当于把相同的地方连一条横线,如下图右,这个图没画好,大家明白意思就行。

      

二、AOP的核心概念

  使用AOP之前,我们需要理解几个概念。

  • 连接点(Join Point):所有可能的需要注入切面的地方。如方法前后、类初始化、属性初始化前后等等。

  • 切入点(Poincut):需要做某些处理(如打印日志、处理缓存等等)的连接点。

  • 通知(Advice):定义在什么时候做什么事情。通知分为前置、后置、异常、最终、环绕通知五类

          

  • 切面(Aspect):通知+切点的集合,定义在什么地方什么时间做什么事情。

  • 目标对象(Target):被通知的对象,也就是真正的业务逻辑,他可以在毫不知情的情况下,被咱们织入切面。而自己专注于业务本身的逻辑。

  • 织入(Weaving):把切面应用到目标对象来创建新的代理对象的过程。

三、Spring对AOP的支持

  

  Spring中AOP代理由Spring的IOC容器负责生成、管理,其依赖关系也由IOC容器负责管理。因此,AOP代理可以直接使用容器中的其它bean实例作为目标,这种关系可由IOC容器的依赖注入提供。Spring创建代理的规则为:

    1、默认使用Java动态代理来创建AOP代理,这样就可以为任何接口实例创建代理了

    2、当需要代理的类不是代理接口的时候,Spring会切换为使用CGLIB代理,也可强制使用CGLIB

  AOP编程其实是很简单的事情,纵观AOP编程,程序员只需要参与三个部分:

    1、定义普通业务组件

    2、定义切入点,一个切入点可能横切多个业务组件

    3、定义增强处理,增强处理就是在AOP框架为普通业务组件织入的处理动作

  所以进行AOP编程的关键就是定义切入点和定义增强处理,一旦定义了合适的切入点和增强处理,AOP框架将自动生成AOP代理,即:代理对象的方法=增强处理+被代理对象的方法。

四、Spring底层AOP的实现原理(了解)

4.1 JDK动态代理

public class MyJDKProxy implements InvocationHandler {

    private UserDao userDao;

    public MyJDKProxy(UserDao userDao) {
        this.userDao = userDao;
    }
    
    // 编写工具方法:生成代理
    public UserDao createProxy(){
        UserDao userDaoProxy = (UserDao) Proxy.newProxyInstance(userDao.getClass().getClassLoader(), userDao.getClass().getInterfaces(), this);
        return userDaoProxy;
    }
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if("save".equals(method.getName())){
            System.out.println("权限校验================");
        }
        return method.invoke(userDao, args);
    }

}

  注意:JDK 给我们提供的动态代理只能代理接口,而不能代理没有接口的类

4.2 CGLib 动态代理

public class MyCglibProxy implements MethodInterceptor {

    private CustomerDao customerDao;
    
    public MyCglibProxy(CustomerDao customerDao) {
        this.customerDao = customerDao;
    }
    
    // 生成代理的方法
    public CustomerDao createProxy(){
        // 创建Cglib的核心类
        Enhancer enhancer = new Enhancer();
        // 设置父类
        enhancer.setSuperclass(CustomerDao.class);
        // 设置回调
        enhancer.setCallback(this);
        // 生成代理
        CustomerDao customerDaoProxy = (CustomerDao) enhancer.create();
        return customerDaoProxy;
    }
    
    @Override
    public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        if("delete".equals(method.getName())){
            Object obj = methodProxy.invokeSuper(proxy, args);
            System.out.println("日志记录==============");
            return obj;
        }
        return methodProxy.invokeSuper(proxy, args);
    }
}

  Cglib代理可以对任何类生成代理,代理的原理是对目标对象进行继承代理.,如果目标对象被final修饰.那么该类无法被cglib代理。

五、Spring使用AspectJ进行AOP的开发

5.1 XML配置

  第一步:引入相应的jar包

  

  第二步:准备目标对象

public class UserServiceImpl implements UserService {

    public void save() {
        System.out.println("保存用户");
    }

    public void delete() {
        System.out.println("删除用户");
    }

    public void update() {
        System.out.println("更新用户");
    }

    public void find() {
        System.out.println("查找用户");
    }

}

  第三步:准备通知

public class MyAdvice {
    // 前置通知:目标方法运行之前调用
    public void before(){
        System.out.println("这是前置通知");
    }
    
    // 后置通知:在目标方法运行之后调用(如果出现异常不会调用)
    public void afterReturning(){
        System.out.println("这是后置通知(如果出现异常不会调用)");
    }
    
    // 环绕通知:在目标方法之前和之后都调用
    public Object around(ProceedingJoinPoint pjp) throws Throwable{
        System.out.println("这是环绕通知之前的部分");
        Object proceed = pjp.proceed();// 调用目标方法
        System.out.println("这是环绕通知之后的部分");
        return proceed;
    }
    
    // 异常拦截通知(如果出现异常,就会调用)
    public void afterException(){
        System.out.println("出现异常了");
    }
    
    // 后置通知:在目标方法运行之后调用(无论是否出现异常,都会调用)
    public void after(){
        System.out.println("这是后置通知(出现异常也会调用)");
    }
}

   第四步:准备applicationContext.xml,并导入aop约束

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xmlns="http://www.springframework.org/schema/beans" 
    xmlns:aop="http://www.springframework.org/schema/aop" 
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
                        http://www.springframework.org/schema/beans/spring-beans-4.2.xsd 
                        http://www.springframework.org/schema/aop 
                        http://www.springframework.org/schema/aop/spring-aop-4.0.xsd ">

</beans>

  第五步:将通知织入目标对象中

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xmlns="http://www.springframework.org/schema/beans" 
    xmlns:aop="http://www.springframework.org/schema/aop" 
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
                        http://www.springframework.org/schema/beans/spring-beans-4.2.xsd 
                        http://www.springframework.org/schema/aop 
                        http://www.springframework.org/schema/aop/spring-aop-4.0.xsd ">
    <!-- 1.准备目标对象 -->
    <bean id="userService" class="cn.itcast.service.impl.UserServiceImpl"></bean>
    <!-- 2.准备通知对象 -->
    <bean id="myAdvice" class="cn.itcast.service.MyAdvice"></bean>
    <!-- 3.将通知织入目标对象 -->
    <aop:config>
        <!-- 配置切入点 
            public void cn.itcast.service.UserServiceImpl.save()
            void cn.itcast.service.UserServiceImpl.save()
            * cn.itcast.service.UserServiceImpl.save()
            * cn.itcast.service.UserServiceImpl.*()
            
            * cn.itcast.service.*ServiceImpl.*(..)
            * cn.itcast.service..*ServiceImpl.*(..)
        -->
        <aop:pointcut expression="execution(* cn.itcast.service..*ServiceImpl.*(..))" id="pointcut"/>
        <aop:aspect ref="myAdvice">
            <!-- 指定名为before的方法作为前置通知 -->
            <aop:before method="before" pointcut-ref="pointcut"/>
            <!-- 后置 -->
            <aop:after-returning method="afterReturning" pointcut-ref="pointcut"/>
            <!-- 环绕通知 -->
            <aop:around method="around" pointcut-ref="pointcut"/>
            <!-- 异常拦截通知 -->
            <aop:after-throwing method="afterException" pointcut-ref="pointcut"/>
            <!-- 后置 -->
            <aop:after method="after" pointcut-ref="pointcut"/>
        </aop:aspect>
    </aop:config>
</beans>

  第六步:编写测试方法

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class Demo {
    @Resource(name="userService")
    private UserService userService;
    
    @Test
    public void fun1() throws Exception {
        userService.save();
    }
}

   结果输出:

  

5.2 注解配置

  第一步:导包

  

  第二步:准备目标对象

public class UserServiceImpl implements UserService {

    public void save() {
        System.out.println("保存用户");
    }

    public void delete() {
        System.out.println("删除用户");
    }

    public void update() {
        System.out.println("更新用户");
    }

    public void find() {
        System.out.println("查找用户");
    }

}

  第三步:准备通知

// 表示该类是一个通知类
@Aspect
public class MyAdvice {
    @Pointcut("execution(* cn.itcast.service..*ServiceImpl.*(..))")
    public void pointcut(){
        
    }
    
    // 前置通知:目标方法运行之前调用
    // 指定该方法是前置通知,并指定切入点
    @Before("MyAdvice.pointcut()")
    public void before(){
        System.out.println("这是前置通知");
    }
    
    // 后置通知:在目标方法运行之后调用(如果出现异常不会调用)
    // 这里的execution(* cn.itcast.service..*ServiceImpl.*(..))等价于MyAdvice.pointcut()
    @AfterReturning("execution(* cn.itcast.service..*ServiceImpl.*(..))")
    public void afterReturning(){
        System.out.println("这是后置通知(如果出现异常不会调用)");
    }
    
    // 环绕通知:在目标方法之前和之后都调用
    @Around("execution(* cn.itcast.service..*ServiceImpl.*(..))")
    public Object around(ProceedingJoinPoint pjp) throws Throwable{
        System.out.println("这是环绕通知之前的部分");
        Object proceed = pjp.proceed();// 调用目标方法
        System.out.println("这是环绕通知之后的部分");
        return proceed;
    }
    
    // 异常拦截通知(如果出现异常,就会调用)
    @AfterThrowing("execution(* cn.itcast.service..*ServiceImpl.*(..))")
    public void afterException(){
        System.out.println("出现异常了");
    }
    
    // 后置通知:在目标方法运行之后调用(无论是否出现异常,都会调用)
    @After("execution(* cn.itcast.service..*ServiceImpl.*(..))")
    public void after(){
        System.out.println("这是后置通知(出现异常也会调用)");
    }
}

   第四步:开启使用注解完成织入

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xmlns="http://www.springframework.org/schema/beans" 
    xmlns:aop="http://www.springframework.org/schema/aop" 
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
                        http://www.springframework.org/schema/beans/spring-beans-4.2.xsd 
                        http://www.springframework.org/schema/aop 
                        http://www.springframework.org/schema/aop/spring-aop-4.0.xsd ">
    <!-- 1.准备目标对象 -->
    <bean id="userService" class="cn.itcast.service.impl.UserServiceImpl"></bean>
    <!-- 2.准备通知对象 -->
    <bean id="myAdvice" class="cn.itcast.service.MyAdvice"></bean>
    <!-- 3.开启使用注解完成织入 -->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>

  第五步:测试

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class Demo {
    @Resource(name="userService")
    private UserService userService;
    
    @Test
    public void fun1() throws Exception {
        userService.save();
    }
}

参考资料:https://www.cnblogs.com/Wolfmanlq/p/6036019.html

     https://www.jianshu.com/p/570c5283b1fc

猜你喜欢

转载自www.cnblogs.com/yft-javaNotes/p/10295292.html