本文已参与「新人创作礼」活动,一起开启掘金创作之路。
为看过上篇文章的,请移步上篇文章吧。 本文适合使用过AOP切面编程的人阅读。
什么是Aop
AOP全称(Aspect Oriented Programming)面向切面编程的简称。
AOP通过预编译方式和运行期动态代理实现,这种在运行时,在不修改源代码的情况下,动态地将代码织入到类的指定方法、指定位置上的编程思想就是面向切面的编程。
AOP的作用:
做一些很多类都在做的,重复做的事,减少重复代码
日志记录、性能统计、事务处理、异常处理
AOP场景的通知:
- 前置通知,方法执行前调用,注解
@Before
- 后置通知,方法执行后调用,注解
@After
- 环绕通知,方法执行前、后调用,注解
@Around
- 返回通知,方法返回后调用,注解
@AfterReturning
- 异常通知,方法执行异常调用,注解
@AfterThrowing
大概了解这是个什么东西后,我们就在上节的代码中实现AOP吧
AOP切面手写简单版
1.预先准备
- 结构变化,新加切面类
LogAspect
,aop调用链TAdvice
,aop切面信息TAopConfig
,代理封装类TJDKDynamicAopProxy
,model暂时未织入,仅当实体类使用
- application.properties 配置添加,这里直接配置写死,省去解析注解得到对应的内容
#AOP切点
pointCut=public .* com.xxx.demo.service.impl..*(.*)
#AOP执行的类
aspectClass=com.xxx.demo.aspect.LogAspect
#AOP前置方法
aspectBefore=before
#AOP后置方法
aspectAfter=after
#AOP异常方法
aspectAfterThrowing=afterThrowing
- 新增切面类 LogAspect
public class LogAspect {
//方法之前执行before方法
public void before(JoinPoint point) {
System.out.println("方法之前调用:"+point.toString());
}
//方法之后执行after方法
public void after(JoinPoint point) {
System.out.println("方法之后调用:"+point.toString());
}
public void afterThrowing(JoinPoint point) {
System.out.println("出现异常调用:"+point.toString());
}
}
- aop目录,存储原始的class信息
/**
* AOP切面调用链封装
*/
@Data
public class TAdvice {
private Object aspectClass;
private Method aspectMethod;
private String throwName;
public TAdvice(Object aspectClass, Method aspectMethod) {
this.aspectClass = aspectClass;
this.aspectMethod = aspectMethod;
}
}
- 切面请求信息 JoinPoint,源码后完善
@Data
public class JoinPoint {
Object target;
Method method;
Object[] args;
Object result;
String throwName;
public Object proceed() throws Exception {
return method.invoke(target,this);
}
}
2.TestApplicationContext上下文内容修改
- 初始化
doInstance()
代码修改initAopConfig
private void doInstance() {
for (String className : this.classNames) {
try {
Class<?> clazz = Class.forName(className);
//只有加了注解的类才初始化
if (!clazz.isAnnotationPresent(TController.class) && !clazz.isAnnotationPresent(TService.class)) {
continue;
}
//如果满足AOP切面条件 此处应该生成代理对象 真实调用应该有invoke实现
Object instance = initAopConfig(clazz);
//保存到IOC容器 beanName -> instance 生成bean name 首字母小写
String beanName = toFristLowerCase(clazz.getSimpleName());
//如果是service 看是否有自定义名称
if(clazz.isAnnotationPresent(TService.class)){
TService service = clazz.getAnnotation(TService.class);
if(!"".equals(service.value())){
beanName = service.value();
}
}
//比如beanName 相同的暂不考虑 主要是体现思想
this.ioc.put(beanName,instance);
//如果是接口实现的 需要把接口全路径对应到service
Class<?>[] interfaces = clazz.getInterfaces();
for (Class<?> i : interfaces) {
this.ioc.put(i.getName(),instance);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
initAopConfig(clazz);
AOP初始化
private Object initAopConfig(Class<?> clazz) throws Exception {
Object instance = clazz.newInstance();
//获取切面的切点 正则匹配全路径类名 匹配成功则生成代理类 不写配置可在这里写死
String pointCut = this.contextConfig.getProperty("pointCut");
//把字符串里面的符号 替换成 可识别的正则符号
//public .* com\.xxx\.demo\.service\.impl\..*\(.*\)
String regxMethodStr = pointCut.replaceAll("\\.","\\\\.")
.replaceAll("\\\\.\\*",".*")
.replaceAll("\\(","\\\\(")
.replaceAll("\\)","\\\\)");
//类名是 class com.xxx.demo.service.impl.HelloServiceImp 所以匹配类名做如下处理
//public .* com\.xxx\.demo\.service\.impl\..*
String regxClassStr = regxMethodStr.substring(0,regxMethodStr.lastIndexOf("\\("));
//class com\.xxx\.demo\.service\.impl\..*
Pattern classPattern = Pattern.compile("class " + regxClassStr.substring(regxClassStr.lastIndexOf(" ")+1));
//类的正则
if(classPattern.matcher(instance.getClass().toString()).matches()){
//匹配成功就创建代理对象 保存切面方法
TAopConfig config = new TAopConfig();
config.setPointCut(pointCut);
config.setAspectClass(this.contextConfig.getProperty("aspectClass"));
config.setAspectBefore(this.contextConfig.getProperty("aspectBefore"));
config.setAspectAfter(this.contextConfig.getProperty("aspectAfter"));
config.setAspectAfterThrowing(this.contextConfig.getProperty("aspectAfterThrowing"));
//匹配方法的正则 public .* com\.xxx\.demo\.service\.impl\..*\(.*\)
Pattern methodPattern = Pattern.compile(regxMethodStr);
config.setMethodPattern(methodPattern);
config.setTarget(instance);
//保存原始类时解析切面信息
config.setTargetClass(clazz);
//匹配上的生成代理对象
instance = new TJDKDynamicAopProxy(config).getProxy();
}
return instance;
}
3.保存原始类以及切面信息
@Data
public class TAopConfig {
//切面信息保存
String pointCut;
String aspectClass;
String aspectBefore;
String aspectAfter;
String aspectAfterThrowing;
Pattern methodPattern;
//原始实例和clazz信息
Object target;
Class<?> targetClass;
Map<Method,Map<String,TAdvice>> adviceCacheMap = new HashMap<Method, Map<String, TAdvice>>();
public void setTargetClass(Class<?> targetClass) {
this.targetClass = targetClass;
//把切面信息组成一个map 给invoke调用
parse();
}
//把切面信息组成一个map 给invoke调用
/**
* 初始化切面类
* 获取对应方法存入map
* 通过
*/
private void parse() {
try {
//初始化切面类
Class<?> clazz = Class.forName(this.aspectClass);
Method[] methods = clazz.getMethods();
Map<String,Method> aspectMap = new HashMap<String, Method>();
for (Method method : methods) {
aspectMap.put(method.getName(),method);
}
Map<String,TAdvice> adviceMap = new HashMap<String, TAdvice>();
Object aspectInstance = clazz.newInstance();
//取出满足切点类的所有方法
for (Method method : this.targetClass.getMethods()) {
String methodName = method.toString();
if(methodName.contains("throws")){
methodName = methodName.substring(0,methodName.lastIndexOf("throws")).trim();
}
//如果方法满足切点,就织入代码
//public .* com\.xxx\.demo\.service\.impl\..*\(.*\)
if(methodPattern.matcher(methodName).matches()){
if(null != this.aspectBefore){
adviceMap.put("before",new TAdvice(aspectInstance,aspectMap.get(this.aspectBefore)));
}
if(null != this.aspectAfter){
adviceMap.put("after",new TAdvice(aspectInstance,aspectMap.get(this.aspectAfter)));
}
if(null != this.aspectAfterThrowing){
adviceMap.put("afterThrow",new TAdvice(aspectInstance,aspectMap.get(this.aspectAfterThrowing)));
}
this.adviceCacheMap.put(method,adviceMap);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
//method 实际为代理对象的方法 这里需要去原targetClass拿到原始方法 才能在Map中找出映射关系
public Map<String, TAdvice> getAdviceMap(Method method) throws NoSuchMethodException {
Map<String, TAdvice> adviceMap = adviceCacheMap.get(method);
if(adviceMap == null){
Method method1 = this.targetClass.getMethod(method.getName(), method.getParameterTypes());
adviceMap = adviceCacheMap.get(method1);
this.adviceCacheMap.put(method,adviceMap);
}
if(adviceMap == null){
return new HashMap<String, TAdvice>();
}
return adviceMap;
}
}
4.代理封装类
public class TJDKDynamicAopProxy implements InvocationHandler {
private TAopConfig config;
public TJDKDynamicAopProxy(TAopConfig config) {
this.config = config;
}
/**
* 代理会走到这个方法
* 1.获取aspect AopConfig配置,切面信息和原始实例类
* 2.保存method -> list<TAopConfig>配置 到map 可能多个切面
* 3.依次调用 invoke
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Exception {
Map<String, TAdvice> adviceMap = new HashMap<String, TAdvice>();
Object result = null;
JoinPoint joinPoint = new JoinPoint();
joinPoint.setArgs(args);
try {
//获取aopConfig中存储的切面信息map
adviceMap = config.getAdviceMap(method);
invokeAspect(adviceMap.get("before"), joinPoint);
//真实方法调用
result = method.invoke(config.getTarget(), args);
joinPoint.setResult(result);
invokeAspect(adviceMap.get("after"), joinPoint);
} catch (Exception e) {
joinPoint.setThrowName(e.getCause().getMessage());
invokeAspect(adviceMap.get("afterThrow"), joinPoint);
throw e;
}
return result;
}
private void invokeAspect(TAdvice advice, JoinPoint joinPoint) {
try {
if (advice == null) return;
//正常来说是 JoinPoint.invoke 来实现,这里简化 源码后再补充
advice.getAspectMethod().invoke(advice.getAspectClass(), joinPoint);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* JDK通过接口创建代理对象 无接口的暂时不考虑
*
* @return
*/
public Object getProxy() {
return Proxy.newProxyInstance(this.getClass().getClassLoader(),
this.config.getTargetClass().getInterfaces(), this);
}
}
5.运行test
还是之前的test
public static void main(String[] args) {
TestApplicationContext context = new TestApplicationContext("classpath:application.properties");
HelloAction action = (HelloAction) context.getBean(HelloAction.class);
String hello = action.hello("张三");
System.out.println(hello);
}
//打印输出
application is init
方法之前调用:JoinPoint(target=null, method=null, args=[张三], result=null, throwName=null)
方法之后调用:JoinPoint(target=null, method=null, args=[张三], result=my name is 张三, throwName=null)
my name is 张三
总结
在实例化对象时判断该类满不满足切面切点,如果满足则创建代理类,在对应的代理类中存有原始类的信息,从而在invoke中获取原始类的信息调用真实的方法
以上内容实现了,接下来MVC还是问题吗?
以上就是本章的全部内容了。本文为学习记录,不支持转载
上一篇:SpringMvc手写简单实现篇 - IOC容器、DI依赖注入篇 下一篇:SpringMvc手写简单实现篇 - MVC完结篇
少壮不努力,老大徒伤悲