Spring-3-Spring中的AOP面向切面编程(动态代理-AOP概念简述-AOP代码演示)

目录

0、动态代理简述

1、JDK动态代理

1.1 JDK动态代理的代码演示

1.1.1 创建一个被代理的对象:接口SomeService

1.1.2 创建接口SomeService的实现类SomeServiceImpl

1.1.3 创建接口SomeService的增强类ServiceUtils

1.1.4 创建一个MyInvocationHandler类, 且实现InvocationHandler接口, 来增强被代理类

1.1.5 测试动态代理的效果-测试代码的基类BaseTest

1.1.6 测试动态代理的效果(通过创建动态代理来对被代理类实现方法增强)

1.1.7 测试结果与项目结构

2、AOP:面向切面编程:简述

2.1 AOP简述(掌握)

2.2 AOP面向切面编程对有什么好处?

2.3 AOP编程术语(掌握)

2.3.1 切面(Aspect)

2.3.2 连接点(JoinPoint)

2.3.3 切入点(Pointcut)

2.3.4 目标对象(Target)

2.3.5 通知(Advice)

3、AOP:面向切面编程:代码演示

3.1 AspectJ 对 AOP 的实现(掌握)

3.2 AspectJ 简介

3.3 AspectJ 的通知类型(理解)

3.4 AspectJ 的切入点表达式(掌握)

3.4.1 切入点表达式的原型解释

3.4.2 切入点表达式的原型举例:

3.5 AspectJ 实现AOP的代码演示

3.5.0 使用AspectJ实现AOP的具体步骤如下

3.5.0 创建一个Maven项目, 项目结构图

3.5.1 加入jar包依赖(Spring依赖+AspectJ依赖):pom.xml

3.5.2 创建目标类(是一个接口):SomeService.java

3.5.3 创建目标类的实现类:SomeServiceImpl.java

3.5.4 创建切面类和编写切面方法:MyAspect.java

3.5.5 创建spring.xml配置文件声名对象, 也即把对象交给Spring容器统一管理

3.5.6 创建springmvc.xml(AOP暂时用不到, 只是web项目必要的配置文件)

3.5.7 AOP的测试类基类:BaseTest.java

3.5.8 AOP的测试类和测试结果:MyTest.java

4、AOP-面向切面变成-回顾

5、AOP中的其他类型的通知-代码演示

5.1 前置通知 @Before:方法有JoinPoint参数

5.1.1 测试结果

5.2 后置通知 @AfterReturning:注解有returning属性

5.2.1 测试结果

5.3 环绕通知 @Around:增强方法有 ProceedingJoinPoint 参数

5.3.1 测试结果

5.4 异常通知 @AfterThrowing:注解中有throwing属性

5.5 最终通知 @After:总是会被执行

5.6 定义切入点 @Pointcut

5.7 目前为止的项目结构

【参考B站视频】https://www.bilibili.com/video/BV1nz4y1d7uy?p=1

0、动态代理简述

1、JDK动态代理

1.1 JDK动态代理的代码演示

1.1.1 创建一个被代理的对象:接口SomeService

package com.wind.service;


import org.springframework.stereotype.Service;

@Service
public interface SomeService {

    void doSome();

    void doOther();
}

1.1.2 创建接口SomeService的实现类SomeServiceImpl

package com.wind.serviceImpl;

import com.wind.service.SomeService;
import org.springframework.stereotype.Service;

@Service
public class SomeServiceImpl implements SomeService {

    @Override
    public void doSome() {
        System.out.println("业务方法===SomeServiceImpl.doSome done...");
    }

    @Override
    public void doOther() {
        System.out.println("业务方法===SomeServiceImpl.doOther done...");
    }
}

1.1.3 创建接口SomeService的增强类ServiceUtils

package com.wind.utils;

import org.springframework.stereotype.Component;

import java.util.Date;

@Component
public class ServiceUtils {

    public static void doLog() {
        System.out.println("非业务方法===方法开始执行的时间=" + new Date());
    }

    public static void doTx() {
        System.out.println("非业务方法===方法执行完毕,提交事务的时间=" + new Date());
    }
}

1.1.4 创建一个MyInvocationHandler类, 且实现InvocationHandler接口, 来增强被代理类

package com.wind.handler;


import com.wind.utils.ServiceUtils;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class MyInvocationHandler implements InvocationHandler {

    //定义一个目标对象:即将来的someServiceImpl对象
    private Object targetObject;

    public MyInvocationHandler(Object targetObject) {
        this.targetObject = targetObject;
    }

    /**
     * 通过代理对象执行目标方法时,会执行invoke方法。
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        System.out.println("动态代理===MyInvocationHandler.invoke() done...+准备执行的方法=" + method.getName());

        //定义一个方法返回值
        Object result = null;

        //动态代理-前置的方法增强
        ServiceUtils.doLog();

        //执行目标类的目标方法,底层通过Method类中的invoke方法完成
        result = method.invoke(targetObject, args);

        //动态代理-后置的方法增强
        ServiceUtils.doTx();

        //返回目标方法的返回值
        return result;
    }
}

1.1.5 测试动态代理的效果-测试代码的基类BaseTest

import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:config/spring.xml"})
public abstract class BaseTest {
}

1.1.6 测试动态代理的效果(通过创建动态代理来对被代理类实现方法增强)

import com.wind.handler.MyInvocationHandler;
import com.wind.service.SomeService;
import com.wind.serviceImpl.SomeServiceImpl;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;

@Component
public class MyApp extends BaseTest {

    @Autowired
    private SomeServiceImpl someServiceImpl;

    @Test
    public void someServiceTest() {
        someServiceImpl.doSome();
        someServiceImpl.doOther();
    }

    /***
     * 使用JDK的Proxy创建动态代理
     */
    @Test
    public void someServiceProxyTest() {

        //1.创建目标类对象
        SomeServiceImpl targetService = new SomeServiceImpl();

        //2.创建InvocationHandler对象
        InvocationHandler invocationHandler = new MyInvocationHandler(targetService);

        //3.创建Proxy动态代理对象
        SomeService targetServiceProxy = (SomeService) Proxy.newProxyInstance(
                targetService.getClass().getClassLoader(),
                targetService.getClass().getInterfaces(),
                invocationHandler
        );

        //4.通过动态代理对象执行invocationHandler中的invoke方法
        targetServiceProxy.doSome();
    }
}

1.1.7 测试结果与项目结构

2、AOP:面向切面编程:简述

2.1 AOP简述(掌握)

AOP(Aspect Orient Programming),面向切面编程,面向切面编程是从动态角度考虑程序运行过程。AOP,面向切面编程,可通过运行期动态代理实现程序功能的统一维护的一种技术。AOP 是 Spring 框架中的一个重要内容。利用 AOP 可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性, 同时提高了开发的效率。

面向切面编程,就是将交叉业务逻辑封装成切面,利用 AOP 容器的功能将切面织入到主业务逻辑中。所谓交叉业务逻辑是指,通用的、与主业务逻辑无 关的代码,如安全检查、事务、日志、缓存等。 若不使用 AOP,则会出现代码纠缠,即交叉业务逻辑与主业务逻辑混合在 一起。这样,会使主业务逻辑变的混杂不清。

例如,转账,在真正转账业务逻辑前后,需要权限控制、日志记录、加载事务、结束事务等交叉业务逻辑,而这些业务逻辑与主业务逻辑间并无直接关系。但是,它们的代码量所占比重能达到总代码量的一半甚至还多。它们的存在,不仅产生了大量的“冗余”代码,还大大干扰了主业务逻辑---转账。

2.2 AOP面向切面编程对有什么好处?

(1)减少重复。(2)专注业务。(3)注意:面向切面编程只是面向对象编程的一种补充。使用 AOP 减少重复代码,专注业务实现:

2.3 AOP编程术语(掌握)

2.3.1 切面(Aspect)

切面,泛指交叉业务逻辑。上例中的事务处理、日志处理就可以理解为切面。常用的切面是通知(Advice),实际就是对主业务逻辑的一种增强。

2.3.2 连接点(JoinPoint)

连接点,是指可以被切面织入的具体方法,通常业务接口中的方法均为连接点。

2.3.3 切入点(Pointcut)

切入点,是指声明的一个或多个连接点的集合。通过切入点指定一组方法。 被标记为 final 的方法是不能作为连接点与切入点的。因为最终的是不能被修改的,不能被增强的。

2.3.4 目标对象(Target)

目标对象,是指将要被增强的对象。即包含主业务逻辑的类的对象。上例中的 StudentServiceImpl 的对象若被增强,则该类称为目标类,该类对象称为目标 对象。当然,不被增强,也就无所谓目标不目标了。

2.3.5 通知(Advice)

通知,表示切面的执行时间,Advice 也叫增强。上例中的 MyInvocationHandler 就可以理解为是一种通知。换个角度来说,通知定义了增强代码切入到目标代码的时间点,是目标方法执行之前执行,还是之后执行等。通知类型不同,切入时间不同。切入点定义切入的位置,通知定义切入的时间。

3、AOP:面向切面编程:代码演示

3.1 AspectJ 对 AOP 的实现(掌握)

对于 AOP 这种编程思想,很多框架都进行了实现。Spring 就是其中之 一,可以完成面向切面编程。然而,AspectJ 也实现了 AOP 的功能,且其实现方式更为简捷,使用更为方便,而且还支持注解式开发。所以,Spring 又将 AspectJ 的对于 AOP 的实现也引入到了自己的框架中。在 Spring 中使用 AOP 开发时,一般使用 AspectJ 的实现方式。

3.2 AspectJ 简介

AspectJ 是一个优秀面向切面的框架,它扩展了 Java 语言,提供了强大的切 面实现。AspetJ官网地址:https://www.eclipse.org/aspectj/

AspetJ是 Eclipse 的开源项目,官网介绍如下:

(1)a seamless aspect-oriented extension to the Javatm programming language:一种基于 Java 平台的面向切面编程的语言。
(2)Java platform compatible:兼容 Java 平台,可以无缝扩展。
(3)easy to learn and use:易学易用。

3.3 AspectJ 的通知类型(理解)

AspectJ 中常用的通知有五种类型:(1)前置通知。(2)后置通知。(3)环绕通知。(4)异常通知。(5)最终通知。

3.4 AspectJ 的切入点表达式(掌握)

AspectJ 定义了专门的表达式用于指定切入点。表达式的原型是:

execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)

3.4.1 切入点表达式的原型解释

(1)modifiers-pattern:访问权限类型
(2)ret-type-pattern:返回值类型
(3)declaring-type-pattern:包名类名

(4)name-pattern(param-pattern):方法名(参数类型和参数个数)

(5)throws-pattern:抛出异常类型

(6)?:表示可选的部分以上表达式共 4 个部分。

【总结】execution(访问权限 方法返回值 方法声明(参数) 异常类型)

切入点表达式要匹配的对象就是目标方法的方法名。所以,execution 表达式中明显就是方法的签名。注意,表达式中黑色文字表示可省略部分,各部分间用空格分开。在其中可以使用以下符号:

3.4.2 切入点表达式的原型举例:

【总结】execution(访问权限 方法返回值 方法声明(参数) 异常类型)

(1)execution(public * *(..)) :指定切入点为:任意公共方法。

(2)execution(* set*(..)) :指定切入点为:任何一个以“set”开始的方法。

(3)execution(* com.xyz.service.*.*(..)) :指定切入点为:定义在 service 包里的任意类的任意方法。

(4)execution(* com.xyz.service..*.*(..)) :指定切入点为:定义在 service 包或者子包里的任意类的任意方法。“..”出现 在类名中时,后面必须跟“*”,表示包、子包下的所有类。

(5)execution(* *..service.*.*(..)) :指定所有包下的 serivce 子包下所有类(接口)中所有方法为切入点。

(6)execution(* *.service.*.*(..)) :指定只有一级包下的 serivce 子包下所有类(接口)中所有方法为切入点。

(7)execution(* *.ISomeService.*(..)) :指定只有一级包下的 ISomeSerivce 接口中所有方法为切入点 。

(8)execution(* *..ISomeService.*(..)) :指定所有包下的 ISomeSerivce 接口中所有方法为切入点。

(9)execution(* com.xyz.service.IAccountService.*(..)):指定切入点为:IAccountService 接口中的任意方法。
(10)execution(* com.xyz.service.IAccountService+.*(..)) :指定切入点为:IAccountService 若为接口,则为接口中的任意方法及其所有 实现类中的任意方法;若为类,则为该类及其子类中的任意方法。

(11)execution(* joke(String,int))) :指定切入点为:所有的 joke(String,int)方法,且 joke()方法的第一个参数是 String,第二个参数是 int。如果方法中的参数类型是 java.lang 包下的类,可 以直接使用类名,否则必须使用全限定类名,如 joke( java.util.List, int)。

(12)execution(* joke(String,*))) :指定切入点为:所有的 joke()方法,该方法第一个参数为 String,第二个参数 可以是任意类型,如 joke(String s1,String s2)和 joke(String s1,double d2) 都是,但 joke(String s1,double d2,String s3)不是。

(13)execution(* joke(String,..))) :指定切入点为:所有的 joke()方法,该方法第一个参数为 String,后面可以有 任意个参数且参数类型不限,如 joke(String s1)、joke(String s1,String s2)和 joke(String s1,double d2,String s3)都是。

(14)execution(* joke(Object)) :指定切入点为:所有的 joke()方法,方法拥有一个参数,且参数是 Object 类 型。joke(Object ob)是,但,joke(String s)与 joke(User u)均不是。

(15)execution(* joke(Object+))) :指定切入点为:所有的 joke()方法,方法拥有一个参数,且参数是 Object 类型或该类的子类。不仅 joke(Object ob)是,joke(String s)和 joke(User u)也 是。

3.5 AspectJ 实现AOP的代码演示

3.5.0 使用AspectJ实现AOP的具体步骤如下

3.5.0 创建一个Maven项目, 项目结构图

参考https://blog.csdn.net/cmm0401/article/details/111773134

3.5.1 加入jar包依赖(Spring依赖+AspectJ依赖):pom.xml

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.wind</groupId>
    <artifactId>ssm-web8-aop</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>war</packaging>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        <spring.version>5.2.5.RELEASE</spring.version>
        <mybatis.version>3.4.6</mybatis.version>
        <mybatis.spring.version>2.0.3</mybatis.spring.version>
        <mysql.version>8.0.22</mysql.version>
        <druid.version>1.2.4</druid.version>
    </properties>
    
    <dependencies>

        <!--测试AOP功能依赖的包-开始-->
        <!--Spring依赖-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <!--AspectJ的依赖-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.2</version>
        </dependency>

        <!--测试AOP功能依赖的包-结束-->

        <!--Spring事务依赖-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <!--Spring原生JDBC依赖-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <!--SpringMVC依赖-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <!--SpringMVC使用的Servlet依赖-->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>4.0.1</version>
        </dependency>

        <!--SpringMVC使用的jsp依赖-->
        <dependency>
            <groupId>javax.servlet.jsp</groupId>
            <artifactId>jsp-api</artifactId>
            <version>2.2.1-b03</version>
        </dependency>

        <dependency>
            <groupId>jstl</groupId>
            <artifactId>jstl</artifactId>
            <version>1.2</version>
        </dependency>

        <!--MyBatis依赖-->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>${mybatis.version}</version>
        </dependency>

        <!--MyBatis与SpringMVC整合时需要的依赖-->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>${mybatis.spring.version}</version>
        </dependency>

        <!--mysql驱动依赖-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>${mysql.version}</version>
        </dependency>

        <!--mysql数据库连接池依赖:使用的是德鲁伊数据库连接池-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>${druid.version}</version>
        </dependency>

        <!--springMVC序列化用的jackson依赖-->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-core</artifactId>
            <version>2.10.2</version>
        </dependency>

        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.10.2</version>
        </dependency>

        <!--单元测试依赖-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.2.5.RELEASE</version>
            <scope>test</scope>
        </dependency>

    </dependencies>


    <build>
        <finalName>ssm-web</finalName>
        <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
            <plugins>
                <plugin>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <version>3.1</version>
                    <configuration>
                        <source>${maven.compiler.source}</source>
                        <target>${maven.compiler.target}</target>
                    </configuration>
                </plugin>
                <plugin>
                    <artifactId>maven-clean-plugin</artifactId>
                    <version>3.1.0</version>
                </plugin>
                <!-- see http://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_war_packaging -->
                <plugin>
                    <artifactId>maven-resources-plugin</artifactId>
                    <version>3.0.2</version>
                </plugin>
                <plugin>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <version>3.8.0</version>
                </plugin>
                <plugin>
                    <artifactId>maven-surefire-plugin</artifactId>
                    <version>2.22.1</version>
                </plugin>
                <plugin>
                    <artifactId>maven-war-plugin</artifactId>
                    <version>3.2.2</version>
                </plugin>
                <plugin>
                    <artifactId>maven-install-plugin</artifactId>
                    <version>2.5.2</version>
                </plugin>
                <plugin>
                    <artifactId>maven-deploy-plugin</artifactId>
                    <version>2.8.2</version>
                </plugin>
            </plugins>
        </pluginManagement>

        <!--
        maven默认扫描src/main/java中的文件而不理会src/main/resources中的xml文件,
        所以后来添加了resource节点,这样就将src/main/resources中的xml文件改变maven默认的扫描策略,
        防止造成src/main/resources下的配置文件打包丢失。
        编译之后的文件中少了mapper.xml,这个和maven有关,maven编译src/java代码的时候,
        默认只会对java文件进行编译然后放在target/classes目录,需要在pom.xml中加入下面配置-->
        <!--如果不添加此节点,mapper.xml文件、config.properties文件、config.spring文件等
        都不会被加载到target的classes中去,也就不能被使用,也就会报错。-->
        <resources>
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.properties</include>
                    <include>**/*.xml</include>
                </includes>
            </resource>
            <resource>
                <directory>src/main/resources</directory>
                <includes>
                    <include>**/*.properties</include>
                    <include>**/*.xml</include>
                </includes>
            </resource>
        </resources>
    </build>

</project>

3.5.2 创建目标类(是一个接口):SomeService.java

package com.wind.bao01;


/**
 * 这个目标接口
 */
public interface SomeService {

    void doSome(String name, Integer age);

    String doOther(String name, Integer age);

    String doAround(String name, Integer age);
}

3.5.3 创建目标类的实现类:SomeServiceImpl.java

package com.wind.bao01;


/**
 * 这个是目标接口的实现类,也就是目标类:被代理的对象
 */
public class SomeServiceImpl implements SomeService {

    /**
     * 目标方法:准备给目标方法做增强,在目标方法执行之前打印目标方法执行的开始时间
     */
    @Override
    public void doSome(String name, Integer age) {
        System.out.println("====目标业务方法执行了。SomeServiceImpl.doSome()====");
    }

    @Override
    public String doOther(String name, Integer age) {
        System.out.println("====目标业务方法执行了。SomeServiceImpl.doOther()====");
        return "doOther.result=abcdefg";
    }

    @Override
    public String doAround(String name, Integer age) {
        System.out.println("====目标业务方法执行了。SomeServiceImpl.doAround()====");
        return "doAround.result=abcdefg";
    }
}

3.5.4 创建切面类和编写切面方法:MyAspect.java

package com.wind.bao01;

import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

import java.util.Date;

/**
 * @Aspect :这是Aspectj框架中的注解,用来声名一个类是作为切面类来使用。
 * 作用:标识当前类是一个切面类。
 * 切面类:用来给业务方法增强功能的类,在这个类中写业务增强功能。
 * 使用位置:该注解定义在一个类的上面。
 */
@Aspect
public class MyAspect {

    /**
     * 在切面类中定义各种各样的切面方法:切面方法是实现业务功能增强的。
     * 切面方法定义的要求:
     * (1)公共的方法,public
     * (2)方法没有返回值
     * (3)方法名称自定义
     * (4)方法可以有参数,也可以没有参数。如果有参数,则参数不是自定义的,有几个参数类型可以使用。
     */

    /**
     * @Before :前置通知的注解。
     * 属性value:切入点表达式execution,用来标识该切面方法在哪些业务方法中被切入。
     * 位置:作用在切面方法的上面。
     * 特点:
     * (1)在目标方法执行之前先执行。
     * (2)不会改变目标方法的执行结果。
     * (3)不会影响目标方法的执行。
     */
    @Before(value = "execution( void com.wind.bao01.SomeServiceImpl.doSome(String,Integer))")
    public void myBefore() {
        //这个方法就是你的切面想要执行的功能
        System.out.println("前置通知,切面功能myBefore()=在目标方法执行之前输出时间=" + new Date());
    }

    @After(value = "execution( void com.wind.bao01.SomeServiceImpl.doSome(String,Integer))")
    public void myAfter() {
        //这个方法就是你的切面想要执行的功能
        System.out.println("后置通知,切面功能myAfter()=在目标方法执行之后输出时间=" + new Date());
    }

}

3.5.5 创建spring.xml配置文件声名对象, 也即把对象交给Spring容器统一管理

<?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
       https://www.springframework.org/schema/aop/spring-aop.xsd">

    <!--把对象交给Spring容器来管理,由Spring容器来创建对象-->

    <!--声名目标对象-->
    <bean id="someServiceImpl" class="com.wind.bao01.SomeServiceImpl"/>

    <bean id="oneClassCglib" class="com.wind.bao01.OneClassCglib"/>

    <!--声名切面列对象-->
    <bean id="myAspect" class="com.wind.bao01.MyAspect"/>

    <!--AspectJ的配置方式:实现AOP功能-->
    <!-- aspectj-autoproxy:
    (1)自动代理生成器:使用AspectJ框架内部的功能,创建目标对象的动态代理对象。
    所创建的动态代理对象是在内存中完成的,通过修改目标对象的内存中的结构,为其创建代理对象,
    所以,目标对象就变成了被修改后的代理对象。
    (2)aspectj-autoproxy它会把Spring容器中所有的目标对象,一次性都生成代理对象。
    (3)如果有接口和该接口的实现类,此时默认使用JDK动态代理方式。-->
    <aop:aspectj-autoproxy/>
    
    <!--
    (1)如果有接口和该接口的实现类,此时默认使用JDK动态代理方式。
    (2)如果没有接口,只有类,则此时默认使用CGLIB动态代理方式。
    (3)另外:下面这句话的含义是:告诉AspectJ框架,即使是接口,只要该接口可以被继承,就使用CGLIB动态代理。-->
    <!--<aop:aspectj-autoproxy proxy-target-class="true"/>-->

</beans>

3.5.6 创建springmvc.xml(AOP暂时用不到, 只是web项目必要的配置文件)

<?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:mvc="http://www.springframework.org/schema/mvc"
       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/mvc
       https://www.springframework.org/schema/mvc/spring-mvc.xsd">

    <!--SpringMVC的配置文件,用来声名Controller和其他web相关的对象-->

    <!--配置组件扫描器-->
    <context:component-scan base-package="com.wind"/>

    <!--视图解析器:添加前缀和后缀。
    SpringMVC框架为了避免对于请求资源路径与扩展名上的冗余,在视图解析器 InternalResouceViewResolver
    中引入了请求的前辍与后辍。而 ModelAndView 中只需给出要跳转页面的文件名即可,对于具体的文件路径与文件扩展名,
    视图解析器会自动完成拼接。-->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <!--视图文件的路径-->
        <property name="prefix" value="/WEB-INF/jsp/"/>
        <!--视图文件的扩展名-->
        <property name="suffix" value=".jsp"/>
    </bean>

    <!--注册注解驱动。
    (1)响应ajax请求,返回json字符串。
    (2)解决静态资源访问问题。-->
    <mvc:annotation-driven/>

    <!--加载静态资源图片啊,jQuery文件啊等等-->
    <mvc:resources location="js/" mapping="/js/**"/>

</beans>

3.5.7 AOP的测试类基类:BaseTest.java

import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:config/*.xml"})
public abstract class BaseTest {
}

3.5.8 AOP的测试类和测试结果:MyTest.java

import com.wind.bao01.SomeService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MyTest extends BaseTest {

    @Test
    public void bao01Test() {
        String config = "config/spring.xml";
        ApplicationContext context = new ClassPathXmlApplicationContext(config);
        SomeService proxy = (SomeService) context.getBean("someServiceImpl");
        System.out.println(proxy.getClass().getName());
        proxy.doSome("yangguo", 20);
    }
}

4、AOP-面向切面变成-回顾

5、AOP中的其他类型的通知-代码演示

【不光前置通知的方法可以包含一个 JoinPoint 类型参数,所有的通知方法均可包含该参数,而且,该参数只能放在通知方法中的第一个参数位置上。】

5.1 前置通知 @Before:方法有JoinPoint参数

在目标方法执行之前执行。被注解为前置通知的方法,可以包含一个 JoinPoint 类型参数,该类型的对象本身就是切入点表达式。通过该参数,可获取切入点表达式、方法签名、目标对象等。

下面代码的功能:在通知方法中获取业务方法信息, 如方法签名/参数等-见方法myBefore2()。

package com.wind.bao01;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

import java.util.Arrays;
import java.util.Date;
import java.util.Objects;

/**
 * @Aspect :这是Aspectj框架中的注解,用来声名一个类是作为切面类来使用。
 * 作用:标识当前类是一个切面类。
 * 切面类:用来给业务方法增强功能的类,在这个类中写业务增强功能。
 * 使用位置:该注解定义在一个类的上面。
 */
@Aspect
public class MyAspect {

    /**
     * 在切面类中定义各种各样的切面方法:切面方法是实现业务功能增强的。
     * 切面方法定义的要求:
     * (1)公共的方法,public
     * (2)方法没有返回值
     * (3)方法名称自定义
     * (4)方法可以有参数,也可以没有参数。如果有参数,则参数不是自定义的,有几个参数类型可以使用。
     */

    /**
     * @Before :前置通知的注解。
     * 属性value:切入点表达式execution,用来标识该切面方法在哪些业务方法中被切入。
     * 位置:作用在切面方法的上面。
     * 特点:
     * (1)在目标方法执行之前先执行。
     * (2)不会改变目标方法的执行结果。
     * (3)不会影响目标方法的执行。
     */
    @Before(value = "execution( void com.wind.bao01.SomeServiceImpl.doSome(String,Integer))")
    public void myBefore() {
        //这个方法就是你的切面想要执行的功能
        System.out.println("前置通知,切面功能myBefore()=在目标方法执行之前输出时间=" + new Date());
    }

    /**
     * 指定通知方法中的参数:JoinPoint
     * JoinPoint:业务方法,要加入到切面功能的业务方法。
     * 作用是:可以在通知方法中获取业务方法执行时的信息,例如方法名称、方法参数等。
     * 如果你的通知方法中需要用到业务方法的信息,就可以加入JoinPoint来获取。
     * 这个JoinPoint参数是由框架自动赋值的,必须是在第一个参数位置上。
     */
    @Before(value = "execution( void com.wind.bao01.SomeServiceImpl.doSome(String,Integer))")
    public void myBefore2(JoinPoint joinPoint) {
        //获取方法的完整信息
        System.out.println("方法的签名(定义)=" + joinPoint.getSignature());
        System.out.println("方法的名称=" + joinPoint.getSignature().getName());
        Object[] args = joinPoint.getArgs();
        for (Object arg : args) {
            if (arg != null) {
                System.out.println("方法的名称=" + arg);
            }
        }

        //这个方法就是你的切面想要执行的功能
        System.out.println("前置通知,切面功能myBefore()=在目标方法执行之前输出时间=" + new Date());
    }

    @After(value = "execution( void com.wind.bao01.SomeServiceImpl.doSome(String,Integer))")
    public void myAfter() {
        //这个方法就是你的切面想要执行的功能
        System.out.println("后置通知,切面功能myAfter()=在目标方法执行之后输出时间=" + new Date());
    }

}

5.1.1 测试结果

5.2 后置通知 @AfterReturning:注解有returning属性

在目标方法执行之后执行。由于是目标方法之后执行,所以可以获取到目标方法的返回值。该注解的 returning 属性就是用于指定接收方法返回值的变量名的。

所以,被注解为后置通知的方法,除了可以包含 JoinPoint 参数外, 还可以包含用于接收返回值的变量。该变量最好为 Object 类型,因为目标方法的返回值可能是任何类型。

package com.wind.bao01;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

import java.util.Arrays;
import java.util.Date;
import java.util.Objects;

/**
 * @Aspect :这是Aspectj框架中的注解,用来声名一个类是作为切面类来使用。
 * 作用:标识当前类是一个切面类。
 * 切面类:用来给业务方法增强功能的类,在这个类中写业务增强功能。
 * 使用位置:该注解定义在一个类的上面。
 */
@Aspect
public class MyAspect {

    /**
     * 在切面类中定义各种各样的切面方法:切面方法是实现业务功能增强的。
     * 切面方法定义的要求:
     * (1)公共的方法,public
     * (2)方法没有返回值
     * (3)方法名称自定义
     * (4)方法可以有参数,也可以没有参数。如果有参数,则参数不是自定义的,有几个参数类型可以使用。
     */


    /**
     * @Before :前置通知的注解。
     * 属性value:切入点表达式execution,用来标识该切面方法在哪些业务方法中被切入。
     * 位置:作用在切面方法的上面。
     * 特点:
     * (1)在目标方法执行之前先执行。
     * (2)不会改变目标方法的执行结果。
     * (3)不会影响目标方法的执行。
     */
    @Before(value = "execution( void com.wind.bao01.SomeServiceImpl.doSome(String,Integer))")
    public void myBefore() {
        //这个方法就是你的切面想要执行的功能
        System.out.println("前置通知,切面功能myBefore()=在目标方法执行之前输出时间=" + new Date());
    }

    /**
     * 注意:@Before直接是可以指定通知方法中的参数的:使用关键字 JoinPoint 来指定。
     * JoinPoint:业务方法,要加入到切面功能的业务方法。
     * 作用是:可以在通知方法中获取业务方法执行时的信息,例如方法名称、方法参数等。
     * 如果你的通知方法中需要用到业务方法的信息,就可以加入JoinPoint来获取。
     * 这个JoinPoint参数是由框架自动赋值的,必须是在第一个参数位置上。
     */
    @Before(value = "execution( void com.wind.bao01.SomeServiceImpl.doSome(String,Integer))")
    public void myBefore2(JoinPoint joinPoint) {
        //获取方法的完整信息
        System.out.println("方法的签名(定义)=" + joinPoint.getSignature());
        System.out.println("方法的名称=" + joinPoint.getSignature().getName());
        Object[] args = joinPoint.getArgs();
        for (Object arg : args) {
            if (arg != null) {
                System.out.println("方法的名称=" + arg);
            }
        }

        //这个方法就是你的切面想要执行的功能
        System.out.println("前置通知,切面功能myBefore()=在目标方法执行之前输出时间=" + new Date());
    }


    /**
     * @AfterReturning :后置通知的注解。
     * 属性:
     * (1)value:切入点表达式execution,用来标识该切面方法在哪些业务方法中被切入。
     * (2)returning 自定义的变量,表示目标方法的返回值的。注:自定义的变量名必须和通知方法饿形参名保持一致。
     * 位置:作用在切面方法的上面。
     * 特点:
     * (1)在目标方法执行之后执行。
     * (2)可以有参数。
     * (3)可以获取到目标方法的返回值,可以根据这个返回值做不同的处理功能。相当于:Object myRes = doOther();
     * (4)可以修改这个返回值。
     */
    @AfterReturning(value = "execution( String com.wind.bao01.SomeServiceImpl.doOther(String,Integer))", returning = "myRes")
    public void myAfter(Object myRes) {
        //myRes:是目标方法执行后返回的值,可以根据这个值来做你的增强功能
        System.out.println("后置通知,切面功能myAfter()=在目标方法执行之后得到的结果=" + myRes);
    }

}
    @Test
    public void bao01Test() {
        String config = "config/spring.xml";
        ApplicationContext context = new ClassPathXmlApplicationContext(config);
        SomeService proxy = (SomeService) context.getBean("someServiceImpl");
        System.out.println(proxy.getClass().getName());
        proxy.doOther("yangguo", 20);
    }

5.2.1 测试结果

5.3 环绕通知 @Around:增强方法有 ProceedingJoinPoint 参数

在目标方法执行之前与之后执行。被注解为环绕增强的方法要有返回值,Object 类型,并且方法可以包含一个 ProceedingJoinPoint 类型的参数。接口 ProceedingJoinPoint 其有一个 proceed()方法,用于执行目标方法。若目标方法有返回值,则该方法的返回值就是目标方法的返回值。最后,环绕增强方法将其返回值返回,该增强方法实际是拦截了目标方法的执行。

package com.wind.bao01;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;

import java.util.Date;

/**
 * @Aspect :这是Aspectj框架中的注解,用来声名一个类是作为切面类来使用。
 * 作用:标识当前类是一个切面类。
 * 切面类:用来给业务方法增强功能的类,在这个类中写业务增强功能。
 * 使用位置:该注解定义在一个类的上面。
 */
@Aspect
public class MyAspect {

    /**
     * 在切面类中定义各种各样的切面方法:切面方法是实现业务功能增强的。
     * 切面方法定义的要求:
     * (1)公共的方法,public
     * (2)方法没有返回值
     * (3)方法名称自定义
     * (4)方法可以有参数,也可以没有参数。如果有参数,则参数不是自定义的,有几个参数类型可以使用。
     */


    /**
     * @Before :前置通知的注解。
     * 属性value:切入点表达式execution,用来标识该切面方法在哪些业务方法中被切入。
     * 位置:作用在切面方法的上面。
     * 特点:
     * (1)在目标方法执行之前先执行。
     * (2)不会改变目标方法的执行结果。
     * (3)不会影响目标方法的执行。
     */
    @Before(value = "execution( void com.wind.bao01.SomeServiceImpl.doSome(String,Integer))")
    public void myBefore() {
        //这个方法就是你的切面想要执行的功能
        System.out.println("前置通知,切面功能myBefore()=在目标方法执行之前输出时间=" + new Date());
    }

    /**
     * 注意:@Before直接是可以指定通知方法中的参数的:使用关键字 JoinPoint 来指定。
     * JoinPoint:业务方法,要加入到切面功能的业务方法。
     * 作用是:可以在通知方法中获取业务方法执行时的信息,例如方法名称、方法参数等。
     * 如果你的通知方法中需要用到业务方法的信息,就可以加入JoinPoint来获取。
     * 这个JoinPoint参数是由框架自动赋值的,必须是在第一个参数位置上。
     */
    @Before(value = "execution( void com.wind.bao01.SomeServiceImpl.doSome(String,Integer))")
    public void myBefore2(JoinPoint joinPoint) {
        //获取方法的完整信息
        System.out.println("方法的签名(定义)=" + joinPoint.getSignature());
        System.out.println("方法的名称=" + joinPoint.getSignature().getName());
        Object[] args = joinPoint.getArgs();
        for (Object arg : args) {
            if (arg != null) {
                System.out.println("方法的名称=" + arg);
            }
        }

        //这个方法就是你的切面想要执行的功能
        System.out.println("前置通知,切面功能myBefore()=在目标方法执行之前输出时间=" + new Date());
    }


    /**
     * @AfterReturning :后置通知的注解。
     * 属性:
     * (1)value:切入点表达式execution,用来标识该切面方法在哪些业务方法中被切入。
     * (2)returning 自定义的变量,表示目标方法的返回值的。注:自定义的变量名必须和通知方法饿形参名保持一致。
     * 位置:作用在切面方法的上面。
     * 特点:
     * (1)在目标方法执行之后执行。
     * (2)可以有参数。
     * (3)可以获取到目标方法的返回值,可以根据这个返回值做不同的处理功能。相当于:Object myRes = doOther();
     * (4)可以修改这个返回值。
     */
    @AfterReturning(value = "execution( String com.wind.bao01.SomeServiceImpl.doOther(String,Integer))", returning = "myRes")
    public void myAfter(Object myRes) {
        //myRes:是目标方法执行后返回的值,可以根据这个值来做你的增强功能
        System.out.println("后置通知,切面功能myAfter()=在目标方法执行之后得到的结果=" + myRes);
    }


    /**
     * @Around :环绕通知的注解。
     * 属性:
     * (1)value:切入点表达式execution,用来标识该切面方法在哪些业务方法中被切入。
     * 位置:作用在切面方法的上面。
     * 特点:
     * (1)它是功能最强的通知。
     * (2)在目标业务方法的前和后都可以执行增强功能。
     * (3)控制目标方法是否可以被调用执行。
     * (4)修改原来的目标方法的执行结果,影响最后的结果。
     * (5)环绕通知,类似于JDK动态代理中的InvocationHandler接口。
     * 参数:
     * (1)ProceedingJoinPoint:就等同于Method,用于执行目标方法。
     * (2)返回值,就是目标方法的执行结果,可以被修改。
     * 用处:环绕通知通常情况下是用来做事务的:在目标方法执行之前开启事务,然后执行目标方法,最后提交事务。
     */
    @Around(value = "execution( String com.wind.bao01.SomeServiceImpl.doAround(String,Integer))")
    public Object myAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {

        Object result = null;

        String name = "";
        Object[] args = proceedingJoinPoint.getArgs();
        if (args != null && args.length >= 1) {
            name = (String) args[0];
        }

        //1.在目标方法之前做增强
        System.out.println("环绕通知===目标方法之前做增强,时间=" + new Date());

        //2.执行目标方法
        if (name.equals("yangguo")) {
            result = proceedingJoinPoint.proceed();
            result = result + ",对目标方法的结果做二次修正";
        } else {
            result = "在AOP.myAround()中,我改变了目标方法执行的结果了";
        }

        //3.在目标方法之后做增强
        System.out.println("环绕通知===目标方法之后做增强,提交事务时间=" + new Date());

        //4.返回最终的结果
        return result;
    }

}
import com.wind.bao01.SomeService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MyTest extends BaseTest {

    @Test
    public void bao01Test() {
        String config = "config/spring.xml";
        ApplicationContext context = new ClassPathXmlApplicationContext(config);
        SomeService proxy = (SomeService) context.getBean("someServiceImpl");
        System.out.println(proxy.getClass().getName());
        //proxy.doSome("yangguo", 20);
        //proxy.doOther("yangguo", 20);
        String around = proxy.doAround("yangguo1", 20);
        System.out.println(around);
    }
}

5.3.1 测试结果

(1)目标方法执行了:

(2)目标方法没有执行:

5.4 异常通知 @AfterThrowing:注解中有throwing属性

在目标方法抛出异常后执行。该注解的 throwing 属性用于指定所发生的异常类对象。当然,被注解为异常通知的方法可以包含一个参数 Throwable,参数名称为 throwing 指定的名称,表示发生的异常对象。

5.5 最终通知 @After:总是会被执行

无论目标方法是否抛出异常,该增强均会被执行。

5.6 定义切入点 @Pointcut

当较多的通知增强方法使用相同的 execution 切入点表达式时,编写、维 护均较为麻烦。AspectJ 提供了@Pointcut 注解,用于定义 execution 切入点表达式。

其用法是,将@Pointcut 注解在一个方法之上,以后所有的 execution 的 value 属性值均可使用该方法名作为切入点。

代表的就是@Pointcut 定义的切 入点。这个使用@Pointcut 注解的方法一般使用 private 的标识方法,即没有实际作用的方法。

5.7 目前为止的项目结构

猜你喜欢

转载自blog.csdn.net/cmm0401/article/details/112131535