IoC 容器
Bean的使用
示例代码
<!-- 首先在 ApplicationContext 的配置文件(此处命名为application-context.xml)中添加 Bean -->
<bean id="myId" class="MyClass"></bean>
// 初始化容器 ApplicationContext context = new
ClassPathXmlApplicationContext("application-context.xml");
// 获取对象
MyClass my = context.getBean("myId", MyClass.class);
Bean 的作用域
Bean 的作用域有 singleton, prototype, request, session, global session, application. 其中后四种是在web应用中使用的。
singleton: bean 的默认作用域, 也可通过 <bean id="myId" class="MyClass" scope="singleton"></bean>
来显式地指定作用域。 保证只会有一个实例存在(后续创建的实例与第一个创建的实例指向同一个对象)_
prototype: 每次引用该bean时都会创建一个新的实例。
Bean实例创建、销毁时对资源的申请和释放
创建 在添加Bean时指定初始化方法的名称,并在类中实现该方法即可
<bean id="myId" class="MyClass" init-method="init"></bean>
public class MyClass {
public void init() {}
}
销毁 在添加Bean时指定销毁方法的名称,并在类中实现该方法即可
<bean id="myId" class="MyClass" init-method="cleanup"></bean>
public class MyClass {
public void cleanup() {}
}
注意: scope为singleton的bean的destroy方法则是在容器关闭时执行,而scope为prototype的bean是不会执行destroy方法的
依赖注入
依赖注入的方法
- 构造函数依赖注入。为需要注入的类增加一个带参数的构造函数来完成依赖注入
public class MyClass {
Dependency dp = null;
public MyClass (Dependency instanceOfSubClassOfDependency) {
dp = instanceOfSubDependency;
}
}
- Setter 方法依赖注入。
public class MyClass {
Dependency dp = null;
public void setDp(Dependency instanceOfSubClassOfDependency) {
dp = instanceOfSubClassOfDependency;
}
}
如果是强依赖,最好选择构造函数注入,如果是可选依赖,最好 选择 Setter 方法注入
示例代码
- 通过构造函数传入基本类型来实现依赖注入
<!-- 对类实现了依赖注入的构造函数的话需要在Spring 的配置文件中进行描述 -->
<bean id="myId" class="MyClass">
<!-- 参数按照构造函数参数列表顺序给出 -->
<constructor-arg value = "..."></constructor-arg>
<!-- 或者通过index属性显式指定(index从0开始计数) -->
<constructor-arg index = "0" value = "..."></constructor-arg>
<!-- 或者通过指定参数类型来确定 -->
<constructor-arg type = "java.lang.String" value = "..."></constructor-arg>
<!-- 或者通过参数名称来指定参数值 -->
<constructor-arg name = "argName" value = "..."></constructor-arg>
</bean>
- 通过构造函数传入集合来实现依赖注入
public class MyClass {
String arg0;
int arg1;
public MyClass (Map<String, String> paras) {
this.arg0 = paras.get("arg0");
this.arg1 = Integer.valueOf(paras.get("arg1"));
}
}
<!-- 相对应的配置文件 -->
<bean id="myId" class="MyClass">
<constructor-arg>
<map>
<entry key = "arg0" value = "value0"></entry>
<entry key = "arg1" value = "value1"></entry>
</map>
</constructor-arg>
</bean>
- 通过配置文件来实现依赖注入
<!-- 设置配置文件的位置, Spring就可以从对应位置加载配置文件 -->
<bean id="myClassProperties" class =
"org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="location" value="classpath:myClass.properties"/> </bean>
<bean id="myId" class="MyClass">
<constructor-arg name = "arg0" value = "${arg0}"></constructor-arg>
<constructor-arg name = "arg1" value = "${arg1}"></constructor-arg>
</bean>
- 注入自定义类
<bean id="myId2" class="MyClass2">
<constructor-arg>
<ref bean="myId"/>
</constructor-arg>
</bean>
<bean id="myId" class="MyClass">
<constructor-arg name = "arg0" value = "${arg0}"></constructor-arg>
<constructor-arg name = "arg1" value = "${arg1}"></constructor-arg>
</bean>
- 通过 setter 函数来注入
public class MyClass {
private String arg0;
private int arg1;
public void setArg0(String arg0) {
this.arg0 = arg0;
}
public void setArg1(int arg1) {
this.arg1 = arg1;
}
}
<bean id="myId" class="MyClass">
<property name="arg0" value="value0"></property>
<property name="arg1" value="value1"></property>
</bean>
自动装配与Annotation
自动装配
如果一个bean的依赖很多,或者需要添加依赖,手动修改配置文件会很低效。所以Spring提供了自动装配(Autowiring)的机制 自动装配有几种类型:
注意: Autowire是根据 setter 进行自动注入的,所以需要实现 setter方法
- byName: 根据Bean名称进行装配
xml
<bean id = "myId" class = "myClass" autowire="byName"></bean>
- byType: 根据Bean类型进行装配
- constructor: 构造函数,根据类型
Annotation
XML文件配置Bean与Annotation的比较:
* XML: 繁琐,代码独立
* Annotation: 简洁,代码耦合
几种Annotation:
1. @Component
: 定义Bean
2. @Value
: properties 注入
3. @Autowired
& @Resource
: 自动装配依赖
4. @PostConstruct
& @PreDestroy
: 生命周期回调
要使用Annotation 需要在 XML配置文件中添加 <context:component-scan base-package="..."/>
@Component("myId")
// 如果不指定id的话,默认为MyClass
public class MyClass {
@Value("value0") String para0;
@Value("value1") int para1;
}
Spring AOP
@AspectJ annotation-based AOP
使用时需要添加aspectj依赖 在spring的配置文件中也需要在beans的namespace中添加aop的tag
<beans xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-2.0.xsd">
<aop:aspectj-autoproxy />
<!-- AOP 的使用 -->
<!-- 定义Aspect: 以定义一个日志功能Aspect为例-->
<bean id = "loggingAspect" class = "package.LoggingAspect"> </bean>
</beans>
// LoggingAspect.java
import org.aspectj.lang.annotation.Aspect;
// Aspect 的实现类需要添加 @Aspect的Annotation
@Aspect
public class LoggingAspect {
// 定义Pointcut
// Pointcut 表达式: designator(modifiers? return-type declaring-type? name(param) throws?) // 1.modifiers 是可选的,表示匹配的函数的修饰符,如public、private等 // 2.declaring-type 是可选的,表示匹配函数所在的包名,类名
// 例1. 匹配所有的public函数
// @Pointcut("execution(public * *(..))")
// private void publicMethod() {}
// 例2. 所有以save开头的函数
// @Pointcut("execution(* save*(..))")
// private void saveMethod() {}
// 例3. 通过组合已有的Pointcut来实现匹配所有以save开头的public函数
// @Pointcut("execution(publicMethod() && saveMethod())")
// private void publicSaveMethod() {}
@Pointcut("execution(* package.ObjectClass.*(..))")
private void pointcutName() {}
// 定义Advice
@Before("package.LoggingAspect.pointcutName()") // 此处也可以直接用pointcut表达式代替而不是填写pointcut名称
public void beforeActionName() {}
// 其他Advice的annotation
// @AfterReturing
// @AfterThrowing
// @After 等等
// 通过JoinPoint 获取函数上下文信息
@Before("pointcutName()")
public void afterActionName(JoinPoint jp) {
System.out.println(jp.getSignature());
}
// 注意@Around与其他有所不同,需要通过ProceedingJoinPoint获取上下文信息
@Around("pointcutName()")
public void aroundActionName(ProceedingJoinPoint pjp) {
System.out.println("Start"); // 调用目标函数执行 pjp.proceed();
System.out.println("Done");
}
// 获取返回值
@AfterReturing(pointcut="pointcutName()", returning="retVal")
public void afterReturingActionName(Object retVal) {
// do something with return value
}
}
Schema-based AOP
<!-- 在配置文件中配置AOP等 -->
<!-- 定义Aspect -->
<aop:config>
<aop:aspect id="loggingAspect" ref="loggingBean">
<!-- 定义Pointcut -->
<aop:pointcut id="pointcutName" expression="...">
<!-- 定义Advice -->
<aop:before pointcut-ref="pointcutName" method="methodInLoggingAspect"></aop:before>
</aop:pointcut>
</aop:aspect>
</aop:config>
<bean id="loggingBean" class="package.LoggingAspect">
</bean>
Spring 数据访问
Spring JDBC
<!-- 定义 JdbcTemplate -->
<bean id="JdbcExampleDao" class="package.JdbcExmapleDao">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 在 Spring 配置文件中配置 DataSource -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<property name="driverClassName" value="${jdbc.dirverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<context:property-placeholder location="db.properties"/>
<!-- db.properties 的内容 -->
jdbc.dirverClassName = com.mysql.jdbc.Driver
jdbc.url = jdbc:mysql://localhost/database
jdbc.username = username
jdbc.password = password
通过 Annotation 配置
@Repository
public class JdbcExampleDao {
private JdbcTemplate jdbcTemplate;
@Resource
public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
}
Spring 事务管理
- 统一的事务编程模型
- 编程式事务及声明式事务(AOP)
// org.springframwork.transaction
// spring 提供的事务接口
public interface PlatformTransactionManager {
TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;
void commit(TransactionStatus status) throws TransactionException;
void rollback(TransactionStatus status) throws TransactionException;
}
TransactionDefinition
- getName: 事务名称
- getIsolationLevel: 隔离级别
- getPropagationBehavior: 传播行为
- getTimeout: 超时时间
- isReadOnly: 是否只读事务
TransactionStatus
- isNewTransaction: 是否是新的事务
- hasSavepoint: 是否有savepoint
- isCompleted
- isRollbackOnly: 事务的结果是否是rollback-only
- setRollbackOnly
隔离级别
- ISOLATION_READ_UNCOMMITTED: 读未提交
- ISOLATION_READ_COMMITTED: 读提交
- ISOLATION_REPEATABLE_READ: 重复读
- ISOLATION_SERIALIZABLE: 串行化
- ISOLATION_DEFAULT: 默认
传播行为
- PROPAGATION_MANATORY 必须在一个事务中运行,不存在事务则抛异常
- PROPAGATION_NEVER 不应该在事务中运行,存在则抛异常
- PROPAGATION_NOT_SURPPORTED 不应该在事务中运行,存在则挂起
- PROPAGATION_SURPPORTS 不需要事务,有则在事务中运行
- PROPAGATION_REQUIRED 默认的传播行为,必须在事务中执行,如果不存在, 则启动新事物 (内部事务会影响外部事物)
- PROPAGATION_NESTED 必须在事务中执行,如果不存在,则启动新事物 (事务之间互不影响)
- PROPAGATION_REQUIRES_NEW 必须在新事务中执行,挂起当前事务
代码示例
声明式事务
<!-- 在spring配置文件中添加 AOP 和 Transaction 的 Schema -->
<beans xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<!-- 定义事务管理器 -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<property name="driverClassName" value="${jdbc.dirverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<context:property-placeholder location="db.properties"/>
<!-- 定义事务Advie -->
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<tx:method name="get*" read-only="true"/>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
<!-- 定义 Pointcut -->
<aop:config>
<aop:pointcut id="daoOperation" expression="execution(Pointcut表达式)"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="daoOperation"/>
</aop:config>
</beans>
- 通过注解的方式来声明事务
<tx:annotation-driven transaction-manager="txManager"/>
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<property name="driverClassName" value="${jdbc.dirverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<context:property-placeholder location="db.properties"/>
@Transactionnal(propagation = Propogation.REQUIRED, rollbackFor = Exception.class)
public void doSomething() {
// do work
}
@Transactional 的配置属性
- value: 使用的TransactionManager
- propagation
- isolation
- timeout
- readOnly
- rollbackFor
- rollbackForClassName
- noRollbackFor
- noRollbackForClassName
编程式事务
- TransactionTemplate
// 定义TransactionTemplate
public class Service {
private final TransactionTemplate transactionTemplate;
public Service(PlatformTransactionManager transactionManager) {
this.transactionTemplate = new TransactionTemplate(transactionManager);
this.transactionTemplate.setIsolationLevel(...);
this.transactionTemplate.setTimeout(...);
...
}
// 使用TransactionTemplate
public Object someMethod() {
return transactionTemplete.execute(new TransactionCallback() {
public Object doInTransaction(TransactionStatus status) {
// doSomething
// return something
}
});
}
}
- PlatformTransactionManager 的实现
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
def.setName("transactionName");
def.setPropagationBehavior(...);
TransactionStatus status = txManager.getTransaction(def);
try {
// do some trasaction logic
} catch (Exception e) {
txManager.roolback(status);
throw ex;
}
txManager.commit(status);
整合MyBatis
添加 mybatis-spring 依赖
定义 SqlSessionFactoryBean
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
</bean>
定义Mapper
public interface UserMapper {
User getUser(String userId);
}
<mapper namespace="package.UserMapper">
<select id = "getUser" resultType = "User">
select * from users where id = #{userId}
</select>
</mapper>
也可以用 Annotation 定义 Mapper
public interface UserMapper {
@Select("select * from users where id = #{userId}")
User getUser(@Param("userId") String userId);
}
定义 Mapper Bean
<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
<property name="mapperInterface" value="package.UserMapper"/>
<property name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>
也可通过Annotation配置
<!-- 首先需要在spring 配置文件中声明mybatis命名空间 -->
<!-- 开始mybatis的扫描 -->
<mybatis:scan base-package="package"/>
使用 Mapper
public class SomeService {
@Resource
private UserMapper userMapper;
public User getUser(String userId) {
return userMapper.getUser(userId);
}
}
通过 SqlSessionTemplate 可以使用 MyBatis 中 session 的接口
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg index="0" ref="sqlSessionFactory"/>
</bean>
public class UserDAO {
@Resource
private SqlSession sqlSession;
public User getUser(String userId) {
return (User) sqlSession.selectOne("package.UserMapper.getUser", userId);
}
}
Spring Web 框架
为什么要使用 Spring Web 框架?
- 需要一个框架来处理地址到处理逻辑的路由
- 需要一个框架来处理MVC
Web MVC 框架
以 Servlet
为核心构建的, 其核心是 DispatherServlet
, 在其背后提供了一些工具来解决上述的问题。
Handler Mapping
用于解决请求路径到处理逻辑的映射关系Controller
用于负责视图和模型之间的关系View Resolver
和View
则是视图相关的工具
DispatherServlet 的使用
在 web.xml
文件中进行配置
<servlet>
<servlet-name>example</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatherServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>example</servlet-name>
<url-pattern>/api/*</url-pattern>
</servlet-mapping>
同时在 WEB-INF
目录下配置一个名为 [servlet-name]-servlet.xml
的配置文件用于配置DispatherServlet
可配置项:
- HandlerMapping
- Controllers
- View 解析相关
注意: DispatherServlet 不是唯一的,可以定义多个 DispatherServlet
可以通过 ContextLoaderListener
来配置多个 DispatherServlet 之间共同的配置
ContextLoaderListener
首先需要在 web.xml
文件中进行配置
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<!-- 指定Spring配置文件 -->
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/applicationContext*.xml</param-value>
</context-param>
实现 Controllers
// 需要在spring配置文件中开启自动发现才能使用Annotation来定义Controller
// <context:component-scan base-package="..."/>
@Controller
@RequestMapping(value = "/hello")
public class HelloController {
// 当访问地址为 /hello/spring 时就会将请求转接到spring函数的处理中,输出"Hello, Spring Web"
@RequestMapping(value = "/spring")
public void spring(HttpServletResponse response) throws IOException {
response.getWriter().write("Hello, Spring Web!!");
}
}
RequestMapping 的属性
- name: 名称
value & path : 路径, 比如 “/hello”
注意:
以下代码效果完全相同@RequestMapping("/hello") @RequestMapping(value = "/hello") @RequestMapping(path = "/hello")
- method: 匹配请求的请求方法,如 “GET”
- params: 请求参数,请求地址中加 “?key=value&key2=value2”. 当请求含有特定参数时才会转发到该请求处理函数
- headers: 请求头
- consumes: 请求的媒体类型,处理请求类型是特定数据类型的请求
- produces: 响应的媒体类型,处理请求特定数据类型的请求
示例:
// RestAPI 的路径参数匹配
@RequestMapping(path="users/{userId}")
public String webMethod(@PathVariable String userId) {
// do Work
}
函数参数:
- HttpServletResponse/HttpServletRequest, HttpSession
- Reader/Writer
- @PathVariable
- @RequestParam
- @RequestHeader
- HttpEntity
- @RequestBody
- Map/Model/ModelMap
函数返回值:
- void 表示该请求处理函数不用返回view,函数自身会处理与view相关的逻辑
- String: view 名称, 也可以用 @ResponseBody 这个 Annotation 来说明返回值不是view名称而是响应内容
- HttpEntity
- View: spring定义的view对象
- Map: 代表返回的Model
- Model
- ModelAndView
示例代码
@RequestMapping(value = "/spring/{user}")
public void spring(
@PathVariable("user") String user,
@RequestParam("msg") String msg,
@RequestHeader("host") String host,
HttpServletRequest request,
Writer writer) throws IOException {
writer.write("URI: " + request.getRequestURI());
writer.write("Hello, " + user + ": " + msg + ", host=" + host);
}
- 表单
@RequestMapping(value = "/spring/login")
public void login(
@RequestParam("name") String name,
@RequestParam("password") String password,
Writer writer)
throws IOException {
// do work
}
对于有很多信息的表单,挨个写 RequestParam 就会显得繁琐, Spring 提供了一个 ModelAttribute 的 Annotation
可以从参数匹配一个model
@RequestMapping(value = "/spring/login")
public void login(@ModelAttribute User user, Writer writer) {
// do work
}
- 上传文件
首先定义一个与上传相关的bean
<bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="maxUploadSize" value="100000"/>
</bean>
@RequestMapping(path = "/form", method = RequestMethod.POST)
public String handleFormUpload(@RequestParam("file") MultipartFile file) {
// do work
}
- HttpEntity
@RequestMapping("/something")
public ResponseEntity<String> handle(HttpEntity<byte[]> requestEntity) {
String requestHeader = requestEntity.getHeaders().getFirst("MyRequestHeader");
byte[] requestBody = requestEntity.getBody();
// do something with request header and body
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.set("MyResponseHeader", "MyValue");
return new ResponseEntity<String>("Hello World", responseHeaders, HttpStatus.CREATED);
}
- @RequestBody & @ResponseBody
@RequestMapping(value = "/spring")
@ResponseBody
public String spring(@RequestBody String body) throws IOException {
return "Hello" + body;
}
View 解析
InternalResourceViewResolver
一般处理 Servlet, JSP<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <!--指定view所在的相对路径--> <proerty name="prefix" value="/WEB-INF/jsp/"/> <!--指定view的扩展名--> <proerty name="suffix" value=".jsp"/> </bean>
如果请求处理函数返回的view名称为
resultView
, 则View的路径为/WEB-INF/jsp/resultView.jsp
FreeMarkerViewResolver
针对Freemarker
模板引擎的 viewResolver<!--需要先配置freemarker模板的路径--> <bean id="freemarkerConfig" class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer"> <proerty name="templateLoaderPath" value="/WEB-INF/freemarker/"/> </bean> <!--然后就与上面类似,定义view的prefix 和 suffix--> <bean id="viewResolver" class="org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver"> <!--是否缓存View的映射--> <proerty name="cache" value="true"/> <proerty name="prefix" value=""/> <proerty name="suffix" value=".ftl"/> </bean>
ContentNegotiatingViewResolver
是 ViewResolver 的组合,可以根据客户端的请求返回不同的view- 根据请求路径中的文件扩展名: 如:
user.json
,user.xml
,user.pdf
- 根据请求头中的
Accept
头来决定: 如application/json
,application/xml
<bean class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver"> <property name="viewResolvers"> <list> <bean id="viewResolver" class="org.springframwork.web.servlet.view.freemarker.FreeMarkerViewResolver"> <property name="cache" value="true"/> <property name="prefix" value=""/> <property name="suffix" value=".ftl"/> </bean> </list> </property> <!--如果通过viewResolvers匹配不到合适的view,就会采用defaultView--> <property name="defaultViews"> <list> <bean class="org.springframework.web.servlet.view.json.MappingJackson2JsonView"/> </list> </property> </bean>
- 根据请求路径中的文件扩展名: 如:
Freemarker
Freemarker 指令
- 开始标签:
<#directivename parameters>
- 结束标签:
</#directivename parameters>
if 指令:
<#if balance < 10> poor guy
<#elseif balance = 10> not so poor
<#else> haha
</#if>
list 指令: freemarker 的 foreach
<#list animals as animal>
${animal.name}<br>
</#list>
include 指令:
将需要重复使用的固定内容插入
<#include "/copyright_footer.html">