【spring】手寫spring
前言
spring和之前將的tomcat差不多,都有一個servlet,但是有了分發的功能。最大的不同是需要把類例項化成物件放到ioc容器。
6大步驟
- 1.載入配置檔案application.properties
public class HLPDidspatcherServlet extends HttpServlet{
private Properties p = new Properties();
private List<String> classNames = new ArrayList<String>();
private Map<String,Object> ioc = new HashMap<String,Object>();
private Map<String,Method> handlerMapping = new HashMap<String,Method>();
@Override
public void init(ServletConfig config) throws ServletException {
String application = config.getInitParameter("contextConfigLocation" );
System.out.println("application = " + application);
//1.載入配置檔案application.properties
doLoadConfig(application);
//2.掃描配置檔案中描述的相關的所有類
doScanner(p.getProperty("scanPackage"));
//3.例項化所有掃描到的類,並放到ioc容器中(自己實現ioc容器)
doInstance();
//4.依賴注入,di從ioc容器中找到加@autowired這個註解的屬性,並找到其在ioc容器中的例項,動態賦值
doAutowired();
//5.把在@controller中加了@requestMapping這個註解的方法和url構造成一個對應關係,一個map結構,key是URL;value是method
initHandlerMapping();
//6.等待使用者請求,根據使用者請求的url去map中找對應的method
// 呼叫doPost/doGet方法,通過反射機制動態呼叫該方法並執行
}}
//1.載入配置檔案application.properties
private void doLoadConfig(String location){
InputStream is = this.getClass().getClassLoader().getResourceAsStream(location);
try {
p.load(is);
} catch (IOException e) {
e.printStackTrace();
}finally {
if(null != is){
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
- 2.掃描配置檔案中描述的類
//2.掃描配置檔案中描述的相關的所有類
//遞迴掃描
private void doScanner(String packageName){
URL url = this.getClass().getClassLoader().getResource("/"+packageName.replaceAll("\\.","/"));
File dir = new File(url.getFile());
for(File file : dir.listFiles()){
if(file.isDirectory()){
doScanner(packageName+"."+file.getName());
}else{
classNames.add(packageName + "." + file.getName().replaceAll(".class","").trim());
}
}
}
- 3.把這些類例項化放到ioc容器中
//3.例項化所有掃描到的類,並放到ioc容器中(自己實現ioc容器)
private void doInstance(){
try {
for (String className : classNames) {
Class<?> clazz = Class.forName(className);
if(clazz.isAnnotationPresent(HLPController.class)){
String beanName = lowerFirst(clazz.getSimpleName());
ioc.put(beanName,clazz.newInstance());
}else if(clazz.isAnnotationPresent(HLPService.class)){
//第一種形式,預設首字母小寫
//第二種形式,如果自己起了名字,優先用自己定義的名字去匹配
//第三種形式,利用介面本身作為key,把其對應類的例項作為值
HLPService service= clazz.getAnnotation(HLPService.class);
String beanName = service.value();
if(!"".equals(beanName.trim())){
ioc.put(beanName,clazz.newInstance());
continue;
}
Object instance = clazz.newInstance();
beanName=lowerFirst(clazz.getSimpleName());
ioc.put(beanName,instance);
Class<?>[] interfaces = clazz.getInterfaces();
for(Class<?> i : interfaces)
{
ioc.put(i.getName(),instance);
}
}else{
continue;
}
}
}catch (Exception e ){
e.printStackTrace();
}
}
對所有加了@Controller的類都放到ioc容器了,其實就是一個map,key,value對;key是類名的首字母小寫,value是類的物件。
但是@service有一些不同,key可能是serviceImpl的類名首字母小寫,也可能是@service裡自定義的名字,還可能是service介面的類名
這一步就是IOC,物件的生命週期交給容器管理
- 4.依賴注入,找到@autowired並把ioc容器中的例項動態注入
//4.依賴注入,di從ioc容器中找到加@autowired這個註解的屬性,並找到其在ioc容器中的例項,動態賦值
private void doAutowired(){
if(ioc.isEmpty()){return;}
for(Map.Entry<String,Object> entry: ioc.entrySet() ){
Field[] fields = entry.getValue().getClass().getDeclaredFields();
for(Field field: fields){
if(!field.isAnnotationPresent(HLPAutowired.class)){continue;}
HLPAutowired autowired = field.getAnnotation(HLPAutowired.class);
String beanName = autowired.value().trim();
if("".equals(beanName)){
beanName=field.getType().getName();
}
//即使是private,也要強吻
field.setAccessible(true);
//開始賦值
try {
field.set(entry.getValue(),ioc.get(beanName));
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
這一步是DI,在加了@autowired @requestMapping等註解的地方像為屬性賦值一樣 ,賦上類的例項。
這是一個和我之前想的不一樣的地方,注入的時候不是遍歷程式碼中的每個類的每個 方法,相反是遍歷ioc容器裡每一個key,value對的value對應的類上的註解。
之前ioc中已經有了key,value對,然後再建立一個key,value對,key是ioc容器中value也就是物件,value是註解比如@autowired中自定義的名字或者是類的型別。
- 5.把加了@requestMapping的方法與url對應起來,key是url,value是method
//5.把在@controller中加了@requestMapping這個註解的方法和url構造成一個對應關係,一個map結構,key是URL;value是method
private void initHandlerMapping(){
if(ioc.isEmpty()){return;}
for(Map.Entry<String,Object> entry: ioc.entrySet()){
Class<?> clazz = entry.getValue().getClass();
if(!clazz.isAnnotationPresent(HLPController.class)){continue;}
String url = "";
if(clazz.isAnnotationPresent(HLPRequestMapping.class)){
HLPRequestMapping requestMapping = clazz.getAnnotation(HLPRequestMapping.class);
url = requestMapping.value();
}
Method[] methods = clazz.getMethods();
for(Method method:methods){
if(!method.isAnnotationPresent(HLPRequestMapping.class)){continue;}
HLPRequestMapping hlpRequestMapping = method.getAnnotation(HLPRequestMapping.class);
String mapping =("/"+url+"/"+hlpRequestMapping.value()).replaceAll("/+","/") ;
handlerMapping.put(mapping,method);
}
}
}
- 6.等待使用者請求,根據使用者的url請求map中的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 {
doDispatcher(req,resp);
}
private void doDispatcher(HttpServletRequest req, HttpServletResponse resp){
if(handlerMapping.isEmpty()){return;}
String url = req.getRequestURI();
String contextPath = req.getContextPath();
url = url.replace(contextPath,"").replaceAll("/+","/");
Method method = handlerMapping.get(url);
}
一直HttpServletRequest如何獲得url?
分三步:
1.獲得 requestURL
String url = req.getRequestURI(); 這個包括http://應用名:埠號/程式名/類名/方法名;而只有類名/方法名是我們需要的,因為requestMapping組成的在ioc中的key,value對中key是這個。
2.獲得http://應用名:埠號/程式名
request.getContextPath();
3.把url中contextPath這部分替換成空,剩下的/+替換成一個的/
url.replace(contextPath,”“).replaceAll(“/+”,”/”);