背景
基于上次手写一个mini 版的spring ioc,考虑把AOP也给接入进来
AOP回顾
AOP概念
AOP(Aspect Oriented Programming)是面向切面编程。是OOP面向对象编程思想的一种补充。
OOP通过继承,封装,多态等概念构建一个对象的层级结构。构建的是一个纵向的关系。面对横向的问题,实现起来比较复杂,比如日志的输出。使用面向对象的思想,每个类都需要增加日志打印的相关代码。但是使用aop就可以很简单的解决这个问题。
aop将影响了多个类的公共行为(如日志打印)封装为一个可重用模块,定义为一个切面(aspect)。切面中包括切入点,通知,连接点等概念。
切入点:就是需要做切面处理的位置,可以通过@PointCut中的execution值指定某个包,某个类或者某个方法。同时也可以使用自定义注解标注。
通知:包括5种,分别是前置,后置,返回,异常,环绕通知。分别定义增强代码执行的时机。
连接点:是可以用来做为切入点的位置。是程序执行的某个位置,可以为程序执行前,也可以是执行后或者抛出异常等一些时机点
aop作用是降低程序的耦合度,提高可重用性和开发效率
AOP简单使用
?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>org.example</groupId>
<artifactId>spring-aop</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.2.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.2.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.2.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.2.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.2.7.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
</dependency>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.6.0</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<compilerArgs>
<arg>-parameters</arg>
</compilerArgs>
</configuration>
</plugin>
</plugins>
</build>
</project>
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
@Aspect
@Component
@EnableAspectJAutoProxy
public class MyServiceAspect {
@Pointcut("execution(* com.itxiongmao.service.*..*(..))")
public void pointCut(){}
@Before("pointCut()")
public void before(){
System.out.println("前置方法执行了.....");
}
@After("pointCut()")
public void after(){
System.out.println("after方法执行了.....");
}
@AfterThrowing("pointCut()")
public void afterThrowing(){
System.out.println("afterThrowing方法执行了.....");
}
@AfterReturning("pointCut()")
public void afterReturning(){
System.out.println("afterReturning方法执行了.....");
}
@Around("pointCut()")
public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
MethodSignature signature = (MethodSignature)proceedingJoinPoint.getSignature();
Method method = signature.getMethod();
Parameter[] parameters = method.getParameters();
Object proceed = proceedingJoinPoint.proceed(proceedingJoinPoint.getArgs());
return proceed;
}
}
spring aop的底层原理-动态代理
spring AOP实现是依赖动态代理
jdk:当被代理类有接口的时候
cglib:asm字节码技术,没有接口就是使用cglib,也可以强制使用cglib
jdk代理
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class JdkProxyDemo<T> {
T obj;
//target:被代理类
public JdkProxyDemo(T target){
this.obj = target;
}
public T getInstance(){
return (T)Proxy.newProxyInstance(obj.getClass().getClassLoader(),
obj.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("方法执行前执行");
try {
Object invoke = method.invoke(obj, args);
System.out.println("方法执行后执行");
return invoke;
}catch (Exception e){
System.out.println("抛出了异常执行");
return null;
}
}
});
}
}
CGLIB动态代理
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class SampleClass {
public void test(){
System.out.println("hello world");
}
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(SampleClass.class);
enhancer.setCallback(new MethodInterceptor() {
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("before method run...");
Object result = methodProxy.invokeSuper(o, objects);
System.out.println("after method run...");
return result;
}
}
);
SampleClass sample = (SampleClass) enhancer.create();
sample.test();
}
}
手写AOP
自定义AOP大致思路
- 创建自己的Around注解,注解参数为Class。创建Aop方法存储容器。本处仅实现了环绕通知。
- 在IOC创建Bean之前,优先扫描包下所有类的所有方法,把包含Around注解方法存储至容器。
- 扫描Bean的时候,检索Bean方法上有没有自定义的标注AOP注解(@AOPTarget)注解,代表需要代理的类
- 在IOC扫描Bean的时候,检索Bean内部是否包含注解其类型为Around注明的Class。并选择性进行IOC是否需要代理
- 在需要代理的场景下进行切面的调用整合。执行前后进行控制。
- 本次只实现了通过一个自定义标识注解,来定位需要代理的类,没有实现像spring execution通过表达式来实现
代码包架构
自定义Aspect切面注解
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 切面注解
*/
//这个注解可以作用在运行期
@Retention(RetentionPolicy.RUNTIME)
//指定该注解可以作用在类上
@Target(ElementType.TYPE)
public @interface Aspect {
}
自定义环绕通知注解 Around
import java.lang.annotation.*;
//这个注解可以作用在运行期
@Retention(RetentionPolicy.RUNTIME)
//指定该注解可以作用在类上
@Target(ElementType.METHOD)
public @interface Around {
String execution() default "";
Class<?> executionClass() default AopTarget.class;
}
自定义Aop代理标识注解 AopTarget注解
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 用于标记哪些方法需要被代理
*/
@Retention(RetentionPolicy.RUNTIME)
//指定该注解可以作用在类上
@Target(ElementType.METHOD)
public @interface AopTarget {
}
自定义AOP参数类 ProceedingJoinPoint
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.lang.reflect.Method;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ProceedingJoinPoint {
//因为待会需要调用目标方法 这个就是目标方法
private Method method;
//方法参数
private Object[] args;
//对象
private Object obj;
/**
* 被代理类会执行的业务方法
* @return Object 业务返回值
* @throws Throwable
*/
public Object proceed() throws Throwable{
return method.invoke(obj,args);
}
}
自定义AOP切面类
import org.springframework.aop.annotation.AopTarget;
import org.springframework.aop.annotation.Around;
import org.springframework.aop.annotation.Aspect;
import org.springframework.aop.point.ProceedingJoinPoint;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class LogAop {
@Around(executionClass = AopTarget.class)
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("前置通知");
Object proceed = joinPoint.proceed();
System.out.println("后置通知");
return proceed;
}
}
ioc扫描添加Aop类
package org.springframework.container;
import lombok.SneakyThrows;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.aop.JdkProxyDemo;
import org.springframework.aop.annotation.AopTarget;
import org.springframework.aop.annotation.Around;
import org.springframework.aop.annotation.Aspect;
import org.springframework.stereotype.*;
import org.springframework.xml.XmlParser;
import java.io.File;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.stream.Collectors;
/**
* @Version 1.0
* @Author huqiang
* @Description ClassPathXmlApplicationContext
* @Date 2023/11/29 11:27
**/
public class ClassPathXmlApplicationContext {
private static final Logger logger = LogManager.getLogger(ClassPathXmlApplicationContext.class);
/**
* spring的ioc名字作为key
*/
private final Map<String, Object> iocNameContainer = new ConcurrentHashMap<>();
/**
* spring的class作为key
*/
private final Map<Class<?>, Object> iocClassContainer = new ConcurrentHashMap<>();
/**
* 根据接口,获取接口下的实现类
* 类似 context.getBean(UserService.class)
*/
private final Map<Class<?>, List<Object>> iocInterfacesContainer = new ConcurrentHashMap<>();
private final Set<String> classFiles = new HashSet<>();
/**
* 标注了Aop切面集合
*/
private final Set<Class<?>> aspectSet = new CopyOnWriteArraySet<>();
/**
* key 对应的class value 对应的List<String> methodName
*/
private final Map<Class<?>, List<String>> aopClassMap = new ConcurrentHashMap<>();
private final Map<Class<?>, Set<Class<?>>> aopTarget = new ConcurrentHashMap<>();
/**
* 提前注入的class
*/
private final Set<Class<?>> beforeDi = new HashSet<>();
private final String xmlPath;
public ClassPathXmlApplicationContext(String xmlPath) {
this.xmlPath = xmlPath;
refresh();
}
@SneakyThrows
private void refresh() {
//解析componentScanPath 包扫描路径
String componentScanPath = XmlParser.parse(xmlPath);
//获取包扫描路径的class文件路径
File file = findClassPath(componentScanPath);
//获取.class文件结尾的包全路径名
findClassFiles(file, componentScanPath, classFiles);
//反射
newInstance(classFiles);
//处理AOP
doAop();
//实现对象的属性的依赖注入
doDI();
logger.fatal("iocNameContainer {}", iocNameContainer);
logger.fatal("iocClassContainer {}", iocClassContainer);
logger.fatal("iocInterfacesContainer {}", iocInterfacesContainer);
}
private void doAop() {
if (aspectSet.isEmpty()) return;
for (Class<?> aopClass : aspectSet) {
Method[] methods = aopClass.getDeclaredMethods();
for (Method method : methods) {
if (method.isAnnotationPresent(Around.class)) {
Around annotation = method.getAnnotation(Around.class);
//这里只实现了用标记注解来实现定位到目标类方法 并未实现通过execution表达式来定位目标类方法
Class<?> aClass = annotation.executionClass();
//获取所有标注了AOPTarget注解的方法所在的类
Set<Class<?>> classes = aopTarget.getOrDefault(aClass, new HashSet<>());
if (classes.isEmpty()) {
return;
}
for (Class<?> targetClass : classes) {
if (aopClassMap.containsKey(targetClass)) {
//找到所有标记了aopTarget注解的方法 aopTarget注解只能标记再方法上,通过类找方法,一个类下有多个方法可标记
List<String> methodNames = aopClassMap.get(targetClass);
for (String methodName : methodNames) {
//!!!!! 很重要!!对于AOP代理的类,需要先填充注入属性,否则会出现异常
// Can not set com.xiaohu.springioc.dao.EmployeesRepository field com.xiaohu.springioc.service.impl.EmployeesServiceImpl.employeesRepository to com.sun.proxy.$Proxy12
//不加这段方法 就只能serviceimpl访问add方法引用repository引用,可以方法增强,
//但是无法从controller 一直往下传递,因为容器从原先bean引用换成了aop引用
//我们通过反射注入repository属性的时候,aop生成的代理对象找不到这个属性,所以就会报错
//所以对应AOP代理对象需要再更换引用的时候提前注入属性
doDiCommon(targetClass);
beforeDi.add(targetClass);
//生成代理对象
//targetClass:被需要切面代理的类 aopClass:标记Aspect的切面类 methodName:被需要切面代理的类下的methodName method:切面类的方法 obj:被代理类的实例
JdkProxyDemo<Object> proxy = new JdkProxyDemo<>(targetClass, aopClass, methodName, method, iocClassContainer.get(targetClass));
Object instance = proxy.getInstance();
logger.fatal("生成代理对象 {}", instance.getClass().getSimpleName());
//替换ioc容器
iocClassContainer.put(targetClass, instance);
//替换名称容器
Annotation[] annotations = new Annotation[]{targetClass.getAnnotation(Component.class), targetClass.getAnnotation(Controller.class),
targetClass.getAnnotation(Service.class), targetClass.getAnnotation(Repository.class)};
if (Arrays.stream(annotations).anyMatch(Objects::nonNull)) {
String className = getBeanName(targetClass, annotations);
iocNameContainer.put(className, instance);
}
//替换接口容器
Class<?>[] interfaces = targetClass.getInterfaces();
for (Class<?> anInterface : interfaces) {
List<Object> childImplList = iocInterfacesContainer.get(anInterface);
for (int i = 0; i < childImplList.size(); i++) {
Object object = childImplList.get(i);
if (object.getClass() == targetClass) {
childImplList.set(i, instance);
break;
}
}
}
}
}
}
}
}
}
}
private void doDI() {
Set<Map.Entry<Class<?>, Object>> entries = iocClassContainer.entrySet();
entries.forEach(it -> doDiCommon(it.getKey()));
}
private void doDiCommon(Class<?> aclass) {
//aop代理类,已提前注入,这里直接返回
if (beforeDi.contains(aclass)) {
return;
}
Field[] declaredFields = aclass.getDeclaredFields();
Set<Field> hasAutowiredField = Arrays.stream(declaredFields).filter(field -> field.isAnnotationPresent(Autowired.class)).collect(Collectors.toSet());
hasAutowiredField.forEach(field -> {
//依赖注入属性
Autowired annotation = field.getAnnotation(Autowired.class);
String value = annotation.value();
Object bean;
if ("".equals(value)) {
//默认按类型获取
Class<?> type = field.getType();
bean = getBean(type);
if (Objects.isNull(bean)) {
throw new IllegalStateException("获取不到 bean: " + type.getName());
}
} else {
//按用户填写的beanName获取
bean = iocNameContainer.getOrDefault(value, new IllegalArgumentException("找不到beanName: " + value));
}
try {
field.setAccessible(true);
field.set(iocClassContainer.get(aclass), bean);
} catch (IllegalAccessException e) {
logger.error("属性注入失败 {}", e.getMessage());
}
});
}
private static File findClassPath(String componentScanPath) {
String path = Objects.requireNonNull(Thread.currentThread().getContextClassLoader().getResource("")).getPath();
String url = path + componentScanPath.replace(".", File.separator);
// windows环境去除路径前面的 '/'
if (System.getProperty("os.name").toLowerCase().contains("win")) {
url = url.replaceFirst("/", "");
}
if (url.contains("test-classes")) {
url = url.replace("test-classes", "classes");
}
return new File(url);
}
public static String getBeanName(Class<?> c, Annotation[] annotations) {
try {
Annotation annotation = Arrays.stream(annotations).filter(Objects::nonNull).collect(Collectors.toList()).get(0);
Method valueMethod = annotation.annotationType().getDeclaredMethod("value");
String value = (String) valueMethod.invoke(annotation);
if (value != null && !value.isEmpty()) {
return value;
}
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
// 处理异常: 可能是注解没有value()方法,或者其他反射调用错误
logger.error("获取beanName 失败 {}", e.getMessage());
}
//没指定beanName 默认用类型首字母小写
return Character.toLowerCase(c.getSimpleName().charAt(0)) + c.getSimpleName().substring(1);
}
private void putIoc(Class<?>[] interfaces, Object instance, String beanName, Class<?> c) {
for (Class<?> anInterface : interfaces) {
iocInterfacesContainer.computeIfAbsent(anInterface, k -> new ArrayList<>()).add(instance);
}
iocNameContainer.compute(beanName, (key, value) -> {
if (value != null) {
throw new IllegalStateException("Bean with name '" + beanName + "' already exists.");
}
return instance;
});
iocClassContainer.compute(c, (key, value) -> {
if (value != null) {
throw new IllegalStateException("Bean with class name '" + c.getSimpleName() + "' already exists.");
}
return instance;
});
}
public Object getBean(String beanName) {
return iocNameContainer.getOrDefault(beanName, null);
}
public <T> T getBean(Class<T> clazz) {
//首先根据class获取,获取不到再通过接口获取
if (iocClassContainer.containsKey(clazz)) {
return clazz.cast(iocClassContainer.get(clazz));
}
List<Object> computed = iocInterfacesContainer.compute(clazz, (key, value) -> {
if (value == null || value.isEmpty()) {
return null;
}
if (value.size() > 1) {
throw new IllegalArgumentException("只能获取到一个bean 但是获取到了 " + value.size() + "个相同类型的bean");
}
return value;
});
return computed == null ? null : clazz.cast(computed.get(0));
}
private void newInstance(Set<String> classFiles) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
for (String classFile : classFiles) {
try {
classFile = classFile.replace(File.separator, ".").replace(".class", "");
Class<?> c = Class.forName(classFile);
if (c.isAnnotationPresent(Aspect.class)) {
//有AOP切面注解 加到切面集合
aspectSet.add(c);
}
//如果类下的方法标注了AopTarget注解
Method[] methods = c.getDeclaredMethods();
for (Method method : methods) {
//如果想要增加其他AOP标记类,忘这个if增加一个if逻辑即可
if (method.isAnnotationPresent(AopTarget.class)) {
String methodName = method.getName();
aopClassMap.computeIfAbsent(c, k -> new ArrayList<>()).add(methodName);
aopTarget.computeIfAbsent(AopTarget.class, k -> new HashSet<>()).add(c);
}
}
Annotation[] annotations = new Annotation[]{c.getAnnotation(Component.class), c.getAnnotation(Controller.class),
c.getAnnotation(Service.class), c.getAnnotation(Repository.class)};
if (Arrays.stream(annotations).anyMatch(Objects::nonNull)) {
String beanName = getBeanName(c, annotations);
Object instance = c.newInstance();
Class<?>[] interfaces = c.getInterfaces();
putIoc(interfaces, instance, beanName, c);
}
} catch (Exception e) {
logger.error("构造bean失败 失败原因 {}", e.getMessage());
throw e;
}
}
}
private void findClassFiles(File classFiles, String componentScanPath, Set<String> classNameList) {
File[] files = classFiles.listFiles();
if (files != null) {
for (File file : files) {
if (file.isFile() && file.getName().endsWith(".class")) {
// 如果是.class文件,添加到列表
String fullPath = file.getAbsolutePath();
int index = fullPath.indexOf(componentScanPath.replace(".", File.separator));
if (index != -1) {
String filePath = fullPath.substring(index);
classNameList.add(filePath);
}
} else if (file.isDirectory()) {
// 如果是目录,递归调用
findClassFiles(file, componentScanPath, classNameList);
}
}
}
}
}
特别需要注意的一点,因为AOP代理的原理就是通过jdk代理或者chlib代理,在原本IOC容器实现的时候,是先反射Bean实例,在反射填充注入Bean实例的属性,但是AOP最终被代理后会生成代理对象,会替换原本IOC容器真实Bean对象,导致原本的属性引用,无法反射注入,是因为代理对象并没有相应的属性字段,所以无法注入,·field.set(iocClassContainer.get(aclass), bean);
这段代码会出现· // Can not set com.xiaohu.springioc.dao.EmployeesRepository field com.xiaohu.springioc.service.impl.EmployeesServiceImpl.employeesRepository to com.sun.proxy.$Proxy12 异常
解决办法:
- 第一种解决办法: 将方法
doAop()
和·doDi()·方法调换顺序,这样可以解决,但是后面又会有新的问题,那就是只能通过被代理类去方法被增强的方法,才会触发AOP,比如我们代理ServiceImpl的下的add方法,只有 容器getBean ServiceImpl 这个实例.add方法才会触发Aop,然而如果我们是通过controller一直往下MVC传递去调add方法,就不会触发AOP
所以就会出现只有通过proxy代理类去访问add方法,才会触发AOP,而通过controller去访问serviceImpl就无法触发AOP,因为controller和proxy之间没有引用
- 第二种解决办法: 既然我们知道第一种解决的原因,那么我们只需要将proxy代理类和controller或者其他类构建一个引用关系即可,所以用到了AOP需要提前在构造Ioc容器的时候,提前注入属性
在生成代理对象的时候,提前处理属性注入,只有AOP代理对象是这样,其他普通的Bean则依然保持反射出实例在属性注入
AOP提前注入了,用一个提前注入集合set接收,后面就不需要再属性注入
测试
package com.xiaohu.springioc;
import com.xiaohu.springioc.controller.EmployeesController;
import org.springframework.container.ClassPathXmlApplicationContext;
public class TestSpringAop {
public static void main(String[] args) {
ClassPathXmlApplicationContext container = new ClassPathXmlApplicationContext("applicationContext.xml");
EmployeesController employeesController = container.getBean(EmployeesController.class);
employeesController.testAdd();
employeesController.selectById(1);
}
}
可以看到 `serviceImpl`的add方法和`repository` 的selectById也被增强了,至此一个简单的aop实现
结论
springAOP的原理是通过JDK反射代理和cglib反射代理,通过解析注解表达式或者自定义切面标识注解,来定位到需要代理的类,类的全路径名和方法名,反射构造出实例,替换原先的IOC容器bean
Spring IoC 实现思路:手写mini springIoc - xiaohugg