`

spring架构中看设计模式和java web的框架原理

 
阅读更多

1. 一个类的私有构造函数表明这个类的实例只有本类方法中生成,外部类只有通过方法的调用得到该类的实例,可以在私有构造函数中完成对类成员的一些初始化的操作。

比如:request类:

	private void exit(HttpServletRequest request, HttpServletResponse response)
			throws IOException {
		// 清空授权
		CookieSessionService.create(request, response, "auth", null).remove();

		// 跳转
		HttpServletResponse httpResponse = (HttpServletResponse) response;
		httpResponse.sendRedirect("/out.html");
		return;
	}

 CookieSessionService类extends SessionServiceBase<User>:

	public static CookieSessionService create(HttpServletRequest request,
			HttpServletResponse response, String name, String domain) {
		CookieSessionService r = new CookieSessionService(request, response,
				name, domain);
		return r;
	}

 这个类有个私有构造函数和对私有成员的初始化操作:

	private static final char SPLIT_CHAR = ':';
	private static final String ENCODING = "UTF-8";
	private tools.web.CookieUtil cookie = null;
	private String name = null;
	private String domain = null;
	Map<String, Object> extra = null;

	private CookieSessionService(HttpServletRequest request,
			HttpServletResponse response, String name, String domain) {
		this.cookie = new CookieUtil(request, response);
		this.name = name;
		this.domain = domain;

		if (tools.StringUtil.isNullOrEmpty(domain)) // 2012-02-29
			this.domain = (String)Global

.getSettings().getParams().get("COOKIE_DOMAIN");

		// 2012-05-23
		extra = tools.MapUtil.create();
		extra.put("ip", tools.web.ServletUtil.getIpAddr(request));
	}

    这里可能对上面的Global.getSettings().getParams().get("COOKIE_DOMAIN")这里面的值是在什么时候放进去的,也许感到疑惑,下面就会慢慢解开其中的设计思想。

    这里Global的功用bean类的设计也是很不错的,设计到很多java常用的面相对象原理:它里面采用静态方法和静态成员,作为一个工程公用的参数存储配置类spring bean ,它通过init-method="init"的application.xml参数配置,使得在jboss或者comcat服务器启动的时候就会调用Global的init()方法,完成一些成员变量的初始化工作:

 

applicationContext.xml:

	<bean id="global" class="com.taobao.freeproj.common.Global" init-method="init">
		<property name="keyValueDao

">
			<ref bean="keyValueDao"/>
		</property>
	</bean>

        <bean id="keyValueDao"		class="com.taobao.freeproj.dao.ibatis.KeyValueDaoImplWrapper">
		<property name

="key">
			<value

>common_key_value</value>
		</property>
	</bean>

    这里有两个方面要说明: 1. 在bean  global中为什么要设置bean引用,是因为类含有KeyValueDao的成员变量,由于它是个bean,是在服务器启动的时候生成实例并放入到spring容器中的,属于单例模式的应用,别的地方如果要用到它的实例,不能通过new的方式生成它的实例,而只能在Global类中通过setxxx()的bean注入方法,这样在初始化的时候自动把这个单例实例注入到bean global中。如果它不是bean 就可以在需要用到的时候才实例化对象,然后拿来使用就行。如下中的类AppSettings和类JobScheduler就是这样可以在用到时再new一个具体的实例,然后把实例中需要的带入的初始化参数通过set方法或者带参数的构造函数来完成,这些将在下面再次谈到。

        2. 接口keyValueDao中的实现类KeyValueDaoImplWrapper类的继承父类KeyValueDaoImpl中含有私有变量: private String key;这里就是通过配置的方式给这个值赋值,因为他是一个bean,这种用在用的时候直接get方式获取就可以了,当然要写一个set方法,这样spring会进行变量注入。这中方式的好处灵活方便,改变它的值时只要改变一个地方就可以,比较容易集中式变更和管理。

Global类成员申明:

	private static final Log LOGGER = LogFactory.getLog("system");// 日志
	private static final String TYPE_CODE = "50";// KV系统类别
	public static final String LOCAL_IP = tools.StringUtil.getLocalIp();// 本机IP
	private static final String SETTINGS_KEY = "globalSettings";// KV配置名称

	/**
	 * 用静态属性,后续还会用到该dao
	 */
	private static KeyValueDao keyValueDao = null;// KV dao

	private static AppSettings settings;// 全局设置

	private static JobScheduler jobScheduler = null;// job调度器

	/**
	 * 哈勃日志,目前供pfp项目使用
	 */
	private static final Log MONITOR_LOGGER = LogFactory.getLog("monitor");

	/**
	 * 系统计数器
	 */
	private static MultiKeyedCounter counter = createCounters();

	private static Context<User> contextStore = new Context<User>();

    3.根据上面的xml中的bean的配置,Global类int()方法在服务器启动时就要调用,完成一些配置的初始化动作,已便于

其他地方获取到这些必要的配置信息:

 

Global类的Init()方法:

 

	public void init() {
		LOGGER.info("开始初始化global");

		// ===初始化工作
		// ===创建调度器工厂
		LOGGER.info("开始初始化jobScheduler");
		try {
			SchedulerFactory 

sf = new StdSchedulerFactory();
			Scheduler 

sched = sf.getScheduler();


			// 创建JobScheduler,设置调度器
			jobScheduler = new JobScheduler(sched);
			jobScheduler.start();// 一定要start 2012-02-14
		} catch (SchedulerException e) {
			LOGGER.error("创建JobScheduler失败", e);
			throw new RuntimeException(e);
		}

		// ===加载其他内容
		reload

();
	}

 

 

     这里要谈谈调度器的设计和使用方法,这里用到了一个抽象工厂类 public interface SchedulerFactory{。。。},这里面只声明了生成对应接口对象Scheduler(public interface Scheduler{...})的工厂方法,具体的方法实现和返回的具体的Scheduler子类在抽象工厂类SchedulerFactory的实现类中完成。具体的实现机制设计到工厂方法,同步方法,单例模式等,以后可以具体分析下调度器的实现机制。这里暂且不谈。

     生成了具体的schedule后,通过JobScheduler的构造函数,把他注入JobScheduler中的私有成员(private Scheduler scheduler = null;)进行具体值得初始化*(或者称为注入):

 
public class JobScheduler {
	private static final String DEFAULT_GROUP_NAME = "group1";
	private static final Log LOGGER = LogFactory.getLog(JobScheduler.class);

	private static final AtomicInteger COUNTER = new AtomicInteger(0);

	private Scheduler scheduler 

= null;
	/**
	 * 不在构造函数里体现group,因为这不是必需的 
	 */
	private String groupName = DEFAULT_GROUP_NAME;
	public JobScheduler(Scheduler scheduler) {
		this.setScheduler(scheduler);
	}

	/**
	 * 开始调度
	 */
	public void start

() {
		// Start up the scheduler (nothing can actually run until the
		// scheduler has been started)

		if (getScheduler() == null) {
			throw new IllegalArgumentException("请先设置正确的Scheduler");
		}

		try {
			if (!getScheduler().isStarted())
				getScheduler().start();
		} catch (SchedulerException e) {
			LOGGER.error(e);
		}
	}


	/**
	 * 增加一个单次执行的任务,要指定jobData和job名称,名称不能重复,参数里名称可以为空
	 */
	public boolean addJob

(Date startTime, Class<? extends Job> runJob,
			Map<String, Object> dataMap, String name) {
		// 处理job名称
		String innerJobName = getJobName(name);

		// 判断是否重复
		if (checkExists(innerJobName, getGroupName()))
			return false;

		JobDetail job = prepareJobDetail(innerJobName, getGroupName(), runJob,
				dataMap);

		// 创建trigger
		SimpleTriggerImpl trigger = new SimpleTriggerImpl();
		trigger.setKey(new TriggerKey(getTriggerName(innerJobName)));
		trigger.setStartTime(startTime);
		trigger.setGroup(getGroupName());

		try {
			getScheduler().scheduleJob(job, trigger);
			return true;
		} catch (SchedulerException e) {
			LOGGER.error(e);
		}// 如果job的名字重复,则引发Quartz.ObjectAlreadyExistsException异常

		return false;
	}
}

     这里有一种思想,可以看作为类的包装管理思想,把具体实现操作的类隐秘封装起来,比如这里虽然暴露给外面的是JobScheduler类,调用的貌似都是他提供的方法,完成start():启动调度器和addJob():添加调度任务的作用,但是实际上是他里面的成员对象scheduler完成这些工作,在外面主要实例化出具体的scheduler子类对象,然后以set方式或者JobScheduler构造函数方式实例它,然后接管完成包装它的JobScheduler类的方法任务。打个比方来说这种关系就是,

成员变量scheduler相当是程序员,是真正完成工程实现的劳动者,而外面的JobScheduler相当于产品经理(PD),它只是需求方的接口人,需求方只要告诉它完成那些功能(也就是调用JobScheduler中的方法),具体方法的实现PD会安排好程序员来完成。

    下面谈谈在初始化方法init()中的reload()方法:

	public static void reload() {
		// ===这里先不清理jobScheduler里的资源,因为下面addjob时会判重的

		// ===加载全局设置,从数据库里加载
		KeyValue kv = keyValueDao.getOne(TYPE_CODE, SETTINGS_KEY);
		if (kv == null) {
			LOGGER.error("globalSettings并不存在,请配置");
			return; // 直接退出
		}
		settings = tools.Json.toObject(kv.getValue(), AppSettings.class);

		LOGGER.info("globalSettings获取成功");

		// 2012-02-15
		if (settings.getParams() == null) {
			LOGGER.error("未配置params");
			settings.setParams(new HashMap<String, Object>());
		}

		// ===计数器持久化的job 2012-02-16
		jobScheduler.addJob

(new Date(), SaveCounterJob.class

, null,
				"COUNTER_JOB", tools.Convert.toInt(getSettings().getParams()
						.get("counter_saving_interval"), 60), -1);

		// 2012-06-21 当机器突然被提升为master,id生成器也要重新初始化;之前不是master时的生成器可能不能产生区间
		tools.generator.GeneratorFacade.removeAll();

		LOGGER.info("global初始化结束");
	}
 

     在reload方法中,通过KeyValue kv = keyValueDao.getOne(TYPE_CODE, SETTINGS_KEY);把存储在数据库中的参数值读出来,其中KeyValue 是一个数据库层的DO类,在数据库表中的存储结构是:

'5'#id#

, 'globalSettings'#key_#

, '{
    "params": {
       	"WHITE_URL_LIST":",/out.html,/403.html,/request.do,/id.do,",
	"ADVANCED_PERMISSION_URL_LIST":",/keyValue.html,/settings.html,",
	"COOKIE_DOMAIN":".riskm.admin.taobao.org",
	"TITLE":"风险信息管理平台riskm",
	"COPYRIGHT":"taobao riskm 2012"
    },
    "comment": ""
}'#value#

, '50'#typecode#

, NULL#sortnumber#

, '2012-05-08 18:43:53'#createtime#

, '2012-08-24 16:16:44'#lastupdatetime#

, '0'#status#

, NULL#comment#

 

然后通过: tools.Json.toObject(kv.getValue(), AppSettings.class);把json数据转化成结构化的数据:AppSettings,这个类的结构是:

public class AppSettings {
	

	/**
	 * 一些url,比如默认的算法的地址,不过可以在client设置里修改。【key=url名称,value=url格式】
	 * 只把不是特别固定的项目放到params里,其他的必须用到的,以属性方式提供
	 */
	private Map<String, Object> params;

	/**
	 * 备注信息
	 */
	private String comment;
。。。。
}

 在上面的jobScheduler.addJob(...)中,SaveCounterJob.class中定义了具体的任务或者操作,它必须implements Job接口:

 

public class SaveCounterJob implements Job {
	private static final Log LOGGER = LogFactory.getLog(SaveCounterJob.class);// 日志

	// private static final CounterDaoImpl DAO = new CounterDaoImpl();

	@Override
	public void execute(JobExecutionContext context)
			throws JobExecutionException {

		LOGGER.debug(this.getClass().getName() + " is running");

		// 获取当前的计数列表,并清零原来的计数器
		Map<String, Long> m = Global.getCounter().setAll(0);

		if (LOGGER.isDebugEnabled())
			LOGGER.debug("保存计数器列表,内容是:" + m);

		// 保存到mysql数据库
		Set<Entry<String, Long>> set = m.entrySet();

		List<Counter> list = new ArrayList<Counter>(set.size());
		int timestamp = getTimestamp();
		String tempKey = null;
		for (Entry<String, Long> item : set) {
			long i = item.getValue();
			if (i > 0) {
				tempKey = item.getKey();

				Counter entry = new Counter();
				entry.setKey(tempKey);
				entry.setValue(item.getValue());
				entry.setTimestamp(timestamp);

				// 2012-05-24 总和计数,重置timestamp
				if (tempKey.startsWith("sum."))
					entry.setTimestamp(1325347200);

				list.add(entry);
			}
		}

		try {
			long startMS = System.currentTimeMillis();
			int i = SpringBeanUtil.getBean(CounterDao.class, "counterDao")


					.save(list);
			LOGGER.warn("保存计数器成功,共保存:" + i + ",耗时:"
					+ (System.currentTimeMillis() - startMS));
		} catch (Exception e) {
			LOGGER.error(e);
		}
	}

	private int getTimestamp() {
		int i = (int) (System.currentTimeMillis() / 1000);
		int m = i % (60 * 2); // 对齐2分钟
		// System.out.println(m);
		// System.out.println(i - m);
		return i - m;
	}
}

   这里通过程序SpringBeanUtil工具类,动态从sprin容器中获取具体的bean,这种方式在以后可以学习利用:

  首先,是在application.xml中配置了counterDao的bean:

	<!--计数器dao-->
	<bean id="counterDao" class="com.taobao.freeproj.dao.ibatis.CounterDaoImpl">
	</bean>

    然后,SpringBeanUtil类必须实现ApplicationContextAware接口,这用在服务器启动时通过它的setApplicationContext(...)的实现ApplicationContext 的bean  的注入

这里有个疑问

  ApplicationContext是个接口,其声明是:public interface ApplicationContext extends EnvironmentCapable, ListableBeanFactory, HierarchicalBeanFactory,
        MessageSource, ApplicationEventPublisher, ResourcePatternResolver {。。。},那注入的时候它的具体实现类是在那配置的呢??通过类的层次结构可以发现,他的是实现类很多,有一个是AbstractApplicationContext,但也看不出具体的作用,但是在web.xml 中有一个上下文的配置,也许就是初始化ApplicationContext子类实例的:

 

   <!-- spring bean -->
   	<context-param>  
      <param-name>contextConfigLocation</param-name>  
      <param-value>  
        /WEB-INF/classes/bean/applicationContext.xml  


      </param-value>  
    </context-param>  
    
    <!--注册配置文件读取器-->
    <listener>
    	<listener-class>org.springframework.web.context.ContextLoaderListener

</listener-class>
   	</listener> 

   通过查看ContextLoaderListener类,发现他下面有个私有成员private ContextLoader contextLoader;,就是通过它的initWebApplicationContext(...)方法完成applicationContext.xml中的bean的实例化工作。具体代码如下:

 

public class ContextLoaderListener extends ContextLoader implements ServletContextListener {

	private ContextLoader contextLoader;
	public ContextLoaderListener() {
	}
	public ContextLoaderListener(WebApplicationContext context) {
		super(context);
	}
	/**
	 * Initialize the root web application context.
	 */
	public void contextInitialized(ServletContextEvent event) {
		this.contextLoader = createContextLoader();
		if (this.contextLoader == null) {
			this.contextLoader = this;


		}
		this.contextLoader.initWebApplicationContext

(event.getServletContext());
         }

 @Deprecated
	protected ContextLoader createContextLoader() {
		return null;
	}
。。。
	}
 
public class ContextLoader {
。。。
	public WebApplicationContext initWebApplicationContext

(ServletContext servletContext) {
		if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
			throw new IllegalStateException(
					"Cannot initialize context because there is already a root application context present - " +
					"check whether you have multiple ContextLoader* definitions in your web.xml!");
		}

		Log logger = LogFactory.getLog(ContextLoader.class);
		servletContext.log("Initializing Spring root WebApplicationContext");
		if (logger.isInfoEnabled()) {
			logger.info("Root WebApplicationContext: initialization started

");
		}
		long startTime = System.currentTimeMillis();

		try {
			// Store context in local instance variable, to guarantee that
			// it is available on ServletContext shutdown.
			if (this.context == null) {
				this.context = createWebApplicationContext(servletContext);
			}
			if (this.context instanceof ConfigurableWebApplicationContext) {
				configureAndRefreshWebApplicationContext((ConfigurableWebApplicationContext)this.context, servletContext);
			}
			servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);

			ClassLoader ccl = Thread.currentThread().getContextClassLoader();
			if (ccl == ContextLoader.class.getClassLoader()) {
				currentContext = this.context;
			}
			else if (ccl != null) {
				currentContextPerThread.put(ccl, this.context);
			}

			if (logger.isDebugEnabled()) {
				logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" +
						WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");
			}
			if (logger.isInfoEnabled()) {
				long elapsedTime = System.currentTimeMillis() - startTime;
				logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms");
			}

			return this.context;
		}
		catch (RuntimeException ex) {
			logger.error("Context initialization failed", ex);
			servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
			throw ex;
		}
		catch (Error err) {
			logger.error("Context initialization failed", err);
			servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err);
			throw err;
		}
	}
。。。
}

 而去看工程的main.log,贼日志的第二行就有:

2012-09-14 09:30:08,718 [] DEBUG core.CollectionFactory - Creating [java.util.concurrent.ConcurrentHashMap]
2012-09-14 09:30:08,750 [] INFO  [localhost].[/] - Initializing Spring root WebApplicationContext


2012-09-14 09:30:08,750 [] INFO  context.ContextLoader - Root WebApplicationContext: initialization started
2012-09-14 09:30:08,812 [] INFO  support.XmlWebApplicationContext - Refreshing org.springframework.web.context.support.XmlWebApplicationContext@39e02d: display name [Root WebApplicationContext]; startup date [Fri Sep 14 09:30:08 CST 2012]; root of context hierarchy
2012-09-14 09:30:08,859 [] DEBUG core.CollectionFactory - Creating [java.util.concurrent.ConcurrentHashMap]
 

 

  通过实现它继承的BeanFactory接口的getBean(...)方法完成从spring中获取具体的bean,具体代码如下:

public class SpringBeanUtil implements ApplicationContextAware {
	private static final Log LOGGER = LogFactory.getLog(SpringBeanUtil.class);// 日志

	private static ApplicationContext ctx = null;

	public static Object getBean(String name) {
		return ctx.getBean(name);
	}

	/**
	 * 2012-03-05 byxxx
	 */
	@SuppressWarnings("unchecked")
	public static <T> 

T getBean(Class<T>

 t, String name) {
		// return null;
		if (ctx == null) {
			LOGGER.error("ApplicationContext is null");
			return null;
		}
		return (T) (ctx.getBean(name));
	}

	@Override
	public void setApplicationContext(ApplicationContext applicationContext)
			throws BeansException {
		ctx = applicationContext;
	}
}

  通过以上,对spring中用到的一些好的设计思想有了更好的理解,同时对java web的spring的bean注入机制原理有了更深的认识,同时也简单的学习和分析了配置类中怎么应用任务触发器进行参数或者配置信息的初始化工作的原理,对bean的加载原理,一些数据的初始化原理有更好的认识  对以后开发项目时多考虑一些架构思想和提高编码的重用高效作用有个很好的总结学习作用。

 

 

 

 

 

 

 

 

 

 

 

 

分享到:
评论

相关推荐

    Spring技术内幕:深入解析Spring架构与设计原理[汇编].pdf

    Spring是Java企业应用开发的主要框架之一,其架构和设计原理对Java开发者具有重要影响。本文将深入解析Spring架构和设计原理,对Spring的核心概念、架构设计和关键技术点进行详细介绍。 1. Spring架构设计原理 ...

    Spring技术内幕:深入解析Spring架构与设计原理.pdf

    Spring技术内幕:深入解析Spring架构与设计原理 Spring技术内幕 Spring是一个基于Java的开源框架,旨在简化Java企业应用的开发。...其设计原理和架构使其能够提供高可靠、可扩展和可维护的应用平台。

    Spring技术内幕:深入解析Spring架构与设计原理

    《Spring技术内幕:深入解析Spring架构与设计原理(第2版)》从源代码的角度对Spring的内核和各个主要功能模块的架构、设计和实现原理进行了深入剖析。你不仅能从本书中参透Spring框架的出色架构和设计思想,还能从...

    Spring技术内幕:深入解析Spring架构与设计原理(第2版) .pdf

    《Spring技术内幕:深入解析Spring架构与设计原理(第2版)》这本书主要聚焦于Spring框架的核心架构和技术细节,帮助读者全面理解Spring的工作机制、设计理念以及实现方式。下面将根据书名及其描述来展开相关知识点。 ...

    spring技术内幕第2版深入解析spring架构与设计原理

    《Spring技术内幕第2版》这本书必定会围绕Spring框架的设计哲学、组件结构、架构模式以及最佳实践等方面展开深入讨论,旨在帮助开发者不仅了解如何使用Spring框架,还能够理解其背后的原理和设计决策。这对于想要...

    Spring技术内幕:深入解析Spring架构与设计原理(第1部分)

     Java开发者社区、专业Spring开发者社区和架构师社区一致鼎力推荐!  深入解析Spring架构原理与设计思想,探究Spring成功的奥秘。  揭开Spring源代码的神秘面纱,展示系统阅读开源软件源代码的方法和秘诀。  ...

    SPRING技术内幕:深入解析SPRING架构与设计原理.pdf 下载

    根据提供的标题和描述,我们可以深入探讨Spring框架的核心架构与设计原理。Spring框架是Java平台上的一个开源框架,它提供了一种全面的编程模型,旨在简化企业级应用的开发过程。接下来,我们将围绕Spring的核心概念...

    Spring技术内幕:深入解析 Spring架构与设计原理.pdf

    你不仅能从本书中参透Spring框架的优秀架构和设计思想,还能从Spring优雅的实现源码中一窥Java语言的精髓。本书在开篇之前对Spring的设计理念和整体架构进行了全面的介绍,能让读者从宏观上厘清Spring各个功能模块...

    架构探险 从零开始写javaweb框架书上源码

    《架构探险:从零开始写JavaWeb框架》是一本深入探讨JavaWeb开发技术的书籍,其核心内容是通过源码分析来帮助读者理解并构建自己的Web框架。书中的源码提供了实际的编程实践,使读者能够亲身体验到JavaWeb框架的实现...

    Spring技术内幕:深入解析Spring架构与设计原理(第2版)+.pdf

    根据提供的文件标题“Spring技术内幕:深入解析Spring架构与设计原理(第2版)”和描述,我们可以了解到这是一本深入探讨Spring框架内部架构和技术细节的专业书籍。虽然标签中出现了“oracle”,但从标题和描述来看,...

    SPRING技术内幕:深入解析SPRING架构与设计原理

    《SPRING技术内幕:深入解析SPRING架构与设计原理》这本书是Java开发领域的一部重量级作品,由具有丰富开发经验的专家撰写,受到了Java开发者社区和Spring开发者社区的高度推崇。书中深入剖析了Spring框架的核心架构...

    spring技术内幕-深入解析spring架构与设计原理

    《Spring技术内幕——深入解析Spring架构与设计原理》是一本深度剖析Spring框架核心机制与设计理念的专业书籍。本书旨在帮助读者全面理解Spring的内部工作原理,从而更好地应用和优化Spring框架在实际开发中的使用。...

    SPRING技术内幕:深入解析SPRING架构与设计原理.zip

    《SPRING技术内幕:深入解析SPRING架构与设计原理》这本书深入探讨了Spring框架的核心机制和设计理念,旨在帮助读者理解并掌握Spring的精髓。Spring作为Java企业级应用开发的重要工具,其灵活性、可扩展性和模块化的...

    spring架构详解 spring架构详解

    Spring 框架是Java开发领域中广泛应用的开源框架,其设计理念和设计模式对于理解和创建高效、可维护的软件系统至关重要。Spring的核心架构由Core、Context和Beans三个主要组件构成,它们共同构建了Spring的基石,...

    Spring技术内幕:深入解析Spring架构与设计原理(第2部分)

     Java开发者社区、专业Spring开发者社区和架构师社区一致鼎力推荐!  深入解析Spring架构原理与设计思想,探究Spring成功的奥秘。  揭开Spring源代码的神秘面纱,展示系统阅读开源软件源代码的方法和秘诀。  ...

    深入解析Spring架构与设计原理

    你不仅能从本书中参透Spring框架的优秀架构和设计思想,还能从Spring优雅的实现源码中一窥Java语言的精髓。本书在开篇之前对Spring的设计理念和整体架构进行了全面的介绍,能让读者从宏观上厘清Spring各个功能模块...

Global site tag (gtag.js) - Google Analytics