在JDK5之后,Java增加了Annotation注解的基本语法,通过注解可以省略XML的配置信息,简化代码的编写形式。
通过封装框架,可以完成解耦,简化开发。
1. 引入工具类
用以找出某个包下的所有以.class结尾的java文件
import java.io.File;
import java.io.FileFilter;
import java.net.URL;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
/**
* 找到某个包下的所有的以.class结尾的java文件
*/
public class ClassScanner {
/**
* 获得包下面的所有的class
*
* @return List包含所有class的实例
*/
public static List<Class<?>> getClassesFromPackage(String packageName) {
List<Class<?>> classes = new ArrayList<>();
// 是否循环搜索子包
boolean recursive = true;
// 包名对应的路径名称
String packageDirName = packageName.replace('.', '/');
Enumeration<URL> dirs;
try {
//从当前正在运行的线程中,加载类加载器,通过给定的包名,找到所有该包下的类
dirs = Thread.currentThread().getContextClassLoader().getResources(packageDirName);
while (dirs.hasMoreElements()) {
URL url = dirs.nextElement();
String protocol = url.getProtocol();
if ("file".equals(protocol)) {
String filePath = URLDecoder.decode(url.getFile(), "UTF-8");
findClassInPackageByFile(packageName, filePath, recursive, classes);
}
}
} catch (Exception e) {
e.printStackTrace();
}
return classes;
}
/**
* 在package对应的路径下找到所有的class
*/
public static void findClassInPackageByFile(String packageName, String filePath, final boolean recursive,
List<Class<?>> classes) {
File dir = new File(filePath);
if (!dir.exists() || !dir.isDirectory()) {
return;
}
// 在给定的目录下找到所有的文件,并且进行条件过滤
File[] dirFiles = dir.listFiles(new FileFilter() {
public boolean accept(File file) {
boolean acceptDir = recursive && file.isDirectory();// 接受dir目录
boolean acceptClass = file.getName().endsWith("class");// 接受class文件
return acceptDir || acceptClass;
}
});
if (dirFiles != null) {
for (File file : dirFiles) {
if (file.isDirectory()) {
findClassInPackageByFile(packageName + "." + file.getName(), file.getAbsolutePath(), recursive, classes);
} else {
String className = file.getName().substring(0, file.getName().length() - 6);
try {
classes.add(Thread.currentThread().getContextClassLoader().loadClass(packageName + "." + className));
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
}
2. 编写注解
controller层的类注解
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Controller {
}
controller层的方法注解
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RequestMapping {
String value() default "";
}
3. 添加application监听器
放置请求路径和对应调用的controller层方法关系的map表的类(考虑线程安全)
import java.lang.reflect.Method;
import java.util.concurrent.ConcurrentHashMap;
public class WebApplicationContext {
private static final ConcurrentHashMap<String, Method> methodMap = new ConcurrentHashMap<>();
public static ConcurrentHashMap<String, Method> getMethodMap() {
return methodMap;
}
}
监听器,找到请求路径对应的应该被调用的controller层的方法,放置到上面的map中
import com.hjt.annotation.Controller;
import com.hjt.annotation.RequestMapping;
import com.hjt.utils.ClassScanner;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import java.lang.reflect.Method;
import java.util.List;
public class ClassLoaderContextListener implements ServletContextListener {
// 这里根据需求可以改
private static String CLASS_PATH = "com.hjt.controller";
public static String getClassPath() {
return CLASS_PATH;
}
public static void setClassPath(String classPath) {
CLASS_PATH = classPath;
}
@Override
public void contextInitialized(ServletContextEvent sce) {
initClassLoader();
}
private void initClassLoader() {
List<Class<?>> classList = ClassScanner.getClassesFromPackage(CLASS_PATH);
if (classList.size() > 0) {
for (Class<?> clazz : classList) {
if (clazz.isAnnotationPresent(Controller.class) && clazz.getMethods().length > 0) {
for (Method method : clazz.getMethods()) {
if (method.isAnnotationPresent(RequestMapping.class)) {
WebApplicationContext.getMethodMap().put(method.getAnnotation(RequestMapping.class).value(), method);
}
}
}
}
}
}
}
4. 编写Servlet类
这里直接重写了service方法,根据需求可以重写不同的方法
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class DispatcherServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
Method method = WebApplicationContext.getMethodMap().get(req.getServletPath());
if (method != null) {
Object instance = null;
try {
instance = method.getDeclaringClass().newInstance();
} catch (InstantiationException | IllegalAccessException e) {
e.printStackTrace();
}
try {
method.invoke(instance, req, resp);
} catch (IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
} else {
// 这里根据需求修改
resp.sendRedirect("failure.jsp");
}
}
}
5. pom依赖
主要引入javax.servlet-api,打包用jar
<!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
6. 打包
整理annotation、listener、servlet和utils四层的内容,利用maven中Lifecycle的install打成jar包,放入本地仓库,以后使用时利用本地坐标引入。
下次使用时,还需要在web.xml中对上面编写的Servlet类(DispatcherServlet)和监听器(ClassLoaderContextListener)进行配置,并引入所需的依赖。
controller层中的类不用再继承HttpServlet方法,而是在类上加上Controller注解,在需要的方法上加上RequestMapping("/XXXXXX")注解。
注意包名要和框架里的定义一致。