appfuse源码分析二(业务层)
 

2008-12-23 作者:刘文涛 来源:blogjava.net

 
1 Manager
2 LookupManager
3 RoleManager
4 UserManager

和包org.appfuse.dao下的

1Dao
2LookupDao
3RoleDao
4UserDao

相关方法是以工厂方法的设计模式组合的.但是他们是通过sping的IoC来实现的。

例如 :在applicationContext-hibernate.xml中定义的lookupDAO:

1<bean id="lookupDAO" class="org.appfuse.dao.hibernate.LookupDAOHibernate">
2       <property name="sessionFactory" ref="sessionFactory"/>
3</bean>

在applicationContext-service.xml中定义的lookupManager:

1<bean id="lookupManager" class="org.appfuse.service.impl.LookupManagerImpl">
2       <property name="lookupDAO" ref="lookupDAO"/>
3</bean>

在LookupManagerImpl中:

1private LookupDAO dao;
2
3public void setLookupDAO(LookupDAO dao) {
4    super.dao = dao;
5    this.dao = dao;
6}

解释一下:

一 :

在bean lookupManager的定义中,它有一个属性为lookupDAO,依赖于bean lookupDAO,在类LookupManagerImpl中,它继承实现了LookupManager接口的方法:

setLookupDAO(LookupDAO dao);

此方法去掉set剩下的部分第一个字母小写后的名字就是lookupDAO.也就是<property name="lookupDAO" ref="lookupDAO"/>中的name.巧合?当然不是,spring正是通过它的IoC,把lookupDAO通过配置文件传给LookupManager.从这个例子可以看到spring他通过xml格式的配置文件处理类之间的调用关系.而且可以看到在lookupManager 中 setLookupDAO(LookupDAO dao)中的参数是个接口,配置文件中

1<property name="lookupDAO" ref="lookupDAO"/>
2<bean id="lookupDAO" class="org.appfuse.dao.hibernate.LookupDAOHibernate">

 指向的是个类。可以看出,通过spring 配置的各个bean的在调用类中是个接口,spring 是面向接口编程的。Manager,RoleManager,UserManager.与LookupManager类似。

二 :

MailEngine是与邮件管理器相关的类,spring框架对邮件服务JavaMail提供了支持。它发送邮件主要是通过

org.springframework.mail.cos.CosMailSenderImpl

org.springframework.mail.javamail.JavaMailSenderImpl

实现。appfuse使用的是后者。

MainEngine其中的两个方法:

1setMailSender(MailSender mailSender)
2setVelocityEngine(VelocityEngine velocityEngine)

在配置文件applicationContext-Server.xml中

1<bean id="mailEngine" class="org.appfuse.service.MailEngine">
2        <property name="mailSender" ref="mailSender"/>
3        <property name="velocityEngine" ref="velocityEngine"/>
4</bean>

通过这个把mailSender和velocityEngine赋予MainEngine。

1 :Bean mainSender的定义

<bean id="mailSender" class="org.springframework.mail.javamail.JavaMailSenderImpl">
        <property name="host" value="${mail.host}"/>
        <property name="username" value="${mail.username}"/>
        <property name="password" value="${mail.password}"/>
</bean>

可以看到mailSender的实现为org.springframework.mail.javamail.JavaMailSenderImpl,它有三个属性 :

1host
2username
3password

它们的值是读配置文件mail.properties读到的。具体看web/WEB-INF/applicationContext-resources.xml中的Bean propertyConfigurer,如下:

1<!-- For mail settings and future properties files -->
2<bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
3    <property name="locations">
4        <list>
5            <value>classpath:mail.properties</value>
6        </list>
7    </property>
8</bean>

2 :Bean velocityEngine的定义

 1<!-- Configure Velocity for sending e-mail -->
 2<bean id="velocityEngine" class="org.springframework.ui.velocity.VelocityEngineFactoryBean">
 3    <property name="velocityProperties">
 4        <props>
 5            <prop key="resource.loader">class</prop>
 6            <prop key="class.resource.loader.class">
 7                    org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader
 8            </prop>
 9            <prop key="velocimacro.library"></prop>
10        </props>
11    </property>
12</bean>

解释一下:Velocity是一种java模版引擎技术,允许任何人使用简单而强大的模版语言来引用定义在java代码中的对象。在spring中对它的支持是VelocityEngineFactoryBean。

3 :MailEngine中的其他方法: 

1send(SimpleMailMessage msg)
2sendMessage(SimpleMailMessage msg, String templateName,Map model)
3sendMessage(String[] emailAddresses,ClassPathResource resource, String bodyText,String subject, String attachmentName)

3.1 :send(SimpleMailMessage msg)调用mailSender把msg发出去。

3.2 :sendMessage(SimpleMailMessage msg, String templateName,Map model)把model中的值通过velocityEngine写到 templateName里的相应位置。把结果放到msg的文本区。然后调用send方法把msg发出去。

3.3 :sendMessage(String[] emailAddresses,
                ClassPathResource resource, String bodyText,
               String subject, String attachmentName) 此方法的作用为;

把mailSender强制转换成JavaMailSenderImpl,然后调用它的createMimeMessage()方法。创建一个类型为MimeMessage的message,根据他创建一个类型为MimeMessageHelper的类helper.然后把message的主题设为subject,text设为bodyText,attachment(附件)的文件为resource,名为attachmentName。
然后用mailSender把它发出去。

三 : UserExistsException  :

继承于 Exception的异常类,当数据库中此用户存在抛出此异常。参考UserManager 中的方法:

saveUser(User user) throws UserExistsException;

四 : UserSecurityAdvice  :

public class UserSecurityAdvice implements MethodBeforeAdvice, AfterReturningAdvice 

这里使用了spring的aop工具,spring提供的aop装备有以下几种 :

1MethodBeforeAdvice(实现before装备)
2AfterReturningAdvice(实现After装备)
3ThrowsAdvice(Throw装备) 
4Methodinterceptor(Around装备).

appfuse在这里使用的是MethodBeforeAdvice ,AfterReturningAdvice

五 :在applicationContext-server.xml中定义了一个名为txProxyTemplate的bean,它是个抽象bean

 1<bean id="txProxyTemplate" abstract="true"
 2        class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
 3    <property name="transactionManager" ref="transactionManager"/>
 4    <property name="transactionAttributes">
 5        <props>
 6            <prop key="save*">PROPAGATION_REQUIRED</prop>
 7            <prop key="remove*">PROPAGATION_REQUIRED</prop>
 8            <prop key="*">PROPAGATION_REQUIRED,readOnly</prop>
 9        </props>
10    </property>
11</bean>

他的类型是个事务代理工厂,

org.springframework.transaction.interceptor.TransactionProxyFactoryBean

它有两个属性 :

1 :transactionManager,依赖与bean transactionManager.

2 :transactionAttributes,它定义了在处理事务时的规则.

1如果代理对象的方法以save开头,则ROPAGATION_REQUIRED 既事务类型为required,
2以remove开头的方法的事务类型为则PROPAGATION_REQUIRED,事务类型为required.
3其他为PROPAGATION_REQUIRED,readOnly.

解释一下事务的概念:

在EJB中,容器提供了声明性事务的支持.EJB 支持7中不同的声明性事务.

1Required(如果存在事务就在事务中运行,如果没有事务生成一个新事务),
2RequiredNew(无论有没有事务都要生成新事务,在自己的事务中运行),
3Mandatory(事务是必须的,如果没有事务报异常),
4Supports(有事务使用事物,没有事务不使用事务),
5NotSupports(不能在事务中运行),
6Never(不能在另一个事务意境中调用此方法)
7Bean-Managed(由Bean类自己管理事务)

在spring中与它们对应的是在接口org.springframework.transaction.TransactionDefinition中定义的:

1int PROPAGATION_REQUIRED = 0;
2int PROPAGATION_SUPPORTS = 1;
3int PROPAGATION_MANDATORY = 2;
4int PROPAGATION_REQUIRES_NEW = 3;
5int PROPAGATION_NOT_SUPPORTED = 4;
6int PROPAGATION_NEVER = 5;
7int PROPAGATION_NESTED = 6;

可以看到spring没有ejb的 Bean-Magaged事务,但是它新增一个PROPAGATION_NESTED事务.

如果现在存在一个事务,就在一个嵌套的事务里运行.这个只有在spring使用基于jdbc3.0的DataSourceTransactionManager时才使用.

六 :在applicationContext-service.xml中定义的三个bean:

1manager
2roleManager
3userManager

都是从txProxyTemplate继承下来的.

1 :manager 的定义为:

1<bean id="manager" parent="txProxyTemplate">
2    <property name="target">
3        <bean class="org.appfuse.service.impl.BaseManager">
4            <property name="DAO" ref="dao"/>
5        </bean>
6    </property>
7</bean>

可以看到它与lookupManager

1<bean id="lookupManager" class="org.appfuse.service.impl.LookupManagerImpl">
2    <property name="lookupDao" ref="lookupDao"/>
3</bean>

的区别:前者继承txProxyTemplate,

 1<bean id="txProxyTemplate" abstract="true"
 2        class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
 3    <property name="transactionManager" ref="transactionManager"/>
 4    <property name="transactionAttributes">
 5        <props>
 6            <prop key="save*">PROPAGATION_REQUIRED</prop>
 7            <prop key="remove*">PROPAGATION_REQUIRED</prop>
 8            <prop key="*">PROPAGATION_REQUIRED,readOnly</prop>
 9        </props>
10    </property>
11</bean>

txProxyTemplate的class是

org.springframework.transaction.interceptor.TransactionProxyFactoryBean

manager  的 target为

org.appfuse.service.impl.BaseManager

所以相对于loopupManager来说通过TransactionProxyFactoryBean实现了事务管理.roleManager 与Manager类似.

2 :userManager的定义如下:

 1<bean id="userManager" parent="txProxyTemplate">
 2        <property name="target">
 3            <bean class="org.appfuse.service.impl.UserManagerImpl">
 4                <property name="userDAO" ref="userDAO"/>
 5            </bean>
 6        </property>
 7        <!-- Override default transaction attributes b/c of UserExistsException -->
 8        <property name="transactionAttributes">
 9            <props>
10                <prop key="save*">PROPAGATION_REQUIRED,-UserExistsException</prop>
11                <prop key="remove*">PROPAGATION_REQUIRED</prop>
12                <prop key="*">PROPAGATION_REQUIRED,readOnly</prop>
13            </props>
14        </property>
15        <!-- This property is overriden in applicationContext-security.xml to add
16             method-level role security -->
17        <property name="preInterceptors">
18            <list>
19                <ref bean="userSecurityInterceptor"/>
20            </list>
21        </property>
22</bean>

可以看到它继承了txProxyTemplate,但是与前面两个不同的是 :

2.1 :它覆盖了txProxyTemplate的属性transactionAttributes.对于target :UserManagerImpl中以save开头的方法事务的类型为required.如果此方法抛出UserExistsException异常,则事务回滚(UserExistsException前面的-表示回滚,如果是+表示提交).

2.2 :还有一个叫preInterceptors的属性,它引用beanuserSecurityInterceptor

1<bean id="userSecurityInterceptor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
2    <property name="advice" ref="userSecurityAdvice"/>
3    <property name="patterns" value=".*saveUser"/>
4</bean>

它有两个属性 :

1 :  advice指向Bean userSecurityAdvice,

2 : patterns为".*saveUser",

userSecurityAdvice的类为就是此包下的UserSecurityAdvice.

总之调用spring的userManager时,spring 会在它前面调用userSecurityAdvice.

七 : userSecurityAdvice的before方法:

通过SecurityContextHolder取得类型为SecurityContext的上下文ctx

SecurityContext ctx = SecurityContextHolder.getContext();

通过ctx取得类型为Authentication的对象auth.

Authentication auth = ctx.getAuthentication();

Acegi为j2ee提供认证服务是通过两个接口Authentication和AuthenticationManager实现的.我们这里用的是Authentication.它有下列方法:

1getPrincipal(),获取用户标识(一般是用户名),
2getCredentials(),证明调用者身份的凭证.(一般是密码)
3getAuthorities().获取用户的角色信息.(Role集合)

在前边org.appfuse.model.User实现了接口UserDetails的一些方法.例如

1getUsername()-------此方法返回的就是用户名
2getPassword()-------此方法返回的就是用户密码
3getAuthorities()----此方法返回用户的角色集.

org.appfuse.model.Role实现了接口GrantedAuthority的方法

getAuthority()------此方法返回角色名.

继续看before方法,

1 :它通过Authentication 实例取的用户授权角色集roles,然后遍历授权角色集
如果授权角色集里有名为Constants.ADMIN_ROLE的角色,令boolean administrator = true.

2 :接下来是:User user = (User) args[0];然后把user的的名字放在String username里。

3 :如果auth.getPrincipal()类型为UserDetails,则currentUser的值为
auth.getPrincipal()).getUsername(),否则为String.valueOf(auth.getPrincipal())。

此时分情况判断:
1。如果username和currentUser不等
 新建一个resolver对象:
 AuthenticationTrustResolver resolver = new AuthenticationTrustResolverImpl();
 通过它判断此用户类型:
 如果是非匿名用户:
  如果不是administrator 抛出AccessDeniedException 异常。
2。如果username和currentUser相等,此用户不是administrator.
 把user的角色集放到一个Set userRoles里。把授权角色放到一个Set authorizedRoles里。
 `如果授权角色集和角色集不等的话,抛出异常 AccessDeniedException.
总之这个类的作用是是保证用户只能更新自己,而不是其他人。
applicationContext-server.xml是上述类的spring文件的配置片段。主要与邮件服务,和与数据库操作相关的bean.
 

org.appfuse.service.impl:
此包下是org.appfuse.server包定义的接口的具体实现。
org.appfuse.service.util:
此包下的类是在具体操作过程中使用到一些对象转换类。
ConvertUtil:
它有如下方法:
convertBundleToMap,
convertListToMap,
convertBundleToProperties,
populateObject(Object obj, ResourceBundle rb)--把rb转换成Map map,然后把map组装到obj里然后返回obj.
getOpposingObject(Object o) 此方法是处理hibernate的pojo对象与struts的form之间的转换。
convert(Object o) 调用getOpposingObject取到与o想对的对象target,然后把o的所有属性值赋给target.返回target.
convertLists(Object o) 此方法的作用是如果o的属性里有一个类型为list的属性,对此属性的每一个元素调用convert方法,把转换后的对象放到一个list里,然后用list重新设置此属性。返回
o.
CurrencyConverter是与钱相关的转换实用类。它实现Converter接口的convert方法。
定义了金钱的格式为“###,###.00”convert方法,可以把String,或Double类型的value转换成格式为“###,###.00”的double类型数返回。
DateUtil:它有以下属性:
String defaultDatePattern:
String timePattern = "HH:mm";
方法:
String getDatePattern()---从ApplicationContext...里根据locale取得“date.format”的值。做为日期格式,放到defaultDatePattern里,如果没有“date.format”,defaultDatePattern = "MM/dd/yyyy"。
getDate(Date aDate)---以defaultDatePattern的格式把aDate转换成字符串返回。
convertStringToDate(String aMask, String strDate)---以aMask的格式把SstrDate转换成日期返回。
getDateTime(String aMask, Date aDate)----以aMask的格式返回aDate的字符串表示形式。
getTimeNow(Date theTime)---以timePattern的形式返回时间。
Calendar getToday()---以Calendar的方式返回当时时间。
getDateTime(String aMask, Date aDate)---以aMask的格式返回aDate的字符串表达。
convertDateToString(Date aDate)---以“date.format”定义的格式返回aDate的字符串表达。
convertStringToDate(String strDate)---以“date.format”定义的格式把strDate转换成日期返回。
DateConverter继承实现了Converter的convert方法,此方法调用此类中定义的另外两个方法实现
字符串到Date/Timestamp,或则Date/Timpstamp到String的转换。其中timestamp的格式:
TS_FORMAT = DateUtil.getDatePattern() + " HH:mm:ss.S";
encodePassword(String password, String algorithm)
 此方法的流程如下:先得到password的字节码放到字节数组 unencodedPassword里。
 根据algorithm得到一个MessageDigest对象。
 md.reset();
        md.update(unencodedPassword);
 byte[] encodedPassword = md.digest();
 StringBuffer buf = new StringBuffer();
 对encodedPassword进行循环判断,如果某一位的值小于0x10,buf添加一个0.
 否则把encodedPassword这一位转换成16进制数,然后添加到buf上。
 返回buf.toString().
encodeString(String str)
 此方法通过sun.misc.BASE64Encoder 把str用base64编码。
decodeString(String str)
 此方法通过sun.misc.BASE64Encoder解码。
TimestampConverter继承了DateConverter.
重构了convertToString(Class type, Object value)---此方法相对父类只是少了一步判断。
新建了一个方法convertToDate(Class type, Object value)。
这个方法相对于父类少了一个参数(格式),此类的日期格式为TS_FORMAT = DateUtil.getDatePattern() + " HH:mm:ss.S"。它与同名父类方法的作用一样。

火龙果软件/UML软件工程组织致力于提高您的软件工程实践能力,我们不断地吸取业界的宝贵经验,向您提供经过数百家企业验证的有效的工程技术实践经验,同时关注最新的理论进展,帮助您“领跑您所在行业的软件世界”。
资源网站: UML软件工程组织