前言
传统配置Spring xml配置文件将Bean托管Spring容器。
但由于配置的繁琐性,随着JDK5支持注解,Spring2.x版本后逐渐使用注解方式代替传统XML配置。
现在开发的新项目惯于使用Spring扫包+注解的方式自动装配,托管于Spring容器。
本篇文章基于上述内容,大致描述Spring新版本核心思想,代码只提炼精髓与关键点,且与源码大相径庭,复杂程度远不及源码的N分之一,仅供参考。
目录
具体实现
一、自定义注解
二、编写Controller及Service
三、自定义模拟Spring容器及Url与Method映射关系
四、自动扫包工具类
五、开始注解托管+依赖注入
六、自定义Servlet
七、开始请求,测试结果
http://127.0.0.1:8080/test/query
http://127.0.0.1:8080/test/add?str=含参方式测试
实现过程:
- 【自定义注解】,模仿目前工程中常用注解,自定义名称:@MyController、@MyService、@MyAutowired、@MyRequestMapping、@MyRequestParam
- 【定义全局Map模拟Spring容器】,Map<Bean名称,Bean实例>
- 【定义全局Map模拟请求url与Controller中方法映射关系】,Map<URL,Method>
- 【自动扫包 + 初始化容器】给出扫包路径,根据路径扫描java文件,且筛选具有@MyController、@MyService注解的类注入容器,其中@MyService的类可以起别名,且@MyService实现的接口类也会同时被注入容器(接口与实现类注入Bean的实例均为实现类的实例)
- 【依赖注入】遍历容器管理的Bean,拿到每个Bean中的Field,筛选具有@MyAutowired的Field,并根据Field的ClassName从容器中获取Bean实例赋值给该Field
- 【自定义Servlet】拦截请求,并统一实现请求分发
- 【自动处理请求参数】根据@MyRequestParam获取具体参数名称,实现自动请求
具体实现
一、自定义注解
@MyController
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyController {
String value() default "";
}
@MyService
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyService {
String value() default "";
}
@MyAutowired
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyAutowired {
String value() default "";
}
@MyRequestMapping
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyRequestMapping {
String value() default "";
}
@MyRequestParam
@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyRequestParam {
String value() default "";
}
二、编写Controller及Service
- 分别在各个地方使用自定义注解@MyController、@MyService、@MyAutowired、@MyRequestMapping、@MyRequestParam
@MyController
@MyRequestMapping("/test")
public class TestController {
@MyAutowired
private TestService testService;
@MyRequestMapping("/add")
public void add(HttpServletRequest request, @MyRequestParam("str") String str) {
testService.add(str);
}
@MyRequestMapping("/query")
public void query(HttpServletRequest request) {
testService.query();
}
}
public interface TestService {
void add(String str);
void query();
}
@MyService
public class TestServiceImpl implements TestService {
@Override
public void add(String str) {
System.out.println("add:" + str);
}
@Override
public void query() {
System.out.println("query方法执行!");
}
}
三、自定义模拟Spring容器及Url与Method映射关系
public interface CommonField {
/**
* 模拟Spring容器,存放Spring初始化Bean
*/
Map<String, Object> myIocContainer = new HashMap<>();
/**
* 模拟SpringMVC请求映射处理器,映射请求url和Method关系
*/
Map<String, Method> myHandlerMapping = new HashMap<>();
}
四、自动扫包工具类
- 本地启动扫包与项目打Jar包后扫包方式略有不同
public class FileUtil {
/**
* 路径扫包,支持递归 / 非递归
*
* @param packagePath 包路径,以"."分隔
* @param recursion 是否递归 true:是
* @return
* @throws Exception
*/
public static List<Class<?>> scanPackage(String packagePath, boolean recursion) throws Exception {
List<Class<?>> result = new ArrayList<>();
String packageRealPath = packagePath.replace(".", "/");
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
Enumeration<URL> resources = classLoader.getResources(packageRealPath);
while (resources.hasMoreElements()) {
URL url = resources.nextElement();
if ("file".equals(url.getProtocol())) {
// 本地项目启动扫包方式
result.addAll(scanFile(classLoader, packagePath, recursion));
} else if ("jar".equals(url.getProtocol())) {
// 项目打包后扫包方式
result.addAll(scanJar(classLoader, packagePath, recursion));
}
}
return result;
}
private static List<Class<?>> scanFile(ClassLoader classLoader, String packagePath, boolean recursion) throws Exception {
List<Class<?>> result = new ArrayList<>();
URI url = Objects.requireNonNull(classLoader.getResource(packagePath.replace(".", "/"))).toURI();
File file = new File(url);
if (file.isDirectory()) {
File[] fileList = file.listFiles();
if (fileList != null && fileList.length > 0) {
for (File _file : fileList) {
if (!_file.isDirectory()) {
String fileName = _file.getName();
if (!file.exists() || !fileName.endsWith(".class")) {
continue;
}
String className = packagePath + "." + fileName.replace(".class", "");
Class<?> clazz = Class.forName(className);
if (isSkipClass(clazz)) {
continue;
}
result.add(clazz);
} else if (recursion) {
result.addAll(scanFile(classLoader, packagePath + "." + _file.getName(), true));
}
}
}
} else {
throw new Exception("错误的扫包路径!");
}
return result;
}
private static List<Class<?>> scanJar(ClassLoader classLoader, String packagePath, boolean recursion) throws Exception {
List<Class<?>> result = new ArrayList<>();
URL url = classLoader.getResource(packagePath.replace(".", "/"));
JarURLConnection jarURLConnection = (JarURLConnection) url.openConnection();
JarFile jarFile = jarURLConnection.getJarFile();
Enumeration<JarEntry> jarEntrys = jarFile.entries();
while (jarEntrys.hasMoreElements()) {
JarEntry jarEntry = jarEntrys.nextElement();
String jarEntryName = jarEntry.getName();
/**
* 读取项目Jar包后遍历所有文件夹及子文件夹,若文件夹路径不是以目标路径开头直接跳过
* 子文件夹一并读取 因此无需手动递归
*/
if (!jarEntryName.replaceAll("/", ".").startsWith(packagePath)) {
continue;
}
if (!jarEntry.isDirectory()) {
if (!jarEntryName.endsWith(".class")) {
continue;
}
String className = jarEntry.getName().replace("/", ".").replace(".class", "");
Class<?> clazz = Class.forName(className);
if (isSkipClass(clazz)) {
continue;
}
result.add(clazz);
}
}
return result;
}
private static boolean isSkipClass(Class<?> clazz) throws Exception {
// 跳过 注解、接口、枚举、八种基本数据类型
if (clazz.isAnnotation() || clazz.isInterface() || clazz.isEnum() || clazz.isPrimitive()) {
return true;
}
return false;
}
}
五、开始注解托管+依赖注入
- 将带@MyController、@MyService注解的类交给容器托管,并实现依赖注入,
- 同时确定@RequestMapping注解的请求映射
public class SpringTest {
public static void test(String packageUrl) throws Exception {
List<Class<?>> classes = FileUtil.scanPackage(packageUrl, true);
if (!CollectionUtils.isEmpty(classes)) {
for (Class<?> clazz : classes) {
if (clazz.isAnnotationPresent(MyController.class)) {
/**
* 如果是Controller注解,首先将Controller类托管Spring
* 然后获取该Controller的请求路径及方法,交给 请求映射处理器
*/
CommonField.myIocContainer.put(clazz.getName(), clazz.newInstance());
String requestBaseUrl = "";
if (clazz.isAnnotationPresent(MyRequestMapping.class)) {
MyRequestMapping requestMapping = clazz.getAnnotation(MyRequestMapping.class);
requestBaseUrl = requestMapping.value();
}
Method[] methods = clazz.getMethods();
for (Method method : methods) {
MyRequestMapping subRequestMapping = method.getAnnotation(MyRequestMapping.class);
if (subRequestMapping != null) {
String requestUrl = requestBaseUrl + subRequestMapping.value();
CommonField.myHandlerMapping.put(requestUrl, method);
}
}
} else if (clazz.isAnnotationPresent(MyService.class)) {
/**
* 如果是Service注解,获取Service别名,
* 若其没有别名,则以类名托管Spring容器
* 若存在别名,则以别名托管Spring容器
* 然后获取Service注解所在类的实现接口,并将接口托管Spring容器
*/
MyService service = clazz.getAnnotation(MyService.class);
String serviceName;
if ("".equals(service.value())) {
serviceName = clazz.getName();
} else {
serviceName = service.value();
}
Object newInstance = clazz.newInstance();
CommonField.myIocContainer.put(serviceName, newInstance);
Class<?>[] serviceInterfaceClasses = clazz.getInterfaces();
for (Class<?> serviceInterfaceClazz : serviceInterfaceClasses) {
CommonField.myIocContainer.put(serviceInterfaceClazz.getName(), newInstance);
}
}
}
/**
* 遍历容器管理的Bean,拿到每个Bean中的Field
* 筛选具有@MyAutowired的Field,并根据Field的ClassName从容器中获取Bean实例赋值给该Field
*/
for (Map.Entry<String, Object> _entry : CommonField.myIocContainer.entrySet()) {
Object _obj = _entry.getValue();
if (_obj != null) {
Class<?> _clazz = _obj.getClass();
Field[] fields = _clazz.getDeclaredFields();
for (Field _field : fields) {
if (_field.isAnnotationPresent(MyAutowired.class)) {
MyAutowired myAutowired = _field.getAnnotation(MyAutowired.class);
String beanName = myAutowired.value();
if ("".equals(beanName)) {
beanName = _field.getType().getName();
}
_field.setAccessible(true);
_field.set(_obj, CommonField.myIocContainer.get(beanName));
}
}
}
}
}
}
}
六、自定义Servlet
- 分发请求,自动传递请求参数
- 具体自定义方式参考此文章,不再赘述:
@Configuration
public class WebConfig {
@Bean
public ServletRegistrationBean registerMyServlet(){
return new ServletRegistrationBean(new MyDispatcherServlet());
}
@Bean
public ServletListenerRegistrationBean<MyListener> registrationListener(){
ServletListenerRegistrationBean<MyListener> servletListenerRegistrationBean = new ServletListenerRegistrationBean<>(new MyListener());
return servletListenerRegistrationBean;
}
}
@WebServlet
public class MyDispatcherServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("POST方法进入MyServlet");
doDispatch(req, resp);
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
System.out.println("GET方法进入MyServlet");
doPost(req, resp);
}
/**
* 请求分发,获取myHandlerMapping中Url请求与Method的映射关系,根据请求锁定具体方法
* 获取Method中@MyRequestParam入参,若存在,获取具体类型后反射调用
*
* @param request
* @param response
*/
private void doDispatch(HttpServletRequest request, HttpServletResponse response) {
String url = request.getRequestURI();
if (CommonField.myHandlerMapping.containsKey(url)) {
Method method = CommonField.myHandlerMapping.get(url);
Map<String, String[]> params = request.getParameterMap();
Class<?>[] parameterTypes = method.getParameterTypes();
Object[] invokeParams = new Object[parameterTypes.length];
if (parameterTypes.length > 0) {
for (int i = 0; i < parameterTypes.length; i++) {
Class<?> _parameterType = parameterTypes[i];
if (HttpServletRequest.class.equals(_parameterType)) {
invokeParams[i] = request;
} else if (HttpServletResponse.class.equals(_parameterType)) {
invokeParams[i] = response;
} else if (String.class.equals(_parameterType)) {
Annotation[][] methodParam = method.getParameterAnnotations();
for (int j = 0; j < methodParam.length; j++) {
for (Annotation annotation : methodParam[j]) {
if (annotation instanceof MyRequestParam) {
String key = ((MyRequestParam) annotation).value();
invokeParams[i] = params.get(key)[0];
}
}
}
}
}
}
try {
method.invoke(CommonField.myIocContainer.get(method.getDeclaringClass().getName()), invokeParams);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
七、开始请求,测试结果
http://127.0.0.1:8080/test/query
http://127.0.0.1:8080/test/add?str=含参方式测试