目录
3.5.1 BeanNameAutoProxyCreator 42
3.5.2 DefaultAdvisorAutoProxyCreator 45
5、Spring3.0 、Hibernate3.3与Struts2的整合 77
5.1.1使用MyEclipse加入Spring与Hibernate功能 77
5.1.4 HibernateDaoSupport类的使用 83
5.2.2把*DaoImpl、*ServiceImpl及*Action交由Spring管理 86
6.2.2编写处理请求的Controller(处理器) 96
6.2.4配置Spring MVC的配置文件,使控制器、视图解析器等生效 98
6.9.2配置MultipartResolver处理器(mvc-servlet.xml) 118
1、Spring介绍
1.1 Spring是什么
官网首页是这样解释的:Spring is the most popular application development framework for enterprise Java™. Millions of developers use Spring to create high performing, easily testable, reusable code without any lock-in.
Spring是一个开源框架,Spring 是于2003 年兴起的一个轻量级的Java 开发框架,它是为了解决企业应用开发的复杂性而创建的。Spring使用基本的JavaBean来完成以前只可能由EJB完成的事情。然而,Spring的用途不仅限于服务器端的开发。从简单性、可测试性和松耦合的角度而言,任何Java应用都可以从Spring中受益。
◆目的:解决企业应用开发的复杂性
◆功能:使用基本的JavaBean代替EJB,并提供了更多的企业应用功能
◆范围:任何Java应用
简单来说,Spring是一个轻量级的IoC(控制反转)和AOP(面向切面)的容器框架。
1.2 Spring官方网站
API下载在文档页面可进去。
1.3 Spring能做什么
13.1通过IoC降低组件间的耦合度,实现软件各层的解藕。
13.2 Spring提供了单例模式、众人多辅助工具类等,使程序员更专注于上层的应用
13.3 AOP编程的支持,开发人员通过Spring可更方便进行面向切面的编程。
13.4声明式事务的支持,程序员不需要再手动编码去维护事务。
13.5可以用非容器依赖的编程方式进行几乎所有的测试工作。
13.6与其它框架的无逢结合,并且可以降低其它框架的使用难度
13.6降低Java EE API的使用难度
1.4 Spring的体系结构
Spring core:最基础,提供IOC和依赖注入。管理bean与bean之间的依赖
Spring Context:上下文容器,beanFactory功能加强的一个自接口
Spring WEB:WEB应用开发的支持
Spring MVC:针对web应用MVC思想实现
Spring DAO:提供了JDBC的抽象层,简化了编码,同时使之更健壮
Spring ORM:与流行的ORM框架的整合
Spring AOP:面向切面,提供与AOP联盟兼容的编成实现。
2、IoC
2.1 IoC的概念
Inverse of Control,控制反转是Spring容器的内核,AOP、声明式事务等功能都是在此基础上进行的。
IoC主要功能是依赖关系的转移。应用的本身不负责依赖对象的创建和维护,而是由Spring容器负责。控制权就由应用转移到了外部容器。
IoC的主要功能由控制反转来解释并不是很好理解。所以提出了新的概念Dependency Injection.
DI依赖注入,调用类对某一接口实现类的依赖关系由第三方(容器或协作类)注入,以转移对某一接口实现类的依赖。也就是在运行期,由外部容器(Spring)动态地将所依赖的对象注入到组件中去。
2.2简单的使用
1、创建工程,导入spring的核心jar包
使用MyEclipse导入Jar包
2、spring配置文件(如:applicationContext.xml)
<?xml version="1.0" encoding="UTF-8"?>
<beans
xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
<!-- 把一个类交由spring管理 -->
<bean id="userDao" class="cn.framelife.spring.dao.impl.UserDaoImpl"></bean>
</beans>
3、写一个接口及一个接口实现类,如UserDao及UserDaoImpl
public interface UserDao {
public void save();
}
public class UserDaoImpl implements UserDao {
public void save() {
System.out.println("保存用户...");
}
}
4、通过main方法测试
//启动spring容器
ApplicationContext context = new ClassPathXmlApplicationContext(new String[]{"applicationContext.xml"});
//通过容器获取到对象,并注入到userDao中去
UserDao userDao = (UserDao) context.getBean("userDao");
2.3三种实例化Bean的方式
2.3.1 使用类构造器实例化(最经常使用)
类似2.2中的使用:
<bean id="userDao" class="cn.framelife.spring.dao.impl.UserDaoImpl"></bean>
注:这里的id也可以换成name
<bean name="userDao" class="cn.framelife.spring.dao.impl.UserDaoImpl"></bean>
如果使用的是id,那么定义的名字中不能包含特殊字符。而使用name,可以使用特殊字符。在结合struts框架的时候,Action一般使用name,因为它配置会有特殊字符存在。
建议一般情况下使用id,而不是name.
bean的命名第一个字母应该是小写。
2.3.2静态工厂的方式实例化
a、使用2.2的例子中的UserDao与UserDaoImpl
b、写一个UserDao的工厂类,通过一个静态方法生产UserDao的实现类对象
public class UserDaoFactory {
public static UserDao create(){
return new UserDaoImpl();
}
}
c、spring配置文件中配置
<bean id="userDaoFromFactory" class="cn.framelife.spring.factory.UserDaoFactory" factory-method="create"></bean>
d、测试
ApplicationContext context = new ClassPathXmlApplicationContext(new String[]{"applicationContext.xml"});
UserDao userDao = (UserDao) context.getBean("userDaoFromFactory");
userDao.save();
2.3.3使用实例工厂实例化
a、使用2.2的例子中的UserDao与UserDaoImpl
b、写一个UserDao的工厂类,通过一个非静态方法生产UserDao的实现类对象
public class UserDaoFactory {
public static UserDao create(){
return new UserDaoImpl();
}
}
c、spring配置文件中配置
<bean id="userDaoFactory" class="cn.framelife.spring.factory.UserDaoFactory" >
</bean>
<bean id="userDaoFromFactory"
factory-bean="userDaoFactory"
factory-method="create">
</bean>
d、测试
ApplicationContext context = new ClassPathXmlApplicationContext(new String[]{"applicationContext.xml"});
UserDao userDao = (UserDao) context.getBean("userDaoFromFactory");
userDao.save();
2.4 Bean的作用域
作用域的配置:(红色)
<bean id="userDao" class="cn.framelife.spring.dao.impl.UserDaoImpl" scope="singleton"></bean>
2.4.1 singleton单例(默认)
整个Spring容器服务中只有一个Bean对象。
2.4.2 prototype
每次从容器中获取到的都是一个新的实例。相当于每次都创建了一个新的对象。
2.4.3 request
每次HTTP请求都会创建一个新的对象。该作用域仅适用于WebApplicationContextg环境中。
2.4.4 session
同一个HTTP Session共享一个Bean,不同的HTTP Session使用不同的Bean。该作用域仅适用于WebApplicationContextg环境中。
2.4.5 globalSession(少用)
同一个全局的Session共享一个Bean,一般用于Portlet应用环境。该作用域仅适用于WebApplicationContextg环境中。
portal是一个基于web的应用,它能提供个性化,单点登陆,不同源的内容聚合,和信息系统的表示层集中。聚合是整合不同web页面源数据的过程。为了提供用户定制的内容,portal可能包含复杂的个性化特征。为不同用户创建内容的portal页,可能包含不同的portlet集。portal应用需要portal容器运行。
2.4.6 注意事项
在使用request、session、globalSession的时候,必需在Web容器中(web.xml)中进行一些额外的配置。
2.5 Bean的初始化与延迟加载
在默认情况下,Spring的ApplicationContext容器在启动的时候,会自动实例化所有singleton的Bean并缓存在容器中。虽然启动时会花费一些时间,但带来两个好处:
1、对Bean提前实例化操作会及早发现一些潜在的配置问题;
2、Bean以缓存的方式保存,当运行期使用到该Bean的时候无需再实例化,加快运行的效率。
如果用户不希望容器启动的时候提前实例化singleton的Bean,可以通过lazy-init属性进行控制:
<bean id="userDao" class="cn.framelife.spring.dao.impl.UserDaoImpl" scope="singleton" lazy-init="true"></bean>
也可以对所有的singleton的Bean进行延迟加载:
<beans
xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"
default-lazy-init="true">
被设置为延迟加载的Bean在某些情况下依旧会提前实例化:如果这个Bean被其它需要提前实例化的Bean引用到,那么Spring也将忽略延迟加载设置。
一般我们不做延迟加载。
2.6 Setter注入
Setter注入又称为属性注入。是通过属性的setXXX()方法来注入Bean的属性值或依赖对象。由于Setter注入具有可选择性和灵活性高的优点,因此Setter注入是实际应用中最常用的注入方式。
2.6.1 ref的方式
a、使用2.2的UserDao及UserDaoImpl
b、创建UserService接口及其实现类UserServiceImpl
public interface UserService {
public void addUser();
}
public class UserServiceImpl implements UserService {
/*
* 在service层使用dao层的接口对象
* 接口对象必须有其setter方法,以供运行期spring注入值
*/
private UserDao userDao;
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
public void addUser() {
userDao.save();
}
}
c、将UserDaoImpl与UserServiceImpl交由Spring管理
<bean id="userDao" class="cn.framelife.spring.dao.impl.UserDaoImpl"></bean>
<!-- property是UserServiceImpl中的属性设置,这些属性必须有setter方法。name是属性的名字,与类中的名字相同。ref是引用,引用的是上面配置好的UserDaoImpl的name -->
<bean id="userService" class="cn.framelife.spring.service.impl.UserServiceImpl">
<property name="userDao" ref="userDao"></property>
</bean>
d、测试
ApplicationContext context = new ClassPathXmlApplicationContext(new String[]{"applicationContext.xml"});
UserService userService = (UserService) context.getBean("userService");
userService.addUser();
2.6.2注入内部Bean
注入内部Bean其实也是ref的方式基本一样,只是在配置的时候不大相同而已。UserDaoImpl不再需要作为单独的Bean交由Spring管理,而是作为UserServiceImpl的一部分来进行配置。
配置:
<!-- property是UserServiceImpl中的属性设置,这些属性必须有setter方法。name是属性的名字,与类中的名字相同。Bean是属性对象所属的类的完整类名 -->
<bean id="userService" class="cn.framelife.spring.service.impl.UserServiceImpl">
<property name="userDao">
<bean class="cn.framelife.spring.dao.impl.UserDaoImpl"></bean>
</property>
</bean>
2.6.3注入基本类型
如:UserService中有一个String类型的字符串username:
public class UserServiceImpl implements UserService {
private UserDao userDao;
private String username;
public void setUsername(String username) {
this.username = username;
}
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
public void addUser() {
System.out.println(username);
userDao.save();
}
}
配置:
<bean id="userService" class="cn.framelife.spring.service.impl.UserServiceImpl">
<property name="userDao">
<bean class="cn.framelife.spring.dao.impl.UserDaoImpl"></bean>
</property>
<property name="username" value="zhangsan"></property>
</bean>
2.6.4注入集合
a、List和Set
类中有一个集合属性及其setter方法。
配置:
<property name="usernames">
<list>
<value>aaa</value>
<value>bbb</value>
<value>ccc</value>
</list>
</property>
或者:
<property name=" usernames">
<set>
<value>aaa</value>
<value>bbb</value>
<value>ccc</value>
</set>
</property>
b、Map
类中的属性:
private Map<Integer, String> map;
public void setMap(Map<Integer, String> map) {
this.map = map;
}
配置:
<property name="map">
<map>
<entry key="1">
<value>aaa</value>
</entry>
<entry key="2">
<value>bbb</value>
</entry>
</map>
</property>
c、Properties
Properties类型其实可以看成是Map类型的一种。Properties的键和值都只能是字符串。
类中的属性:
private Properties mails;
public void setMails(Properties mails) {
this.mails = mails;
}
配置:
<property name="mails">
<props>
<prop key="mailOfZhang">zhangsan_2013@163.com</prop>
<prop key="mailOfLi">Lixiaolong_abcxx@qq.com</prop>
</props>
</property>
2.7 构造器注入
还是使用2.6中的例子。只是UserServiceImpl中的属性不再需要其setter方法,而是需要一个带参的构造方法。
UserServiceImpl:
public class UserServiceImpl implements UserService {
private UserDao userDao;
private String username;
public UserServiceImpl(UserDao userDao, String username) {
this.userDao = userDao;
this.username = username;
}
public void addUser() {
System.out.println(username);
userDao.save();
}
}
配置:
<bean id="userDao" class="cn.framelife.spring.dao.impl.UserDaoImpl"></bean>
<!-- index表示的是构造方法中的第几个参数。从0开始。 -->
<bean id="userService" class="cn.framelife.spring.service.impl.UserServiceImpl">
<constructor-arg index="0" ref="userDao"></constructor-arg>
<constructor-arg index="1" value="zhangsan"></constructor-arg>
</bean>
2.8 使用Annotation注入
使用注解功能,必须是JDK5.0及以上版本。
2.8.1命名空间
使用Annotation的方式,需要在spring的配置文件中配置命名空间。命名空间中,隐式地注册了多个对注解进行解析处理的处理器。
a、引入context命名空间(红色部分)
<beans
xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd">
b、打开注解功能
<context:annotation-config />
2.8.2 @Autowired与@Resource
@Autowired与@Resource在使用上是没有任何区别的。即可以放于属性声明之前,也可以放于属性的setter方法头上。
只是@Autowired注解是由Spring提供的,它是按类型进行装配。而@Resource是由JavaEE提供的,它是按属性的名字进行装配,找不到名字后再找类型按类型装配。
@Autowired是推荐使用的。
使用:
配置文件中,UserDaoImpl与UserServiceImpl是相互对立,没有任何关系的。
<bean id="userDao" class="cn.framelife.spring.dao.impl.UserDaoImpl"></bean>
<bean id="userService" class="cn.framelife.spring.service.impl.UserServiceImpl"></bean>
类中通过注解进行装配注入:
@Autowired private UserDao userDao;
或
@Autowired
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
或
@Resource private UserDao userDao;
或
@Resource
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
2.8.3 @Autowired按名字装配
@Autowired@Qualifier("userDao") private UserDao dao;
2.8.4 注意
一般来说,基本数据类型的属性不使用Annotation的方式注入,Annotation只用于Bean的装配。而且,如果不是必须,基本数据类型的属性一般不使用注入的方式。
2.9 Spring自动扫描和管理Bean
通过Spring容器自动扫描和管理Bean,我们不再需要在配置文件中使用<bean>标签管理Bean。
2.9.1引入context 命名空间
2.9.2打开扫描功能
<context:component-scan base-package="cn.framelife.spring"/>
在打开扫描功能后,注解功能也打开了,所以不再需要再打开注解功能。
bean-package是要扫描的类的包路径。
2.9.3扫描标注了以下注解的类
@Service 业务层组件,如service
@Controller 控制层组件,如Struts中的Action
@Repository 数据访问层组件,如dao
@Component 当组件不好归类时使用,泛指
2.9.4获取Bean
在扫描的时候,Bean的id,如UserDaoImpl会默认为userDaoImpl。
但如果有需要,我们是可以修改这个默认名字的。
如:
@Service("userService")
public class UserServiceImpl implements UserService {
这个时候我们可以使用userService来获取对象。
2.9.5 注解作用域
@Service("userService")@Scope("prototype")
public class UserServiceImpl implements UserService {
2.10 多个spring配置文件
如有a.xml和b.xml两个配置文件。a.xml为主要使用配置文件。我们可以在a.xml中引入b.xml。
<import resource="b.xml"/>
3、AOP
3.1概念
Aspect Oriented Programing,面向切面编程。
利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
AOP主要用于日志记录,性能统计,安全控制(权限控制),事务处理,异常处理等。将日志记录,性能统计,安全控制,事务处理,异常处理等代码从业务逻辑代码中划分出来,通过对这些行为的分离,我们希望可以将它们独立到非指导业务逻辑的方法中,进而改变这些行为的时候不影响业务逻辑的代码。
如:AOP做权限控制的时候。首先拦截所有业务Bean时面的所有方法,判断用户是否有权限,有权限才能执行这些方法。而判断是否有权限这个功能就是一个切面。
3.2 AOP术语
3.2.1:连接点(Joinpoint)
程序执行的某个特定位置:如类开始初始化前,类初始化后,类某个方法调用前。一个类或一段代码拥有一些边界性质的特定点,这些代码中的特定点就被称为“连接点”。Spring仅支持方法的连接点,既仅能在方法调用前,方法调用后,方法抛出异常时等这些程序执行点进行织入增强。我们知道黑客攻击系统需要找到突破口,否则无法进行攻击,从某种程度上说AOP是一个黑客,连接点就是攻击的突破口。
连接点由两个信息确定:第一是用方法表示的程序执行点;第二是用相对点表示的方位。如在Test.foo()方法执行前的连接点,执行点为Test.foo(),方位为该方法执行前的位置。Spring使用切点对执行点进行定位,而方位则在增强类型中定义。
3.2.2:切点(Pointcut)
每个程序类都拥有多个连结点,如一个拥有两个方法的类,这两个方法都是连接点,既连接点是程序中客观存在的事务。但在这为数众多的连接点中,如何定位到某个感兴趣的连接点上呢?AOP通过“切点”定位特定的连接点。通过数据库查询的概念来理解切点和连接点的关系再适合不过了:连接点相当于数据库中的记录,而切点相当于查询条件。切点和连接点不是一对一的关系,一个切点可以匹配多个连接点。
在Spring中,切点通过org.springframework.aop.Pointcut接口进行描述,它使用类和方法作为连接点的查询条件,Spring AOP的规则解析引擎负责解析切点所设定的查询条件,找到对应的连接点。其实确切的说,应该是执行点而非连接点,因为连接点是方法执行前,执行后等包含方位信息的具体程序执行点,而切点只定位到某个方法上,所以说如果希望定位到某个连接点上,还需要提供方位信息。
3.2.3:增强(Advice)
增强是织入到目标类连接点上的一段程序代码。在Spring中,增强除了用于描述一段程序代码外,还拥有另一个和连接点相关的信息,这便是执行点的方位。结合执行点方位信息和切点信息,我们就可以找到特定的连接点了。正因为增强即包含了用于添加到目标链接点上的一段执行逻辑,又包含了用于定位连接点的方位信息,所以Spring所提供的增强接口都是带方位名的:BeforeAdvice等。所以只有结合切点和增强两者一齐上阵才能确定特定的连接点并实施增强逻辑。
3.2.4:目标对象(Target)
增强逻辑的织入目标类。如果没有AOP,目标业务类需要自己实现所有逻辑,如ForumService所示。在AOP的帮助下,ForumService只实现了那些非横切逻辑的程序逻辑,而性能监视和事务管理等这些横切逻辑则可以使用AOP动态织入到特定的连接点上。
3.2.5:引介(Introduction):
引介是一种特殊的增强,它为类添加一些属性和方法。这样,即使一个业务类原本没有实现某个接口,通过AOP的引介功能,我们可以动态的为该事务添加接口的实现逻辑,让业务类成为这个接口的实现类。
3.2.6:织入(Weaving):
织入是将增强添加对目标类具体连接点上的过程,AOP象一台织布机,将目标类增强或引介AOP这台织布机天衣无缝的编织在一起。
3.2.7:代理(Proxy)
一个类被AOP织入增强后,就产生了一个结果类,它是融合了原类和增强逻辑的代理类。根据不同的代理方式,代理类及可能是和原类具有相同的接口的类,也可能是原类的子类,所以我们可以采用调用原类得相同方式调用代理类。
3.2.8:切面(Aspect)
切面由切点和增强(引介)组成,它既包括了横切逻辑的定义,也包括了连接点的定义,Spring AOP就是负责实施切面的框架,它将切面所定义的横切逻辑织入到切面所指定的链接点中。
注意
AOP的工作重心在于如何将增强应用于目标对象的连接点上,这里首先包括两个工作:第一:如何通过切点和增强定位到连接点上;第二:如何在增强中编写切面的代码。
3.3创建增强
3.3.1增强类型
前置增强:org.springframework.aop.MethodBeforeAdvice
后置增强:org.springframework.aop.AfterReturningAdvice
环绕增强:org.aopalliance.intercept.MethodInterceptor
异常抛出增强:org.springframework.aop.ThrowsAdvice
引介增强:org.springframework.aop.support.DelegatingIntroductionInterceptor
3.3.2前置增强
如:在UserDao方法前加入前置增强
a、创建一个增强类实现MethodBeforeAdvice接口
public class UserDaoBeforeAdvice implements MethodBeforeAdvice {
@Override
public void before(Method method, Object[] args, Object object)
throws Throwable {
System.out.println("我是前置增强");
}
}
b、配置
<!-- 把增强类交由spring管理 -->
<bean id="userDaoBeforeAdvice" class="cn.framelife.spring.advice.UserDaoBeforeAdvice"></bean>
<!-- 把目标类交由spring管理 -->
<bean id="userDao" class="cn.framelife.spring.dao.impl.UserDaoImpl"></bean>
<!--
设置代理类
p:target-ref 目标对象
p:proxyInterfaces 代理所要实现的接口,也就是目标对象的接口
p:interceptorNames 织入的增强Bean,可以是多个,用","号分开
-->
<bean id="adviceUserDao"
class="org.springframework.aop.framework.ProxyFactoryBean"
p:proxyInterfaces="cn.framelife.spring.dao.UserDao"
p:interceptorNames="userDaoBeforeAdvice"
p:target-ref="userDao"
/>
c、测试
ApplicationContext context = new ClassPathXmlApplicationContext(new String[]{"applicationContext.xml"});
//通过代理来获取userDao对象
UserDao userDao = (UserDao) context.getBean("adviceUserDao");
userDao.save();
d、结果
我是前置增强
保存用户...
3.3.3后置增强
a、创建一个类实现接口AfterReturningAdvice
public class UserDaoAfterAdvice implements AfterReturningAdvice {
@Override
public void afterReturning(Object object, Method method, Object[] args,
Object arg3) throws Throwable {
System.out.println("我是后置增强");
}
}
b、配置
<bean id="userDaoBeforeAdvice" class="cn.framelife.spring.advice.UserDaoBeforeAdvice"></bean>
<bean id="userDaoAfterAdvice" class="cn.framelife.spring.advice.UserDaoAfterAdvice"></bean>
<bean id="userDao" class="cn.framelife.spring.dao.impl.UserDaoImpl"></bean>
<bean id="adviceUserDao"
class="org.springframework.aop.framework.ProxyFactoryBean"
p:proxyInterfaces="cn.framelife.spring.dao.UserDao"
p:interceptorNames="userDaoBeforeAdvice,userDaoAfterAdvice"
p:target-ref="userDao"
/>
c、测试与3.3.2中的测试一样
d、结果
我是前置增强
保存用户...
我是后置增强
3.3.4环绕增强
环绕增强与struts2的AOP类似。
a、创建一个类实现接口MethodInterceptor
public class UserDaoSurroundAdvice implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("环绕增强在方法前执行...");
Object object = invocation.proceed();
System.out.println("环绕增强在方法后执行...");
return object;
}
}
b、配置
<bean id="userDaoSurroundAdvice" class="cn.framelife.spring.advice.UserDaoSurroundAdvice"></bean>
<bean id="userDao" class="cn.framelife.spring.dao.impl.UserDaoImpl"></bean>
<bean id="adviceUserDao"
class="org.springframework.aop.framework.ProxyFactoryBean"
p:proxyInterfaces="cn.framelife.spring.dao.UserDao"
p:interceptorNames=" userDaoSurroundAdvice"
p:target-ref="userDao"
/>
c、测试与3.3.2中的测试一样
d、结果
环绕增强在方法前执行...
保存用户...
环绕增强在方法后执行...
3.3.5异常抛出增强
a、创建一个类实现接口ThrowsAdvice
public class UserDaoThrowsAdvice implements ThrowsAdvice {
public void afterThrowing(Method method,Object[] args,Object taglet,Exception ex)throws Throwable{
System.out.println("我是异常抛出接口");
System.out.println(method.getName());
System.out.println(ex.getMessage());
}
}
b、让UserDaoImpl中的save方法抛出异常
String str;
public void save() {
System.out.println("保存用户...");
//System.out.println(str.length());
throw new RuntimeException("运行时异常...");
}
c、配置
<bean id="userDaoThrowAdvice" class="cn.framelife.spring.advice.UserDaoThrowsAdvice"></bean>
<!--
p:proxyTargetClass="false"
如果目标对象是一个类,而不是一个接口,我们设置为true
-->
<bean id="userDao" class="cn.framelife.spring.dao.impl.UserDaoImpl"></bean>
<bean id="adviceUserDao"
class="org.springframework.aop.framework.ProxyFactoryBean"
p:proxyInterfaces="cn.framelife.spring.dao.UserDao"
p:interceptorNames="userDaoThrowAdvice"
p:target-ref="userDao"
p:proxyTargetClass="false"
/>
d、测试与3.3.2中的测试一样
e、结果
保存用户...
Exception in thread "main" java.lang.RuntimeException: 运行时异常...
at cn.framelife.spring.dao.impl.UserDaoImpl.save(UserDaoImpl.java:12)
at cn.framelife.spring.dao.impl.UserDaoImpl$$FastClassByCGLIB$$18bd6dee.invoke(<generated>)
at net.sf.cglib.proxy.MethodProxy.invoke(MethodProxy.java:191)
at org.springframework.aop.framework.Cglib2AopProxy$CglibMethodInvocation.invokeJoinpoint(Cglib2AopProxy.java:688)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:150)
at org.springframework.aop.framework.adapter.ThrowsAdviceInterceptor.invoke(ThrowsAdviceInterceptor.java:124)我是异常抛出接口
save
运行时异常...
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
at org.springframework.aop.framework.Cglib2AopProxy$DynamicAdvisedInterceptor.intercept(Cglib2AopProxy.java:621)
at cn.framelife.spring.dao.impl.UserDaoImpl$$EnhancerByCGLIB$$5bbe38b0.save(<generated>)
at cn.framelife.spring.test.Test.main(Test.java:17)
3.3.6引介增强
引介增强是为目标类创建新的方法和属性,引介增强的连接点是类级别的,不是方法级别的。通过引介增强,我们可以为目标类添加一个接口的实现,即原来目标类未实现某个接口,通过引介增强可以为目标类创建实现某个接口的代理。
a、新建一个接口AInterface
public interface AInterface {
public void say();
}
b、增强类继承DelegatingIntroductionInterceptor实现AInterface
public class IntroductionAdvice extends DelegatingIntroductionInterceptor implements AInterface {
/*
* 实现AInterface中的方法
*/
public void say() {
System.out.println("UserDao要说话");
}
/*
* 重写DelegatingIntroductionInterceptor的invoke方法
*/
public Object invoke(MethodInvocation mi) throws Throwable {
System.out.println("方法执行前执行");
System.out.println(mi.getClass().getName());
System.out.println(mi.getMethod().getName());
Object object = super.invoke(mi);
System.out.println("方法执行后执行");
return object;
}
}
c、配置
<bean id="userDao" class="cn.framelife.spring.dao.impl.UserDaoImpl"></bean>
<bean id="introductionAdvice" class="cn.framelife.spring.advice.IntroductionAdvice"></bean>
<!--
代理类设置
p:proxyTargetClass="true" 引介增强一定要通过创建子类来生成代理,所以要设置为true。
也不需要配置p:proxyInterfaces目标类的接口
p:interfaces 引介增强所实现的接口
-->
<bean id="aProxy"
class="org.springframework.aop.framework.ProxyFactoryBean"
p:interfaces="cn.framelife.spring.dao.AInterface"
p:interceptorNames="introductionAdvice"
p:target-ref="userDao"
p:proxyTargetClass="true" />
d、测试
ApplicationContext context = new ClassPathXmlApplicationContext(new String[]{"applicationContext.xml"});
UserDao userDao = (UserDao) context.getBean("aProxy");
userDao.save();
System.out.println("-------------");
AInterface a = (AInterface)userDao;
a.say();
System.out.println("-------------");
userDao.save();
e、结果
方法执行前执行
org.springframework.aop.framework.Cglib2AopProxy$CglibMethodInvocation
save
保存用户...
方法执行后执行
-------------
方法执行前执行
org.springframework.aop.framework.Cglib2AopProxy$CglibMethodInvocation
say
UserDao要说话
方法执行后执行
-------------
方法执行前执行
org.springframework.aop.framework.Cglib2AopProxy$CglibMethodInvocation
save
保存用户...
方法执行后执行
3.4创建切面
在前面使用增强的时候,我们发现增强会被织入到目标类的所有的方法中。我们如果把增强织入到目标类的特定的方法中,需要使用切点进行目标连接点的定位。然后我们可以通过切点及增强生成一个切面了。
3.4.1切点类型
静态方法切点:org.springframework.aop.support.StaticMethodMatcherPointcut
动态方法切点:org.springframework.aop.support.DynamicMethodMatcherPointcut
注解切点:org.springframework.aop.support.annotation.AnnotationMatchingPointcut
表达式切点:org.springframework.aop.support.ExpressionPointcut
流程切点:org.springframework.aop.support.ControlFlowPointcut
3.4.2静态方法来匹配切面
org.springframework.aop.support.StaticMethodMatcherPointcutAdvisor
a、增强类
在这里使用的是3.3.2的前置增强。
b、切面类继承StaticMethodMatcherPointcutAdvisor
public class StaticAdvisor extends StaticMethodMatcherPointcutAdvisor {
/*
* 切点方法匹配规则
*/
public boolean matches(Method method, Class<?> clazz) {
return method.getName().equals("save");
}
/*
* 重写StaticMethodMatcherPointcut的getClassFilter()
* 匹配哪个类下的方法
*/
public ClassFilter getClassFilter() {
ClassFilter classFilter = new ClassFilter() {
public boolean matches(Class<?> clazz) {
return UserDaoImpl.class.isAssignableFrom(clazz);
}
};
return classFilter;
}
}
c、配置
<!-- 增强Bean -->
<bean id="userDaoBeforeAdvice" class="cn.framelife.spring.advice.UserDaoBeforeAdvice"></bean>
<!-- 目标Bean -->
<bean id="userDao" class="cn.framelife.spring.dao.impl.UserDaoImpl"></bean>
<!-- 管理切面类。p:advice-ref把增强放入切面 -->
<bean id="staticAdvisor" class="cn.framelife.spring.advisor.StaticAdvisor" p:advice-ref="userDaoBeforeAdvice"></bean>
<!--
设置父代理类
p:interceptorNames 放切面,而不再是增强
-->
<bean id="paramProxy"
class="org.springframework.aop.framework.ProxyFactoryBean"
p:interceptorNames="staticAdvisor"
p:proxyTargetClass="true" />
<!-- 设置子代理类 -->
<bean id="userDaoProxy" parent="paramProxy" p:target-ref="userDao"></bean>
d、测试
ApplicationContext context = new ClassPathXmlApplicationContext(new String[]{"applicationContext.xml"});
UserDao userDao = (UserDao) context.getBean("userDaoProxy");
//UserDaoImpl中有两个方法,save方法中有切面,delete中没切面
userDao.save();
System.out.println("----------");
userDao.delete();
e、结果
我是前置增强:save
保存用户...
----------
删除用户...
3.4.3静态正则表达式方法配置切面
这种方法不需要自己写切面类。
org.springframework.aop.support.RegexpMethodPointcutAdvisor是正则表达式方法匹配的切面实现类。
a、增强类
在这里使用的是3.3.2的前置增强。
b、配置
<!-- 增强Bean -->
<bean id="userDaoBeforeAdvice" class="cn.framelife.spring.advice.UserDaoBeforeAdvice"></bean>
<!-- 目标Bean -->
<bean id="userDao" class="cn.framelife.spring.dao.impl.UserDaoImpl"></bean>
<!--
设置切面Bean
patterns 用正则表达式定义目标类全限定方法名的匹配模式串
目标类全限定方法名,指的是带类名的方法名。如: cn.framelife.spring.dao.impl.UserDaoImpl.delete()
<value>.*delete</value> 匹配模式串
-->
<bean id="regexpAdvisor"
class="org.springframework.aop.support.RegexpMethodPointcutAdvisor"
p:advice-ref="userDaoBeforeAdvice">
<property name="patterns">
<list>
<value>.*delete</value>
</list>
</property>
</bean>
<!--
设置代理类
-->
<bean id="regexpProxy"
class="org.springframework.aop.framework.ProxyFactoryBean"
p:interceptorNames="regexpAdvisor"
p:target-ref="userDao"
p:proxyTargetClass="true" />
c、测试
ApplicationContext context = new ClassPathXmlApplicationContext(new String[]{"applicationContext.xml"});
UserDao userDao = (UserDao) context.getBean("regexpProxy");
userDao.save();
System.out.println("----------");
userDao.delete();
d、结果
保存用户...
----------
我是前置增强:delete
删除用户...
e、常用的正则表达式规则
1、.*set.*表示所有类中的以set为前缀的方法。如:com.abc.UserDao.setName()。
2、com\.abc\.service\..* 表示com.abc.service.包下所有的类的所有的方法。
Com.abc.service.a.User.setName()
Com.abc.service.UserService.save()
3、com\.abc\.service\..*Service\..* 表示com.abc.service包下以Service结尾的类的所有的方法。如:com.abc.service.UserService.save()
4、com\.abc\.service\..*Service\.save.+匹配所有以save为前缀的方法,而且save后必须拥有一个或多个字符。如:com.abc.service.UserService.saveUser()
3.4.4动态切面
org.springframework.aop.support.DynamicMethodMatcherPointcut类即有静态切点检查的方法,也有动态切点检查的方法。由于动态切点检查会对性能造成很大的影响,我们应当避免在运行时每次都对目标类的各个方法进行检查。
Spring检查动态切点的机制:在创建代理时对目标类的每个连接点进行静态切点检查,如果仅静态切点检查就可以知道连接点是不匹配的,在运行期就不进行动态检查了;如果静态切点检查是匹配的,在运行期才进行动态方法检查。
动态切面通过DefaultPointcutAdvisor切面类与DynamicMethodMatcherPointcut切点结合起来生成。主要是针对连接点方法的参数。
a、增强类
在这里使用的是3.3.2的前置增强。
b、切点类继承DynamicMethodMatcherPointcut
public class DynamicPointcut extends DynamicMethodMatcherPointcut {
//用list集合保存参数名
private List<String> specialNames = new ArrayList<String>();
public DynamicPointcut(){
specialNames.add("aa");
specialNames.add("bb");
}
/*
* 对类进行静态切点检查
*/
public ClassFilter getClassFilter() {
return new ClassFilter() {
@Override
public boolean matches(Class<?> targetClass) {
System.out.println("使用getClassFilter静态检查:"+targetClass.getName());
return UserDaoImpl.class.isAssignableFrom(targetClass);
}
};
}
/*
*对方法进行静态切点检查
*/
public boolean matches(Method method, Class<?> targetClass) {
System.out.println("使用matches(method,targetClass)方法静态检查:"+targetClass.getName()+"--"+method.getName());
return method.getName().equals("out");
}
/*
*对方法进行动态切点检查
*/
public boolean matches(Method method, Class<?> targetClass, Object[] args) {
System.out.println("使用matches(method,targetClass)方法动态检查:"+targetClass.getName()+"--"+method.getName()+"的参数");
String name = (String)args[0];
return specialNames.contains(name);
}
}
c、配置
<!-- 增强Bean -->
<bean id="userDaoBeforeAdvice" class="cn.framelife.spring.advice.UserDaoBeforeAdvice"></bean>
<!-- 目标Bean -->
<bean id="userDao" class="cn.framelife.spring.dao.impl.UserDaoImpl"></bean>
<!-- 切面 -->
<bean id="dynamicAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
<!-- 设置切点 -->
<property name="pointcut">
<bean class="cn.framelife.spring.pointcut.DynamicPointcut"></bean>
</property>
<!-- 织入增强 -->
<property name="advice" ref="userDaoBeforeAdvice"></property>
</bean>
<!--
设置代理
-->
<bean id="dynamicProxy"
class="org.springframework.aop.framework.ProxyFactoryBean"
p:interceptorNames="dynamicAdvisor"
p:target-ref="userDao"
p:proxyTargetClass="true" />
d、测试
ApplicationContext context = new ClassPathXmlApplicationContext(new String[]{"applicationContext.xml"});
UserDao userDao = (UserDao) context.getBean("dynamicProxy");
System.out.println("----------");
userDao.save();
System.out.println("----------");
userDao.out("aa");
System.out.println("----------");
userDao.out("1111");
System.out.println("----------");
e、结果
使用getClassFilter静态检查:cn.framelife.spring.dao.impl.UserDaoImpl
使用matches(method,targetClass)方法静态检查:cn.framelife.spring.dao.impl.UserDaoImpl--out
使用getClassFilter静态检查:cn.framelife.spring.dao.impl.UserDaoImpl
使用matches(method,targetClass)方法静态检查:cn.framelife.spring.dao.impl.UserDaoImpl--save
使用getClassFilter静态检查:cn.framelife.spring.dao.impl.UserDaoImpl
使用matches(method,targetClass)方法静态检查:cn.framelife.spring.dao.impl.UserDaoImpl--delete
使用getClassFilter静态检查:cn.framelife.spring.dao.impl.UserDaoImpl
使用matches(method,targetClass)方法静态检查:cn.framelife.spring.dao.impl.UserDaoImpl--clone
使用getClassFilter静态检查:cn.framelife.spring.dao.impl.UserDaoImpl
使用matches(method,targetClass)方法静态检查:cn.framelife.spring.dao.impl.UserDaoImpl--toString
----------
使用getClassFilter静态检查:cn.framelife.spring.dao.impl.UserDaoImpl
使用matches(method,targetClass)方法静态检查:cn.framelife.spring.dao.impl.UserDaoImpl--save
保存用户...
----------
使用getClassFilter静态检查:cn.framelife.spring.dao.impl.UserDaoImpl
使用matches(method,targetClass)方法静态检查:cn.framelife.spring.dao.impl.UserDaoImpl--out
使用matches(method,targetClass)方法动态检查:cn.framelife.spring.dao.impl.UserDaoImpl--out的参数
我是前置增强:out
out输出名字为:aa
----------
使用matches(method,targetClass)方法动态检查:cn.framelife.spring.dao.impl.UserDaoImpl--out的参数
out输出名字为:1111
----------
3.4.5流程切面
一个类中的某一方法使用目标类的两个方法,那么我们可以使用流程切面给这两个方法都积入增强。
如:UserServiceImpl类(使用类)operate方法中使用UserDaoImpl(目标类)的两个方法。
流程切面使用ControlFlowPointcut与DefaultPointcutAdvisor结合形成。
a、增强类
在这里使用的是3.3.2的前置增强。
b、UserServiceImpl类
public class UserServiceImpl implements UserService {
private UserDao userDao;
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
public void addUser() {
userDao.save();
}
@Override
public void operate() {
userDao.delete();
userDao.save();
}
}
c、配置
<!-- 增强Bean -->
<bean id="userDaoBeforeAdvice" class="cn.framelife.spring.advice.UserDaoBeforeAdvice"></bean>
<!-- 目标Bean -->
<bean id="userDao" class="cn.framelife.spring.dao.impl.UserDaoImpl"></bean>
<!-- 切点 -->
<bean id="controlFlowPointcut" class="org.springframework.aop.support.ControlFlowPointcut">
<!-- 指定流程切点的类 -->
<constructor-arg type="java.lang.Class" value="cn.framelife.spring.service.impl.UserServiceImpl"></constructor-arg>
<!-- 指定流程切点的方法 -->
<constructor-arg type="java.lang.String" value="operate"></constructor-arg>
</bean>
<!-- 切面 -->
<bean id="controlFlowAdvisor"
class="org.springframework.aop.support.DefaultPointcutAdvisor"
p:pointcut-ref="controlFlowPointcut"
p:advice-ref="userDaoBeforeAdvice"/>
<!--
设置代理
-->
<bean id="controlFlowProxy"
class="org.springframework.aop.framework.ProxyFactoryBean"
p:interceptorNames="controlFlowAdvisor"
p:target-ref="userDao"
p:proxyTargetClass="true" />
<!-- 把使用目标类的Bean交由Spring管理 -->
<bean id="userService" class="cn.framelife.spring.service.impl.UserServiceImpl">
<property name="userDao" ref="controlFlowProxy"></property>
</bean>
d、测试
ApplicationContext context = new ClassPathXmlApplicationContext(new String[]{"applicationContext.xml"});
UserService userService = (UserService) context.getBean("userService");
userService.operate();
System.out.println("----------------");
userService.addUser();
e、结果
我是前置增强:delete
删除用户...
我是前置增强:save
保存用户...
----------------
保存用户...
3.4.6复合切面
在前面的例子中,我们所定义的切面都只有一个切点而已。有时候我们一个切面需要多个切点,也就是多个条件才能决定连接点。多个切点组成一个切点,这样的切点是复合切点。由复合切点加上增强形成的切面,称为复合切面。
a、增强类
在这里使用的是3.3.2的前置增强。
b、一个普通类中有一个获取Pointcut的方法
public class MyPointcut {
//方法名是get开头
public Pointcut getMyComposablePointcut(){
//创建一个复合切点
ComposablePointcut cp = new ComposablePointcut();
//创建一个流程切点(参数:使用类、类中的方法)
Pointcut pt1 = new ControlFlowPointcut(UserServiceImpl.class,"operate");
//创建一个静态方法切点
Pointcut pt2 = new StaticMethodMatcherPointcut() {
public boolean matches(Method method, Class<?> clazz) {
return UserDaoImpl.class.isAssignableFrom(clazz)&&method.getName().equals("delete");
}
};
//两个切点进行交集操作.
return cp.intersection(pt1).intersection(pt2);
}
}
c、配置
<!-- 增强Bean -->
<bean id="userDaoBeforeAdvice" class="cn.framelife.spring.advice.UserDaoBeforeAdvice"></bean>
<!-- 目标Bean -->
<bean id="userDao" class="cn.framelife.spring.dao.impl.UserDaoImpl"></bean>
<!-- 切点所在类 -->
<bean id="myPoincut" class="cn.framelife.spring.pointcut.MyPointcut"></bean>
<!--
切面
p:pointcut #{ myPoincut.myComposablePointcut} 是由MyPointcut的getMyComposablePointcut方法获取的
-->
<bean id="composableAdvisor"
class="org.springframework.aop.support.DefaultPointcutAdvisor"
p:pointcut="#{ myPoincut.myComposablePointcut}"
p:advice-ref="userDaoBeforeAdvice"/>
<!--
设置代理
-->
<bean id="composableProxy"
class="org.springframework.aop.framework.ProxyFactoryBean"
p:interceptorNames="composableAdvisor"
p:target-ref="userDao"
p:proxyTargetClass="true" />
<!-- 把使用目标类的Bean交由Spring管理 -->
<bean id="userService" class="cn.framelife.spring.service.impl.UserServiceImpl">
<property name="userDao" ref="composableProxy"></property>
</bean>
d、测试
ApplicationContext context = new ClassPathXmlApplicationContext(new String[]{"applicationContext.xml"});
UserService userService = (UserService) context.getBean("userService");
userService.operate();
e、结果
我是前置增强:delete
删除用户...
保存用户...
3.4.7引介切面
a、使用3.3.6引介增强里面的东西(接口与增强类)
c、配置
<!-- 目标Bean -->
<bean id="userDao" class="cn.framelife.spring.dao.impl.UserDaoImpl"></bean>
<!--
切面
constructor-arg 设置引介增强
-->
<bean id="introductionAdvisor"
class="org.springframework.aop.support.DefaultIntroductionAdvisor">
<constructor-arg>
<bean class="cn.framelife.spring.advice.IntroductionAdvice"></bean>
</constructor-arg>
</bean>
<!--
设置代理
-->
<bean id="incluctionProxy"
class="org.springframework.aop.framework.ProxyFactoryBean"
p:interceptorNames="introductionAdvisor"
p:target-ref="userDao"
p:proxyTargetClass="true" />
d、测试
ApplicationContext context = new ClassPathXmlApplicationContext(new String[]{"applicationContext.xml"});
UserDao userDao = (UserDao) context.getBean("aProxy");
userDao.save();
System.out.println("-------------");
AInterface a = (AInterface)userDao;
a.say();
System.out.println("-------------");
userDao.save();
e、结果
方法执行前执行
org.springframework.aop.framework.Cglib2AopProxy$CglibMethodInvocation
save
保存用户...
方法执行后执行
-------------
方法执行前执行
org.springframework.aop.framework.Cglib2AopProxy$CglibMethodInvocation
say
UserDao要说话
方法执行后执行
-------------
方法执行前执行
org.springframework.aop.framework.Cglib2AopProxy$CglibMethodInvocation
save
保存用户...
方法执行后执行
3.5自动创建代理
3.5.1 BeanNameAutoProxyCreator
在之前的切面使用中,最后得通过ProxyFactoryBean类生成一个代理类,然后通过代理类对象获取目标类与目标类织入的增强信息。一个代理类对应一个目标类。如果我们有多个目标类的切面信息(切点与增强)都一样的,而我们必须在spring的配置文件中配置多个代理类来为多个目标类设置代理。
BeanNameAutoProxyCreator可以为多个Bean名字类似的目标类进行代理设置,而且多个目标类只需要设置一次。
下面使用前置增强、静态正则表达式方法配置的切面加上BeanNameAutoProxyCreator来进行测试:
a、 增强类
在这里使用的是3.3.2的前置增强。
b、 两个目标类
public class ProductDaoImpl {
public void save(){
System.out.println("product被保存");
}
}
public class UserDaoImpl implements UserDao {
String str;
public void save() {
System.out.println("保存用户...");
}
public void delete() {
System.out.println("删除用户...");
}
}
c、 配置
<!-- 增强Bean -->
<bean id="userDaoBeforeAdvice" class="cn.framelife.spring.advice.UserDaoBeforeAdvice"></bean>
<!-- 目标Bean -->
<bean id="userDao" class="cn.framelife.spring.dao.impl.UserDaoImpl"></bean>
<bean id="productDao" class="cn.framelife.spring.dao.impl.ProductDaoImpl"></bean>
<!-- 切面 -->
<bean id="regexpAdvisor"
class="org.springframework.aop.support.RegexpMethodPointcutAdvisor"
p:advice-ref="userDaoBeforeAdvice">
<property name="patterns">
<list>
<value>.*save</value>
</list>
</property>
</bean>
<!--
设置代理
p:beanNames="*Dao" 表示以Dao结尾的Bean都交由这个代理设置管理
-->
<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator"
p:interceptorNames="regexpAdvisor"
p:beanNames="*Dao"
p:proxyTargetClass="true" />
d、 测试
UserDao userDao = (UserDao) context.getBean("userDao");
userDao.save();
userDao.delete();
ProductDaoImpl productDao = (ProductDaoImpl) context.getBean("productDao");
productDao.save();
e、 结果
我是前置增强:save
保存用户...
删除用户...
我是前置增强:save
product被保存
3.5.2 DefaultAdvisorAutoProxyCreator
org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator能够扫描容器中的Advisor,并将Advisor自动自动织入到匹配的Bean中,即为匹配的目标Bean自动创建代理。
把3.5.1中的配置修改如下:
<!-- 增强Bean -->
<bean id="userDaoBeforeAdvice" class="cn.framelife.spring.advice.UserDaoBeforeAdvice"></bean>
<!-- 目标Bean -->
<bean id="userDao" class="cn.framelife.spring.dao.impl.UserDaoImpl"></bean>
<bean id="productDao" class="cn.framelife.spring.dao.impl.ProductDaoImpl"></bean>
<!-- 切面 -->
<bean id="regexpAdvisor"
class="org.springframework.aop.support.RegexpMethodPointcutAdvisor"
p:advice-ref="userDaoBeforeAdvice">
<property name="patterns">
<list>
<value>.*save</value>
</list>
</property>
</bean>
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"></bean>
也就是去掉3.5.1中的代理类配置那一块。加上上面配置中红色部分。
测试与结果与3.5.1的一样。
3.6基于@AspectJ的AOP
在前面,我们分别使用Pointcut、Advice、Advisor接口来描述切点、增强、切面。而现在我们使用@AdpectJ注解来描述。
3.6.1一个简单的例子
A、目标类:
@Repository
public class UserDaoImpl implements UserDao {
public void save() {
System.out.println("保存用户...");
}
public void delete() {
System.out.println("删除用户...");
}
}
B、通过一个POJO使用@AspectJ管理切面
/*
* 通过@Aspect把这个类标识管理一些切面
*/
@Aspect
@Component
public class FirstAspect {
/*
* 定义切点及增强类型
*/
@Before("execution(* save(..))")
public void before(){
System.out.println("我是前置增强...");
}
}
C、配置
配置的时候要引入aop命名空间及打开@AspectJ切面驱动器
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
<context:component-scan base-package="cn.framelife.spring"></context:component-scan>
<!—
驱动器自动为Spring容器中那些匹配@AspectJ切面的Bean创建代理,完成切面积入
-->
<aop:aspectj-autoproxy/>
D、测试
ApplicationContext context = new ClassPathXmlApplicationContext(new String[]{"applicationContext.xml"});
UserDao userDao = (UserDao) context.getBean("userDaoImpl");
userDao.save();
userDao.delete();
E、结果
我是前置增强...
保存用户...
删除用户...
3.6.2增强类型及其使用
@Before 前置增强
@Before(value = "切入点表达式或命名切入点", argNames = "指定命名切入点方法参数列表参数名字,可以有多个用“,”分隔")
3.6.1中的增强使用:@Before(value="execution(* save(..))")是缺省了value,直接键入值。
A、 目标类:
@ServiceF
public class UserService {
public void say(String name){
System.out.println("service say:"+name);
}
public void run(String way){
System.out.println("service run:"+way);
}
}
B、一个POJO设置切面
@Aspect
@Component
public class BeforAspectj {
/*
*单独配置切点。给切点命名。
*/
@Pointcut(value="execution(* say(..)) && args(param)",argNames="param")
public void beforePointcut(String param){ }
@Before(value="beforePointcut(s)",argNames="s")
public void beforeAdvice(String s){
System.out.println("beforeAdvice:"+s);
}
}
或者把前面的切点定义那一块直接定义到@Before增强的value中:
@Aspect
@Component
public class BeforAspectj {
@Before(value="execution(* say(..)) && args(s)",argNames="s")
public void beforeAdvice(String s){
System.out.println("beforeAdvice:"+s);
}
}
C、配置与3.6.1的一样
D、测试
ApplicationContext context = new ClassPathXmlApplicationContext(new String[]{"applicationContext.xml"});
UserService service = (UserService) context.getBean("userService");
service.say("zhangsan");
service.run("street");
E、结果
beforeAdvice:zhangsan
service say:zhangsan
service run:street
@AfterReturning 后置增强
@AfterReturning(
value="切入点表达式或命名切入点",
pointcut="切入点表达式或命名切入点",
argNames="参数列表参数名",
returning="目标对象的返回值")
pointcut与value是一样的。如果使用pointcut来声明,那么前面声明的value就没用了。
A、目标类:
@Service
public class UserService {
public void say(String name){
System.out.println("service say:"+name);
}
public void run(String way){
System.out.println("service run:"+way);
}
public String getName(String name){
System.out.println("service getName:"+name);
return "MR"+name;
}
}
B、一个POJO设置切面
@Aspect
@Component
public class AfterReturningAspectj {
/*
* 即获取返回值,又获取传入的参数
*/
@AfterReturning(
value="execution(* cn.framelife.spring..*.getName(..)) && args(sname)",
returning="name",
argNames="name,sname")
public void afterGetNameAdvice(Object object,String sname){
System.out.println("afterGetNameAdvice:"+ (String)object+"--"+sname);
}
/*
* 只要增强,返回值和参数都不理会
*/
@AfterReturning(value="execution(* cn.framelife.spring..*.run(..))")
public void afterRunAdvice(){
System.out.println("afterRunAdvice");
}
}
C、配置与3.6.1的一样
D、测试
ApplicationContext context = new ClassPathXmlApplicationContext(new String[]{"applicationContext.xml"});
UserService service = (UserService) context.getBean("userService ");
service.getName("zhangsan");
service.run("street");
E、结果
service getName:zhangsan
afterGetNameAdvice:MRzhangsan--zhangsan
service run:street
afterRunAdvice
@AfterThrowing 异常抛出增强、@After Final增强
@AfterThrowing (
value="切入点表达式或命名切入点",
pointcut="切入点表达式或命名切入点",
argNames="参数列表参数名",
throwing="异常对应参数名")
@After ( value="切入点表达式或命名切入点", argNames="参数列表参数名")
@After不管是抛出异常或者是正常退出,该增强都会执行。相当于try-catch-final里的final代码块。
A、目标类
@Service
public class UserService {
public void tryThrow(){
System.out.println("service tryThrow");
throw new RuntimeException("i am a runtime exception");
}
}
B、一个POJO为切面
@Aspect
@Component
public class ThrowAspectj {
@AfterThrowing(value="execution(* cn.framelife.spring..tryThrow(..))",
throwing="exception",
argNames="exception")
public void afterThrowAdvisor(Exception exception){
System.out.println("afterThrowAdvisor:"+exception.getMessage());
}
@After(value="execution(* cn.framelife.spring..tryThrow(..))")
public void finalThrowAdvisor(){
System.out.println("finalThrowAdvisor");
}
}
C、配置与3.6.1的一样
D、测试
ApplicationContext context = new ClassPathXmlApplicationContext(new String[]{"applicationContext.xml"});
UserService service = (UserService) context.getBean("userService ");
service.tryThrow();
E、结果
service tryThrow
afterThrowAdvisor:i am a runtime exception
finalThrowAdvisor
Exception in thread "main" java.lang.RuntimeException: i am a runtime exception
at cn.framelife.spring.aspectj.UserService.tryThrow(UserService.java:22)
at cn.framelife.spring.aspectj.UserService$$FastClassByCGLIB$$a5ec6628.invoke(<generated>)
at net.sf.cglib.proxy.MethodProxy.invoke(MethodProxy.java:191)
at org.springframework.aop.framework.Cglib2AopProxy$CglibMethodInvocation.invokeJoinpoint(Cglib2AopProxy.java:688)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:150)
at org.springframework.aop.aspectj.AspectJAfterThrowingAdvice.invoke(AspectJAfterThrowingAdvice.java:55)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
at org.springframework.aop.aspectj.AspectJAfterAdvice.invoke(AspectJAfterAdvice.java:42)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:89)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
at org.springframework.aop.framework.Cglib2AopProxy$DynamicAdvisedInterceptor.intercept(Cglib2AopProxy.java:621)
at cn.framelife.spring.aspectj.UserService$$EnhancerByCGLIB$$ca746149.tryThrow(<generated>)
at cn.framelife.spring.test.Test.main(Test.java:20)
@Around 环绕增强
@Around ( value="切入点表达式或命名切入点", argNames="参数列表参数名")
A、目标类
@Service
public class UserService {
public void say(String name){
System.out.println("service say:"+name);
}
public void run(String way){
System.out.println("service run:"+way);
}
}
B、一个POJO管理切面
@Aspect
@Component
public class AroundAspect {
@Around(value="execution(* cn.framelife.spring..round(..))")
public Object aroundAdvisor(ProceedingJoinPoint point) throws Throwable{
System.out.println("before method");
Object target = point.proceed();
System.out.println("after method");
return target;
}
}
C、配置与3.6.1的一样
D、测试
ApplicationContext context = new ClassPathXmlApplicationContext(new String[]{"applicationContext.xml"});
UserService service = (UserService) context.getBean("userService");
service.round();
service.run("street");
E、结果
before method
service round
after method
service run:street
afterRunAdvice
@DeclareParents 引介增强
@DeclareParents(
value=" AspectJ语法类型表达式",
defaultImpl=引入接口的默认实现类)
下面的例子是Waiter为目标类,然后让目标类拥有ISeller接口的功能:
A、两个接口与两个类
public interface IWaiter {
public void service();
}
目标类:
@Component
public class Waiter implements IWaiter {
@Override
public void service() {
System.out.println("service");
}
}
运行期织入到目标类的功能类:
public interface ISeller {
public void sell();
}
public class Seller implements ISeller {
@Override
public void sell() {
System.out.println("sell");
}
}
B、 POJO管理切面
@Aspect
@Component
public class DeclareAspect {
/*
* value里面配置目标类
* defaultImpl是功能类的实现类
*/
@DeclareParents(
value="cn.framelife.spring.aspectj.Waiter",
defaultImpl=Seller.class)
private ISeller seller; //使用功能类接口声明一个对象
}
C、配置与3.6.1的一样
D、测试
ApplicationContext context = new ClassPathXmlApplicationContext(new String[]{"applicationContext.xml"});
IWaiter waiter = (IWaiter) context.getBean("waiter");
waiter.service();
ISeller seller = (ISeller)waiter;
seller.sell();
E、结果
service
sell
3.6.3切点函数
带@开头的函数都是针对注解类的。而不带@的函数是针对普通类的。
execution()
execution(<修饰符模式>? <返回类型模式> <方法名模式>(<参数模式>) <异常模式>?)
除了返回类型模式、方法名模式和参数模式外,其它都是可选的。
1)通过方法签名定义切点
l execution(public * *(..))
匹配所有目标类的public方法,但不匹配SmartSeller和protected void showGoods()方法。第一个*代表返回类型,第二个*代表方法名,而..代表任意入参的方法;
l execution(* *To(..))
匹配目标类所有以To为后缀的方法。它匹配NaiveWaiter和NaughtyWaiter的greetTo()和serveTo()方法。第一个*代表返回类型,而*To代表任意以To为后缀的方法;
2)通过类定义切点
l execution(* com.baobaotao.Waiter.*(..))
匹配Waiter接口的所有方法,它匹配NaiveWaiter和NaughtyWaiter类的greetTo()和serveTo()方法。第一个*代表返回任意类型,com.baobaotao.Waiter.*代表Waiter接口中的所有方法;
l execution(* com.baobaotao.Waiter+.*(..))
匹配Waiter接口及其所有实现类的方法,它不但匹配NaiveWaiter和NaughtyWaiter类的greetTo()和serveTo()这两个Waiter接口定义的方法,同时还匹配NaiveWaiter#smile()和NaughtyWaiter#joke()这两个不在Waiter接口中定义的方法。
3)通过类包定义切点
在类名模式串中,“.*”表示包下的所有类,而“..*”表示包、子孙包下的所有类。
l execution(* com.baobaotao.*(..))
匹配com.baobaotao包下所有类的所有方法;
l execution(* com.baobaotao..*(..))
匹配com.baobaotao包、子孙包下所有类的所有方法,如com.baobaotao.dao,com.baobaotao.servier以及com.baobaotao.dao.user包下的所有类的所有方法都匹配。“..”出现在类名中时,后面必须跟“*”,表示包、子孙包下的所有类;
l execution(* com..*.*Dao.find*(..))
匹配包名前缀为com的任何包下类名后缀为Dao的方法,方法名必须以find为前缀。如com.baobaotao.UserDao#findByUserId()、com.baobaotao.dao.ForumDao#findById()的方法都匹配切点。
4)通过方法入参定义切点
切点表达式中方法入参部分比较复杂,可以使用“*”和“ ..”通配符,其中“*”表示任意类型的参数,而“..”表示任意类型参数且参数个数不限。
l execution(* joke(String,int)))
匹配joke(String,int)方法,且joke()方法的第一个入参是String,第二个入参是int。它匹配NaughtyWaiter#joke(String,int)方法。如果方法中的入参类型是java.lang包下的类,可以直接使用类名,否则必须使用全限定类名,如joke(java.util.List,int);
l execution(* joke(String,*)))
匹配目标类中的joke()方法,该方法第一个入参为String,第二个入参可以是任意类型,如joke(String s1,String s2)和joke(String s1,double d2)都匹配,但joke(String s1,double d2,String s3)则不匹配;
l execution(* joke(String,..)))
匹配目标类中的joke()方法,该方法第一个入参为String,后面可以有任意个入参且入参类型不限,如joke(String s1)、joke(String s1,String s2)和joke(String s1,double d2,String s3)都匹配。
l execution(* joke(Object+)))
匹配目标类中的joke()方法,方法拥有一个入参,且入参是Object类型或该类的子类。 它匹配joke(String s1)和joke(Client c)。如果我们定义的切点是execution(* joke(Object)),则只匹配joke(Object object)而不匹配joke(String cc)或joke(Client c)。
args()
该函数的入参是一个类名,表示目标类的方法入参对象是指定的类(包含子类),切点匹配。它允许类后后使用+通配符后缀,但添加也不添加+效果是一样的。
User类:
public class User implements Serializable {
}
目标类:
@Service
public class UserService {
public void say(User user){
System.out.println("say");
}
}
切面管理:
@Aspect
@Component
public class BeforAspectj {
@Before(value="args(cn.framelife.spring.aspectj.User)")
public void beforeAdvice(){
System.out.println("beforeAdvice");
}
}
测试:
ApplicationContext context = new ClassPathXmlApplicationContext(new String[]{"applicationContext.xml"});
UserService service = (UserService) context.getBean("userService");
service.say(new User());
结果:
beforeAdvice
say
within()
通过类的匹配模式串声明切点。该函数定义的连接点是针对目标类(不能是接口)而言,而不是针对运行时的对象,这与execution()函数相同。Execution()所指定的连接点可以是小到参数,而within()指定的连接点最小只能是类。
within(cn.framelife.spring.aspectj.UserService)
匹配UserService类下所有的方法。
within(cn.framelife.spring.aspectj.*)
匹配cn.framelife.spring.aspectj包下所的的类,但不包括子孙包。cn.framelife.spring.aspectj.abc.AbcService是不匹配的。
within(cn.framelife.spring.aspectj..*)
匹配cn.framelife.spring.aspectj包及其子孙包下所的的类。
target()与this()
target()函数是通过判断目标类是否按类型匹配指定类决定连接点是否匹配。
target(cn.framelife.spring.aspectj.UserService)
匹配这UserService类及其子孙类的所有方法。如果UserService是一个接口,那么会匹配UserService的实现类及实现类的子孙类中的所有的方法。
this()函数是通过判断代理类是否按类型匹配指定类决定连接点是否匹配。
一般情况下,使用this()和target()定义切点,两者是等效的:
target(cn.framelife.spring.aspectj.UserService)与this(cn.framelife.spring.aspectj.UserService)是一样的。无论UserService是一个类还是一个接口。
两者区别体现在通过引介切面产生代理对象时。
@annotation()
表示标注了某个自定义注解的方法,使用切面。
自定义一个方法级别的注解
@Retention(value=RetentionPolicy.RUNTIME)
@Target(value=ElementType.METHOD)
public @interface BeforeAdvisor {
boolean value() default false;
}
POJO管理切面
@Aspect
@Component
public class BeforAspectj {
@Before(value="@annotation(cn.framelife.spring.aspectj.annotation.BeforeAdvisor)")
public void beforeAdvice(){
System.out.println("beforeAdvice");
}
}
目标类
@Service
public class UserService {
@BeforeAdvisor
public void annotation(){
System.out.println("annotation");
}
}
测试
ApplicationContext context = new ClassPathXmlApplicationContext(new String[]{"applicationContext.xml"});
UserService service = (UserService) context.getBean("userService");
service.annotation();
结果
beforeAdvice
annotation
@args()
该函数的入参是一个注解类的类名,表示运行时目标类方法的入参对象的类标注了指定的注解。
自定义注解类:
@Retention(value=RetentionPolicy.RUNTIME)
@Target(value=ElementType.TYPE)
public @interface UserAnnotation {
}
管理切面:
@Aspect
@Component
public class BeforAspectj {
@Before(value="@args(cn.framelife.spring.aspectj.annotation.UserAnnotation)")
public void beforeAdvice(){
System.out.println("beforeAdvice");
}
}
User类(使用上面定义的注解):
@UserAnnotation
public class User implements Serializable {
}
目标类:
@Service
public class UserService {
public void annotation(){
System.out.println("annotation");
}
public void say(User user){
System.out.println("say");
}
}
测试:
ApplicationContext context = new ClassPathXmlApplicationContext(new String[]{"applicationContext.xml"});
UserService service = (UserService) context.getBean("userService");
service.say(new User());
结果:
beforeAdvice
say
@within和@target()
@winthin(A)匹配任意标注了@A的目标类。@target(A)匹配@A的类及子孙类。
@annotation是标注在目标类的方法
@args是标注目标类方法的入参对象的类
@winthin与@target是标注目标类
注意
上面的函数(特别是args())除了可以指定类名外,还可以指定参数名,将目标对象连接点上的方法入参绑定到增强的方法中。如:@Before中的例子。
3.6.4通配符与逻辑运算符
@Aspectj支持3种通配符:
* 匹配任意字符,但它只能匹配上下文中的一个元素
.. 匹配任意字符,可以匹配上下文中的多个元素,但在表示类时,必须和*联合使用,而在表示入参时则单独使用。
+ 表示按类型匹配指定类及其子孙类,必须跟在类名后面。如cn.framelife.spring.UserService+表示UserService类及其子类。
函数支持:
支持所有的通配符的函数:execution()、within()
仅支持+通配符的函数:args()、this()、targ()。虽然这三个函数可以支持+通配符,但对于这些函数来说使用和不使用+都是一样的。
不支持通配符的函数:@args、@within、@target()、@annotation()。也就是所有用于注解上的函数都不支持通配符。
@Aspectj支持的逻辑运算符:
&& 与
|| 或
! 非
3.7基于Schema的AOP
基于Schema定义的切面和前现两种方式定义的切面,内容上都差不多,只是表现形式不一样而已。
3.7.1一般增强的使用
目标类
public class Target {
public void say(){
System.out.println("say...");
}
public String getName(int id,String name){
System.out.println("getName...");
return "MR"+name+id;
}
public void around(){
System.out.println("around...");
}
public void targetThrow(){
System.out.println("targetThrow");
throw new RuntimeException("我是一个运行期异常...");
}
}
POJO(增强所在的类)
public class Pojo {
public void before() {
System.out.println("前置增强");
}
public void afterReturning(String retName, int id, String name) {
System.out.println("后置增强,返回值为:"+retName+" 入参为:"+id+"-"+name);
}
public Object around(ProceedingJoinPoint point) throws Throwable{
System.out.println("方法执行之前");
Object object = point.proceed();
System.out.println("方法执行之后");
return object;
}
public void throwEx(Exception ex){
System.out.println("抛出异常增强,异常信息:"+ex.getMessage());
}
public void finalEx(){
System.out.println("Final增强");
}
}
aop命名空间与Schema方式配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
<!-- 目标类 -->
<bean id="target" class="cn.framelife.spring.schema.Target"></bean>
<!-- 增强所在的类 -->
<bean id="advisor" class="cn.framelife.spring.schema.Pojo"></bean>
<!-- 配置基于schema的切面 -->
<aop:config proxy-target-class="true">
<!-- 确定增强所在类,并引入 -->
<aop:aspect ref="advisor">
<!—
前置增强
method是配置增强所在类中的方法
-->
<aop:before method="before" pointcut="target(cn.framelife.spring.schema.Target)"/>
<!—
后置增强
Returning 是返回值,必须和method中的参数名是一样的
在Schema配置中,多个切点函数的与操作是and,或操作是or
-->
<aop:after-returning method="afterReturning" pointcut="execution(* cn.framelife.spring.schema..getName(..)) and args(id,name)" returning="retName" arg-names="retName,id,name"/>
<!-- 环绕增强 -->
<aop:around method="around" pointcut="execution(* cn.framelife.spring.schema..around(..))"/>
<!—
抛出异常增强
throwing是异常对象,必须和method中的参数是一样的
-->
<aop:after-throwing method="throwEx" pointcut="execution(* cn.framelife.spring.schema..targetThrow(..))" throwing="ex"/>
<!-- Final增强 -->
<aop:after method="finalEx" pointcut="execution(* cn.framelife.spring.schema..targetThrow(..))"/>
</aop:config>
</beans>
测试
ApplicationContext context = new ClassPathXmlApplicationContext(new String[]{"applicationContext.xml"});
Target target = (Target) context.getBean("target");
target.say();
target.getName(10, "Zhang");
target.around();
target.targetThrow();
结果
前置增强
say...
前置增强
getName...
后置增强,返回值为:MRZhang10 入参为:10-Zhang
前置增强
方法执行之前
around...
方法执行之后
前置增强
targetThrow
抛出异常增强,异常信息:我是一个运行期异常...
Final增强
Exception in thread "main" java.lang.RuntimeException: 我是一个运行期异常...
3.7.2引介增强的使用
我们还是使用3.6.2@DeclareParents中的例子:Waiter为目标类,然后让目标类拥有ISeller接口的功能:
需要两个接口与两个类:
public interface IWaiter {
public void service();
}
目标类:
@Component
public class Waiter implements IWaiter {
@Override
public void service() {
System.out.println("service");
}
}
运行期织入到目标类的功能类:
public interface ISeller {
public void sell();
}
public class Seller implements ISeller {
@Override
public void sell() {
System.out.println("sell");
}
}
配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
<!-- 目标类 -->
<bean id="waiter" class="cn.framelife.spring.schema.Waiter"></bean>
<!-- 增强所在的类 -->
<bean id="advisor" class="cn.framelife.spring.schema.Pojo"></bean>
<aop:config proxy-target-class="true">
<!—
虽然引介增强不需要在增强所在的类中定义一个方法用于增强的实现,但<aop:aspect ref="advisor">中的ref属性依然要指定一个增强Bean
-->
<aop:aspect ref="advisor">
<!—
引介增强
types-matching 目标类
implement-interface 要织入目标类的接口
default-impl 织入接口的实现类
-->
<aop:declare-parents
types-matching="cn.framelife.spring.schema.IWaiter+"
implement-interface="cn.framelife.spring.schema.ISeller"
default-impl="cn.framelife.spring.schema.Seller"/>
</aop:aspect>
</aop:config>
</beans>
测试
ApplicationContext context = new ClassPathXmlApplicationContext(new String[]{"applicationContext.xml"});
IWaiter waiter = (IWaiter) context.getBean("waiter");
waiter.service();
ISeller seller = (ISeller)waiter;
seller.sell();
结果
service
sell
3.8 注意
Spring为我们开发者提供了多种AOP的编程方式。我们该如何选择呢?
1、 如果项目采用的是JDK5.0以上版本,我们可以选择@AspectJ的方式。这是第一选择。
2、 如果使用的是低版本的JDK,那么可以考虑使用<aop:aspect>,也就是Schema的方式。
3、 如果需要一些特殊的切面需要,如基于ControlFlowPointcut的流程切面,那么我们只能使用基于API的Advisor方式(也就是通过spring给我提供的接口与类)来进行构建。
4、 在做基于Spring的事务管理的时候,使用<aop:aspect>的方式更加方便。
4、Spring与JDBC的整合
4.1 引入aop、tx的命名空间
为了事务配置的需要,我们引入aop、tx的命名空间
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">
4.2配置数据源
在配置数据源之前,首先要导入连接数据库时所需要的jar包。
<!-- 配置数据源 -->
<bean id="dataSource"
class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<!-- jdbc连接的4个必须参数 -->
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://127.0.0.1:3306/test"/>
<property name="username" value="root"/>
<property name="password" value="test"/>
<!-- 连接池启动初始值 -->
<property name="initialSize" value="5"/>
<!-- 最大空闲值 -->
<property name="maxIdle" value="20"/>
<!-- 最小空闲值 -->
<property name="minIdle" value="5"/>
<!-- 最大连接值 -->
<property name="maxActive" value="500"/>
</bean>
4.3配置事务
4.3.1 基于Schema的方式
<!-- 指定事务管理器 -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 设置事务增强 -->
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<tx:method name="get*" read-only="true"/>
<tx:method name="add*" rollback-for="Exception"/>
</tx:attributes>
</tx:advice>
<!-- 作用Shcema的方式配置事务,这里是把事务设置到了service层-->
<aop:config>
<aop:pointcut id="servicePointcut" expression="execution(* cn.framelife.spring.jdbc.*ServiceImpl.*(..))" />
<aop:advisor advice-ref="txAdvice" pointcut-ref="servicePointcut"/>
</aop:config>
4.3.2基于注解的方式
基本配置:
<!-- 配置数据源 -->
<bean id="dataSource"
class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<!-- jdbc连接的4个必须参数 -->
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://127.0.0.1:3306/test"/>
<property name="username" value="root"/>
<property name="password" value="test"/>
<!-- 连接池启动初始值 -->
<property name="initialSize" value="5"/>
<!-- 最大空闲值 -->
<property name="maxIdle" value="20"/>
<!-- 最小空闲值 -->
<property name="minIdle" value="5"/>
<!-- 最大连接值 -->
<property name="maxActive" value="500"/>
</bean>
<!-- 指定事务管理器 -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 打开tx事务注解管理功能 -->
<tx:annotation-driven transaction-manager="txManager"/>
@Transactional的使用
放在需要事务的类的头上。如:我们想把事务集中在业务层管理,那就在service层的各个类上标注上@Transactional注解。
4.3.3事务的传播行为
Spring给我们提供了7种类型的事务传播行为:
PROPAGATION_REQUIRED--支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。
PROPAGATION_SUPPORTS--支持当前事务,如果当前没有事务,就以非事务方式执行。
PROPAGATION_MANDATORY--支持当前事务,如果当前没有事务,就抛出异常。
PROPAGATION_REQUIRES_NEW--新建事务,如果当前存在事务,把当前事务挂起。
PROPAGATION_NOT_SUPPORTED--以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
PROPAGATION_NEVER--以非事务方式执行,如果当前存在事务,则抛出异常。
我们可以通过事务的propagation属性来进行配置。
4.3.4事务的隔离级别
事务的隔离级别是由数据库本身提供的,一般不需要我们去改变什么东西。
1、 事务并发会产生的问题
A、 脏读 如:事务A读到事务B未提交的数据
B、 不可重复读 如:多次读取同一个事务得到的结果不同
C、 幻读 如:事务A读到事务B已提交的数据
2、 隔离级别
隔离级别 | 解释 | 允许并发的问题 | 相应数据库 |
READ UNCOMMITED | 可读取未提交的数据 | ABC | |
READ COMMITED | 读已提交事务 | BC | SQL Server默认 |
REPEATABLE READ | 可重复读 | C | MySQL默认 |
SERIALIZABLE | 序列化 |
4.4JDBCTemplate的使用
public class UserDaoImpl implements UserDao {
private JdbcTemplate jdbcTemplate;
/**
* 通过dataSource的setter方法,在运行时注入一个dataSouce对象,然后根据这个对象创建一个JdbcTemplate对象
* @param dataSource
*/
public void setDataSource(DataSource dataSource){
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
/**
* 增加、修改、删除都是使用的JdbcTemplate的update方法。
* update方法中第一个参数是sql语句,未知的值用占位符?代替
* 第二个参数是一个Object数组。数组里面的各项是上面的占位符的值
* 第三个参数是一个int数组。数组的各项是第二个参数中的值在数据库中的类型
*/
public void save(User user) {
jdbcTemplate.update("insert into user(username,password) values(?,?)",
new Object[]{user.getUsername(),user.getPassword()},
new int[]{java.sql.Types.VARCHAR,java.sql.Types.VARCHAR}
);
}
public void update(User user) {
jdbcTemplate.update("update user set username=?,password=? where id=?",
new Object[]{user.getUsername(),user.getPassword(),user.getId()},
new int[]{java.sql.Types.VARCHAR,java.sql.Types.VARCHAR,java.sql.Types.INTEGER}
);
}
public void delete(Integer id) {
jdbcTemplate.update("delete from user where id = ?",
new Object[]{id},
new int[]{java.sql.Types.INTEGER}
);
}
/**
* 根据id获取单个数据是通过queryForObject方法
* 这个方法前面三个参数都与update方法一样
* 第四个参数是一个org.springframework.jdbc.core.RowMapper接口的对象
* 实现RowMapper接口,必须实现里面的mapRow(ResultSet rs, int rowNum)方法
* 这个方法是通过ResultSet把一条记录放到一个实体类对象中,并返回这个实体类对象
*/
public User getUserById(Integer id) {
User user = jdbcTemplate.queryForObject("select * from user where id=?",
new Object[]{id},
new int[]{java.sql.Types.INTEGER}, new RowMapper<User>() {
public User mapRow(ResultSet rs, int rowNum) throws SQLException {
User user = new User();
user.setId(rs.getInt("id"));
user.setUsername(rs.getString("username"));
user.setPassword(rs.getString("password"));
return user;
}
});
return user;
}
/**
* 通过一条没有占位符的select语句来查询多条记录,并返回一个List集合
* query方法里面的两个参数
* 第一个是select语句
* 第二个是RowMapper接口的对象
*/
public List<User> getUsersBySql(String sql) {
return jdbcTemplate.query(sql, new RowMapper<User>() {
public User mapRow(ResultSet rs, int rowNum) throws SQLException {
User user = new User();
user.setId(rs.getInt("id"));
user.setUsername(rs.getString("username"));
user.setPassword(rs.getString("password"));
return user;
}
});
}
}
4.5完整的配置
4.5.1基于Schema
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">
<!-- 配置数据源 -->
<bean id="dataSource"
class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<!-- jdbc连接的4个必须参数 -->
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://127.0.0.1:3306/test"/>
<property name="username" value="root"/>
<property name="password" value="test"/>
<!-- 连接池启动初始值 -->
<property name="initialSize" value="5"/>
<!-- 最大空闲值 -->
<property name="maxIdle" value="20"/>
<!-- 最小空闲值 -->
<property name="minIdle" value="5"/>
<!-- 最大连接值 -->
<property name="maxActive" value="500"/>
</bean>
<!-- 指定事务管理器 -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!—
设置事务增强
read-only 表示对应的事务应该被最优化为只读事务。
rollback-for="Exception" 是指出现异常的时候回滚事务
-->
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<tx:method name="get*" read-only="true"/>
<tx:method name="add*" rollback-for="Exception"/>
<tx:method name="update*"/>
</tx:attributes>
</tx:advice>
<!-- 作用Shcema的方式配置事务 -->
<aop:config>
<aop:pointcut id="servicePointcut" expression="execution(* cn.framelife.spring.jdbc.*ServiceImpl.*(..))" />
<aop:advisor advice-ref="txAdvice" pointcut-ref="servicePointcut"/>
</aop:config>
<!-- 把相关的Bean交由Spring管理 -->
<bean id="userDao" class="cn.framelife.spring.jdbc.UserDaoImpl">
<property name="dataSource" ref="dataSource"></property>
</bean>
<bean id="userService" class="cn.framelife.spring.jdbc.UserServiceImpl">
<property name="userDao" ref="userDao"></property>
</bean>
</beans>
4.5.2基于注解
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">
<context:component-scan base-package="cn.framelife.spring"></context:component-scan>
<!-- 配置数据源 -->
<bean id="dataSource"
class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<!-- jdbc连接的4个必须参数 -->
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://127.0.0.1:3306/test"/>
<property name="username" value="root"/>
<property name="password" value="test"/>
<!-- 连接池启动初始值 -->
<property name="initialSize" value="5"/>
<!-- 最大空闲值 -->
<property name="maxIdle" value="20"/>
<!-- 最小空闲值 -->
<property name="minIdle" value="5"/>
<!-- 最大连接值 -->
<property name="maxActive" value="500"/>
</bean>
<!-- 指定事务管理器 -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 打开tx事务注解管理功能 -->
<tx:annotation-driven transaction-manager="txManager"/>
</beans>
5、Spring3.0 、Hibernate3.3与Struts2的整合
5.1整合Spring与Hibernate
5.1.1使用MyEclipse加入Spring与Hibernate功能
使用MyEclipse工具主要是为了让工程拥有把数据表生成实体类与映射的功能。然后在这个过程中,把实体类或映射文件的路径加入到spring的配置文件中。而且在Spring与Hibernate整合后,我们不需要Hibernate的配置文件,Hibernate相关功能的配置都写在spring的配置文件中。
A、加入Spring功能
这个很简单,要注意的是,再加入Spring功能的步骤中,是否需要MyEclipse给我们提供的配置文件。如果是新的工程,一般都是需要的。
B、加入Hibernate功能
在加入Hibernate功能之前,我们必须得有Spring的配置文件,或者说我们必须在有Spring功能之后。
设置Hibernate的相关功能在Spring的配置文件中设置。如果还没有Spring的功能(配置文件),就选择New Spring configuration file,否则就按下面的下面的设置。SessionFactory Id是把SessionFactory类交由Spring管理时,它的Bean Id。
选择和配置数据源,这和Hibernate单独设置的时候是一样的。可参考Hibernate的文档。
C、移除MyEclipse给我们提供的Jar包,并导入我们的Jar包
把MyEclipse的Jar包移除后,把我们的Jar包放到项目的lib目录下,然后刷新工程。
5.1.2 配置数据源与SessionFactory
这个在上面5.1.1的过程中,已由MyEclipse自动帮我们配置好了。而映射文件或实体类的路径那是在通过MyEclipse中的工具把数据表生成实体类及映射的时候自动生成的。
<!-- 配置数据源 这个与JDBC整合的时候是一样的-->
<bean id="dataSource"
class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://127.0.0.1:3306/test"/>
<property name="username" value="root"/>
<property name="password" value="test"/>
<property name="initialSize" value="5"/>
<property name="maxIdle" value="20"/>
<property name="minIdle" value="5"/>
<property name="maxActive" value="500"/>
</bean>
<!-- 配置SessionFactory -->
<bean id="sessionFactory"
class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<!-- 引入数据源 -->
<property name="dataSource">
<ref bean="dataSource" />
</property>
<!-- Hibernate的相关配置 -->
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">
org.hibernate.dialect.MySQLDialect
</prop>
</props>
</property>
<!-- 映射文件的路径。这是通过MyEclipse中的工具把数据表生成实体类及映射的时候自动生成的。 -->
<property name="mappingResources">
<list>
<value>cn/framelife/ssh/entity/User.hbm.xml</value>
</list>
</property>
</bean>
5.1.3 配置事务
除了事务管理类与JDBC时不一样之外,其它都相同。
<!-- 配置事务 -->
<bean id="txManager"
class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory"></property>
</bean>
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<tx:method name="get*" read-only="true"/>
<tx:method name="add*" rollback-for="Exception"/>
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="pointcut"
expression="execution(* cn.framelife.ssh.service..*(..))" />
<aop:advisor pointcut-ref="pointcut" advice-ref="txAdvice" />
</aop:config>
5.1.4 HibernateDaoSupport类的使用
当我们配置好SessionFactory后,就可以直接使用SessionFactory来生产Session,再用Session与数据库交互就行了。
Spring提供了org.springframework.orm.hibernate3.support.HibernateDaoSupport工具类,使Spring与Hibernate整合后,不再需要使用Hibernate的API,而使用HibernateDaoSupport类就可以拥有Hibernate API的功能。
数据层,*DaoImpl类实现*Dao接口,并继承HibernateDaoSupport类:
public class UserDaoImpl extends HibernateDaoSupport implements UserDao {
/**
* 保存或更新对象
*/
public void saveOrUpdateUser(User user) {
getHibernateTemplate().saveOrUpdate(user);
}
/**
* 根据id删除一条记录
*/
public void deleteById(Integer id) {
getHibernateTemplate().delete(this.findUserById(id));
}
/**
* 根据id获取一个记录对象
*/
public User findUserById(Integer id) {
return (User) getHibernateTemplate().get(User.class, id);
}
/**
* 根据Hql语句,以及开始位置、最大多少条数进行分页查询
*/
public List<User> findUserByLimit(final String hql, final int start,
final int limit) {
List<User> list = getHibernateTemplate().executeFind(new HibernateCallback() {
public Object doInHibernate(Session session) throws HibernateException,
SQLException {
Query query = session.createQuery(hql);
query.setFirstResult(start);
query.setMaxResults(limit);
List list = query.list();
return list;
}
});
return list;
}
/**
* 根据一个User对象查询与这个对象中的非空属性一致的数据
*/
public List<User> findUserByExample(User user) {
return getHibernateTemplate().findByExample(user);
}
/**
* 根据Hql语句查询多条记录对象
*/
public List<User> findUserByHql(String hql) {
return getHibernateTemplate().find(hql);
}
}
5.2集成Struts2
5.2.1 web.xml的配置
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<!-- OpenSessionInViewFilter的主要功能是用来把一个Hibernate Session和一次完整的请求过程对应的线程相绑定 -->
<filter>
<filter-name>OpenSessionInViewFilter</filter-name>
<filter-class>
org.springframework.orm.hibernate3.support.OpenSessionInViewFilter</filter-class>
<init-param>
<param-name>singleSession</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>OpenSessionInViewFilter</filter-name>
<url-pattern>*.action</url-pattern>
</filter-mapping>
<!-- struts2的设置 -->
<filter>
<filter-name>struts2</filter-name>
<filter-class>
org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>
<init-param>
<param-name>actionPackages</param-name>
<param-value>cn.framelife.ssh.struts</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>struts2</filter-name>
<url-pattern>*.action</url-pattern>
</filter-mapping>
<!-- 配置contextConfigLocation参数设置Spring配置文件的路径 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
<!-- 通过配置ContextLoaderListener监听器,使服务器启动时,自动加载applicationContext配置,启动Spring容器 -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
</web-app>
5.2.2把*DaoImpl、*ServiceImpl及*Action交由Spring管理
<bean id="userDao" class="cn.framelife.ssh.dao.impl.UserDaoImpl">
<!-- 这里的sessionFactory属性是供HibernateDaoSupport使用的 -->
<property name="sessionFactory" ref="sessionFactory"></property>
</bean>
<bean id="userService" class="cn.framelife.ssh.service.impl.UserServiceImpl">
<property name="userDao" ref="userDao"></property>
</bean>
<bean id="userAction" class="cn.framelife.ssh.struts.UserAction" scope="prototype">
<property name="userService" ref="userService"></property>
</bean>
5.2.3配置struts2的Action
<struts>
<package name="a" extends="struts-default">
<!-- 整合spring后,这里的class是spring配置文件中的Action的Bean id -->
<action name="add" class="userAction">
<result name="success">/success.jsp</result>
</action>
</package>
</struts>
5.3 完整的Spring配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">
<!-- 数据源 -->
<bean id="dataSource"
class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName"
value="com.mysql.jdbc.Driver">
</property>
<property name="url" value="jdbc:mysql://127.0.0.1:3306/test"></property>
<property name="username" value="root"></property>
<property name="password" value="test"></property>
</bean>
<!-- 配置SessionFactory -->
<bean id="sessionFactory"
class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<!-- 引入数据源 -->
<property name="dataSource">
<ref bean="dataSource" />
</property>
<!-- Hibernate的相关配置 -->
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">
org.hibernate.dialect.MySQLDialect
</prop>
</props>
</property>
<!-- 映射文件或者映射类的路径。这是通过MyEclipse中的工具把数据表生成实体类及映射的时候自动生成的。 -->
<property name="mappingResources">
<list>
<value>cn/framelife/ssh/entity/User.hbm.xml</value>
</list>
</property>
</bean>
<!-- 配置事务 -->
<bean id="txManager"
class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory"></property>
</bean>
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<tx:method name="get*" read-only="true"/>
<tx:method name="add*" rollback-for="Exception"/>
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="pointcut"
expression="execution(* cn.framelife.ssh.service..*(..))" />
<aop:advisor pointcut-ref="pointcut" advice-ref="txAdvice" />
</aop:config>
<!-- 管理Bean -->
<bean id="userDao" class="cn.framelife.ssh.dao.impl.UserDaoImpl">
<property name="sessionFactory" ref="sessionFactory"></property>
</bean>
<bean id="userService" class="cn.framelife.ssh.service.impl.UserServiceImpl">
<property name="userDao" ref="userDao"></property>
</bean>
<bean id="userAction" class="cn.framelife.ssh.struts.UserAction">
<property name="userService" ref="userService"></property>
</bean>
</beans>
5.4 基于Annotation的整合
下面的做法是通过注解的方式把Struts2中的Action、Dao层的实现类、Service层的实现类交由Spring管理,不需要在配置文件中进行配置。但为了方便,事务的管理依然使用的是Schema的方式。如果有需要,可以参照4.3.2中的方式,使用@Transactional对service层进行事务管理。
5.4.1前期工作
给工程加入Spring与Hihernate的功能,这个步骤也5.1.1的相同。
打开Spring的扫描功能。
配置数据源。
配置SessionFactory。由于我们的实体类也是基于Annotation的,所以SessionFactory的class是AnnotationSessionFactoryBean。
把数据表生成Annotation的形式管理映射的实体类。
配置Schema方式的事务管理。
web.xml的配置与5.2.1一样。
由于struts2的配置都由注解完成,所以我们不再需要struts.xml配置文件。
5.4.2完整的Spring配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">
<!-- 打开Spring的扫描功能 -->
<context:component-scan base-package="cn.framelife.ssh"></context:component-scan>
<!-- 数据源 -->
<bean id="dataSource"
class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName"
value="com.mysql.jdbc.Driver">
</property>
<property name="url" value="jdbc:mysql://127.0.0.1:3306/test"></property>
<property name="username" value="root"></property>
<property name="password" value="test"></property>
</bean>
<!-- 配置SessionFactory.如果实体映射是基于注解的话,使用AnnotationSessionFactoryBean -->
<bean id="sessionFactory"
class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
<!-- 引入数据源 -->
<property name="dataSource">
<ref bean="dataSource" />
</property>
<!-- Hibernate的相关配置 -->
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">
org.hibernate.dialect.MySQLDialect
</prop>
</props>
</property>
<!-- 映射文件或者映射类的路径。这是通过MyEclipse中的工具把数据表生成实体类及映射的时候自动生成的。 -->
<property name="annotatedClasses">
<list>
<value>cn.framelife.ssh.entity.User</value>
</list>
</property>
</bean>
<!-- 配置事务 -->
<bean id="txManager"
class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory"></property>
</bean>
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<tx:method name="get*" read-only="true"/>
<tx:method name="add*" rollback-for="Exception"/>
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="pointcut"
expression="execution(* cn.framelife.ssh.service..*(..))" />
<aop:advisor pointcut-ref="pointcut" advice-ref="txAdvice" />
</aop:config>
</beans>
5.4.3*DaoImpl
@Repository
public class UserDaoImpl extends HibernateDaoSupport implements UserDao {
/**
* 给HibernateDaoSupport注入一个SessionFactory.
* 如果是xml的话,是直接把sesionFactory注入就行了。而这里需要一个额外的setter方法。
*/
@Autowired
public void setMySessionFactory(SessionFactory sessionFactory){
super.setSessionFactory(sessionFactory);
}
/**
* 保存或更新对象
*/
public void saveOrUpdateUser(User user) {
getHibernateTemplate().saveOrUpdate(user);
}
/**
* 根据id删除一条记录
*/
public void deleteById(Integer id) {
getHibernateTemplate().delete(this.findUserById(id));
}
/**
* 根据id获取一个记录对象
*/
public User findUserById(Integer id) {
return (User) getHibernateTemplate().get(User.class, id);
}
/**
* 根据Hql语句,以及开始位置、最大多少条数进行分页查询
*/
public List<User> findUserByLimit(final String hql, final int start,
final int limit) {
@SuppressWarnings("unchecked")
List<User> list = getHibernateTemplate().executeFind(new HibernateCallback() {
public Object doInHibernate(Session session) throws HibernateException,
SQLException {
Query query = session.createQuery(hql);
query.setFirstResult(start);
query.setMaxResults(limit);
List list = query.list();
return list;
}
});
return list;
}
/**
* 根据一个User对象查询与这个对象中的非空属性一致的数据
*/
public List<User> findUserByExample(User user) {
return getHibernateTemplate().findByExample(user);
}
/**
* 根据Hql语句查询多条记录对象
*/
public List<User> findUserByHql(String hql) {
return getHibernateTemplate().find(hql);
}
}
5.4.4*ServiceImpl
@Service
public class UserServiceImpl implements UserService {
@Autowired private UserDao userDao;
public void addUser(User user) {
userDao.saveOrUpdateUser(user);
}
}
5.4.5*Action
/**
* prototype 设置一次请求一个action对象
*
<package name="a" extends="struts-default" namespace="/user">
<action name="add" class="userAction">
<result name="success">/success.jsp</result>
</action>
</package>
* @ParentPackage相当于package extends
* @Namespace 相当于package namespace
* @Results 相当于result结果集
*/
@Controller
@Scope("prototype")
@ParentPackage(value = "struts-default")
@Namespace("/user")
@Results({
@Result(name="success",value="/success.jsp"),
})
public class UserAction {
/*
* 如果参数过多的话,应该使用struts2驱动模型
*/
private String username;
private String password;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Autowired private UserService userService;
/**
* 如果想执行这个方法。页面上调用 user/user.action.
*/
public String execute(){
User user = new User();
user.setUsername(username);
user.setPassword(password);
userService.addUser(user);
return "success";
}
/**
* 如果执行的是这个方法。页面调用user/user!next.action
*/
public String next(){
System.out.println("aaaaaaaaaaa");
return "success";
}
}
6、Spring MVC
6.1Spring3 MVC的优点:
1、Spring3 MVC的学习难度小于Struts2,Struts2用不上的多余功能太多。呵呵,当然这不是决定因素。
2、Spring3 MVC很容易就可以写出性能优秀的程序,Struts2要处处小心才可以写出性能优秀的程序(指MVC部分)
3、Spring3 MVC的灵活是你无法想像的,Spring的扩展性有口皆碑,Spring3 MVC当然也不会落后,不会因使用了MVC框架而感到有任何的限制。
6.2一个简单的例子
6.2.1配置web.xml
指除了Control层外的其它Bean的Spring配置文件,定义DispatcherServlet。这里是把spring与spring mvc的功能随着服务器启动而启动。
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<!-- 除了Control层外的其它Bean的Spring容器设置,这个与SSH整合的时候一样 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- 配置DispatcherServlet -->
<!-- 名字为mvc,那么我们在WEB-INF中需要一个名为mvc-servlet.xml的spring mvc配置文件来对Control层的Bean、相关页面以及Spring mvc提供的一些工具Bean进行管理 -->
<servlet>
<servlet-name>mvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<!-- 接收页面以.abc结尾的请求 -->
<servlet-mapping>
<servlet-name>mvc</servlet-name>
<url-pattern>*.abc</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
</web-app>
6.2.2编写处理请求的Controller(处理器)
package cn.framelife.mvc.control;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
import cn.framelife.mvc.entity.User;
/**
* @Controller 通过Spring的自动扫描功能,把一个POJO转化为处理请求的控制器
*通过@RequestMapping标注,可以将特定的url和具体的controller类或controller类中的方法绑定。
* @RequestMapping("/user") 处理来自/user的请求,一般来说是用于区分不同模块的
*/
@Controller
@RequestMapping("/user")
public class UserControl {
/**
* 处理/user的请求,请求的方法为POST
* 参数的User对象是把页面的表单值放进一个User对象中
* ModelAndView 是返回一个View。在这里我们是一个JSP页面。
*/
@RequestMapping(method=RequestMethod.POST)
public ModelAndView createUser(User user){
System.out.println(user.getUsername()+"-"+user.getPassword());
//ModelAndView.setViewName("/success") 根据mvc的配置文件,可知返回的是/user/success.jsp
ModelAndView view = new ModelAndView();
view.setViewName("/success");
//把一个user对象放到ModelAndView中
user.setUsername("li");
view.addObject(user);
return view;
}
}
6.2.3编写视图文件
我们在WebRoot/uesr目录下有一个add.jsp及一个success.jsp页面。add.jsp是用以表单输入。而success.jsp是Controller处理完后返回的页面。
add.jsp:
<body>
<form action="user.abc" method="post">
用户名:<input type="text" name="username"><br/>
密 码:<input type="text" name="password"><br/>
<input type="submit">
</form>
</body>
success.jsp:
<body>
success!!${user.username}.
</body>
6.2.4配置Spring MVC的配置文件,使控制器、视图解析器等生效
mvc-servlet.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">
<!-- 扫描控制器 -->
<context:component-scan base-package="cn.framelife.mvc.control"></context:component-scan>
<!-- 视图名称解析器 -->
<!--
Controller中:ModelAndView.setViewName("/success")
配置的是/user/success.jsp
-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"
p:prefix="/user"
p:suffix=".jsp"></bean>
</beans>
6.2.5配置其它的Bean
诸如service层、dao层等Bean在applicationContext.xml中配置,如我们之前做的工作一样。
6.3Spring MVC处理一个请求的过程
我们这里以6.2的例子来说:
1、 DispatcherServlet接受来自浏览器的user/add.abc请求
2、 DispatcherServlet使用DefaultAnnotationHandlerMapping查找负责处理这个请求的Controller
3、 DispatcherServlet将这个请求交由2中查找到的UserController
4、 Controller完成业务处理后,返回ModelAndView模型对象,其中它的视图名为”/success”,而且这个模型对象这包含一个键为user的User对象
5、 DispatcherServlet调用InternalResourceViewResolver对ModelAndView中视图名进行解析,得到/user/success.jsp这个视图
6、 DispatcherServlet把User对象中的属性渲染到/user/success.jsp中
7、 DispatcherServlet把/user/success.jsp这个视图返回给浏览器。
6.4Multiaction Controller
package cn.framelife.mvc.control;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
import cn.framelife.mvc.entity.User;
@Controller
@RequestMapping("/user")
public class UserControl {
/**
* 这个方法接收/user/create.abc请求
*/
@RequestMapping("create")
public ModelAndView createUser(User user){
System.out.println("createUser");
ModelAndView view = new ModelAndView();
view.setViewName("/success");
return view;
}
/**
* 这个方法接收/user/update.abc请求
*/
@RequestMapping("update")
public ModelAndView update(){
System.out.println("updateUser");
ModelAndView view = new ModelAndView();
view.setViewName("/success");
return view;
}
}
6.5 ModelAndView
Controller处理方法的返回值为ModelAndView,既包含视图信息,也包含模型数据信息。
6.5.1ModelAndView中的方法
可以使用以下方法添加模型数据:
addObject(Object modelObject)
addObject(String modelName, Object modelObject)
addAllObjects(Map modelMap)
可以通过以下方法设置视图:
setViewName(String viewName)
setView(View view)
6.5.2重定向:
这里的重定向只能重定向到其它的处理方法,不能重定向到页面。如果想重定向到页面,那么在另外的的处理方法中跳转就是。
@RequestMapping(value="/add",method = RequestMethod.GET)
public ModelAndView initForm(){
User user = new User();
return new ModelAndView("/add").addObject(user);
}
@RequestMapping("create")
public ModelAndView createUser(){
ModelAndView view = new ModelAndView();
//这里会重定向到add.abc的处理方法中,再由add.abc的处理方法作页面跳转
RedirectView redirectView = new RedirectView("add.abc");
view.setView(redirectView);
return view;
}
6.6Controler处理方法获取值
6.6.1获取页面值
页面Form表单:
<form action="user/create.abc" method="post">
用户名:<input type="text" name="username"><br/>
密 码:<input type="text" name="password"><br/>
其它:<input type="text" name="other"><br/>
<input type="submit">
</form>
A、 绑定到同名参数中
/*
* @RequestParam可以用来提取名为“username”的String类型的参数,并将之作为输入参数传入
*/
@RequestMapping("create")
public ModelAndView createUser(@RequestParam("username") String username,String password,String other){
System.out.println(username+"-"+password+"-"+other);
ModelAndView view = new ModelAndView();
view.setViewName("/success");
return view;
}
B、 绑定到实体类模型数据中
User实体类:
public class User implements java.io.Serializable {
private Integer id;
private String username;
private String password;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
Controller中的方法:
@RequestMapping("create")
public ModelAndView createUser(User user){
System.out.println(user.getUsername()+"-"+user.getPassword());
ModelAndView view = new ModelAndView();
view.setViewName("/success");
return view;
}
C、既有模型又有同名参数
/**
* 页面表单值传输时,如果User类中有同名的属性,那就绑定到User对象中
* 如果User类中没有的属性,可以使用同名参数接收
* 如果User类中也有,而我们又有同名参数,两个都可以接收
*/
@RequestMapping("create")
public ModelAndView createUser(User user,String username,String other){
System.out.println(user.getUsername()+"-"+user.getPassword()+"-"+username+"-"+other);
ModelAndView view = new ModelAndView();
view.setViewName("/success");
return view;
}
6.6.2获取Servlet API
Controller中的处理方法:
@RequestMapping("create")
public ModelAndView createUser(HttpServletRequest request,HttpServletResponse response,HttpSession session){
String username = request.getParameter("username");
System.out.println(username);
request.setAttribute("requestName", "aaaaaaaaaaaa");
session.setAttribute("sessionName", "bbbbbbbbbbbb");
Cookie cookie = new Cookie("cookieName", "ccccccccccc");
response.addCookie(cookie);
ModelAndView view = new ModelAndView();
view.setViewName("/success");
return view;
}
Success.jsp页面显示:
<body>
${requestName}<br/>
${sessionName}<br/>
${cookie.cookieName.value}<br/>
</body>
6.7数据转换
例子:把一个字符串封装而一个对象。
如:username:password格式的数据ZhangSan:1234,我们把这个数据封装成一个User对象。下面分别使用属性编辑器与转换器来实现。
6.7.1自定义属性编辑器
A、写一个属性编辑器继承PropertyEditorSupport
package cn.framelife.mvc.converter;
import java.beans.PropertyEditorSupport;
import cn.framelife.mvc.entity.User;
public class UserEditor extends PropertyEditorSupport {
public void setAsText(String text) throws IllegalArgumentException {
System.out.println("setAsText");
User user = new User();
if(text != null){
String[] items = text.split(":");
user.setUsername(items[0]);
user.setPassword(items[1]);
}
setValue(user);
}
}
B、Controller范围的编辑器
在Controller中注册及使用编辑器:
/**
* @InitBinder注解把编辑器绑定到当前Controller中
*/
@InitBinder
public void initBinder(WebDataBinder binder){
//注册自定义的编辑器
binder.registerCustomEditor(User.class, new UserEditor());
}
/**
* 第一个参数user是一个模型数据,接收页面的username用password
* 第二个参数converterUser通过@RequestParam注解,把页面的other参数交由UserEditor转成一个User对象
*/
@RequestMapping("create")
public ModelAndView createUser(User user,@RequestParam("other")User converterUser){
System.out.println(user.getUsername()+"--"+user.getPassword());
System.out.println(converterUser.getUsername()+"--"+converterUser.getPassword());
ModelAndView view = new ModelAndView();
view.setViewName("/success");
return view;
}
C、 全局范围的编辑器
实现WebBindingInitializer接口,并在实现类中注册属性编辑器:
package cn.framelife.mvc.converter;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.support.WebBindingInitializer;
import org.springframework.web.context.request.WebRequest;
import cn.framelife.mvc.entity.User;
public class MyBindingInitializer implements WebBindingInitializer {
public void initBinder(WebDataBinder binder, WebRequest request) {
//注册自定义的属性编辑器。这里可以注册多个属性编辑器
binder.registerCustomEditor(User.class, new UserEditor());
}
}
配置WebBindingInitializer实现类:
<!-- 配置全局范围的属性编辑器 -->
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
<property name="webBindingInitializer">
<bean class="cn.framelife.mvc.converter.MyBindingInitializer"></bean>
</property>
</bean>
使用属性编辑器:
和Controller范围内的使用一样
/**
* 第一个参数user是一个模型数据,接收页面的username用password
* 第二个参数converterUser通过@RequestParam注解,把页面的other参数交由UserEditor转成一个User对象
*/
@RequestMapping("create")
public ModelAndView createUser(User user,@RequestParam("other")User converterUser){
System.out.println(user.getUsername()+"--"+user.getPassword());
System.out.println(converterUser.getUsername()+"--"+converterUser.getPassword());
ModelAndView view = new ModelAndView();
view.setViewName("/success");
return view;
}
6.7.2转换器
A、写一个转换器类继承Converter
package cn.framelife.mvc.converter;
import org.springframework.core.convert.converter.Converter;
import cn.framelife.mvc.entity.User;
/**
* Converter<S源类型/T目标类型>
*
*/
public class StringToUserConverter implements Converter<String, User> {
public User convert(String source) {
User user = new User();
if(source != null){
String[] items = source.split(":");
user.setUsername(items[0]);
user.setPassword(items[1]);
}
return user;
}
}
B、配置(mvc-servlet.xml)
<!-- 装配转换器 -->
<bean id="conversionService"
class="org.springframework.context.support.ConversionServiceFactoryBean">
<property name="converters">
<list>
<!-- 这里可以配置多个自定义的转换器 -->
<bean class="cn.framelife.mvc.converter.StringToUserConverter"></bean>
</list>
</property>
</bean>
<!-- 装配自定义的转换器 -->
<mvc:annotation-driven conversion-service="conversionService"/>
D、 Controller的处理方法中接收页面数据
/**
* 第一个参数user是一个模型数据,接收页面的username用password
* 第二个参数converterUser通过@RequestParam注解,把页面的other参数交由转换器StringTouserConverter转成一个User对象
*/
@RequestMapping("create")
public ModelAndView createUser(User user,@RequestParam("other")User converterUser){
System.out.println(user.getUsername()+"--"+user.getPassword());
System.out.println(converterUser.getUsername()+"--"+converterUser.getPassword());
ModelAndView view = new ModelAndView();
view.setViewName("/success");
return view;
}
6.7.3注意
如果Controller范围的属性编辑器、全局范围的属性编辑器、转换器同时存在,那么Spring MVC将按以下的优先顺序查找对应类型的编辑器来处理:
查询Controller范围的属性编辑器
查询转换器
查询全局范围的属性编辑器
6.7.4数据格式化
Spring内建的格式化转换器:
类名 | 说明 |
DateFormatter | java.util.Date<---->String 实现日期的格式化/解析 |
NumberFormatter | java.lang.Number<---->String 实现通用样式的格式化/解析 |
CurrencyFormatter | java.lang.BigDecimal<---->String 实现货币样式的格式化/解析 |
PercentFormatter | java.lang.Number<---->String 实现百分数样式的格式化/解析 |
NumberFormatAnnotationFormatterFactory | @NumberFormat注解类型的数字字段类型<---->String ①通过@NumberFormat指定格式化/解析格式 ②可以格式化/解析的数字类型:Short、Integer、Long、Float、Double、BigDecimal、BigInteger |
JodaDateTimeFormatAnnotationFormatterFactory | @DateTimeFormat注解类型的日期字段类型<---->String ①通过@DateTimeFormat指定格式化/解析格式 ②可以格式化/解析的日期类型: joda中的日期类型(org.joda.time包中的):LocalDate、LocalDateTime、LocalTime、ReadableInstant java内置的日期类型:Date、Calendar、Long
classpath中必须有Joda-Time类库,否则无法格式化日期类型 |
注解驱动格式化的使用:
A、启动注解驱动格式化功能
之前我们配置自定义转换器的时候,使用的是BeanConversionServiceFactoryBean。
org.springframework.context.support.ConversionServiceFactoryBean
改成:
org.springframework.format.support.FormattingConversionServiceFactoryBean。
因为FormattingConversionServiceFactoryBean即可以注册自定义的转换器,还可以注册自定义的注解驱动的格式转换器,使项目支持注解驱动格式化功能。
<bean id="conversionService"
class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
<property name="converters">
<list>
<!-- 这是之前配置自定义的转换器 -->
<bean class="cn.framelife.mvc.converter.StringToUserConverter"></bean>
</list>
</property>
</bean>
B、页面
<form action="user/create.abc" method="post">
用户名:<input type="text" name="username"><br/>
密 码:<input type="text" name="password"><br/>
生日:<input type="text" name="birthday"><br/>
工资:<input type="text" name="salary"><br/>
其它:<input type="text" name="other"><br/>
<input type="submit">
</form>
C、实体类中使用格式化注解
public class User implements java.io.Serializable {
private Integer id;
private String username;
private String password;
// 将如1999-09-09这样的字符串转换成Date对象
@DateTimeFormat(pattern = "yyyy-MM-dd")
private Date birthday;
// 把如5,500.00这个的字符串转换成long类型的数据
@NumberFormat(pattern = "#,###.##")
private long salary;
public long getSalary() {
return salary;
}
public void setSalary(long salary) {
this.salary = salary;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
D、Controler中处理
@RequestMapping("create")
public ModelAndView createUser(User user){
System.out.println(user.getBirthday()+"=="+user.getSalary());
ModelAndView view = new ModelAndView();
view.setViewName("/success");
return view;
}
6.8数据校验与国际化
6.8.1 JSR-303
JSR-303 是JAVA EE 6 中的一项子规范,叫做Bean Validation,官方参考实现是Hibernate Validator。
此实现与Hibernate ORM 没有任何关系。JSR 303 用于对Java Bean 中的字段的值进行验证。
Spring MVC 3.x之中也大力支持 JSR-303,可以在控制器中对表单提交的数据方便地验证。
JSR 303内置的约束规则:
@AssertTrue / @AssertFalse
验证适用字段:boolean
注解说明:验证值是否为true / false
属性说明:-
@DecimalMax / @DecimalMin
验证适用字段:BigDecimal,BigInteger,String,byte,short,int,long
注解说明:验证值是否小于或者等于指定的小数值,要注意小数存在精度问题
属性说明:公共
@Digits
验证适用字段:BigDecimal,BigInteger,String,byte,short,int,long
注解说明:验证值的数字构成是否合法
属性说明:integer:指定整数部分的数字的位数。fraction: 指定小数部分的数字的位数。
@Future / @Past
验证适用字段:Date,Calendar
注解说明:验证值是否在当前时间之后 / 之前
属性说明:公共
@Max / @Min
验证适用字段:BigDecimal,BigInteger,String,byte,short,int,long
注解说明:验证值是否小于或者等于指定的整数值
属性说明:公共
@NotNull / @Null
验证适用字段:引用数据类型
注解说明:验证值是否为非空 / 空
属性说明:公共
@Pattern
验证适用字段:String
注解说明:验证值是否配备正则表达式
属性说明:regexp:正则表达式flags: 指定Pattern.Flag 的数组,表示正则表达式的相关选项。
@Size
验证适用字段:String,Collection,Map,数组
注解说明:验证值是否满足长度要求
属性说明:max:指定最大长度,min:指定最小长度。
@Valid
验证适用字段:引用类型
注解说明:验证值是否需要递归验证
属性说明:无
使用Spring MVC 和 JSR-303的标注做表单提交的服务器端验证时,@Valid 标注的Command对象和BindingResult参数一定要紧挨着。要不然数据绑定错误直接抛异常,不会封装成一个BindingResult对象。
6.8.2Spring MVC数据校验
<mvc:annotation-driven/>会默认装配好一个LocalValidatorFactoryBean,通过在处理方法的入参上标注@Valid注解就可让Spring MVC在完成数据绑定后执行数据校验操作。
A、需要的额外的JAR包
除了Spring MVC本身的JAR包外,我们还需要一些额外的JAR包。
B、添加JSR 303注解
package cn.framelife.mvc.entity;
import java.util.Date;
import javax.validation.constraints.DecimalMax;
import javax.validation.constraints.DecimalMin;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Past;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.format.annotation.NumberFormat;
public class User implements java.io.Serializable {
private Integer id;
/*
* 测试的时候,发现在页面获取到的数据传到User模式后,都是非空的。
*/
@NotNull(message = "用户名不能为空")
private String username;
@Size(min = 2, max = 6, message = "长度是2-6之间")
private String password;
@Past(message = "必须是一个过去的时间")
@DateTimeFormat(pattern = "yyyy-MM-dd")
private Date birthday;
@DecimalMin(value = "1000.00", message = "工资必须大于1000.00")
@DecimalMax(value = "10000.00", message = "工资必须小于10000.00")
@NumberFormat(pattern = "#,###.##")
private long salary;
@Pattern(regexp = "1[3|4|5|8][0-9]\\d{4,8}", message = "手机号码不匹配")
private String phone;
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public long getSalary() {
return salary;
}
public void setSalary(long salary) {
this.salary = salary;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
C、Controller中的处理
@Controller
@RequestMapping("/user")
public class UserControl {
/**
* 此方法是用于第一次给页面添加一个user模型数据的
*/
@RequestMapping(value="/add",method = RequestMethod.GET)
public ModelAndView initForm(){
User user = new User();
return new ModelAndView("/add").addObject(user);
}
/**
* 给入参的模型对象User加入@Valid注解,说明这个对象里面的属性需要校验
* BindingResult入参是用以判断上面的校验是否出错
*/
@RequestMapping("create")
public ModelAndView createUser(@Valid User user,BindingResult bindingResult){
ModelAndView view = new ModelAndView();
System.out.println(user.getUsername()+"----");
//如果校验有问题,跑回原来的add.jsp页面,如果没问题跑到success.jsp页面
if(bindingResult.hasErrors()){
view.setViewName("/add");
}else{
view.setViewName("/success");
}
return view;
}
}
D、页面输入及输出
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<!-- 导入spring的表单标签库 -->
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<base href="<%=basePath%>">
<title>增加</title>
<meta http-equiv="pragma" content="no-cache">
<meta http-equiv="cache-control" content="no-cache">
<meta http-equiv="expires" content="0">
<meta http-equiv="keywords" content="keyword1,keyword2,keyword3">
<meta http-equiv="description" content="This is my page">
<!--
<link rel="stylesheet" type="text/css" href="styles.css">
-->
</head>
<body>
<!-- 在使用表单标签的时候需要获取到一个user模型数据,那我们不能直接访问页面(通过/user/add.abc访问),必须通过访问一次服务器,获取到一个user数据模型后才行,否则会出错 -->
<form:form modelAttribute="user" action="user/create.abc">
<!-- 输出所有的错误信息 -->
<form:errors path="*" /><br/>
<hr/>
<!-- 输出单个错误信息 -->
<form:errors path="username" ></form:errors><br/>
用户名:<form:input path="username"/><br/>
<form:errors path="password" ></form:errors><br/>
密 码:<form:password path="password"/><br/>
<form:errors path="birthday" ></form:errors><br/>
生日:<form:input path="birthday"/><br/>
<form:errors path="salary" ></form:errors><br/>
工资:<form:input path="salary"/><br/>
<form:errors path="phone" ></form:errors><br/>
电话:<form:input path="phone"/><br/>
<input type="submit">
</form:form>
</body>
</html>
6.8.3通过国际化资源显示错误信息
A、校验属性
/*
* 使用资源文件,这里不再需要message参数
*/
@Pattern(regexp = "1[3|4|5|8][0-9]\\d{4,8}")
private String phone;
B、资源文件(src下的messages.properties)
Pattern.user.phone=\u624B\u673A\u53F7\u7801\u4E0D\u5339\u914D
上面键值对中,
键是校验类javax.validation.constraints.Pattern的类名+被校验的模型对象名+属性名
值是”手机号码不匹配”
C、配置本地化资源文件(mvc-servlet.xml)
<!-- 配置国际化资源文件 -->
<!--
ReloadableResourceBundleMessageSource加载时,默认使用DefaultResourceLoader,他会先判断资源path是否带有classpath:前缀,如果有,用ClassPathResource去加载资源文件,如果没有试着用文件协议的url去访问,再没有就在contextPath即WEB-INF下查找
-->
<bean id="messageSource"
class="org.springframework.context.support.ReloadableResourceBundleMessageSource"
p:basename="classpath:messages">
</bean>
D、显示
页面显示与6.8.2中一样。
6.9文件上传
6.9.1加入JAR
由于SpringMVC使用的是commons-fileupload实现,所以除了之前使用到的Spring MVC用到的一些JAR外,还需要下面两个JAR: commons-fileupload-x.x.x.jar和commons-io-x.x.x.jar。
6.9.2配置MultipartResolver处理器(mvc-servlet.xml)
<!-- 设置MultipartResolver用以文件上传 -->
<!--
p:defaultEncoding请求的编码格式,必须与JSP页面的编码一样
p:maxUploadSiz上传文件的大小。5MB
p:uploadTempDir上传文件的临时路径,文件上传完后,临时目录中的临时文件会被自动清除
-->
<bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver"
p:defaultEncoding="UTF-8"
p:maxUploadSize="5242880"
p:uploadTempDir="upload/temp"/>
6.9.3Controller中处理
在Controller的方法中添加MultipartFile参数。该参数用于接收表单中file组件的内容
/**
* 如果只是上传一个文件,则只需要MultipartFile类型接收文件即可
* 如果想上传多个文件,那么这里就要用MultipartFile[]类型来接收文件,并且还要指定@RequestParam注解
* 并且上传多个文件时,前台表单中的所有<input type="file"/>的name都应该是file,否则参数里的file无法获取到所有上传的文件
*/
@RequestMapping("upload")
public ModelAndView upload(String name,@RequestParam("file") MultipartFile[] file,HttpServletRequest request) throws IllegalStateException, IOException{
String realPath = request.getSession().getServletContext().getRealPath("/uploadFile");
File pathFile = new File(realPath);
if(!pathFile.exists()){
pathFile.mkdirs();
}
for(MultipartFile f : file){
f.transferTo(new File(realPath+"/"+f.getOriginalFilename()));
}
ModelAndView view = new ModelAndView();
view.setViewName("/success");
System.out.println("----------------");
return view;
}
6.9.4编写页面表单
注意enctype="multipart/form-data"以及<input type="file" name="****"/>
<form action="user/upload.abc" enctype="multipart/form-data" method="post">
<input type="text" name="name">
<input type="file" name="file">
<input type="file" name="file">
<input type="submit" value="上传">
</form>
6.9.5 MultipartFile中的方法
使用getSize()方法获得文件长度,以此决定允许上传的文件大小。
使用isEmpty()方法判断上传文件是否为空文件,以此决定是否拒绝空文件。
使用getInputStream()方法将文件读取为java.io.InputStream流对象。
使用getContentType()方法获得文件类型,以此决定允许上传的文件类型。
使用transferTo(dest)方法将上传文件写到服务器上指定的文件。