首先介绍下这次的总体思路:
下载地址:https://download.csdn.net/download/drsbbbl/12264912
github(用的码云):https://gitee.com/mibaowei/ssm.git
在spring中最核心的就是IOC容器,默认采用的是通过单例的模式来进行管理我们注入到Ioc中的bean(当然我们也是可以修改成其他的模式,暂且不讨论),在spring中单例模式是采用注册的方式来实现的单例模式,所以我也是采用map注册的方式实现bean的实例化。关于spring中的注解,采用和spring一样的注解名字,在springMVC方面,采用统一的前端控制器dispatcherServlet,处理统一请求进行分发。
对应 mybatis 是采用代理的方式 对接口进行生成对应的代理对象,加载对应的xml 中的 数据和 对应的 执行占位符的情况 对占位符数据进行set 对应的数值。并且通过动态注入的方式进行 接口的调用,并且实现对应的调用对应xml中的sql片段。
说这么多 下面发下代码:
首先是 实现 对应的IOC容器:
采用最简单的 map 的方式 来实现 ioc容器
对应的key 就是类的名字的简写
实际在spring中。是通过 beanFactory的方式,进行生成对应的bean对象 采用 type 和name 的两种方式,我这里只是简写,只采用了一种方式进行 使用 就是类的全名 不像 spring中的是 类名 首字母小写 我没有采用这种方式,
/**
* bean 工厂
*
* @author mbb
*
*/
public class BeanFactory {
private static final Map<String, Object> IOC = new HashMap<>();
/**
* 得到 ioc 容器
*
* @return
*/
public static Map<String, Object> getIOC() {
return IOC;
}
//
public static Object getBean(String beanName) {
return IOC.get(beanName);
}
public static void doInstance() {
Set<Class<?>> classSet = ClassNames.getClassSet();
if (classSet.isEmpty()) {
return;
}
for (Class<?> clazz : classSet) {
try {
Object instance = clazz.newInstance();
IOC.put(clazz.getName(), instance);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
简单说明下对应的注解:
除了 DAO注解 应该其他的都见过,
说明下DAO注解 是注在 对应的 Mapper接口上的。
简单截图下 使用方式:
和spring用法类型 不在说明
关于注解不懂的 可以翻翻我之前的文章
关于扫包的 类 进行说明:
public class GetPaceOverClass {
public static void getAllPackOverClass(Map<String, Object> ioc, String packageName) {
// List<Class<?>> list=new ArrayList<Class<?>>();
// 1. 首先判断 包的写法 并且 对包 进行 修改 修改为 类的加载器能够加载的模式:
// 类的加载器 加载 为 反斜杠的形式
// packageName = packageName.replace(".", System.lineSeparator());
// 2.创建 类的加载器 对 资源 进行加载 并且 启动 加载
ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
// packageName=packageName.replace(System.lineSeparator(),".");
try {
// 3 获取到 此包下所有的资源
Enumeration<URL> resources = contextClassLoader.getResources(packageName);
while (resources.hasMoreElements()) {
// 4.获取到 当前资源
URL nextElement = resources.nextElement();
// 获取 此 url 的类型 在网络传输中代表 协议名称
// String type = nextElement.getProtocol();
// 获取当前路劲名字
String file = nextElement.getFile();
File typeFile = new File(file);
if (typeFile.isDirectory()) {
// 此时代表 还是个文件 不是 对应的 jar文件
// 递归调用 自己下一层
File[] listFiles = typeFile.listFiles();
for (File file2 : listFiles) {
getAllPackOverClass(ioc, packageName + "/" + file2.getName());
}
}
// 判断是否 是文件 并且 以 .class 结尾
else if (typeFile.getName().endsWith(".class")) {
// 读到对应类文件了 开始解析是否有注解
try {
System.out.println(packageName);
String temp = packageName.replace("/", ".");
System.out.println(temp);
Class<?> clzz = Class.forName(temp.substring(0, packageName.lastIndexOf(".")));
if (clzz.isInterface()) {
if (clzz.isAnnotationPresent(Dao.class)) {
}
}
isAnnotation(ioc, clzz);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// return list;
}
private static void isAnnotation(Map<String, Object> ioc, Class<?> clzz) {
ClassNames.addClass(clzz);
// 得到需要注入到ioc容器中的类
if (clzz.isAnnotationPresent(Dao.class)) {
// 对mydao接口特殊处理 生成他的代理类
//此处 处理了 特殊的接口 Mapper 接口 要生成对应的 特殊的 代理类
Object SqlSessionFactoryBean = ioc.get("SqlSessionFactoryBean");
if (SqlSessionFactoryBean == null) {
Object newInstance = null;
try {
newInstance = edu.mbb.springmybatis.SqlSessionFactoryBean.class.newInstance();
} catch (InstantiationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
ioc.put("SqlSessionFactoryBean", newInstance);
}
SqlSessionFactoryBean sqlsession = (edu.mbb.springmybatis.SqlSessionFactoryBean) ioc
.get("SqlSessionFactoryBean");
Object myDaoAnnotionProxy = sqlsession.getMyDaoAnnotionProxy(clzz);
ioc.put(clzz.getSimpleName(), myDaoAnnotionProxy);
}
if (clzz.isAnnotationPresent(Controller.class) || clzz.isAnnotationPresent(Service.class)) {
try {
//把 注释了 Controller 和 service 注释的类 加载进入 对应的 IOC容器中去。并 new 对应的实体类 实现单例 spring 的单例体现
//真正spring中单例 是采用注册登记的方式 进行注册生成的 此处 是简写的方式
ioc.put(clzz.getSimpleName(), clzz.newInstance());
} catch (InstantiationException e) {
// TODO Auto-generated catch block
System.out.println("bean注入失败");
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
// 存在了 进行 注解的操作
}
}
对应的 DI操作:
package edu.mbb.ioc;
import java.lang.reflect.Field;
import java.util.Set;
import edu.mbb.annocation.Autowried;
public class DIClass {
public static void Di() {
Set<Class<?>> classSet = ClassNames.getClassSet();
if (classSet == null || classSet.size() == 0) {
return;
}
for (Class<?> class1 : classSet) {
Field[] declaredFields = class1.getDeclaredFields();
for (Field field : declaredFields) {
//判断是否存在 对应的 注解
if (field.isAnnotationPresent(Autowried.class)) {
//从ioc容器中 取出对应的 实体类
Object bean = BeanFactory.getBean(class1.getSimpleName());
if (bean == null) {
System.out.println("注入失败");
return;
}
field.setAccessible(true);
Class<?> type = field.getType();
//得到对应DI的 类型名字 用于查找对应的 IOC中的对象
String simpleName = type.getSimpleName();
try {
//调用set给对应属性赋值
field.set(bean, BeanFactory.getBean(simpleName));
} catch (IllegalArgumentException e) {
e.printStackTrace();
System.out.println("注入失败");
} catch (IllegalAccessException e) {
e.printStackTrace();
System.out.println("注入失败");
}
}
}
}
}
}
取出 所有的类 ,并且 把 对应的所有的类 取出来 看看当前的类 中 是否存在对应的autowire 注解
当存在对应的autowire注解的时候 从ioc容器中 取出对应的 单例 对象
通过 set 的方式 给当前 对象中 属性进行赋值
对应的 DispatcherServlet 对应的实现
原理很简单 先上代码:
package edu.mbb.core;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import edu.mbb.annocation.Controller;
import edu.mbb.annocation.RequestMapping;
import edu.mbb.ioc.BeanFactory;
public class DispatcherServlet extends HttpServlet {
// mvc的核心控制类
// 解析对应的 requestMapping
private Map<String, Method> handlerMapping = new HashMap<>();
// 处理controller 的 类 特殊处理
private Map<String, Object> controllerMap = new HashMap<>();
@Override
public void init(ServletConfig config) throws ServletException {
// 初始化handler映射
initHandlerMapping();
}
private void initHandlerMapping() {
// 得到ioc容器
Map<String, Object> ioc = BeanFactory.getIOC();
if (ioc.isEmpty()) {
return;
}
try {
Set<String> keySet = ioc.keySet();
for (String key : keySet) {
Class<? extends Object> clazz = ioc.get(key).getClass();
// 只处理controller 可以放开处理所有的注解情况
if (!clazz.isAnnotationPresent(Controller.class))
continue;
// 类上的requestmapping
String baseUrl = "";
if (clazz.isAnnotationPresent(RequestMapping.class)) {
RequestMapping annotation = clazz.getAnnotation(RequestMapping.class);
baseUrl = annotation.value();
}
Method[] methods = clazz.getMethods();
for (Method method : methods) {
if (!method.isAnnotationPresent(RequestMapping.class))
continue;
RequestMapping annotation = method.getAnnotation(RequestMapping.class);
String url = annotation.value();
url = (baseUrl + "/" + url).replaceAll("/+", "/");
handlerMapping.put(url, method);
controllerMap.put(url, ioc.get(key));
}
System.out.println(handlerMapping);
System.out.println(controllerMap);
}
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.doPost(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
try {
doDispatch(req, resp);
} catch (Exception e) {
resp.getWriter().write("500!!!Server Exception");
}
}
private void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
if (handlerMapping.isEmpty())
return;
String uri = request.getRequestURI();
String contentPath = request.getContextPath();
uri = uri.replace(contentPath, "").replaceAll("/+", "/");
if (!handlerMapping.containsKey(uri)) {
response.getWriter().write("404 Not Found");
return;
}
Method method = handlerMapping.get(uri);
// 获取方法参数列表类型
Class<?>[] parameterTypes = method.getParameterTypes();
// 获取请求的参数
Map<String, String[]> parameterMap = request.getParameterMap();
// 保存参数值
Object[] paramValues = new Object[parameterTypes.length];
for (int i = 0; i < parameterTypes.length; i++) {
// 根据参数名,进行处理
String requestParam = parameterTypes[i].getSimpleName();
if (requestParam.equals("HttpServletRequest")) {
paramValues[i] = request;
continue;
}
if (requestParam.equals("HttpServletResponse")) {
paramValues[i] = response;
continue;
}
if (requestParam.equals("String")) {
for (Map.Entry<String, String[]> param : parameterMap.entrySet()) {
String value = Arrays.toString(param.getValue()).replaceAll("\\[|\\]", "").replaceAll(",\\s", ",");
paramValues[i] = value;
}
}
}
// 利用反射调用方法
try {
method.invoke(controllerMap.get(uri), paramValues);
} catch (Exception e) {
e.printStackTrace();
}
}
}
我们配置对应的 所有的请求 都进这个 servlet
对应的wb.xml配置;
<servlet>
<servlet-name>myDispatcherServlet</servlet-name>
<servlet-class>edu.mbb.core.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>myDispatcherServlet</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
对应的 加载对应的映射关系。
也就是把所有 加上 RequestMapper的注解的类中的方法。
对应的 url 也就是 类上面的 RequestMapping 加上对应的 方法上的RequestMapper 中的值 为 key 对应的method为 value 进行 一一对应存储,在初始化中方法进行调用。
通过request 可以获取到对应的请求地址,就可以找到对应的map中的需要调用的方法。通过 invoke 就可以 调用对应的方法并且通过 方法的参数列表就可以 设置对应的参数列表
String uri = request.getRequestURI();
String contentPath = request.getContextPath();
uri = uri.replace(contentPath, "").replaceAll("/+", "/");
我这里 只设置了 String类型和 request response 的处理 其他的可以自行添加。
关于spring 的启动类:
public class ContextLoaderListener implements ServletContextListener {
@Override
public void contextDestroyed(ServletContextEvent arg0) {
// TODO Auto-generated method stub
}
@Override
public void contextInitialized(ServletContextEvent arg0) {
// TODO Auto-generated method stub
ServletContext servletContext = arg0.getServletContext();
// 取出对应的初始化参数
String initParameter = servletContext.getInitParameter("contextPath");
if (initParameter == null || initParameter.length() > 0) {
initParameter = "classpath:application.properties";
}
// 加载配置文件开始
String[] split = initParameter.split(":");
// 读到对应的配置文件
String path = split[1];
String basePackage = null;
String xmlPath = null;
InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream(path);
Properties pro = new Properties();
try {
pro.load(resourceAsStream);
// 得到扫描包的路径
basePackage = pro.getProperty("scanPackage");
xmlPath = pro.getProperty("xmlPath");
} catch (IOException e) {
// TODO Auto-generated catch block
System.out.println("配置文件路径不存在");
e.printStackTrace();
}
// 开始解析对应的注解和ioc解析
// 开始 扫包开始 把注解全扫出来 ioc 注入完成
GetPaceOverClass.getAllPackOverClass(BeanFactory.getIOC(), basePackage);
// 进行依赖注入 扫描注解 autowite
DIClass.Di();
// 初始化 mybatis配置文件信息
// 解析xml配置文件 进行 开始解析
// xmlPath
GetXMLConfiguration.init(xmlPath);
/// EmpMapper employeeMapper = (EmpMapper)
/// BeanFactory.getIOC().get("EmpMapper");
// Emp findById = employeeMapper.findById("100");
// System.out.println(findById);
}
}
监听 tomcat容器的启动。也就是 监听Application对象的生成 对应的数据 监听 在xml进行配置。;
<context-param>
<param-name>contextPath</param-name>
<param-value>classpath:application.properties</param-value>
</context-param>
<listener>
<listener-class>edu.mbb.core.ContextLoaderListener</listener-class>
</listener>
mybatis中解析 对应的xml
简单说下 数据结构:
public class GetXMLConfiguration {
public static void init(String xmlPath) {
ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
try {
Enumeration<URL> resource = contextClassLoader.getResources(xmlPath);
while (resource.hasMoreElements()) {
URL nextElement = resource.nextElement();
// 获取当前路径的名字
String file = nextElement.getFile();
File tem = new File(file);
File[] list = tem.listFiles();
for (File fileName : list) {
if (fileName.exists()) {
if (fileName.getName().endsWith(".xml")) {
// 开始解析xml
SAXReader sax = new SAXReader();
Document read = sax.read(fileName);
Element rootElement = read.getRootElement();
// rootElement.get
// System.out.println(name);
// nameSpace 名字
String namespace = rootElement.attributeValue("namespace");
List<Element> elements = rootElement.elements();
for (Element element : elements) {
String id = element.attributeValue("id");
String text = element.getText();
Configuration.insertXmlToInFaceMethodSql(id, text);
}
Configuration.insertMapperInFaceToXml(namespace, Configuration.getXmlToInFaceMethodSql());
System.out.println(Configuration.getmapperInFaceToXml());
}
}
}
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static void main(String[] args) {
init("mapper");
}
}
@Service
public class MyDefalutSqlSession implements MySqlSession {
private MyExecutor executor = new MyBaseExecutor();
@Override
public <T> T selectOne(String sql, Class<?> returnType, String methodName) throws SQLException {
return executor.query(sql, returnType, methodName);
}
@Override
public <T> T getMapper(Class<T> clazz) {
return (T) Proxy.newProxyInstance(clazz.getClassLoader(), new Class[] { clazz }, new MyMapperProxy(this));
}
}
生成代理对象
package edu.mbb.mybatis;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import edu.mbb.core.Configuration;
public class MyMapperProxy implements InvocationHandler {
private MySqlSession sqlSession;
public MyMapperProxy() {
}
public MyMapperProxy(MySqlSession sqlSession) {
this.sqlSession = sqlSession;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("--------");
// 类名字
String mapperClass = method.getDeclaringClass().getName();
// 方法的名字
String name = method.getName();
Class<?> returnType = method.getReturnType();
Map<String, Object> xmlByInfaceName = (Map<String, Object>) Configuration.getXmlByInfaceName(mapperClass);
// 得到这个方法的sql
String sql = xmlByInfaceName.get(name).toString();
// 判断sql中的 变量值
/*
* SELECT ar.id, ar.aut_id, ar.title, ar.type, ar.content,
* ar.create_time, au.name, au.age, au.sex, au.email FROM article ar,
* author au WHERE ar.aut_id = au.id AND ar.id =
*/
// #{id}
// Parameter[] parameters = method.getParameters();
sql = replaceSql(sql, args, mapperClass + name);
Object selectOne = sqlSession.selectOne(sql, returnType, mapperClass + name);
System.out.println(sql);
return selectOne;
}
public String replaceSql(String sql, Object[] args, String mapperClass) {
// String temp = "SELECT ar.id, ar.aut_id, ar.title, ar.type,
// ar.content,ar.create_time, au.name, au.age, au.sex, au.email
// FROMarticle ar, author auWHERe ar.aut_id = au.id AND ar.id = #{id}
// and id=15";
// 带?的sql
String parse = parse(sql, "#{", "}", Configuration.getXmlArgs(mapperClass), Configuration.getArgsValues(),
args);
return parse;
}
public static String parse(String text, String openToken, String closeToken, List<String> args,
Map<String, Object> argsValue, Object[] temp) {
if (text == null || text.isEmpty()) {
return "";
}
char[] src = text.toCharArray();
int offset = 0;
// search open token
int start = text.indexOf(openToken, offset);
if (start == -1) {
return text;
}
final StringBuilder builder = new StringBuilder();
StringBuilder expression = null;
int i = 0;
while (start > -1) {
if (start > 0 && src[start - 1] == '\\') {
// this open token is escaped. remove the backslash and
// continue.
builder.append(src, offset, start - offset - 1).append(openToken);
offset = start + openToken.length();
} else {
// found open token. let's search close token.
if (expression == null) {
expression = new StringBuilder();
} else {
expression.setLength(0);
}
builder.append(src, offset, start - offset);
builder.append("?");
System.out.println(builder.toString());
offset = start + openToken.length();
System.out.println(offset);
int end = text.indexOf(closeToken, offset);
System.out.println(end);
while (end > -1) {
if (end > offset && src[end - 1] == '\\') {
// this close token is escaped. remove the backslash and
// continue.
expression.append(src, offset, end - offset - 1).append(closeToken);
offset = end + closeToken.length();
System.out.println(offset);
end = text.indexOf(closeToken, offset);
} else {
expression.append(src, offset, end - offset);
args.add(expression.toString());
System.out.println(expression.toString());
argsValue.put(expression.toString(), temp[i]);
i++;
System.out.println(expression.toString());
offset = end + closeToken.length();
System.out.println(offset);
break;
}
}
if (end == -1) {
// close token was not found.
builder.append(src, start, src.length - start);
offset = src.length;
} else {
// builder.append(handler.handleToken(expression.toString()));
offset = end + closeToken.length();
}
}
start = text.indexOf(openToken, offset);
}
if (offset < src.length) {
builder.append(src, offset, src.length - offset);
}
System.out.println(argsValue);
System.err.println(args);
return builder.toString();
}
public static void main(String[] args) {
String temp = "SELECT ar.id, ar.aut_id, ar.title, ar.type, ar.content,ar.create_time, au.name, au.age, au.sex, au.email FROMarticle ar, author auWHERe ar.aut_id = #{test} AND ar.id = #{id} and id=18";
String parse = parse(temp, "#{", "}", Lists.newArrayList(), Maps.newHashMap(), new Object[] { "100", "200" });
System.out.println(parse);
}
}
解析 XML 并生成对应的sql 在此处说明 解析XML 并把对应的 值 替换为 实际参数 参考 mybatis 的源码 parse 这个是 mybatis 源码。
至此 解析完毕
代码 我上传上去