之前我们用300行代码手写过 Spring 核心逻辑 【Spring】仿写Spring核心逻辑–实现IOC、DI、MVC,虽然也达到了我们想要的目的,实现了Spring的最基本功能,但是它把所有功能都放在一个类(MYDispatchServlet)中,没有用到任何设计模式。另外,它也没有实现Spring另一核心 aop 相关逻辑。
所以,本篇我们就开始对它进行重构与扩充,原则是尽量模仿着Spring原本的样子,达到高仿真的目的。
1.要实现的目标
跟上篇一样,我们还是先来明确一下要实现的目标:
目标一:IOC。可以通过注解 @MYController 将Bean交给IOC容器去管理
目标二:DI。可以通过注解 @MYAutowired 将相应Bean依赖注入进来
目标三:MVC。可以通过注解 @MYRequestMapping 和 @MYRequestParam 完成请求的分发处理
@MYController
@MYRequestMapping("/web")
public class MyAction {
@MYAutowired
IQueryService queryService;
@MYAutowired
IModifyService modifyService;
@MYRequestMapping("/query.json")
public MYModelAndView query(HttpServletRequest request, HttpServletResponse response,
@MYRequestParam("name") String name){
String result = queryService.query(name);
// 将结果返回到浏览器
return out(response,result);
}
@MYRequestMapping("/first.html")
// 相比于mini,重构版可以通过 ModelAndView返回我们定义好的页面,然后通过模板版引擎解析model中的数据
public MYModelAndView query(@MYRequestParam("name") String name) {
String result = queryService.query(name);
// 构建model
Map<String, Object> model = new HashMap<String, Object>();
model.put("name", name);
model.put("data", result);
model.put("token", 123456);
// 返回我们自定义的ModelAndView,并指定页面
// 注:这里我们既可以写 first.html 也可以直接写 first(框架内部会做处理)
return new MYModelAndView("first", model);
}
@MYRequestMapping("/add*.json")
public MYModelAndView add(HttpServletRequest request, HttpServletResponse response,
@MYRequestParam("name") String name, @MYRequestParam("addr") String addr){
String result = null;
try {
// 该方法会抛出自定义异常
result = modifyService.add(name,addr);
return out(response,result);
} catch (Exception e) {
// 将异常信息保存在Map中,然后放入Model
Map<String,Object> model = new HashMap<String,Object>();
// 注:这里在单独测 mvc 模块时要去掉getCause
model.put("detail",e.getMessage());
model.put("stackTrace", Arrays.toString(e.getStackTrace()).replaceAll("\\[|\\]",""));
return new MYModelAndView("500",model);
}
}
private MYModelAndView out(HttpServletResponse resp, String str){
try {
resp.getWriter().write(str);
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}
下面只给出500.html,关于Service和页面模板这里就不全部展示了。
<!-- 500.html -->
<!DOCTYPE html>
<html lang="zh-cn">
<head>
<meta charset="utf-8">
<title>服务器好像累了</title>
</head>
<body>
<font size='25' color='blue'>500 服务器好像有点累了,需要休息一下</font><br/>
<!-- 需要模板引擎解析的数据 -->
<b>Message:¥{detail}</b><br/>
<b>StackTrace:¥{stackTrace}</b><br/>
<font color='green'><i>Copyright@YZH</i></font>
</body>
</html>
目标四:AOP。除了上面三个基本的目标外,我们还需要实现 AOP,即可以通过切面实现对方法的增强
@Slf4j // 借助slf4j去打印日志
public class LogAspect {
// 在调用一个方法之前,执行before方法
public void before(MYJoinPoint joinPoint){
joinPoint.setUserAttribute("startTime_" + joinPoint.getMethod().getName(),System.currentTimeMillis());
// 这个方法中的逻辑,是由我们自己写的
log.info("Invoker Before Method!!!" +
"\nTargetObject:" + joinPoint.getThis() +
"\nArgs:" + Arrays.toString(joinPoint.getArguments()));
}
// 在调用一个方法之后,执行after方法
public void after(MYJoinPoint joinPoint){
log.info("Invoker After Method!!!" +
"\nTargetObject:" + joinPoint.getThis() +
"\nArgs:" + Arrays.toString(joinPoint.getArguments()));
long startTime = (Long) joinPoint.getUserAttribute("startTime_" + joinPoint.getMethod().getName());
long endTime = System.currentTimeMillis();
System.out.println("use time :" + (endTime - startTime));
}
public void afterThrowing(MYJoinPoint joinPoint, Throwable ex){
log.info("出现异常" +
"\nTargetObject:" + joinPoint.getThis() +
"\nArgs:" + Arrays.toString(joinPoint.getArguments()) +
"\nThrows:" + ex.getMessage());
}
}
配置文件 application.properties:
# 要扫描的包
scanPackage=com.xupt.yzh.demo
# 模板的位置
templateRoot=layouts
# 切点表达式
pointCut=public .* com.xupt.yzh.demo.service..*Service..*(.*)
# 切面类
aspectClass=com.xupt.yzh.demo.aspect.LogAspect
# 切面前置通知
aspectBefore=before
# 切面后置通知
aspectAfter=after
# 切面异常通知
aspectAfterThrow=afterThrowing
# 切面 异常类型
aspectAfterThrowingName=java.lang.Exception
2.框架结构
我们先来看看整体的项目结构,见下图:
问题一:可以看到中间有一个 framework,但我们还没有说过它是干什么的?
没错,它就是需要我们去实现的框架内容。为了实现高仿的目的,我们也按照 Spring 的方式创建出 annotation、aop、beans、context、webmvc.这几个包:
问题二:那这五个包里面都有些什么类?
首先我们来看 annotation 包,它里面的内容就是第一部分我们说过的那五个注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MYController {
String value() default "";
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MYService {
String value() default "";
}
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MYAutowired {
String value() default "";
}
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MYRequestMapping {
String value() default "";
}
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MYRequestParam {
String value() default "";
boolean required() default true;
}
剩下的四个包我们放在后面的文章中,按照 IOC/DI --> AOP --> MVC 的顺序去逐一实现:
- 【Spring】重构–仿写Spring核心逻辑(二)实现IOC/DI(beans包)
- 【Spring】重构–仿写Spring核心逻辑(三)实现IOC/DI(context包)
- 【Spring】重构–仿写Spring核心逻辑(四)实现MVC(webmvc包)
- 【Spring】重构–仿写Spring核心逻辑(五)实现AOP(aop包)
完整代码我放到 GitHub 上了,可以点击这里跳转…