写在前面
本文使用JAVA类库完成一个迷你版的springmvc框架,旨在深入理解springmvc的内部实现原理。
目录结构
自定义注解
元注解:就是注解的注解
@Retention
@Retention(RetentionPolicy.SOURCE) //注解仅存在于源码中,在class字节码文件中不包含
@Retention(RetentionPolicy.CLASS) // 默认的保留策略,注解会在class字节码文件中存在,但运行时无法获得,
@Retention(RetentionPolicy.RUNTIME) // 注解会在class字节码文件中存在,在运行时可以通过反射获取到
@Target
@Target(ElementType.CONSTRUCTOR) //用于描述构造器
@Target(ElementType.FIELD) //成员变量、对象、属性(包括enum实例)
@Target(ElementType.LOCAL_VARIABLE) //用于描述局部变量
@Target(ElementType.METHOD) //用于描述方法
@Target(ElementType.PACKAGE) //用于描述包
@Target(ElementType.PARAMETER) //用于描述参数
@Target(ElementType.TYPE) //用于描述类、接口(包括注解类型) 或enum声明
@Documented
标记注解表示是否将注解信息加入JAVA文档中
controller
@Documented //javadoc
@Target(ElementType.TYPE) //注解作用在类上
@Retention(RetentionPolicy.RUNTIME) //限制注解的生命周期
public @interface Controller {
/**
* 作用于该类上的注解有一个value属性,其实就是controller
* @return
*/
public String value();
}
Qualifier
@Documented
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Qualifier {
public String value();
}
Repository
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Repository {
public String value();
}
RequestMapping
@Documented
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface RequestMapping {
public String value();
}
Service
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Service {
public String value();
}
DispatcherServlet
dispacherServlet是springmvc的核心控制器,是一个HttpServlet的子类,下面我们来实现它;
@WebServlet(name="dispatcherServlet",urlPatterns="/*",loadOnStartup=1,
initParams={@WebInitParam(name="base-package",value="com.flashhold")})
public class DispatcherServlet extends HttpServlet{
//扫描的基包
private String basePackage="";
//基包下面所有带包路径权限定类名
private List<String> packageNames=new ArrayList<String>();
//注解实例化 注解上的名称:实例化对象
private Map<String,Object>instanceMap=new HashMap<>();
//带包路径的权限定名称:注解的名称
private Map<String,String>nameMap=new HashMap<>();
//url地址和方法的映射关系
private Map<String,Method>urlMethodMap=new HashMap<>();
//method和权限定名映射关系
private Map<Method,String>methodPackageMap=new HashMap<>();
@WebServlet 以前我们定义的servlet需要在Web.xml中配置,Servlet3.0以后出现了基于注解的servlet,在springmvc中DispacherServlet启动需要配置扫描包,比如: 本文为了方便直接在初始化时引入。
<context:component-scan base-package="com.flashhold.myspringmvc">
</context:component-scan>
初始化dispacherServlet
初始化完成的动作:
- 扫描基包下的类拿到信息
- 拿到@Controller、@Service、@Repository注解相应的名称,初始化修饰的类
- 扫描类中的字段,如果有@Qualifier注解,我们需要完成注入
- 扫描@RequestMapping注解,如果有完成到Controller的关系映射。
@Override
public void init(ServletConfig config) throws ServletException {
// TODO Auto-generated method stub
basePackage=config.getInitParameter("base-package");
try {
//扫描基包得到全部的带包路径全限定名
scanBasePackage(basePackage);
//把所有带有注解的类实例化放入map中key为注解的名称
instance(packageNames);
//注入
springIOC();
//完成url地址和方法的映射关系
HandlerUrlMethodMap();
} catch (Exception e) {
// TODO: handle exception
}
}
扫描包
private void scanBasePackage(String basePackage) {
URL url=this.getClass().getClassLoader().getResource(basePackage.replaceAll("\\.", "/"));
File basePackageFile=new File(url.getPath());
System.out.println("scan"+basePackageFile);
File[]cFiles=basePackageFile.listFiles();
for (File file : cFiles) {
if(file.isDirectory()){
scanBasePackage(basePackage+"."+file.getName());
}else if(file.isFile()){
packageNames.add(basePackage+"."+file.getName().split("\\.")[0]);
}
}
}
实例化
private void instance(List<String> packageNames) throws ClassNotFoundException,
InstantiationException, IllegalAccessException {
// TODO Auto-generated method stub
if(packageNames.size()<1){
return;
}
for (String string : packageNames) {
Class c=Class.forName(string);
if(c.isAnnotationPresent(Controller.class)){
Controller controller=(Controller) c.getAnnotation(Controller.class);
String controllerName=controller.value();
instanceMap.put(controllerName, c.newInstance());
nameMap.put(string, controllerName);
System.out.println("Controller:"+string+"value:"+controller.value());
}else if(c.isAnnotationPresent(Service.class)){
Service service=(Service) c.getAnnotation(Service.class);
String serviceName=service.value();
instanceMap.put(serviceName, c.newInstance());
nameMap.put(string, serviceName);
System.out.println("Service:"+string+"value:"+service.value());
}else if(c.isAnnotationPresent(Repository.class)){
Repository repository=(Repository) c.getAnnotation(Repository.class);
String repositoryName=repository.value();
instanceMap.put(repositoryName, c.newInstance());
nameMap.put(string, repositoryName);
System.out.println("Controller:"+string+"value:"+repository.value());
}
}
}
注入
private void springIOC() throws IllegalArgumentException, IllegalAccessException {
// TODO Auto-generated method stub
for (Map.Entry<String,Object>entry: instanceMap.entrySet()) {
Field[] fields=entry.getValue().getClass().getDeclaredFields();
for (Field field : fields) {
if(field.isAnnotationPresent(Qualifier.class)){
String name=field.getAnnotation(Qualifier.class).value();
field.setAccessible(true);
field.set(entry.getValue(), instanceMap.get(name));
}
}
}
}
URL映射
private void HandlerUrlMethodMap() throws ClassNotFoundException {
// TODO Auto-generated method stub
if(packageNames.size()<1){
return;
}
for (String string : packageNames) {
Class c=Class.forName(string);
if(c.isAnnotationPresent(Controller.class)){
Method[]methods=c.getMethods();
StringBuffer baseUrl=new StringBuffer();
if(c.isAnnotationPresent(RequestMapping.class)){
RequestMapping requestMapping=(RequestMapping) c.getAnnotation(RequestMapping.class);
baseUrl.append(requestMapping.value());
}
for (Method method : methods) {
if(method.isAnnotationPresent(RequestMapping.class)){
RequestMapping requestMapping=method.getAnnotation(RequestMapping.class);
baseUrl.append(requestMapping.value());
urlMethodMap.put(baseUrl.toString(), method);
methodPackageMap.put(method, string);
}
}
}
}
}
doGet和doPost
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
// TODO Auto-generated method stub
doPost(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
String uri=req.getRequestURI();
String contextPath=req.getContextPath();
String path=uri.replaceAll(contextPath, "");
Method method=urlMethodMap.get(path);
if(method!=null){
String packageName=methodPackageMap.get(method);
String controllerName=nameMap.get(packageName);
UserController userController=(UserController) instanceMap.get(controllerName);
try {
method.setAccessible(true);
method.invoke(userController);
} catch (Exception e) {
// TODO: handle exception
}
}
}
Controller层
@Controller("userController")
@RequestMapping("/user")
public class UserController {
@Qualifier("userServiceImpl")
private UserService userService;
@RequestMapping("/insert")
public void insert(){
userService.insert();
}
}
Service接口和实现类
public interface UserService {
public void insert();
}
@Service("userServiceImpl")
public class UserServiceImpl implements UserService{
@Qualifier("userDaoImpl")
private UserDao userDao;
@Override
public void insert() {
// TODO Auto-generated method stub
userDao.insert();
}
}
Dao层接口和实现类
public interface UserDao {
public void insert();
}
@Repository("userDaoImpl")
public class UserDaoImpl implements UserDao{
@Override
public void insert() {
// TODO Auto-generated method stub
System.out.println("我是dao");
}
}