手寫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();
}
}
}
}
}