Spring AOP_0802_03

                                             第三篇:Spring AOP认识

1、Spring AOP简介

1.1、什么是AOP?

        AOP(Aspect Oriented Programming,简称AOP),即面向切面编程(也有人称面向方面编程)。它是OOP(面向对象编程)的一种补充。OOP(面向对象编程):https://www.cnblogs.com/cycanfly/p/5235426.html

1.2、AOP设计思想(将重复代码集成起来如日志、事务、权限、异常等等,再利用切面Aspect织入到目标对象上)

        AOP采用横向抽取机制,将分散在各个方法中的重复代码提取出来,然后在程序编译或运行时,再将这些提取出来的代码应用到需要执行的地方。采用传统的OOP(面向对象编程)思想显然是办不到的。但它却不是OOP的替代品,而是OOP的延伸和补充。AOP(面向切面编程):https://baike.baidu.com/item/AOP/1332219?fr=aladdin

1.3、优点:AOP的使用,使得开发人员在编写业务逻辑时可以专心核心业务开发,而不用过多关注其它业务逻辑的实现。(提高了开发效率、增强了代码的可维护性)。

扫描二维码关注公众号,回复: 3158463 查看本文章

1.4、目前最流行的AOP框架(两种):Spring AOP和AspectJ

      1.4.1、Spring AOP:使用纯Java实现,不需要专门的编译过程和类加载器,在运行期间通过代理方式向目标类织入增强的的代码。

       1.4.2、AspectJ:是一个基于Java语言的AOP框架,从Spring2.0开发就开始引入AspectJ的支持,AspectJ扩展了Java语言,提供了一个专门的编译器,在编译时提供横向代码的织入。


2、AOP术语

     Aspect、Joinpoint、Pointcut、Advice、Target Object、Proxy和Weaving

  • Aspect(切面):指封装的用于横向插入系统功能(如事务、日志)的类,需要在spring配置文件中通过<bean>元素指定(参考基于XML的声明式AspectJ配置文件,bean)。
  • Joinpoint(连接点):在Spring AOP中,连接点就是还方法的调用(在程序执行过程中的某个阶段点,它实际上是对象的一个操作,例如方法的调用或异常的抛出)。
  • Pointcut(切入点):是切面与程序流程的交叉点。通过在程序中,切入点是指类或方法名。
  • Advice(通知/增强处理):AOP框架在特定的切入点执行增强处理,即在定义好的切入点处所要执行的程序代码。可以将其理解为切面类中的方法,它是切面的具体实现。
  • Target Object(目标对象):指所有被通知对象,也称为被增强对象。
  • Proxy(代理):将通知应用到目标对象之后,被动态创建的对象。
  • Weaving(织入):将切面代码插入到目标对象中,从而生成代理对象的过程。

3、AOP动态代理

3.1、简介:AOP中的代理就是由AOP框架动态生成的一个对象,该对象可以作为目标对象使用。

3.2、Spring中AOP代理(两种):JDK代理和CGLIB代理

         JDK动态代理的使用有一定的局限性,就是使用动态代理的对象必须实现一个或者多个接口。如果要对没有实现接口的类进行代理,那么可以使用CGLIB代理。

         实际上,Spring中的AOP代理,默认就是JDK动态代理方式来实现的。

         在Spring中,使用ProxyFactoryBean是创建AOP代理的最基本的方式。

3.2.1、例子:JDK代理实现

-------------------------------------UserDao-------------------------------------------------------------------

package com.itheima.jdk;
public interface UserDao {
    public void addUser();
    public void deleteUser();
}

---------------------------------------------------UserDaoImpl-----------------------------------------------------

package com.itheima.jdk;

import org.springframework.stereotype.Repository;

// 目标类
public class UserDaoImpl implements UserDao {
    public void addUser() {
        System.out.println("添加用户");
    }
    public void deleteUser() {
        System.out.println("删除用户");
    }
}

---------------------------------------------------MyAspect -----------------------------------------------------

package com.itheima.aspect;
//切面类:可以存在多个通知Advice(即增强的方法)
public class MyAspect {
    public void check_Permissions(){
        System.out.println("模拟检查权限...");
    }
    public void log(){
        System.out.println("模拟记录日志...");
    }
}
 

---------------------------------------------------JdkProxy代理类-----------------------------------------------------

package com.itheima.jdk;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import com.itheima.aspect.MyAspect;
/**
 * JDK代理类
 */
public class JdkProxy implements InvocationHandler{
    // 声明目标类接口
    private UserDao userDao;
    // 创建代理方法
    public  Object createProxy(UserDao userDao) {
        this.userDao = userDao;
        // 1.类加载器
        ClassLoader classLoader = JdkProxy.class.getClassLoader();
        // 2.被代理对象实现的所有接口
        Class[] clazz = userDao.getClass().getInterfaces();
        // 3.使用代理类,进行增强,返回的是代理后的对象
        return  Proxy.newProxyInstance(classLoader,clazz,this);
    }
    /*
     * 所有动态代理类的方法调用,都会交由invoke()方法去处理
     * proxy 被代理后的对象 
     * method 将要被执行的方法信息(反射) 
     * args 执行方法时需要的参数
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) 
                                                                throws Throwable {
        // 声明切面
        MyAspect myAspect = new MyAspect();
        // 前增强
        myAspect.check_Permissions();
        // 在目标类上调用方法,并传入参数
        Object obj = method.invoke(userDao, args);
        // 后增强
        myAspect.log();
        return obj;
    }
}
---------------------------------------------------JdkTest测试类-----------------------------------------------------

package com.itheima.jdk;
public class JdkTest{
    public static void main(String[] args) {
        // 创建代理对象
        JdkProxy jdkProxy = new JdkProxy();
         // 创建目标对象
        UserDao userDao= new UserDaoImpl();
        // 从代理对象中获取增强后的目标对象
        UserDao userDao1 = (UserDao) jdkProxy.createProxy(userDao);
        // 执行方法
        userDao1.addUser();
        userDao1.deleteUser();
    }
}

---------------------------------------------------控制台输出结果------------------------------------------------------------------------

模拟检查权限...

添加用户

模拟记录日志...

模拟检查权限...

删除用户

模拟记录日志...

3.2.2、例子:CGLIB代理实现

-------------------------------------UserDao-------------------------------------------------------------------

package com.itheima.cglib;
//目标类
public class UserDao {
    public void addUser() {
        System.out.println("添加用户");
    }
    public void deleteUser() {
        System.out.println("删除用户");
    }
}

---------------------------------------------------MyAspect -----------------------------------------------------

package com.itheima.aspect;
//切面类:可以存在多个通知Advice(即增强的方法)
public class MyAspect {
    public void check_Permissions(){
        System.out.println("模拟检查权限...");
    }
    public void log(){
        System.out.println("模拟记录日志...");
    }
}
 

---------------------------------------------------JdkProxy代理类-----------------------------------------------------

package com.itheima.cglib;
import java.lang.reflect.Method;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import com.itheima.aspect.MyAspect;
// 代理类
public class CglibProxy implements MethodInterceptor{
    // 代理方法
    public  Object createProxy(Object target) {
        // 创建一个动态类对象
        Enhancer enhancer = new Enhancer();
        // 确定需要增强的类,设置其父类
        enhancer.setSuperclass(target.getClass());
        // 添加回调函数
        enhancer.setCallback(this);
        // 返回创建的代理类
        return enhancer.create();
    }
    /**
     * proxy CGlib根据指定父类生成的代理对象
     * method 拦截的方法
     * args 拦截方法的参数数组
     * methodProxy 方法的代理对象,用于执行父类的方法 
     */
    @Override
    public Object intercept(Object proxy, Method method, Object[] args, 
                                 MethodProxy methodProxy) throws Throwable {
         // 创建切面类对象
        MyAspect myAspect = new MyAspect();
        // 前增强
        myAspect.check_Permissions();
        // 目标方法执行
        Object obj = methodProxy.invokeSuper(proxy, args);
        // 后增强
        myAspect.log();    
        return obj;
    }
}

---------------------------------------------------JdkTest测试类-----------------------------------------------------

package com.itheima.cglib;
// 测试类
public class CglibTest {
    public static void main(String[] args) {
        // 创建代理对象
        CglibProxy cglibProxy = new CglibProxy();
             // 创建目标对象
        UserDao userDao = new UserDao();
         // 获取增强后的目标对象
        UserDao userDao1 = (UserDao)cglibProxy.createProxy(userDao);
        // 执行方法
        userDao1.addUser();
        userDao1.deleteUser();
    }
}

---------------------------------------------------控制台输出结果------------------------------------------------------------------------

模拟检查权限...

添加用户

模拟记录日志...

模拟检查权限...

删除用户

模拟记录日志...

4、Spring的通知类型(五种)

环绕通知、前置通知、后置通知、异常通知、引介通知

5、AspectJ开发

5.1、简介:AspectJ是一个基于Java语言的AOP框架,它提供了强大的AOP功能。(新版本的Spring框架也建议使用AspectJ来开发AOP)

5.2、使用AspectJ实现AOP有两种方式:基于xml的声明式AspectJ、基于注解的声明式AspectJ

5.2.1、基于xml的声明式AspectJ

       基于XML的声明式AspectJ是指通过XML文件来定义切面、切入点及通知,所有的切面、切入点及通知都必须定义在<aop:config>元素内。spring配置文件中< beans>元素下可以包含一个或多个<aop:config>元素,一个<aop:config>元素又可以包含属性和子元素,其子元素包括<aop:pointcut>、<aop:advisor>、<aop:aspect>,在配置这3个子元素时必须按照此顺序来定义。<aop:config>元素及子元素如下图

AOP配置元素 描述
<aop:config> 顶层的AOP配置元素,大多数的<aop:*>元素必须包含在<aop:config>元素内
<aop:aspect> 定义切面
<aop:aspect-autoproxy> 启用@AspectJ注解驱动的切面
<aop:pointcut> 定义切点
<aop:advisor> 定义AOP通知器
<aop:before> 定义AOP前置通知
<aop:after> 定义AOP后置通知(不管被通知的方法是否执行成功)
<aop:after-returning> 定义成功返回后的通知
<aop:after-throwing> 定义抛出异常后的通知
<aop:around> 定义AOP环绕通知
<aop:declare-parents> 为被通知的对象引入额外的接口,并透明地实现

<?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"
        xsi:schemaLocation="http://www.springframework.org/schema/beans 
        http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
        http://www.springframework.org/schema/aop 
        http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">
    <!-- 1 目标类 -->
    <bean id="userDao" class="com.itheima.jdk.UserDaoImpl" />
    <!-- 2 切面 -->
    <bean id="myAspect" class="com.itheima.aspectj.xml.MyAspect" />
    <!-- 3 aop编程 -->
    <aop:config>
        <!-- 配置切面<aop:aspect> 属性id,定义该切面的唯一标识符。属性ref,引用普通的Spring Bean -->
        <aop:aspect ref="myAspect">
          <!-- 3.1 配置切入点,通知最后增强哪些方法 -->

          <!-- <aop:pointcut > 作为 <aop:config>子元素定义时表示全局切入点,可以被多个切面共享 -->

          <!-- <aop:pointcut > 作为<aop:aspect>子元素定义时表示只对当前切面有效 -->

          <!-- <aop:pointcut >id切入点的唯一标识符,execution切入点表达式 。

                 第一个*表示返回类型,使用*表示返回类型->所有类型

                 第二个*表示所有类,第三个*表示所有方法,(..)其中..表示参数

           -->
          <aop:pointcut expression="execution(* com.itheima.jdk.*.*(..))"  id="myPointCut" />
            <!-- 3.2 关联通知Advice和切入点pointCut -->
            <!-- 3.2.1 前置通知 -->
            <aop:before method="myBefore" pointcut-ref="myPointCut" />
            <!-- 3.2.2 后置通知,在方法返回之后执行,就可以获得返回值
             returning属性:用于设置后置通知的第二个参数的名称,类型是Object -->
            <aop:after-returning method="myAfterReturning"
                pointcut-ref="myPointCut" returning="returnVal" />
            <!-- 3.2.3 环绕通知 -->
            <aop:around method="myAround" pointcut-ref="myPointCut" />
            <!-- 3.2.4 抛出通知:用于处理程序发生异常-->
            <!-- * 注意:如果程序没有异常,将不会执行增强 -->
            <!-- * throwing属性:用于设置通知第二个参数的名称,类型Throwable -->
            <aop:after-throwing method="myAfterThrowing"
                pointcut-ref="myPointCut" throwing="e" />
            <!-- 3.2.5 最终通知:无论程序发生任何事情,都将执行 -->
            <aop:after method="myAfter" pointcut-ref="myPointCut" />
        </aop:aspect>
    </aop:config>
</beans>

pointcut与pointcut-ref只使用其中一个,指定一个已经存在的切入点名称如配置代码中myPointCut。

method指定一个方法名。

throwing只对<after-throwing>元素有效,指定一个形参名,异常通知方法可以通过该形参访问目标方法所抛出的异常。

returning只对<after-returning>元素有效,指定一个形参名,后置通知方法可以通过该形参访问目标方法返回值。

5.2.2、基于注解的声明式AspectJ

        5.2.2.1 注解说明     

 注解名称 描述
@Aspect 用于定义一个切面
@Pointcut 用于定义切面表达式,实际上这个方法名就是一个返回值为void,且方法体为空的普通方法
@Before 定义前置通知
@AfterReturning 定义后置通知
@Around 定义环绕通知
@AfterThrowing 定义异常通知
@After  定义最终final通知
@DeclareParents  定义引介通知

     

       5.2.2.2高效方式

       <!-- 指定需要扫描的包,使注解生效 -->
      <context:component-scan base-package="com.itheima" />
      <!-- 启动基于注解的声明式AspectJ支持 -->
      <aop:aspectj-autoproxy />

例子:

---------------------------------------------------MyAspect -----------------------------------------------------------------------

package com.itheima.aspectj.annotation;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
/**
 * 切面类,在此类中编写通知
 */
@Aspect
@Component
public class MyAspect {
    // 定义切入点表达式
    @Pointcut("execution(* com.itheima.jdk.*.*(..))")
    // 使用一个返回值为void、方法体为空的方法来命名切入点
    private void myPointCut(){}
    // 前置通知
    @Before("myPointCut()")
    public void myBefore(JoinPoint joinPoint) {
        System.out.print("前置通知 :模拟执行权限检查...,");
        System.out.print("目标类是:"+joinPoint.getTarget() );
        System.out.println(",被织入增强处理的目标方法为:"
                       +joinPoint.getSignature().getName());
    }
    // 后置通知
    @AfterReturning(value="myPointCut()")
    public void myAfterReturning(JoinPoint joinPoint) {
        System.out.print("后置通知:模拟记录日志...," );
        System.out.println("被织入增强处理的目标方法为:"
                      + joinPoint.getSignature().getName());
    }
    // 环绕通知    
    @Around("myPointCut()")
    public Object myAround(ProceedingJoinPoint proceedingJoinPoint) 
            throws Throwable {
        // 开始
        System.out.println("环绕开始:执行目标方法之前,模拟开启事务...");
        // 执行当前目标方法
        Object obj = proceedingJoinPoint.proceed();
        // 结束
        System.out.println("环绕结束:执行目标方法之后,模拟关闭事务...");
        return obj;
    }
    // 异常通知
    @AfterThrowing(value="myPointCut()",throwing="e")
    public void myAfterThrowing(JoinPoint joinPoint, Throwable e) {
        System.out.println("异常通知:" + "出错了" + e.getMessage());
    }
    // 最终通知
    @After("myPointCut()")
    public void myAfter() {
        System.out.println("最终通知:模拟方法结束后的释放资源...");
    }
}
---------------------------------------------------配置文件-----------------------------------------------------------------------

<?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-4.3.xsd
  http://www.springframework.org/schema/aop 
  http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
  http://www.springframework.org/schema/context 
  http://www.springframework.org/schema/context/spring-context-4.3.xsd">
      <!-- 指定需要扫描的包,使注解生效 -->
      <context:component-scan base-package="com.itheima" />
      <!-- 启动基于注解的声明式AspectJ支持 -->
      <aop:aspectj-autoproxy />
</beans>
---------------------------------------------------测试类TestAnnotationAspectj -----------------------------------------------------------------------

package com.itheima.aspectj.annotation;
import org.springframework.context.ApplicationContext;
import 
    org.springframework.context.support.ClassPathXmlApplicationContext;
import com.itheima.jdk.UserDao;
// 测试类
public class TestAnnotationAspectj  {
    public static void main(String args[]) {
        String xmlPath = 
                  "com/itheima/aspectj/annotation/applicationContext.xml";
        ApplicationContext applicationContext = 
                 new ClassPathXmlApplicationContext(xmlPath);
        // 1 从spring容器获得内容
        UserDao userDao = (UserDao) applicationContext.getBean("userDao");
        // 2 执行方法
        userDao.addUser();
    }
}
 

学习《Java EE企业级应用开发教程》(出版社:中国工信出版社集团  人民邮电出版社 )

注:个人小结,供自己以后复习

猜你喜欢

转载自blog.csdn.net/qq_31935419/article/details/81389312