Spring Aop的引出与基本使用
● 需求说明
1. 有 Vehicle(交通工具接口, 有一个 run 方法), 下面有两个实现类 Car 和 Ship
2. 当运行 Car 对象 的 run 方法和 Ship 对象的 run 方法时,输入如下内容, 注意观察前后有统一的输出
3.我将会使用三种方法来带大家实现这个小案例,分别是传统的OOP方法、手写动态代理方法和Spring Aop方法实现
(1)传统OOP实现
先定义 Vehicle接口
package com.hykedu.spring.proxy2;
/**
* @author 程序员蛇皮
* @version 1.0
* Vehicle 交通工具
*/
public interface Vehicle {
public void run();
public String fly(int price);
}
定义Car类并实现 Vehicle接口
package com.hykedu.spring.proxy2;
import org.springframework.stereotype.Service;
/**
* @author 程序员蛇皮
* @version 1.0
*/
@Service
public class Car implements Vehicle {
@Override
public void run() {
System.out.println("交通工具开始运行了...");
System.out.println("小汽车在路上running");
System.out.println("交通工具停止运行了...");
}
}
定义Ship类并实现 Vehicle接口
package com.hykedu.spring.proxy2;
import org.springframework.stereotype.Service;
/**
* @author 程序员蛇皮
* @version 1.0
*/
@Service
public class Ship implements Vehicle {
@Override
public void run() {
System.out.println("交通工具开始运行了...");
System.out.println("大轮船在水上running");
System.out.println("交通工具停止运行了...");
}
}
记得在在bean工厂配置Car和Ship对象
编写测试类Test
package com.hykedu.spring.proxy2;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* @author 程序员蛇皮
* @version 1.0
*/
public class Test {
public static void main(String[] args) {
ApplicationContext ioc = new ClassPathXmlApplicationContext("beans06.xml");
Vehicle car = ioc.getBean("car", Car.class);
Vehicle ship = ioc.getBean("ship", Ship.class);
car.run();
System.out.println("--------------------");
ship.run();
}
}
运行结果如下
虽然我们完成了该需求,但是有没有发现代码有很多冗余的部分
有没有办法能够做的更灵活些呢,当然是有的啦,下面来介绍另一种方法–动态代理
(2)动态代理实现
编写VehicleProxyProvider类
package com.hykedu.spring.proxy2;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* @author 程序员蛇皮
* @version 1.0
*/
public class VehicleProxyProvider {
//设置一个将来要运行的对象,只要是实现了 Vehicle 接口的就 ok
private Vehicle target_vehicle;
public VehicleProxyProvider(Vehicle target_vehicle) {
this.target_vehicle = target_vehicle;
}
//编写代码,返回一个 Vehicle 的代理对象
public Vehicle getProxy() {
//获取类的加载器
ClassLoader classLoader = target_vehicle.getClass().getClassLoader();
// 2.获取接口类型数组, 为什么是接口信息,而不是方法,是因为我们都是走接口的.
Class<?>[] interfaces = target_vehicle.getClass().getInterfaces();
// 3. 创 建 InvocationHandler 对 象 - 以 匿 名 内 部 类 的 方 式 方 式 来 获 取InvocationHandler
// 这个对象有一个方法是 invoke 到时可以通过反射,动态调用目标对象的方法
InvocationHandler invocationHandler = new InvocationHandler() {
/*
invoke方法是将来执行我们的target_vehicle的方法时会调用到
proxy 表示代理对象
method 就是通过代理对象调用方法时,的哪个方法 例如这里就是代理对象.run()
return 表示 代理对象.run(xx)
*/
@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;
}
};
//将上面的 loader, interfaces, invocationHandler 构建一个 Vehicle的代理对象
/*
1.Proxy.newProxyInstance 可以返回一个代理对象
2.ClassLoader loader:类的加载器
3.Class<?>[] interfaces 就是将来要代理的对象的接口信息
4.InvocationHandler h 调用处理器/对象 有一个非常重要的方法invoke()
*/
Vehicle proxy = (Vehicle) Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);
return proxy;
}
}
然后删除Car类和Ship类代码的冗余部分
编写Test1测试类
package com.hykedu.spring.proxy2;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* @author 程序员蛇皮
* @version 1.0
*/
public class Test {
public static void main(String[] args) {
ApplicationContext ioc = new ClassPathXmlApplicationContext("beans06.xml");
Vehicle car = ioc.getBean("car", Car.class);
Vehicle ship = ioc.getBean("ship", Ship.class);
//这里可以切换 Vehicle 的 实现类(对象)
VehicleProxyProvider vehicleProxyProvider1 = new VehicleProxyProvider(car);
Vehicle proxy1 = vehicleProxyProvider1.getProxy();
VehicleProxyProvider vehicleProxyProvider2 = new VehicleProxyProvider(ship);
Vehicle proxy2 = vehicleProxyProvider2.getProxy();
//动态代理的 动态怎么体现
//1. proxy 运行类型是 com.sun.proxy.$Proxy0 该类型被转型成 Vehicle
// 因此可以调用 Vehicle 的接口方法
//2. 当执行 run() 的时候会调用, 根据 Java 的动态绑定机制, 这时直接调用 Car的 run(),而是 proxy 对象的 invocationHandler 的 invoke 方法(!!!!!!)
//3. invoke 方法使用反射机制来调用 run()方法注意这个 run 方法也可以是Vehicle 的其它方法)
// 这时就可以在调用 run()方法前,进行前置处理和后置处理
//4. 也就是说 proxy 的 target_vehicle 运行类型只要是实现了 Vehicle 接口
// ,就可以去调用不同的方法, 是动态的,变化的,底层就是 使用反射完成的
proxy1.run();
System.out.println("--------------------");
proxy2.run();
}
}
运行结果如下
如何?是不是解决了代码冗余的问题?
下面我们再通过一个案例深入了解一下动态代理
需求分析:有一只聪明的小狗,它会算加减法。请使用动态代理方式完成,并要求考虑代理对象调用方法(底层是反射调用)时, 可能出现的异常,输出如下信息
定义SmartAnimalable聪明动物接口
package com.hykedu.spring.aop.proxy;
/**
* @author 程序员蛇皮
* @version 1.0
*/
public interface SmartAnimalable {
float getSum(float i,float j);
float getSub(float i,float j);
}
编写聪明小狗SmartDog类并实现定义SmartAnimalable聪明动物接口
package com.hykedu.spring.aop.proxy;
import com.hykedu.spring.aop.proxy.SmartAnimalable;
/**
* @author 程序员蛇皮
* @version 1.0
*/
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;
}
}
定义动态代理类
package com.hykedu.spring.aop.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
/**
* @author 程序员蛇皮
* @version 1.0
*/
public class MyProxyProvider {
public SmartAnimalable target_smartAnimalable;
public MyProxyProvider(SmartAnimalable target_smartAnimalable) {
this.target_smartAnimalable = target_smartAnimalable;
}
public SmartAnimalable getProxy() {
ClassLoader classLoader = target_smartAnimalable.getClass().getClassLoader();
Class<?>[] interfaces = target_smartAnimalable.getClass().getInterfaces();
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));
result = method.invoke(target_smartAnimalable, args);
System.out.println("方法执行正常结束-日志-方法名-" + method.getName() + "-结果result= " + result);
return result;
} catch (Exception e) {
e.printStackTrace();
System.out.println("方法执行异常-日志-方法名-" + method.getName() + "-异常类型=" + e.getClass().getName());
} finally {
System.out.println("方法最终结束-日志-方法名-" + method.getName());
}
return result;
}
};
SmartAnimalable smartAnimalable = (SmartAnimalable) Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);
return smartAnimalable;
}
}
编写测试类
package com.hykedu.spring.aop.proxy;
/**
* @author 程序员蛇皮
* @version 1.0
*/
public class AopTest {
public static void main(String[] args) {
MyProxyProvider myProxyProvider = new MyProxyProvider(smartDog);
SmartAnimalable proxy = myProxyProvider.getProxy();
proxy.getSum(10.78f,89.7f);
System.out.println("=========");
proxy.getSub(10.78f,89.7f);
}
}
运行结果
有没有发现
经过我们动态代理的方法,会在方法执行前、方法执行后、亦或者是出现异常情况(自己尝试)、方法最终结束的时候执行我们实现编写好的方法,亦或者是说我们使用了动态代理的方法将方法执行的各个时间节点切入了我们编写的方法,这就是动态代理的好处
有了前面的基础,理解Spring就水到渠成了
AOP 的全称(aspect oriented programming) ,面向切面编程
我的理解:面向切面编程可以简单理解为方法执行的各个时间节点就是一个切面,每一个切面我们都可以将需要的方法和功能切入进去
● AOP 实现方式
1.基于动态代理的方式[内置 aop 实现](我们刚刚实现过了)
2.使用框架 aspectj 来实现
基本说明
● 说明
- 需要引入核心的 aspect 包
- 在切面类中声明通知方法
- 前置通知:@Before
- 返回通知:@AfterReturning
- 异常通知:@AfterThrowing
- 后置通知:@After
- 环绕通知:@Around
3. 五种通知和前面写的动态代理类方法的对应关系(环绕通知以后再聊)
下面我们用aspect框架实现AOP
前面两部和上面的动态代理一样,唯一的区别是聪明小狗要加@Component注解,不然无法进行代理
定义SmartAnimalable聪明动物接口
package com.hykedu.spring.aop.aspectj;
/**
* @author 程序员蛇皮
* @version 1.0
*/
public interface SmartAnimalable {
float getSum(float i,float j);
float getSub(float i,float j);
}
编写聪明小狗SmartDog类并实现定义SmartAnimalable聪明动物接口
package com.hykedu.spring.aop.aspectj;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
/**
* @author 程序员蛇皮
* @version 1.0
*/
@Component
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;
}
}
定义切面类
package com.hykedu.spring.aop.aspectj;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import java.util.Arrays;
/**
* @author 程序员蛇皮
* @version 1.0
*/
@Aspect//表示一个切面类[底层切面编程的支撑(动态代理+反射+动态绑定...)]
@Component
public class SmartAnimalAspect {
/**
* 1.@Before 表示前置通知:即在我们的目标对象执行方法前执行
* 2.value = "execution(public float com.hykedu.spring.aop.aspectj.SmartDog.getSum(float ,float ))"
* 指定切入哪个类的哪个方法 形式是:访问修饰符 返回类型 全类名.方法名(形参列表)
* 3.showBeginLog方法可以理解成一个切入方法,这个方法名由程序员指定
* 4.JoinPoint joinPoint 在底层执行时,由Aspectj切面框架,会给该切入方法传入 joinPoint对象
*/
@Before(value = "execution(public float com.hykedu.spring.aop.aspectj.SmartDog.*(float ,float ))")
public void showBeginLog(JoinPoint joinPoint) {
//通过连接点对象joinPoint 可以获得方法签名
Signature signature = joinPoint.getSignature();
System.out.println("切面类中的showBeginLog()-方法执行前-日志-方法名-" + signature.getName() + "-参数 " + Arrays.asList(joinPoint.getArgs()));
}
//返回通知
@AfterReturning(value = "execution(public float com.hykedu.spring.aop.aspectj.SmartDog.getSum(float ,float ))")
public void showSuccessEndLog(JoinPoint joinPoint) {
//通过连接点对象joinPoint 可以获得方法签名
Signature signature = joinPoint.getSignature();
System.out.println("切面类中的showSuccessEndLog()-方法执行正常结束-日志-方法名-" + signature.getName() + "-参数 " + Arrays.asList(joinPoint.getArgs()));
}
//异常通知
@AfterThrowing(value = "execution(public float com.hykedu.spring.aop.aspectj.SmartDog.getSum(float ,float ))")
public void showExceptionLog(JoinPoint joinPoint) {
//通过连接点对象joinPoint 可以获得方法签名
Signature signature = joinPoint.getSignature();
System.out.println("切面类中的showExceptionLog()-方法执行异常-日志-方法名-" + signature.getName() + "-参数 " + Arrays.asList(joinPoint.getArgs()));
}
//最终通知-无论有没有异常,都会在方法执行后通知
@After(value = "execution(public float com.hykedu.spring.aop.aspectj.SmartDog.getSum(float ,float ))")
public void finallyEndLog(JoinPoint joinPoint) {
//通过连接点对象joinPoint 可以获得方法签名
Signature signature = joinPoint.getSignature();
System.out.println("切面类中的finallyEndLog()-最终通知-日志-方法名-" + signature.getName() + "-参数 " + Arrays.asList(joinPoint.getArgs()));
}
}
在bean工厂的xml文件加入下面的语句 **
<aop:aspectj-autoproxy/>
编写测试类
package com.hykedu.spring.aop.aspectj;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* @author 程序员蛇皮
* @version 1.0
*/
public class Test {
public static void main(String[] args) {
ApplicationContext ioc = new ClassPathXmlApplicationContext("beans08.xml");
SmartAnimalable smartAnimalable = ioc.getBean(SmartAnimalable.class);
smartAnimalable.getSum(10,10);
smartAnimalable.getSub(10,10);
System.out.println(smartAnimalable.getClass());//输出的是代理对象
}
}
运行结果
感谢小伙伴们看到最后,相信大家对aop切面编程已经有一个基本的认识了,如果觉得我的文章还不错,麻烦给我点个赞(最好能转发一下(doge)),顺便关注一下我的微信公众号程序员蛇皮,谢谢大家!