1 动态代理
1.1 通过案例理解动态代理
(2)准备代码: Vehicle.java,Car.java,Ship.java
public interface Vehicle {
public void run();
public String fly(int height);
}
public class Car implements Vehicle{
@Override
public void run(){
System.out.println("小汽车在水上running...");
}
@Override
public String fly(int height) {
return "小汽车飞到了" + height + "米";
}
}
public class Ship implements Vehicle{
@Override
public void run() {
System.out.println("大轮船在水上running...");
}
@Override
public String fly(int height) {
return "大轮船飞到了" + height + "米";
}
}
(3)动态代理实现:创建代理类VehicleProxyProvider.java(该类中有详细注释,演示了动态代理的实现过程)
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* 该类返回一个代理对象
*/
public class VehicleProxyProvider {
//该属性表示真正要执行的对象(实现了Vehicle接口的类)
private Vehicle target_vehicle;
//构造器
public VehicleProxyProvider(Vehicle target_vehicle) {
this.target_vehicle = target_vehicle;
}
//编写一个方法,可以返回一个代理对象
public Vehicle getProxy(){
//得到类的加载器
ClassLoader classLoader = target_vehicle.getClass().getClassLoader();
//得到接口信息
Class<?>[] interfaces = target_vehicle.getClass().getInterfaces();
//使用基于接口的匿名内部类创建InvocationHandler对象
InvocationHandler h = new InvocationHandler(){
/**
* invoke方法在将来调用target_vehicle的方法时,会使用到
* @param proxy 表示代理对象
* @param method 就是想要通过代理对象调用的那个方法
* @param args 表示要调用的方法需要传入的参数
* @return 表示target_vehicle的方法执行后的结果
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//这里可以写调用方法前要进行的操作
System.out.println("交通工具开始运行了...");
//通过反射调用该方法
Object result = method.invoke(target_vehicle, args);
//这里可以写调用方法后要进行的操作
System.out.println("交通工具停止运行了...");
return result;
}
};
//java.lang.reflect中的Proxy类有动态代理的相关方法,静态方法newProxyInstance()可以创建一个代理对象
/*
ClassLoader loader:类的加载器
Class<?>[] interfaces:要代理对象的接口的信息
InvocationHandler h:调用处理器/对象,InvocationHandler是一个接口,
里面有一个方法public Object invoke(Object proxy, Method method, Object[] args)
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
*/
Vehicle proxy = (Vehicle)Proxy.newProxyInstance(classLoader, interfaces, h);
return proxy;
}
}
(4)测试代码:
@Test
public void proxyRun(){
//创建代理类对象,传入要代理的对象
VehicleProxyProvider vehicleProxyProvider = new VehicleProxyProvider(new Ship());
//获取到代理对象,该对象可以代理执行方法
//1.proxy编译类型是Vehicle
//2.运行类型是代理类型class com.sun.proxy.$Proxy8
Vehicle proxy = vehicleProxyProvider.getProxy();
System.out.println(proxy.getClass());
//所以这里调用的不是ship中的run方法,而是代理对象的invoke
proxy.run();
String fly = proxy.fly(100);
System.out.println(fly);
}
(5)运行结果:
1.2 通过案例理解横切关注点
(1)需求说明:
有一个 SmartAnimal 接口,可以完成简单的加减法, 要求在执行 getSum()和 getSub()时,输出执行前,执行过程,执行后的日志输出
(2)准备代码:SmartAnimalable.java,SmartDog.java
public interface SmartAnimalable {
//求和
float getSum(float i, float j);
//求差
float getSub(float i, float j);
}
public class SmartDog implements SmartAnimalable {
@Override
public float getSum(float i, float j) {
float result = i + j;
System.out.println("方法内部打印result = " + result);
return result;
}
@Override
public float getSub(float i, float j) {
float result = i - j;
System.out.println("方法内部打印result = " + result);
return result;
}
}
(3)动态代理实现:创建MyProxyProvider.java,加入横切关注点(该类中有详细注释,介绍了几个横切关注点包括 前置通知、返回通知、异常通知、最终通知)
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
public class MyProxyProvider {
//定义我们要执行的目标对象, 该对象需要实现SmartAnimalable
private SmartAnimalable target_obj;
//构造器
public MyProxyProvider(SmartAnimalable target_obj) {
this.target_obj = target_obj;
}
//方法, 可以返回代理对象,该代理对象可以执行目标对象
public SmartAnimalable getProxy() {
//1. 先到的类加载器/对象
ClassLoader classLoader = target_obj.getClass().getClassLoader();
//2. 得到要执行的目标对象的接口信息
Class<?>[] interfaces = target_obj.getClass().getInterfaces();
//3. 创建InvocationHandler
InvocationHandler invocationHandler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = null;
try {
System.out.println("方法执行前-日志-方法名-" + method.getName() + "-参数 "
+ Arrays.asList(args)); //这里从AOP看,就是一个横切关注点-前置通知
//使用反射调用方法
result = method.invoke(target_obj, args);
System.out.println("方法执行正常结束-日志-方法名-" + method.getName() + "-结果result= "
+ result);//从AOP看, 也是一个横切关注点-返回通知
} catch (Exception e) {
e.printStackTrace();
//如果反射执行方法时,出现异常,就会进入到catch{}
System.out.println("方法执行异常-日志-方法名-" + method.getName()
+ "-异常类型=" + e.getClass().getName());//从AOP看, 也是一个横切关注点-异常通知
} finally {//不管你是否出现异常,最终都会执行到finally{}
//从AOP的角度看, 也是一个横切关注点-最终通知
System.out.println("方法最终结束-日志-方法名-" + method.getName());
}
return result;
}
};
//创建代理对象
SmartAnimalable proxy =
(SmartAnimalable)Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);
return proxy;
}
}
(4)测试代码:
@Test
public void smartDogTestByProxy() {
SmartAnimalable smartAnimalable = new SmartDog();
MyProxyProvider myProxyProvider =
new MyProxyProvider(smartAnimalable);
//我们返回了代理对象
SmartAnimalable proxy =
myProxyProvider.getProxy();
proxy.getSum(10, 2);
System.out.println("====================");
proxy.getSub(10, 2);
}
(5)运行结果:
2 AOP 的基本介绍
概念:AOP 的全称(aspect oriented programming) ,面向切面编程
AOP 实现方式
3 AOP 编程快速入门
3.1 基本说明
(1)需要引入核心的 aspect 包(如果是maven项目,第一篇文章导入的依赖包含了该依赖项,这步可以省略)
(2)在切面类中声明通知方法
- 前置通知:@Before
- 返回通知:@AfterReturning
- 异常通知:@AfterThrowing
- 后置通知:@After
- 环绕通知:@Around
3.2 快速入门案例
使用 aop 编程的方式,来实现手写的动态代理案例效果,就以上一个案例为例
public interface SmartAnimalable {
//求和
float getSum(float i, float j);
//求差
float getSub(float i, float j);
}
@Component
public class SmartDog implements SmartAnimalable {
@Override
public float getSum(float i, float j) {
//System.out.println("日志-方法名-getSum-参数 " + i + " " + j);
float result = i + j;
System.out.println("方法内部打印result = " + result);
//System.out.println("日志-方法名-getSum-结果result= " + result);
return result;
}
@Override
public float getSub(float i, float j) {
//System.out.println("日志-方法名-getSub-参数 " + i + " " + j);
float result = i - j;
System.out.println("方法内部打印result = " + result);
//System.out.println("日志-方法名-getSub-结果result= " + result);
return result;
}
}
(2)创建SmartAnimalAspect.java(该类有详细注释,介绍了AOP编程的详细步骤)
/**
* 切面类 , 类似于我们以前自己写的MyProxyProvider,但是功能强大很多
*/
@Aspect //表示是一个切面类[底层切面编程的支撑(动态代理+反射+动态绑定...)]
@Component //会注入SmartAnimalAspect到容器
public class SmartAnimalAspect {
//希望将showBeginLog方法切入到SmartDog-getSum前执行-前置通知
/**
* 1. @Before 表示前置通知:即在我们的目标对象执行方法前执行
* 2. value = "execution(public float com.spring.aop.aspectj.SmartDog.getSum(float, float)
* 指定切入到哪个类的哪个方法 形式是:访问修饰符 返回类型 全类名.方法名(形参列表)
* 3. showBeginLog方法可以理解成就是一个切入方法, 这个方法名是可以程序员指定 比如:showBeginLog
* 4. JoinPoint joinPoint 在底层执行时,由AspectJ切面框架, 会给该切入方法传入 joinPoint对象
* , 通过该方法,程序员可以获取到 相关信息
* @param joinPoint
*/
@Before(value = "execution(public float com.spring.aop.aspectj.SmartDog.getSum(float, float))")
public void showBeginLog(JoinPoint joinPoint) {
//通过连接点对象joinPoint 可以获取方法签名
Signature signature = joinPoint.getSignature();
System.out.println("SmartAnimalAspect3-切面类showBeginLog()-方法执行前-日志-方法名-" + signature.getName() + "-参数 "
+ Arrays.asList(joinPoint.getArgs()));
}
//返回通知:即把showSuccessEndLog方法切入到目标对象方法正常执行完毕后的地方
//1. 如果我们希望把目标方法执行的结果,返回给切入方法
//2. 可以再 @AfterReturning 增加属性 , 比如 returning = "res",表示返回值
//3. 同时在切入方法增加 Object res
//4. 注意: returning = "res" 和 Object res 的 res名字一致
@AfterReturning(value = "execution(public float com.spring.aop.aspectj.SmartDog.getSum(float, float))", returning = "res")
public void showSuccessEndLog(JoinPoint joinPoint, Object res) {
Signature signature = joinPoint.getSignature();
System.out.println("SmartAnimalAspect3-切面类showSuccessEndLog()-方法执行正常结束-日志-方法名-" + signature.getName() + " 返回的结果是=" + res);
}
//异常通知:即把showExceptionLog方法切入到目标对象方法执行发生异常的的catch{}
@AfterThrowing(value = "execution(public float com.spring.aop.aspectj.SmartDog.getSum(float, float))", throwing = "throwable")
public void showExceptionLog(JoinPoint joinPoint, Throwable throwable) {
Signature signature = joinPoint.getSignature();
System.out.println("SmartAnimalAspect3-切面类showExceptionLog()-方法执行异常-日志-方法名-" + signature.getName() + " 异常信息=" + throwable);
}
//最终通知:即把showFinallyEndLog方法切入到目标方法执行后(不管是否发生异常,都要执行 finally{})
@After(value = "execution(public float com.spring.aop.aspectj.SmartDog.getSum(float, float))")
public void showFinallyEndLog(JoinPoint joinPoint) {
Signature signature = joinPoint.getSignature();
System.out.println("SmartAnimalAspect3-切面类showFinallyEndLog()-方法最终执行完毕-日志-方法名-" + signature.getName());
}
}
(3)创建 beans07.xml 并配置
<context:component-scan base-package="com.spring.aop.aspectj"/>
<!-- 开启基于注解的AOP功能-->
<aop:aspectj-autoproxy/>
(4)测试代码
@Test
public void smartDogTestByProxy(){
ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("beans07.xml");
SmartAnimalable bean = ioc.getBean(SmartAnimalable.class);
System.out.println(bean.getClass());
bean.getSum(1,2);
}
(5)运行结果
3.3 细节说明
(1)切入表达式的更多配置,比如使用模糊配置,以下表示SmartDog类中的所有方法,都会被执行该前置通知方法
@Before(value="execution(* com.aop.proxy.SmartDog.*(..))")
(2)以下表示所有访问权限,所有包的下所有有类的所方法,都会被执行该前置通知方法
@Before(value="execution(* *.*(..))")
(3)当 spring 容器开启了 <!-- 开启基于注解的 AOP 功能 --> <aop:aspectj-autoproxy/> , 我们获 取注入的对象, 需要以接口的类型来获取, 因为你注入的对象.getClass() 已经是代理类型了
注意:如果不加该配置,AOP编程不会生效
(4)当 spring 容器开启了 <!-- 开启基于注解的 AOP 功能 --> <aop:aspectj-autoproxy/> , 我们获 取注入的对象, 也可以通过 id 来获取, 但是也要转成接口类型
4 AOP-切入表达式
4.1 具体使用
4.1.1 作用
通过表达式的方式定位一个或多个具体的连接点。
4.1.2 语法细节
(1)语法
value = "execution([权限修饰符] [返回值类型] [简单类名/全类名].[方法名]([参数列表]))"
(2)举例说明
表达式:
value = "execution(* com.spring.ArithmeticCalculator.*(..))"
含义:
- ArithmeticCalculator接口中声明的所有方法。
- 第一个“*”代表任意修饰符及任意返回值。
- 第二个“*”代表任意方法。
- “..”匹配任意数量、任意类型的参数。
- 若目标类、接口与该切面类在同一个包中可以省略包名。
表达式:
value = "execution(public * ArithmeticCalculator.*(..))"
含义:ArithmeticCalculator接口的所有公有方法
表达式:
value = "execution(public double ArithmeticCalculator.*(..))"
含义:ArithmeticCalculator 接口中返回 double 类型数值的公有方法
表达式:
value = "execution(public double ArithmeticCalculator.*(double,..)"
含义:ArithmeticCalculator 接口中返回 double 类型数值,第一个参数为 double 类型的公有方法。“..” 匹配任意数量、任意类型的参数。
表达式:
value = "execution(public double ArithmeticCalculator.*(double, double))"
含义:ArithmeticCalculator 接口中返回 double 类型数值,参数类型为 double,double类型的公有方法
4.1.3 “&&”、“Ⅱ”、“!”的使用
在AspectJ中,切入点表达式可以通过“&&”、“Ⅱ”、“!”等操作符结合起来。
表达式:
value = "execution(* *.add(int,.)) || execution(* *.sub(int,…))"
含义:
任意类中第一个参数为int类型的add方法或sub方法
4.1.4 注意事项和细节
(1)切入表达式也可以指向类的方法, 这时切入表达式会对该类/对象生效
(2)切入表达式也可以指向接口的方法, 这时切入表达式会对实现了接口的类/对象生效
(3)切入表达式也可以对没有实现接口的类,进行切入
5 AOP-JoinPoint
通过 JoinPoint 可以获取到调用方法的签名
JoinPoint对象常用方法:
joinPoint.getSignature().getName():获取目标方法名
joinPoint.getSignature().getDeclaringType().getSimpleName():获取目标方法所属类的简单类名
joinPoint.getSignature().getDeclaringTypeName():获取目标方法所属类的类名
joinPoint.getSignature().getModifiers():获取目标方法声明类型(public、private、protected)
Object[] args = joinPoint.getArgs():获取传入目标方法的参数,返回一个数组
joinPoint.getTarget():获取被代理的对象
joinPoint.getThis():获取代理对象自己
6 AOP-返回通知获取结果
在返回通知方法获取返回结果
/**
* returning = "res", Object res 名称保持一致
* @param joinPoint
* @param res 调用 getSum() 返回的结果
*/
@AfterReturning(value = "execution(public float com.spring.aop.joinpoint.SmartDog.getSum(float, float))", returning = "res")
public void showSuccessEndLog(JoinPoint joinPoint, Object res) {
System.out.println("返回通知" + "--结果是--" + res );
}
7 AOP-异常通知中获取异常
如何在异常通知方法中获取异常信息。
@AfterThrowing(value = "execution(public float com.spring.aop.joinpoint.SmartDog.getSum(float, float))", throwing = "throwable")
public void showExceptionLog(JoinPoint joinPoint, Throwable throwable) {
System.out.println("异常通知 -- 异常信息--" + throwable);
}
8 AOP-环绕通知
环绕通知可以完成其它四个通知要做的事情
注意:在环绕通知中一定要调用 joinPoint.proceed()来执行目标方法
@Around(value="execution(public float com.hspedu.spring.aop.aspectj.SmartDog.getSum(float, float))")
public Object doAround(ProceedingJoinPoint joinPoint) {
Object result = null;
String methodName = joinPoint.getSignature().getName();
try {
//1.相当于前置通知完成的事情
Object[] args = joinPoint.getArgs();
List<Object> argList = Arrays.asList(args);
System.out.println("AOP 环绕通知--" + methodName + "方法开始了--参数有: " + argList);
//在环绕通知中一定要调用 joinPoint.proceed()来执行目标方法
result = joinPoint.proceed();
//2.相当于返回通知完成的事情
System.out.println("AOP 环绕通知" + methodName + "方法结束了--结果是:" + result);
} catch (Throwable throwable) {
//3.相当于异常通知完成的事情
System.out.println("AOP 环绕通知" + methodName + "方法抛异常了--异常对象:" + throwable);
} finally {
//4.相当于最终通知完成的事情
System.out.println("AOP 后置通知" + methodName + "方法最终结束了...");
}
return result;
}
9 AOP-切入点表达式重用
为了统一管理切入点表达式,可以使用切入点表达式重用技术。
/*
* 这样定义的一个切入点表达式,就可以在其它地方直接使用
*/
@Pointcut(value = "execution(public float com.spring.aop.joinpoint.SmartDog.getSum(float, float))")
public void myPointCut() {
}
@Before(value = "myPointCut()")
public void showBeginLog(JoinPoint joinPoint) { //前置方法
//得到方法的签名
Signature signature = joinPoint.getSignature();
//得到方法名.
String method_name = signature.getName();
//得到参数
Object[] args = joinPoint.getArgs();
System.out.println("前置通知" + "--调用的方法是 " + method_name + "--参数是--" + Arrays.asList(args));
}
@After(value = "myPointCut()")
public void showFinallyEndLog() {
System.out.println("最终通知 -- AOP-切入点表达式重用");
}
/**
* returning = "res", Object res 名称保持一致
* @param joinPoint
* @param res 调用 getSum() 返回的结果
*/
@AfterReturning(value = "myPointCut()", returning = "res")
public void showSuccessEndLog(JoinPoint joinPoint, Object res) {
System.out.println("返回通知" + "--结果是--" + res);
}
@AfterThrowing(value = "myPointCut()", throwing = "throwable")
public void showExceptionLog(JoinPoint joinPoint, Throwable throwable) {
System.out.println("异常通知 -- 异常信息--" + throwable);
}
10 AOP-切面优先级问题
如果同一个方法,有多个切面在同一个切入点切入,那么执行的优先级如何控制.
基本语法:@order(value=n) 来控制 n 值越小,优先级越高(默认值很大,优先级很低)
@Aspect //表示这个类是一个切面类
@Order(value = 2)
@Component //需要加入 IOC 容器
public class SmartAnimalAspect2 {}
细节说明:
- 不能理解成:优先级高的每个消息通知都先执行,这个和方法调用机制(和 Filter 过滤器链式调用类似)
11 AOP-基于 XML 配置 AOP
前面我们是通过注解来配置 aop 的,在 spring 中,我们也可以通过 xml 的方式来配置 AOP
案例:
在xml配置 SmartAnimalAspect.java 类为 SmartDog.java 的切入类
(1)准备代码martAnimalAspect.java、SmartDog.java,把注解全部去掉
public class SmartAnimalAspect {
public void showBeginLog(JoinPoint joinPoint) {
//通过连接点对象joinPoint 可以获取方法签名
Signature signature = joinPoint.getSignature();
System.out.println("SmartAnimalAspect3-切面类showBeginLog()-方法执行前-日志-方法名-" + signature.getName() + "-参数 "
+ Arrays.asList(joinPoint.getArgs()));
}
public void showSuccessEndLog(JoinPoint joinPoint, Object res) {
Signature signature = joinPoint.getSignature();
System.out.println("SmartAnimalAspect3-切面类showSuccessEndLog()-方法执行正常结束-日志-方法名-" + signature.getName() + " 返回的结果是=" + res);
}
public void showExceptionLog(JoinPoint joinPoint, Throwable throwable) {
Signature signature = joinPoint.getSignature();
System.out.println("SmartAnimalAspect3-切面类showExceptionLog()-方法执行异常-日志-方法名-" + signature.getName() + " 异常信息=" + throwable);
}
public void showFinallyEndLog(JoinPoint joinPoint) {
Signature signature = joinPoint.getSignature();
System.out.println("SmartAnimalAspect3-切面类showFinallyEndLog()-方法最终执行完毕-日志-方法名-" + signature.getName());
}
}
public class SmartDog implements SmartAnimalable {
@Override
public float getSum(float i, float j) {
//System.out.println("日志-方法名-getSum-参数 " + i + " " + j);
float result = i + j;
System.out.println("方法内部打印result = " + result);
//System.out.println("日志-方法名-getSum-结果result= " + result);
return result;
}
@Override
public float getSub(float i, float j) {
//System.out.println("日志-方法名-getSub-参数 " + i + " " + j);
float result = i - j;
System.out.println("方法内部打印result = " + result);
//System.out.println("日志-方法名-getSub-结果result= " + result);
return result;
}
}
(2)配置xml文件
<!-- 配置 SmartAnimalAspect bean -->
<bean id="smartAnimalAspect" class="com.spring.aop.xml.SmartAnimalAspect"/>
<!--配置 SmartDog-->
<bean class="com.spring.aop.xml.SmartDog" id="smartDog"/>
<aop:config>
<!-- 配置统一切入点 -->
<aop:pointcut expression="execution(public float com.spring.aop.xml.SmartDog.getSum(float, float))"
id="myPointCut"/>
<aop:aspect ref="smartAnimalAspect" order="1">
<!-- 配置各个通知对应的切入点 -->
<aop:before method="showBeginLog" pointcut-ref="myPointCut"/>
<aop:after-returning method="showSuccessEndLog" pointcut-ref="myPointCut" returning="res"/>
<aop:after-throwing method="showExceptionLog" pointcut-ref="myPointCut" throwing="throwable"/>
<aop:after method="showFinallyEndLog" pointcut-ref="myPointCut"/>
<!-- 还可以配置环绕通知 -->
<!-- <aop:around method=""/> -->
</aop:aspect>
</aop:config>
(3)测试代码
@Test
public void test1(){
ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("beans08.xml");
SmartAnimalable smartDog = ioc.getBean(SmartAnimalable.class);
smartDog.getSum(1,1);
}
(4)运行结果