2、Spring AOP 的简单配置
(1)添加pom依赖
完整:
<?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.zhoulz</groupId>
<artifactId>spring_aop_study</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<!--添加的maven依赖-->
<!--添加的spring-context依赖-->
<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.3.RELEASE</version>
</dependency>
<!--添加junit的依赖-->
<!-- https://mvnrepository.com/artifact/junit/junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!--添加的spring-aop依赖-->
<!-- https://mvnrepository.com/artifact/org.springframework/spring-aop -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.2.3.RELEASE</version>
</dependency>
<!--cglib依赖-->
<!-- https://mvnrepository.com/artifact/cglib/cglib -->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
<!--aspectj weaver依赖 —— 提供了一种功能更强的织入方式-->
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.5</version>
<!--<scope>runtime</scope>--> <!--这个一定要注释掉,不然@Aspect出不来-->
</dependency>
<!-- https://mvnrepository.com/artifact/aopalliance/aopalliance -->
<dependency>
<groupId>aopalliance</groupId>
<artifactId>aopalliance</artifactId>
<version>1.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-aspects -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.3.RELEASE</version>
</dependency>
</dependencies>
</project>
(2)编写配置
首先进行pom依赖的添加,然后是xml配置文件的配置:applicatonContext.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
">
<!--上面添加了context命名空间-->
<!--然后又添加了aop命名空间-->
<!--开启包的扫描-->
<context:component-scan base-package="com.zhoulz"></context:component-scan>
<!--开启aop的注解功能。见上面:先像context命名空间那样加后,再开启:-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
添加注解,见 LogUtil:
package com.zhoulz.util;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.util.Arrays;
@Aspect
@Component
public class LogUtil {
/**
* 通知注解有以下几种类型:
* @Before:前置通知,在方法执行之前完成
* @After:后置通知,在方法执行之后执行
* @AfterReturning:返回通知,在返回结果之后运行
* @AfterThrowing:异常通知,出现异常的时候使用
* @Around:环绕通知
*
* 注意:在方法 的参数列表中不要随便添加参数值,会有异常信息
* */
@Before("execution(public Integer com.zhoulz.service.MyCalculator.add(Integer,Integer))")
public static void start(){ //方法里面暂时不用传入参数
System.out.println("方法开始执行,参数是:" );
}
@AfterReturning("execution(public Integer com.zhoulz.service.MyCalculator.add(Integer,Integer))")
public static void stop(){
System.out.println("方法执行结束,结果是:" );
}
@AfterThrowing("execution(public Integer com.zhoulz.service.MyCalculator.add(Integer,Integer))")
public static void logException(){
System.out.println("方法抛出异常:");
}
@After("execution(public Integer com.zhoulz.service.MyCalculator.add(Integer,Integer))")
public static void logFinally(){
System.out.println("方法执行结束。。。。over");
}
}
/** * 通知注解有以下几种类型: * @Before:前置通知,在方法执行之前完成 * @After:后置通知,在方法执行之后执行 * @AfterReturning:返回通知,在返回结果之后运行 * @AfterThrowing:异常通知,出现异常的时候使用 * @Around:环绕通知 * * 注意:在方法 的参数列表中不要随便添加参数值,会有异常信息 * * 切入点表达式: * 最精确的匹配方式: —— 但是太死板了(别的方法就用不了了) * execution(public Integer com.zhoulz.service.MyCalculator.add(Integer,Integer)) * 在实际的生产环境中,更多的时候是使用通配符的方式: “*”、“..” * * : * 1、可以用来匹配一个或多个字符: * execution(public Integer com.zhoulz.service.MyCalculator.*(Integer,Integer)) * execution(public Integer com.zhoulz.service.M*Calculator.*(Integer,Integer)) * 2、匹配任意类型的参数: * execution(public Integer com.zhoulz.service.M*Calculator.*(Integer,*)) * 3、* 在进行匹配的时候,只能匹配一层路径,不能匹配多层 * execution(public Integer com.zhoulz.service.*.*.M*Calculator*.*(Integer,*)) * 4、* 不能够匹配访问修饰符,如果不确定访问修饰符是什么,可以直接省略不写 * execution(Integer com.zhoulz.service.M*Calculator.*(Integer,*)) —— 省略了public * 5、返回值可以使用*来代替,但是不能空着不写 * execution(* com.zhoulz.service.M*Calculator.*(Integer,*)) * .. : * 1、可以匹配多个参数,任意类型 * execution(public Integer com.zhoulz..M*Calculator*.*(..)) * 2、可以匹配多层路径, * execution(* com.zhoulz..M*Calculator.*(..)) * * 最偷懒的方式: * execution(* *(..)) * * 如果表达式是以*开头,那么可以代替所有: * execution(* com.*(..)) —— 不可以 * execution(* com..*(..)) —— 可以 * execution(* com.*.*(..)) —— 可以 * * 使用通配符的时候不是越简洁越好,更多的是要选择符合要求或者符合规则的匹配方式, * 此时就要求在定义标识符的时候必须要遵守项目规范 * * 在使用 表达式的时候还支持逻辑运算符: * && : 多个条件必须同时满足; * || : 多个条件只要满足其中一个即可; * ! : 取反,处理这种情况的其他都满足; * 如:execution(public Integer com.zhoulz.service.MyCalculator.*(..)) && execution(* *(..)) * !execution(public Integer com.zhoulz.service.MyCalculator.add(Integer,Integer)) * * 通知的正常执行顺序: * 如果正常执行:@Before —> @After —> @AfterReturning * 如果异常结束:@Before —> @After —> @AfterThrowing * * 如果想要在方法中获取对应的参数或者方法参数等信息的时候,必须要使用JoinPoint对象,并且此参数必须是第一个 * getSignature()、getArgs()、signature.getName() * 如果方法中有返回值,那么必须要在注解中添加returning = "result",这个result必须要和参数列表中的参数名称保持一致 * 如果需要添加异常信息,那么在注解中要添加throwing = "e",这个e必须要和参数列表中的参数名称保持一致 * 如果想要添加其他参数,必须要添加args(参数列表),ArgName(参数列表) * * 通知方法(即下面的start、stop等方法)在定义的时候有没有什么特殊的要求? * 通知方法在定义的时候对访问修饰符、返回值类型都没有明确的要求, * 但是要注意,参数不能随便添加 * * 环绕通知: * 环绕通知在执行的时候是优于普通通知的 * 如果是正常结束,那么执行的顺序是: * 环绕前置通知— @Before—环绕后置通知—环绕返回通知—@After—@AfterReturning * 如果是异常结束,那么执行的顺序是: * 环绕前置通知— @Before—环绕异常通知—环绕返回通知—@After—@AfterReturning * (注意:异常时,外部(即环绕通知外的通知)没有异常通知@AfterThrowing了,即,没有异常捕获了) * 即;如果出现异常的时候,在环绕通知中解决了,那么普通通知是接受不到异常的, * 如果想让普通通知接收到,需要进行抛出:在环绕通知中加上throw throwable * 然后执行顺序是: * 环绕前置通知— @Before—环绕异常通知—环绕返回通知—@After—@AfterThrowing * * 当应用程序中包含多个切面的时候,具体的执行顺序是怎样的? * :按照切面类的首字母进行排序操作,按照字典序A->Z * 如果需要人为的规定顺序,可以在切面上添加@Order注解,同时可以添加具体的值 * 值越小越优先 1 —> 2147483647 * */
代码示例: 代码说明见上面
LogUti:
package com.zhoulz.util;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.util.Arrays;
@Aspect
@Component
@Order(100)
public class LogUtil {
/**
* 代码说明见上面
*
* */
//如果有多个匹配的表达式相同,能否做抽象?可以
// : 定义一个没有返回值的空方法,给该方法添加@Pointcut注解,后续在使用(匹配表达式)的时候直接调用该方法名称
// 此处的方法只是起一个声明的作用,能够供内部的其他通知方法进行调用
@Pointcut("execution(public Integer com.zhoulz.service.MyCalculator.*(Integer,*))")
public void myPointCut(){} // —— 然后后面用的时候直接写:myPointCut() 即可
//还可以定义多个
@Pointcut("execution(* *(..))")
public void myPointCut2(){}
@Before("execution(public Integer com.zhoulz.service.M*Calculator.*(Integer,*))")
//public static void start(Method method,Object ... args){ //原来的写法 //方法里面暂时不用传入参数
public static void start(JoinPoint joinPoint){
//获取方法签名
Signature signature = joinPoint.getSignature();
//获取方法参数信息
Object[] args = joinPoint.getArgs();
//获取方法名称
//System.out.println(signature.getName());
System.out.println("Log----"+signature.getName()+"方法开始执行,参数是:" +Arrays.asList(args));
}
//@AfterReturning(value = "execution(public Integer com.zhoulz.service.MyCalculator.*(Integer,*))",returning = "result")
//匹配表达式进行了抽象,见上面的 myPointCut()方法
@AfterReturning(value = "myPointCut()",returning = "result")
public static void stop(JoinPoint joinPoint,Object result){ // —— 怎么加结果result?:上面的通知注解要改动
Signature signature = joinPoint.getSignature();
System.out.println("Log----"+signature.getName()+"方法执行结束,结果是:" +result);
}
@AfterThrowing(value = "myPointCut()",throwing = "e")
public static void logException(JoinPoint joinPoint,Exception e){
Signature signature = joinPoint.getSignature();
System.out.println("Log----"+signature.getName()+"方法抛出异常:" + e.getMessage());
}
@After("myPointCut2()")
public static void logFinally(JoinPoint joinPoint){
Signature signature = joinPoint.getSignature();
System.out.println("Log----"+signature.getName()+"方法执行结束。。。。over");
}
/* //环绕通知 —— 把上面4个通知任意拿过来执行
@Around("myPointCut2()")
public Object around(ProceedingJoinPoint pjp) throws Throwable { //需要传参
Signature signature = pjp.getSignature();
Object[] args = pjp.getArgs();
Object result = null;
try {
System.out.println("环绕通知start:"+signature.getName()+"方法开始执行,参数为:"+ Arrays.asList(args));
//通过反射的方式调用目标的方法,相当于执行method.invoke(),可以自己修改结果值
result = pjp.proceed(args);
result = 100;
System.out.println("环绕通知stop:"+signature.getName()+"方法执行结束了");
} catch (Throwable throwable) {
//throwable.printStackTrace();
System.out.println("环绕异常通知:"+signature.getName()+"出现异常了");
throw throwable;
}finally {
System.out.println("环绕返回通知:"+signature.getName()+"方法返回结果是:"+result);
}
return result;
}*/
}
再创建一个切面类:SecurityUtil
package com.zhoulz.util;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.util.Arrays;
@Aspect
@Component
@Order(100)
public class SecurityUtil {
//如果有多个匹配的表达式相同,能否做抽象?可以
// : 定义一个没有返回值的空方法,给该方法添加@Pointcut注解,后续在使用(匹配表达式)的时候直接调用该方法名称
// 此处的方法只是起一个声明的作用,能够供内部的其他通知方法进行调用
@Pointcut("execution(public Integer com.zhoulz.service.MyCalculator.*(Integer,*))")
public void myPointCut(){} // —— 然后后面用的时候直接写:myPointCut() 即可
//还可以定义多个
@Pointcut("execution(* *(..))")
public void myPointCut2(){}
@Before("execution(public Integer com.zhoulz.service.M*Calculator.*(Integer,*))")
//public static void start(Method method,Object ... args){ //原来的写法 //方法里面暂时不用传入参数
public static void start(JoinPoint joinPoint){
//获取方法签名
Signature signature = joinPoint.getSignature();
//获取方法参数信息
Object[] args = joinPoint.getArgs();
//获取方法名称
//System.out.println(signature.getName());
System.out.println("Security----"+signature.getName()+"方法开始执行,参数是:" +Arrays.asList(args));
}
//@AfterReturning(value = "execution(public Integer com.zhoulz.service.MyCalculator.*(Integer,*))",returning = "result")
//匹配表达式进行了抽象,见上面的 myPointCut()方法
@AfterReturning(value = "myPointCut()",returning = "result")
public static void stop(JoinPoint joinPoint, Object result){ // —— 怎么加结果result?:上面的通知注解要改动
Signature signature = joinPoint.getSignature();
System.out.println("Security----"+signature.getName()+"方法执行结束,结果是:" +result);
}
@AfterThrowing(value = "myPointCut()",throwing = "e")
public static void logException(JoinPoint joinPoint,Exception e){
Signature signature = joinPoint.getSignature();
System.out.println("Security----"+signature.getName()+"方法抛出异常:" + e.getMessage());
}
@After("myPointCut2()")
public static void logFinally(JoinPoint joinPoint){
Signature signature = joinPoint.getSignature();
System.out.println("Security----"+signature.getName()+"方法执行结束。。。。over");
}
/*//环绕通知 —— 把上面4个通知任意拿过来执行
@Around("myPointCut2()")
public Object around(ProceedingJoinPoint pjp) throws Throwable { //需要传参
Signature signature = pjp.getSignature();
Object[] args = pjp.getArgs();
Object result = null;
try {
System.out.println("环绕通知start:"+signature.getName()+"方法开始执行,参数为:"+ Arrays.asList(args));
//通过反射的方式调用目标的方法,相当于执行method.invoke(),可以自己修改结果值
result = pjp.proceed(args);
result = 100;
System.out.println("环绕通知stop:"+signature.getName()+"方法执行结束了");
} catch (Throwable throwable) {
//throwable.printStackTrace();
System.out.println("环绕异常通知:"+signature.getName()+"出现异常了");
throw throwable;
}finally {
System.out.println("环绕返回通知:"+signature.getName()+"方法返回结果是:"+result);
}
return result;
}*/
}
Calculator 接口:
package com.zhoulz.service;
import org.springframework.stereotype.Service;
//@Service
public interface Calculator {
public Integer add(Integer i,Integer j) throws NoSuchMethodException;
public Integer sub(Integer i,Integer j) throws NoSuchMethodException;
public Integer mul(Integer i,Integer j) throws NoSuchMethodException;
public Integer div(Integer i,Integer j) throws NoSuchMethodException;
}
实现类:MyCalculator —— 注意,这里取消了对Calculator 接口 的继承(所以不是其实现类了)
package com.zhoulz.service;
import com.zhoulz.util.LogUtil;
import org.springframework.stereotype.Service;
import java.lang.reflect.Method;
@Service
public class MyCalculator /*implements Calculator */{
public Integer add(Integer i, Integer j) throws NoSuchMethodException {
//现在,想添加日志的输出功能,怎么做?
//最简单的做法:在每行代码里面都做一个最基本的输出 —— 但是这样做效率太低了
//System.out.prIntegerln("add方法开始执行,参数是:" + i + "----" + j );
//改进: —— 定义一个LogUtil类(包含start()方法和stop()方法)
//通过反射
/*Method add = MyCalculator.class.getMethod("add", Integer.class, Integer.class);
//然后add传入到下面的方法中 —— 下面的都同理
LogUtil.start(add,i,j);*/
Integer result = i + j;
//System.out.prIntegerln("add方法执行结束,结果是:" + result);
//LogUtil.stop(add,result);
return result;
}
public Integer sub(Integer i, Integer j) throws NoSuchMethodException {
//System.out.prIntegerln("sub方法开始执行,参数是:" + i + "----" + j );
/*Method sub = MyCalculator.class.getMethod("sub", Integer.class, Integer.class);
LogUtil.start(sub,i,j);*/
Integer result = i - j;
//System.out.prIntegerln("sub方法执行结束,结果是:" + result);
//LogUtil.stop(sub,result);
return result;
}
public Integer mul(Integer i, Integer j) throws NoSuchMethodException {
//System.out.prIntegerln("mul方法开始执行,参数是:" + i + "----" + j );
//Method mul = MyCalculator.class.getMethod("mul", Integer.class, Integer.class);
//LogUtil.start(mul,i,j);
Integer result = i * j;
//System.out.prIntegerln("mul方法执行结束,结果是:" + result);
//LogUtil.stop(mul,result);
return result;
}
public Integer div(Integer i, Integer j) throws NoSuchMethodException {
//System.out.prIntegerln("div方法开始执行,参数是:" + i + "----" + j );
//Method div = MyCalculator.class.getMethod("div", Integer.class, Integer.class);
//LogUtil.start(div,i,j);
Integer result = i / j;
//System.out.prIntegerln("div方法执行结束,结果是:" + result);
//LogUtil.stop(div,result);
return result;
}
//再加一个方法
public Integer show(Integer i, Double j){
System.out.println("show ........");
return i;
}
}
测试类:
import com.zhoulz.service.Calculator;
import com.zhoulz.service.MyCalculator;
import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
@Test
public void test01() throws NoSuchMethodException {
//ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
//Calculator calculator = context.getBean("myCalculator", Calculator.class);
//或者直接写
//Calculator calculator = context.getBean(Calculator.class);
//取消MyCalculator对Calculator的继承,再测试 —— 结果正常(即没有接口实现的话,则用的是cglib进行动态代理)
MyCalculator calculator = context.getBean(MyCalculator.class);
calculator.add(10,2);
// calculator.sub(10,2);
calculator.div(10,0);
calculator.show(10,2.5);
//System.out.println(calculator.getClass()); //class com.sun.proxy.$Proxy24
//取消继承后,结果为:class com.zhoulz.service.MyCalculator$$EnhancerBySpringCGLIB$$77df863d
}
@Test
public void test02() throws NoSuchMethodException {
MyCalculator calculator = context.getBean(MyCalculator.class);
//calculator.add(10,2);
calculator.div(10,0);
}
}
结果:
Security----div方法开始执行,参数是:[10, 2]
Log----div方法开始执行,参数是:[10, 2]
Log----div方法执行结束。。。。over
Log----div方法执行结束,结果是:5
Security----div方法执行结束。。。。over
Security----div方法执行结束,结果是:5