实现简单springAOP
在IOC的基础上进行扩展
原理
-
AOP面向切面编程,是OOP面向对象编程的一种补充
-
AOP通过继承、封装、多态等等概念构建一个对象的层级结构。构建的是一个纵向的关系,面对横向的问题,实现起来比较复杂,比如日志文件的输出。使用面向对象的思想,每个类都需要添加日志打印的相关代码,但是使用AOP就可以很简单解决这个问题
-
AOP影响了很多类的公共行为(如日志打印)可以封装为一个模块,定义为一个切面,切面中包含切入点、通知、连接点等概念
-
切入点:需要做切面处理的位置,可以通过@PointCut中的execution指定某个包、某个类或者某个方法。同时也可以自定义注解标注
-
通知:包括5种:分别是前置、后置、返回、异常和环绕通知,分别定义增强代码执行的时机
-
连接点:是可以用来作为切入点的位置,是程序执行的某个位置,可以为程序执行前,也可以是执行后或者抛出异常等一些时间点
-
AOP的作用是降低耦合度,提高重用性和开发效率
官方框架使用
- 需要开启aop注解
<aop:aspectj-autoproxy/>
- 强制使用cglib
<aop:aspectj-autoproxy proxy-target-class="true"/>
- 使用jdk代理必须要实现同一个接口,代理出来的类和接口的实现类是兄弟关系,必须使用接口接收
@Autowired
private OrderService orderService;
- cglib代理的思想是生成一个子类,使用当前的类就能接受(未实现接口)
@Autowired
private OrderService orderService;
实现
思路:在依赖注入DI之前将容器中的对象替换为代理增强之后的对象
切面类
package com.hodor.aop;
import org.springframework.annotation.Around;
import org.springframework.annotation.Aspect;
import org.springframework.aop.ProceedingJoinPoint;
/**
* @author :hodor007
* @date :Created in 2021/2/16
* @description :切面类
* @version: 1.0
*/
@Aspect
public class LogAop {
@Around(execution = "com.hodor.service.impl.OrderServiceImpl.addOrder")
public Object around(ProceedingJoinPoint joinPoint) {
Object result = null;
//调用目标对象的目标方法
try {
System.out.println("==>前置通知......");
result = joinPoint.proceed();
System.out.println("==>返回通知......");
} catch (Throwable throwable) {
System.out.println("==>异常通知......" + throwable.getMessage());
} finally {
System.out.println("==>后置通知......");
}
return result;
}
}
自定义注解
- @Around注解,简化版,没有模仿execution切入表达式
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD) //作用在类上
@Documented //会被javadoc处理
public @interface Around {
String execution() default "";
}
- @Aspect注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE) //作用在类上
@Documented //会被javadoc处理
public @interface Aspect {
}
反射注解
-
反射@Aspect和@Around注解,获取增强的目标方法com.hodor.service.impl.OrderServiceImpl.addOrder,@Autowired注入的对象是被代理后的对象
-
使用线程安全的Set存储AOP切面类(src/main/java/org/springframework/container/ClassPathXmlApplicationContext.java)
//存放AOP切面类的集合
private Set<Class<?>> aopClassSet = new CopyOnWriteArraySet<>();
- 获取类中所有的方法,有注解@Around的方法(只实现了@Around注解)
自定义ProceedingJoinPoint
用于执行目标类的,目标方法(如注入的orderService的addOrder方法)
public class ProceedingJoinPoint {
//目标方法
private Method method;
//目标对象
private Object target;
//目标方法参数
private Object[] args;
public ProceedingJoinPoint(Method method, Object target, Object[] args) {
this.method = method;
this.target = target;
this.args = args;
}
public Object proceed() {
try {
//调用目标方法
return method.invoke(target, args);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} finally {
}
return null;
}
//省略getter和setter......
}
自定义JdkProxy动态代理类
用于执行AOP切面类中的增强方法,同时增强类中的增强方法通过参数ProceedingJoinPoint调用了目标类的方法,实现了对目标类方法的增强(通过这个方式给目标类包裹上了增强的代码)
/**
* @author :hodor007
* @date :Created in 2021/2/16
* @description :JdkProxy动态代理
* @version: 1.0
*/
public class JdkProxy<T> {
/**
* 目标类class
*/
private Class<?> targetClass;
/**
* aop切面类class
*/
private Class<?> aopClass;
/**
* 目标对象
*/
private Object targetObject;
/**
* 要被代理的方法的名字
*/
String methodName;
/**
* AOP切面类的方法(带@Around注解的)
*/
Method aopMethod;
public JdkProxy(Class<?> targetClass, Class<?> aopClass, Object targetObject, String methodName, Method aopMethod) {
this.targetClass = targetClass;
this.aopClass = aopClass;
this.targetObject = targetObject;
this.methodName = methodName;
this.aopMethod = aopMethod;
}
/**
* 核心的动态代理类
* 如果是目标的方法就封装jointpoint作为参数,反射aop切面类的class调用aop切面类中的增强方法
* 同时传入的jointPoint参数的process方法反射了目标类的class,执行了目标类的目标方法(套娃),在这个动作的前后可以执行增强(查看LogAop切面类)
* 获取jdk代理对象
* @return
*/
public Object getProxy() {
return Proxy.newProxyInstance(targetClass.getClassLoader(), targetClass.getInterfaces(), new InvocationHandler() {
//
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//调用目标对象的目标方法
if(methodName.equals(method.getName())) {
ProceedingJoinPoint joinPoint = new ProceedingJoinPoint(method, targetObject, args);
return aopMethod.invoke(aopClass, joinPoint);
} else {
return method.invoke(targetObject, args);
}
}
});
}
//省略getter和setter......
}
将容器中的对象替换为代理对象
这么做导致IOC中后一步@Autowired依赖注入失败,代理的对象没法依赖注入
java.lang.IllegalArgumentException: Can not set com.hodor.dao.OrderDao field com.hodor.service.impl.OrderServiceImpl.orderDao to com.sun.proxy.$Proxy9
原因是经过了动态代理,目标对象中没有名为***Dao的属性了,没法注入
调换AOP和DI的顺序,直接从容器中取出service执行是有切面增强的(如下图),但是由于先执行依赖注入,所以OrderController中的属性OrderService还是未代理的,调用OrderController中的方法还是没有增强的
解决方案:依旧是先AOP增强再执行依赖注入,只对动态代理的类提前执行依赖注入(将依赖注入的代码抽取出来作为单独的方法,同一个类中有多个AOP增强的方法,只将这个类提前注入一次)
在AOP之后的依赖注入DI步骤中要跳过需要动态代理的类(已经提前注入过了)
/**
* 5. 实现依赖注入
*/
private void doDi() {
Set<Class<?>> classes = iocContainer.keySet();
if(classes != null) {
//通过class遍历所有的对象
for (Class<?> aClass : classes) {
if(!proxyClassSet.contains(aClass)) {
doDiByClass(aClass);
}
}
}
}
执行结果