IOC功能简单实现
创建自定义注解,功能与spring自带注解功能一致
元注解(负责注解其他注解)功能说明:
- @Target:说明了Annotation所修饰的对象范围(被标注的注解可用在什么地方)。
- ElementType.FIELD:注解可标注在属性上。
- ElementType.METHOD:注解可标注在方法上。
- ElementType.TYPE:注解可标注在类、接口(包括注解类型) 或enum声明上。
- @Retention:该Annotation被保留的时间长短
- SOURCE:在源文件中有效(即源文件保留)。
- CLASS:在class文件中有效(即class保留)
- RUNTIME:在运行时有效(即运行时保留)
- @Documented:注解表明这个注释是由 javadoc记录的,在默认情况下也有类似的记录工具。 如果一个类型声明被注释了文档化,它的注释成为公共API的一部分。
创建自定义注解
-
@CustomController = @Controller
package custom.annotation; import java.lang.annotation.*; @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface CustomController { String value() default ""; }
-
@CustomRequestMapping = @RequestMapping
package custom.annotation; import java.lang.annotation.*; @Target({ElementType.TYPE,ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface CustomRequestMapping { String value() default ""; }
-
@CustomRequestParam = @RequestParam
package custom.annotation; import java.lang.annotation.*; @Target({ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface CustomRequestParam { String value() default ""; }
-
@CustomAutowired = @Autowired
package custom.annotation; import java.lang.annotation.*; @Target({ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface CustomAutowired { String value() default ""; }
-
@CustomService = @Service
package custom.annotation; import java.lang.annotation.*; @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface CustomService { String value() default ""; }
创建IOC实现类
package custom.servlet;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import custom.annotation.CustomAutowired;
import custom.annotation.CustomController;
import custom.annotation.CustomRequestMapping;
import custom.annotation.CustomService;
/**
* 简单版的IOC容器实现类
* @author li
*
*/
@SuppressWarnings("serial")
public class CustomDispatcherServletByDesignMode extends HttpServlet {
// 保存application.properties配置文件中的内容
private Properties contextConfig = new Properties();
// 保存描到的类名称
private List<String> classNames = new ArrayList<String>();
// ioc容器,主要关注设计思想和原理,简化实现,不使用ConcurrentHashMap
private Map<String, Object> ioc = new HashMap<String, Object>();
// 保存url与Method的映射关系
private Map<String, Method> handlerMapping = new HashMap<String, Method>();
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// TODO Auto-generated method stub
this.doPost(req, resp);
}
/**
* 使用了委派模式,委派模式的具体逻辑在doDispath中实现
*/
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// TODO Auto-generated method stub
try {
doDispath(req,resp);
} catch (Exception e) {
// 发生异常时,页面显示500错误
resp.getWriter().write("500");
}
}
private void doDispath(HttpServletRequest req, HttpServletResponse resp) throws Exception{
// 获取访问地址 例如:/custom-spring/demo/query
String url = req.getRequestURI();
// 获取属于哪个项目 /custom-spring
String contextPath = req.getContextPath();
// 获取具体调用地址 /demo/query
url = url.replace(contextPath, "").replaceAll("/+", "/");
if(!this.handlerMapping.containsKey(url)){
resp.getWriter().write("404");
return;
}
Method method = (Method) this.handlerMapping.get(url);
Map<String, String[]> params = req.getParameterMap();
String beanName = toLowerFirstCase(method.getDeclaringClass().getSimpleName());
// 虽然完成了动态委派并进行了反射调用,但是对url参数的处理还是静态的。
method.invoke(ioc.get(beanName), new Object[]{req,resp,params.get("name")[0]});
}
/**
* 生成初始类对象
*/
@Override
public void init(ServletConfig config) throws ServletException {
// 加载配置文件
doLoadConfig(config.getInitParameter("contextConfigLocation"));
// 扫描相关类
doScanner(contextConfig.getProperty("scanPackage"));
// 初始化扫描到的类,并将他们放入IOC容器中
doInstance();
// 完成依赖注入
doAutowired();
// 初始化HandlerMapping
initHandlerMapping();
}
/**
* 用于加载配置文件
* @param contextConfigLocation
*/
private void doLoadConfig(String contextConfigLocation){
InputStream fis = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation);
try {
contextConfig.load(fis);
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}finally {
if(null != fis){
try {
fis.close();
} catch (Exception e2) {
// TODO: handle exception
e2.printStackTrace();
}
}
}
}
/**
* 扫描相关类
* @param scanPackage
*/
private void doScanner(String scanPackage){
// 转换为文件路径,就是把.替换成/
URL url = this.getClass().getClassLoader().getResource("/"+scanPackage.replaceAll("\\.", "/"));
File classDir = new File(url.getFile());
for(File file : classDir.listFiles()){
if(file.isDirectory()){
doScanner(scanPackage+"."+file.getName());
}else{
if(!file.getName().endsWith(".class")){
continue;
}
String clazzName = (scanPackage+"."+file.getName().replaceAll(".class", ""));
classNames.add(clazzName);
}
}
}
/**
* 工厂模式的具体实现
*/
private void doInstance() {
// 初始化,为DI做准备
if(classNames.isEmpty()){
return;
}
try {
for(String className : classNames){
Class<?> clazz = Class.forName(className);
/**
* 根据注解判断哪些类需要进行初始化
*/
if(clazz.isAnnotationPresent(CustomController.class)) {
Object instance = clazz.newInstance();
// Spring默认类名首字母小写
String beanName = toLowerFirstCase(clazz.getSimpleName());
ioc.put(beanName, instance);
}else if(clazz.isAnnotationPresent(CustomService.class)){
// 自定义beanName
CustomService service = clazz.getAnnotation(CustomService.class);
String beanName = service.value();
// Spring默认类名首字母小写
if("".equals(beanName.trim())){
beanName = toLowerFirstCase(clazz.getSimpleName());
}
Object instance = clazz.newInstance();
ioc.put(beanName, instance);
// 根据类型自动赋值
for(Class<?> i: clazz.getInterfaces()){
if(ioc.containsKey(i.getName())){
throw new Exception(i.getName()+"不存在!");
}
// 接口类型直接作为key,具体实现类的实例作为value
ioc.put(i.getName(), instance);
}
}else{
continue;
}
}
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
}
/**
* 将类名首字母改成小写,Spring默认类名首字母小写
* @return
*/
private String toLowerFirstCase(String beanName) {
char[] chars = beanName.toCharArray();
// 大小字母ASCII码比小写字母ASCII码小32
chars[0] +=32;
return String.valueOf(chars);
}
// 自动进行依赖注入
private void doAutowired() {
if(ioc.isEmpty()){
return;
}
// 获取所有的属性,包括private、protected、default
// 正常来说只获得public
for(Map.Entry<String, Object> entry : ioc.entrySet()) {
Field[] fields = entry.getValue().getClass().getDeclaredFields();
for(Field field:fields){
if(!field.isAnnotationPresent(CustomAutowired.class)){
continue;
}
CustomAutowired autowired = field.getAnnotation(CustomAutowired.class);
// 若没有定义CustomAutowired的value,则默认根据类型注入
String beanName = autowired.value().trim();
if("".equals(beanName)){
// 获取接口的类型,作为key到ioc容器中取value
beanName = field.getType().getName();
}
// public以外的类型,只要加了@CustomAutowired的属性都强制进行赋值
// 反射中叫暴力访问
field.setAccessible(true);
try {
// 利用反射机制动态给属性赋值
field.set(entry.getValue(), ioc.get(beanName));
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
}
}
}
/**
* 初始化url和Method的对应关系,是策略模式的应用案例
*/
private void initHandlerMapping() {
if(ioc.isEmpty()){
return;
}
for(Map.Entry<String, Object> entry : ioc.entrySet()){
Class<?> clazz = entry.getValue().getClass();
if(!clazz.isAnnotationPresent(CustomController.class)) {
continue;
}
String baseUrl = "";
if(clazz.isAnnotationPresent(CustomRequestMapping.class)){
CustomRequestMapping requestMapping = clazz.getAnnotation(CustomRequestMapping.class);
baseUrl = requestMapping.value();
}
// 默认获取所有public方法
for(Method method : clazz.getMethods()) {
// 没有@CustomRequestMapping标注的方法不处理
if(!method.isAnnotationPresent(CustomRequestMapping.class)){
continue;
}
CustomRequestMapping requestMapping = method.getAnnotation(CustomRequestMapping.class);
String url = (baseUrl + requestMapping.value()).replace("/+", "/");
handlerMapping.put(url, method);
}
}
}
}
测试代码实现
实现controller
package custom.controller;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import custom.annotation.CustomAutowired;
import custom.annotation.CustomController;
import custom.annotation.CustomRequestMapping;
import custom.annotation.CustomRequestParam;
import custom.service.CustomDemoService;
@CustomController
@CustomRequestMapping("/demo")
public class DemoAction {
@CustomAutowired
private CustomDemoService demoService;
@CustomRequestMapping("/query")
public void query(HttpServletRequest req,HttpServletResponse resp,@CustomRequestParam("name") String name){
String result = demoService.get(name);
try {
resp.getWriter().write(result);
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
}
}
实现service接口
package custom.service;
public interface CustomDemoService {
String get(String name);
}
实现service 实现类
package custom.service.impl;
import custom.annotation.CustomService;
import custom.service.CustomDemoService;
@CustomService
public class DemoService implements CustomDemoService {
public String get(String name){
return "My name is" + name;
}
}
配置配置文件 application.properties
放在src目录下:
# 扫描package名称
scanPackage=custom
配置web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" id="WebApp_ID" version="3.1">
<display-name>spring</display-name>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>index.htm</welcome-file>
<welcome-file>index.jsp</welcome-file>
<welcome-file>default.html</welcome-file>
<welcome-file>default.htm</welcome-file>
<welcome-file>default.jsp</welcome-file>
</welcome-file-list>
<servlet>
<servlet-name>custommvc</servlet-name>
<servlet-class>custom.servlet.CustomDispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>application.properties</param-value>
</init-param>
// servlet init()执行时机,-1表示servlet被调用时执行,只执行一次,便于调试IOC容器加载实例过程
<load-on-startup>-1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>custommvc</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
</web-app>