目录
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 测试动态代理的效果(通过创建动态代理来对被代理类实现方法增强)
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.8 AOP的测试类和测试结果:MyTest.java
5.1 前置通知 @Before:方法有JoinPoint参数
5.2 后置通知 @AfterReturning:注解有returning属性
5.3 环绕通知 @Around:增强方法有 ProceedingJoinPoint 参数
5.4 异常通知 @AfterThrowing:注解中有throwing属性
【参考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 的标识方法,即没有实际作用的方法。