手写Spring之spring初体验V1版本
1.目标及说明
本文讲述spring框架的基础部分,如何用自己的方式手动去实现GPDispatcherServlet,其中配置文件只有一个application.properties。Controller、service、dao部分也不进行赘述。
2.基本思路
3.源码实现
3.1.自定义配置 application.properties 文件
为了解析方便,我们用application.properties来代替application.xml文 件 ,具体配置内容如下:
scanPackage=com.gupaoedu.demo
3.2.配置web.xml文件
大家都知道,所有依赖于web容器的项目,都是从读取web.xml文件开始的。我们先配置好web.xml
中的内容。
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:javaee="http://java.sun.com/xml/ns/javaee"
xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
version="2.4">
<display-name>Gupao Web Application</display-name>
<servlet>
<servlet-name>gpmvc</servlet-name>
<servlet-class>com.gupaoedu.mvcframework.v2.servlet.GPDispatchServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>application.properties</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>gpmvc</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
</web-app>
其中GPDispatcherServlet是有自己模拟Spring实现的核心功能类。
3.3自定义 Annotation
@GPService 注解:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface GPService {
String value() default "";
}
@GPAutowired 注 解 :
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface GPAutowired {
String value() default "";
}
@GPController 注 解 :
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface GPController {
String value() default "";
}
@GPRequestMapping 注 解 :
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface GPRequestMapping {
String value() default "";
}
@GPRequestParam 注 解 :
@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface GPRequestParam {
String value() default "";
}
3.4 实现GPDispatcherServlet.java
public class GPDispatcherServlet extends HttpServlet {
private Properties contextConfig = new Properties();
//享元模式,缓存
private List<String> classNames = new ArrayList<String>();
//ioc容器:key:类名首字母小写,value:对应的实例对象
private Map<String,Object> ioc = new HashMap<String, Object>();
private Map<String,Method> handlerMapping = new HashMap<String, Method>();
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
super.doGet(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//6.委派,根据URL去找到一个对应的Method并通过response返回
try {
doDispatch(req,resp);
} catch (Exception e) {
e.printStackTrace();
resp.getWriter().write("500 Exception,Detail :" + Arrays.toString(e.getStackTrace()));
}
}
private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception {
String url = req.getRequestURI();
String contextPath = req.getContextPath();
url = url.replaceAll(contextPath,"").replaceAll("/+","/");
if(!this.handlerMapping.containsKey(url)){
resp.getWriter().write("404 Not Found!!!");
return;
}
Map<String,String[]> params = req.getParameterMap();
Method method = this.handlerMapping.get(url);
Class<?> [] parameterTypes = method.getParameterTypes();
Object [] paramValues = new Object[parameterTypes.length];
for (int i = 0; i < parameterTypes.length; i++) {
Class paramterType = parameterTypes[i];
if(paramterType == HttpServletRequest.class){
paramValues[i] = req;
}else if(paramterType == HttpServletResponse.class){
paramValues[i] = resp;
}else if(paramterType == String.class){
//通过运行时的状态拿到注解的值
Annotation[] [] pa = method.getParameterAnnotations();
for (int j = 0; j < pa.length ; j ++) {
for(Annotation a : pa[j]){
if(a instanceof GPRequestParam){
String paramName = ((GPRequestParam) a).value();
if(!"".equals(paramName.trim())){
String value = Arrays.toString(params.get(paramName)).replaceAll("\\[|\\]","")
.replaceAll("\\s,",",");
paramValues[i] = value;
}
}
}
}
}
}
//暂时硬编码
String beanName = toLowerFirstCase(method.getDeclaringClass().getSimpleName());
method.invoke(ioc.get(beanName),new Object[]{req,resp,params.get("name")});
}
@Override
public void init(ServletConfig config) throws ServletException {
//1.加载配置文件
doLoadConfig(config.getInitParameter("contextConfigLocation"));
//2.扫描相关的类
doScanner(contextConfig.getProperty("scanPackage"));
//============IOC=============
//3.初始化Ioc容器,将扫描到的相关的类实例化,保存到Ioc容器中
doInstrance();
//在DI之前加入AOP。
//AOP:新生成的代理对象
//============DI=============
//4.完成依赖注入
doAutowired();
//============MVC=============
//5.初始化HandlerMapping
doInitHandlerMapping();
System.out.println("GP Spring framework init Finished");
}
private void doInitHandlerMapping() {
if(ioc.isEmpty()){
return;
}
for (Map.Entry<String, Object> entry : ioc.entrySet()) {
Class<?> clazz = entry.getValue().getClass();
if(!clazz.isAnnotationPresent(GPController.class)){
continue;
}
//提取Controller类上的url
String baseUrl = "";
if(clazz.isAnnotationPresent(GPRequestMapping.class)){
GPRequestMapping requestMapping = clazz.getAnnotation(GPRequestMapping.class);
baseUrl = requestMapping.value();
}
//只获取public方法
for(Method method : clazz.getMethods()){
if(!method.isAnnotationPresent(GPRequestMapping.class)){
continue;
}
//提取每个方法上的url
GPRequestMapping gpRequestMapping = method.getAnnotation(GPRequestMapping.class);
//使用正则/+,处理多/问题
String url = "/" + baseUrl + "/" + gpRequestMapping.value().replaceAll("/+","/");
handlerMapping.put(url,method);
System.out.println("Mapped : " + url + "," + method);
}
}
}
private void doAutowired() {
if(ioc.isEmpty()){
return;
}
for (Map.Entry<String,Object> entry:ioc.entrySet()) {
//把所有private/protected/default/public修饰的变量都获取出来
for (Field field:entry.getValue().getClass().getDeclaredFields()) {
if(!field.isAnnotationPresent(GPAutowired.class)){
continue;
}
GPAutowired autowired = field.getAnnotation(GPAutowired.class);
String beanName = autowired.value().trim();
//如果用户没有自定义的beanName,就默认根据类型注入
if("".equals(beanName)){
//field.getType().getName() 获取字段的类型
beanName = field.getType().getName();
}
//暴力访问
field.setAccessible(true);
try {
//ioc.get(beanName) 相当于通过接口的全名拿到接口的实现的实例
field.set(entry.getValue(),ioc.get(beanName));
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
private void doInstrance() {
if(classNames.isEmpty()){
return;
}
try {
for (String className : classNames) {
Class<?> clazz = Class.forName(className);
//包含GPController注解或GPService注解
if(clazz.isAnnotationPresent(GPController.class )){
String beanName = toLowerFirstCase(clazz.getSimpleName());
Object instance = clazz.newInstance();
//赋值到ioc容器
ioc.put(beanName,instance);
}else if(clazz.isAnnotationPresent(GPService.class)){
//2.在多个包下出现相同类名,只能规定自己起一个全局唯一名字
//如使用注解GPService("aService")自定义命名
String beanName = clazz.getAnnotation(GPService.class).value();
if("".equals(beanName.trim())){
beanName = toLowerFirstCase(clazz.getSimpleName());
}
//1.默认的类名首字母小写
Object instance = clazz.newInstance();
ioc.put(beanName,instance);
//3.如果是接口
//判断有多少个实现类,如果只有一个,默认就选择这个实现类
//如果有多个,只能抛异常
for (Class<?> i :clazz.getInterfaces()) {
if(ioc.containsKey(i.getName())){
throw new Exception("The" + i.getName() + "is exists!!");
}
ioc.put(i.getName(),instance);
}
}else{
continue;
}
}
}catch (Exception e) {
e.printStackTrace();
}
}
private String toLowerFirstCase(String simpleName) {
char [] chars = simpleName.toCharArray();
chars[0] += 32;
return String.valueOf(chars);
}
private void doScanner(String scanPackage) {
URL url = this.getClass().getClassLoader().getResource("/" + scanPackage.replaceAll("\\.","/"));
File classPath = new File(url.getFile());
// 当成是一个classPath文件夹
for (File file : classPath.listFiles()) {
if(file.isDirectory()){
doScanner(scanPackage + "." + file.getName());
}else{
if(!file.getName().endsWith(".class")){
continue;
}
String className = (scanPackage + "." + file.getName().replace(".class",""));
//Class.forName(className);
classNames.add(className);
}
}
}
private void doLoadConfig(String contextConfigLoation) {
InputStream is = this.getClass().getClassLoader().getResourceAsStream(contextConfigLoation);
try {
contextConfig.load(is);
} catch (IOException e) {
e.printStackTrace();
}finally {
if(null != is){
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}