`

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的加载原理,一些数据的初始化原理有更好的认识  对以后开发项目时多考虑一些架构思想和提高编码的重用高效作用有个很好的总结学习作用。

 

 

 

 

 

 

 

 

 

 

 

 

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics