文章目录
1 AOP
AOP(面向切面编程),通过预编译方式和运行期动态代理来实现程序功能的统一与扩展的技术
AOP可以对业务逻辑部分进行隔离,从而使业务逻辑耦合降低,提高代码复用和开发效率
1.1 为什么使用AOP
考虑如下的场景,在无人超市业务中,用户在超市内购买产品等行为由超市业务负责,但是现在想增加安全检测的新功能,即用户进入超市后,进行人脸检测来判断该用户是否为在逃嫌犯,这部分的功能,如果写入业务中,将会使得原本庞大的业务逻辑更加复杂,且这部分功能与主体业务无关,属于公共功能,所以采用AOP技术,在用户购买商品前后,添加新的安全检测功能
通过AOP技术,在不对源码修改的情况下,为程序添加了新的功能,非侵入式地更新了代码功能
1.2 AOP的本质
为了在buy()方法执行前后执行新的方法,且不对源码进行修改,自然会使用动态代理,让代理去执行buy()方法,可以在 method.invoke(Object object,Object[] args)方法前后织入新的方法,执行method前执行的方法就是前置拦截,执行method后执行的方法就是后置拦截
1,根据配置文件,将需要执行的公共方法注册到HashMap中
键为方法名,值为Method类的具体方法
2,根据配置文件,为需要执行的公共方法从属的类生成对象
将对象装入IOC容器
2,采用代理,在方法执行前,找到前置拦截的方法通过invoke执行
3,代理中执行method.invoke(Object object, Object[] args)
4,采用代理,在方法执行后,找到后置拦截的方法通过invoke执行
5,返回method执行后的结果
AOP的实现要结合IOC容器,因为被调用的公共方法的执行方式是invoke,invoke需要对象和参数来执行
2 简单模拟AOP
现在简单模拟对数据库的增删改查,但在每一种操作前后,添加上建立日志,安全检测,缓存数据的功能
模拟访问数据库:
2.1 公共方法从属的类
日志功能:
安全功能:
缓存功能:
2.2 BeanFactory
为公共方法从属的类生成对象,以便invoke执行
xml配置文件:
解析配置文件,生成HashMap,K为字符串类型对象名,V为Object类型对象
/**
* @author 雫
* @date 2021/2/22 - 17:19
* @function 根据ioc.cfg.xml文件创建bean工厂
*/
public class BeanFactory {
private static final Map<String, Object> beanPool;
static {
beanPool = new HashMap<>();
}
public static void scan(String xmlPath) throws Throwable {
new AbstractXMLParser() {
@Override
public void dealElement(Element element, int i) throws Throwable {
String beanName = element.getAttribute("id");
String className = element.getAttribute("class");
Class<?> klass = Class.forName(className);
beanPool.put(beanName, klass.newInstance());
}
}.parseTag(AbstractXMLParser.getOneDocument(xmlPath), "bean");
}
public static List<String> getAllId() {
List<String> ids = new ArrayList<>();
for(String id : beanPool.keySet()) {
ids.add(id);
}
return ids;
}
public static Object getBean(String id) {
return beanPool.get(id);
}
}
2.3 MethodFactory
将要执行的公共方法装入HashMap种,用的时候根据方法名取出
XML配置文件:
解析配置文件,生成HashMap,K为字符串类型方法名,V为Method类型的具体方法
/**
* @author 雫
* @date 2021/2/22 - 17:26
* @function 根据aop.cfg.xml文件创建方法工厂
*/
public class MethodFactory {
private static final Map<String, Method> methodPool;
static {
methodPool = new HashMap<>();
}
public static void scan(String xmlPath) throws Throwable {
new AbstractXMLParser() {
@Override
public void dealElement(Element element, int i) throws Throwable {
String methodName = element.getAttribute("methodName");
String className = element.getAttribute("class");
int parameterCount = Integer.valueOf(element.getAttribute("parameterCount"));
Class<?> klass = Class.forName(className);
Class<?>[] parameterTypes = new Class[parameterCount];
int count = parameterCount;
new AbstractXMLParser() {
@Override
public void dealElement(Element element, int i) throws Throwable {
String parameterName = element.getAttribute("parameterName");
String strParameterType = element.getAttribute("parameterType");
parameterTypes[i] = TypeParserExtend.getType(strParameterType);
if(i == (count) - 1) {
Method method = klass.getDeclaredMethod(methodName, parameterTypes);
methodPool.put(methodName, method);
}
}
}.parseTag(element, "parameter");
}
}.parseTag(AbstractXMLParser.getOneDocument(xmlPath), "method");
}
public static List<String> getNames() {
List<String> names = new ArrayList<>();
for(String k : methodPool.keySet()) {
names.add(k);
}
return names;
}
public static Method getMethod(String name) {
return methodPool.get(name);
}
}
2.4 建立动态代理
/**
* @author 雫
* @date 2021/2/22 - 18:24
* @function 织入了拦截器的动态代理
*/
public class Proxy {
@SuppressWarnings("all")
public static <T> T getProxy(Object object) {
Class<?> klass = object.getClass();
ClassLoader classLoader = klass.getClassLoader();
Class<?>[] interfaces = klass.getInterfaces();
return (T) java.lang.reflect.Proxy.newProxyInstance(classLoader, interfaces, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
/*前置拦截*/
MethodFactory.getMethod("beforeLog")
.invoke(BeanFactory.getBean("log"),
getInterceptArgs(MethodFactory.getMethod("beforeLog"), object, method, args));
MethodFactory.getMethod("beforeSecurity")
.invoke(BeanFactory.getBean("security"),
getInterceptArgs(MethodFactory.getMethod("beforeSecurity"), object, method, args));
MethodFactory.getMethod("beforeCache")
.invoke(BeanFactory.getBean("cache"),
getInterceptArgs(MethodFactory.getMethod("beforeCache"), object, method, args));
Object result = method.invoke(object, args);
/*后置拦截*/
MethodFactory.getMethod("afterLog")
.invoke(BeanFactory.getBean("log"),
getInterceptArgs(MethodFactory.getMethod("afterLog"), object, method, args));
MethodFactory.getMethod("afterSecurity")
.invoke(BeanFactory.getBean("security"),
getInterceptArgs(MethodFactory.getMethod("afterSecurity"), object, method, args));
MethodFactory.getMethod("afterCache")
.invoke(BeanFactory.getBean("cache"),
getInterceptArgs(MethodFactory.getMethod("afterCache"), object, method, args));
return result;
}
});
}
public static <T> T getProxy(Class<?> klass) throws IllegalAccessException, InstantiationException {
return getProxy(klass.newInstance());
}
/**
* @Author 雫
* @Description 为拦截方法的执行生成实参数组
* @Date 2021/2/23 9:55
* @Param [interceptMethod, object, method, args]
* @return java.lang.Object[]
**/
public static Object[] getInterceptArgs(Method interceptMethod, Object object, Method method, Object[] args) {
Object[] objects = new Object[interceptMethod.getParameterCount()];
objects[0] = object;
objects[1] = method;
if(objects.length > interceptMethod.getParameterCount()) {
objects[2] = args;
}
return objects;
}
}
2.5 测试&总结
测试:
在不改变访问数据库业务源码的情况下,通过AOP为代码增加了新的功能
总结:
1,生成IOC容器和方法容器,采用的都是饿汉模式,与程序上下文脱节,无法处理中间数据,只能处理简单应用场景
2,对于method执行需要的args参数,如果拦截器需要从中获取信息,暂时无法处理Object[]的实参数组