动态代理是用InvocationHandler接口和Proxy类来实现的一种代理模式
SpringAOP即面向切面编程,它对 AOP 进行了封装,使用面向对象的思想来实现,所以AOP的底层是用动态代理实现的
java中提供了一个InvocationHandler接口,用来继承实现动态代理
public interface InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
从源码可以看出,该接口只定义了一个方法,invoke()
invoke()方法3个参数的说明:
Object proxy: 代理对象的实例(Proxy的一个动态实例) 可以通过反射机制获取代理类的信息
method: 真实对象要实现的业务方法(由Proxy实例的静态代码块得到)
args: 第二个参数 method 方法的参数
接下来以一个例子理解动态代理
(1) 创建一个类,继承InvocationHandler接口
public class MyProxyInvocationHandler implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return null;
}
}
(2) 添加方法,得到生成的代理类
类中除了重写的invoke方法,其余基本是固定写法,目的是为了通过传入真实对象(要使用代理的对象)得到代理类
具体解释见代码注释
//用这个类,自动生成代理类
public class MyProxyInvocationHandler implements InvocationHandler {
//被代理的类
public Object object;
//得到一个代理实例(代理类)
public Object getProxy(Object object){
this.object = object;
//通过Proxy.newProxyInstance方法创建一个代理对象 即得到代理类
// 3个参数为 要代理的对象的类加载器、接口、及实现了InvocationHandler接口的类,一般即自身
return Proxy.newProxyInstance(object.getClass().getClassLoader(),object.getClass().getInterfaces(),this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return null;
}
}
(3) 重写invoke方法
① 在invoke方法中,可以通过 method 执行要代理的类的自身的方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 将代理的对象和执行参数代入执行方法 也就可以执行要代理的对象自身的方法
method.invoke(object,args);
return null;
}
② 在method.invoke()函数前后,可以添加额外的执行方法,也就是面向切面编程的思想(原来是在要代理的类中添加,但是这样就可以在这个方法里面添加)
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("在"+method.getName()+"前添加了一个方法");
// 将代理的对象和执行参数代入执行方法 也就可以执行代理类自身的方法
method.invoke(object,args);
System.out.println("在"+method.getName()+"后添加了一个方法");
return null;
}
Spring 框架对 AOP 进⾏了封装,Spring 使用面向对象的思想来实现 AOP
Spring 框架中不需要创建 InvocationHandler,只需要创建⼀个切面对象,将所有的非业务代码在切⾯对象中完成,Spring 框架底层会自动根据切面类以及目标类生成⼀个代理对象
上述概念有点繁琐,下面通过使用SpringAOP的一个例子来理解
思路:编写一个计算器类 拥有加减乘除方法,然后通过SpringAOP给计算器添加额外的打印日志方法
(1) 编写MyCalculator 接口及其实现类 并添加上@Component注解(代表交给springIOC容器管理)
public interface MyCalculator {
int add(int num1,int num2);
int sub(int num1,int num2);
int mul(int num1,int num2);
int div(int num1,int num2);
}
public class MyCalculatorImpl implements MyCalculator{
public int add(int num1, int num2) {
return num1+num2;
}
public int sub(int num1, int num2) {
return num1-num2;
}
public int mul(int num1, int num2) {
return num1*num2;
}
public int div(int num1, int num2) {
return num1/num2;
}
}
(2) 编写切面日志类,使用SpringAOP,实现额外添加打印日志方法的功能
@Aspect 代表该类是一个切面类
//切面日志类 通过SpringAOP来实现功能 @Aspect注解表示该类为一个切面类
@Aspect
@Component
public class AspectLogger {
}
(3)在AspectLogger类中添加方法,并使用SpringAOP注解
//切面日志类 通过SpringAOP来实现功能 @Aspect注解表示该类为一个切面类
@Aspect
@Component
public class AspectLogger {
//Before 表示在value对应方法执行前执行 .*代表该类下的所有方法 (..)表示参数通配符 也可以使用.方法名()来指定一个方法
@Before(value="execution(public int MyCalculatorImpl.*(..))")
// JoinPoint:切入点
public void executeBeforeMethod(JoinPoint joinPoint){
//获取⽅法名
String name = joinPoint.getSignature().getName();
//获取参数
String args = Arrays.toString(joinPoint.getArgs());
System.out.println(name+"⽅法的参数是:"+ args);
}
@After(value = "execution(public int MyCalculatorImpl.*(..))")
public void after(JoinPoint joinPoint){
//获取⽅法名
String name = joinPoint.getSignature().getName();
System.out.println(name+"⽅法执⾏完毕");
}
//返回后执行 returning赋予的String要和方法的第二个参数Object result <- 即这个result名字对应
@AfterReturning(value = "execution(public int MyCalculatorImpl.*(..))",returning = "result")
public void afterReturning(JoinPoint joinPoint,Object result){
//获取⽅法名
String name = joinPoint.getSignature().getName();
System.out.println(name+"⽅法的结果是"+result);
}
//抛出异常后执行 参数对应和上一个一样
@AfterThrowing(value = "execution(public int MyCalculatorImpl.*(..))",throwing = "exception")
public void afterThrowing(JoinPoint joinPoint,Exception exception){
//获取⽅法名
String name = joinPoint.getSignature().getName();
System.out.println(name+"⽅法抛出异常:"+exception);
}
}
代码说明:
@Before @After @AfterReturning @AfterThrowing :表示方法执行的位置和时机
JoinPoint对象:JoinPoint即切入点,它封装了SpringAop中切面方法的信息
在切面方法通过JoinPoint参数,可以获取到封装了该方法信息的JoinPoint对象
JionPoint的常用方法 :
方法名 | 功能 |
---|---|
Signature getSignature() | 获取封装了署名信息的对象,在该对象中可以获取到目标方法名,所属类的Class信息等 |
Object[] getArgs() | 获取传入目标方法的参数对象 |
Object getTarget() | 获取被代理的对象 |
Object getThis() | 获取代理对象 |
(4) 在Test类中编写main方法进行测试
public static void main(String[] args) {
// ApplicationContext applicationContext = new AnnotationConfigApplicationContext("com.ruoxi");
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-aop.xml");
MyCalculator myCalculator = (MyCalculator) applicationContext.getBean("myCalculatorImpl");
myCalculator.add(1,2);
myCalculator.sub(1,5);
myCalculator.mul(1,3);
myCalculator.div(4,2);
}
}
通过输出结果可以看出,已成功通过SpringAOP的方式给MyCalulator的各个函数添加了打印日志的方法