Spring学习笔记(二)

这里的 Spring 指的还是 SpringFramework ;Spring 是做什么的上一篇中已经说的很详细了,也主要是说的怎么用,这篇主要是对上篇的知识补充,多是理论的补充,有些重复内容,写的也很混乱….
Spring AOP 详细的整理已出!强行下一篇就是!

概述

Spring 提供了一站式解决方案,它主要包含六大模块:

  1. Spring Core
    核心功能:IoC 容器,解决对象的创建及其依赖关系
    jar 包组成由:
    spring-expression-4.3.11.RELEASE.jar
    spring-beans-4.3.11.RELEASE.jar
    spring-core-4.3.11.RELEASE.jar
    commons-logging-1.2.jar
    spring-context-4.3.11.RELEASE.jar
  2. Spring Web
    这是 spring 对 web 模块的支持,也就是说可以与 Struts 整合,把 Action 的创建交给 spring;
    或者可以使用自家的 springMVC,相关的 jar 包有:
    spring-web-4.3.11.RELEASE.jar
    spring-webmvc-portlet-4.3.11.RELEASE.jar
    spring-webmvc-4.3.11.RELEASE.jar
    spring-websocket-4.3.11.RELEASE.jar
  3. Spring DAO
    这是 spring 对 jdbc 的支持,比如 JdbcTemplate 模板工具类,相关的 jar 包有:
    spring-jdbc-4.3.11.RELEASE.jar
    spring-oxm-4.3.11.RELEASE.jar
    spring-jms-4.3.11.RELEASE.jar
    spring-tx-4.3.11.RELEASE.jar
    spring-orm-4.3.11.RELEASE.jar
  4. Spring ORM
    这是 spring 对 ORM 的支持,既可以与 hibernate 整合,也可以选择使用 spring 对 hibernate 操作的封装
  5. Spring AOP
    上篇中说过了,对 AOP 的支持,也就是面向切面编程,相关的 jar 包有:
    spring-aop-4.3.11.RELEASE.jar
    spring-instrument-4.3.11.RELEASE.jar
    spring-aspects-4.3.11.RELEASE.jar
    spring-instrument-tomcat-4.3.11.RELEASE.jar
  6. Spring EE
    这是 spring 对 javaEE 其他模块的支持

Bean的创建

关于 Bean 的生命周期在上一篇说的非常非常详细了,总结就是:如果你设置了单例模式,也就是默认的,并且懒加载没有开启(默认)那么它会在启动的时候创建(实例化);但是如果设置的是 prototype 就是在获取的时候才进行实例化

使用构造函数

在装配 Bean 的时候,上一篇使用的是 property 标签,通过相应的 Set 方法进行注入,其实还可以使用 constructor-arg 标签来进行初始化,它们的区别是一个是通过 set 方法,一个是通过构造函数(不一定非要是构造,其他带参数的方法也行,比如下面要说的工厂方式),默认用的就是无参的构造函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<bean id="student" class="com.rc.sp.Student">
<constructor-arg name="id" value="1"/>
<constructor-arg name="name" value="student"/>
<constructor-arg name="dream">
<list>
<value>soldier</value>
<value>scientist</value>
<value>pilot</value>
</list>
</constructor-arg>
<constructor-arg name="score">
<map>
<entry key="math" value="90"/>
<entry key="english" value="85"/>
</map>
</constructor-arg>
</bean>

但是需要注意下顺序,如果顺序和构造函数不同要使用 index (从 0 开始)或者 name 属性来指定;name 省略其实也可以

使用工厂

创建 bean 的方式中,除了使用构造函数常见的还有就是使用工厂模式了,并且分为静态工厂和实例工厂;如果使用的不是静态方法,那么就需要先创建工厂对象然后通过方法进行获取实例:

1
2
3
4
<!-- 先创建工厂对象 -->
<bean id="factory" class="com.bfchengnuo.TestFactory"/>
<!-- 使用工厂的方法创建实例 -->
<bean id="user" factory-bean="factory" factory-method="getInstance"/>

如果使用的是静态的,那么就不需要创建工厂对象了:

1
2
<!-- 使用工厂的方法创建实例 -->
<bean id="user" class="com.bfchengnuo.TestFactory" factory-method="getStaticInstance"/>

p命名空间

p 命名空间是用来简化属性注入的,就是说让我们少写一些 property 标签,使用之前需要先引入 xmlns,但是没有对应的 Schema 文件,因为没有办法预先知道用户使用的属性名称,所以也就无法定义 Schema 文件。
所以说只需要加入 xmlns:p="http://www.springframework.org/schema/p" 就可以使用了,具体的使用方式是:

1
2
<bean id="rob" class="..TestBean" p:name="Rob Harrop" p:spouse-ref="sally"/>
<bean id="sally" class="..." />

这样是不是简洁多了呢,使用 p:name 直接注入其 name 属性的值,p:spouse-ref 就是注入 spouse 属性,不过是引用的其他 bean,当然使用的还是 set 方法来进行注入的。

AOP编程

Spring中的AOP 文章已经整理完,关于 AOP 的详细信息推荐去看看!
在 Spring 中的 AOP 处理中,如果加入容器的目标对象有实现接口就使用 JDK 代理,如果目标对象没有实现接口就使用 Cglib 代理。

另外补充下,最早做 AOP 的是 Eclipse 的 Aspect,Spring 也是引用的它

代理对象的创建-Cglib

不知道以前是否写过,在创建代理对象的时候,如果目标对象有接口那么用 JavaAPI 的那一套就可以创建代理对象了,但是如果目标对象没有实现接口,那么就白搭了,好在有一个叫 Cjlib 的库,它被许多 AOP 框架使用,它就是为了解决这个问题,具体的介绍自行搜索,这里只说明它的作用
它是通过继承的方式来实现的,首先有一个通用的工具类 Enhancer,通过这个工具类设置父类为我们的目标对象,然后设置回调函数,最后创建一个子类返回就可以了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class ProxyFactory implements MethodInterceptor{
// 维护目标对象
private Object target;
public ProxyFactory(Object target){
this.target = target;
}

public Object getProxy(){
// 1.工具类
Enhancer en = new Enhancer();
// 2.设置父类
en.setSuperclass(target.getClass());
// 3.设置回调函数
en.setCallback(this);
// 4.创建子类(代理对象)
return en.create();
}

@Override
public Object intercept(Object obj, Method method, Object[] args,
MethodProxy proxy) throws Throwable {
System.out.println("开始");
// 执行目标对象的方法
Object returnValue = method.invoke(target, args);
return returnValue;
}
}

怎么用就不用说了吧…..因为本质是采用的继承的方式,所以有几个注意事项:

  • 代理的类不能为 final,否则报错
  • 目标类的方法不能为 final 或者 static,否则不会进行拦截

当然不要忘记了代理类实现接口。
JDK 中的代理一般是 $ 开头,Cglib 一般就是代理对象名开头后面再加一些东西

关注分离

前面也是提到过这个词的,什么关注分离思想,关注点是什么呢?其实指的就是重复代码;在一些方法中,我们可能需要写许多重复代码,而真正的核心代码就只有几行。
比如 DAO 层中的 session 处理的代码,又是事务又是连接的,每个方法都需要写,这些代码就可以看作是关注点,我们要做的就是分离它
使用代理,可以在运行期间,执行核心业务代码的时候动态植入关注点代码

AOP 的目标就是让关注点代码和业务代码进行分离

对比上一篇“专业”的解释,我们来一把通俗的介绍,当然可能并不准确;
通知/增强: 就是 关注点,也就是重复的代码;
切面: 就是关注点形成的类,也就是说很多重复的代码就可以形成一个切面;
切入点:运行时在哪里织入切面类的代码,拦截的作用;

面向切面编程:对很多重复的代码进行抽取,然后在运行的时候往业务方法上进行织入切面类代码。

Spring中使用AOP

提起 AOP 那其实要说下 Aspect 了,它值得去单独去学习,也不少的内容,这里就只是简单的说下 Spring 中常用的几种形式,如果有必要,会单独开一篇写 AspectJ ,并且一篇都不一定写的完,大体浏览了下,内容确实不少,慢慢来吧

首先必要的在 xml 中引入命名空间和相关 jar 包就不细说了,记得配置开启注解,这个比较省事,大都采用这种形式吧

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
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">
<!-- 自动扫描方式加载对象 -->
<context:component-scan base-package="com.bfchengnuo"/>

<!-- 启动 @AspectJ 支持 -->
<aop:aspectj-autoproxy/>
</beans>

上一篇中使用的 XML 配置,然后下面简单介绍下注解的使用:

  • @Aspect
    声明该类是一个切面, Spring 将会把它当作一个特殊的 Bean(一个切面),也就是说不对这个类本身进行动态代理 。
  • @Pointcut
    定义切入点,切入点的名称就是此方法名,也就是确定拦截哪些方法,就是为哪些类生成代理对象
    参数为切入点表达式:@Pointcut("execution (* com.bfchengnuo.service..*.*(..))")
    第一个 * 指的是任何返回值,(..) 指的是任何参数,.. 指的是本包、以及及其子包下,多个可以使用 || 分割,甚至可以在前面加 ! ,其他的应该就更好理解了;需要注意的是最后一定是定义到方法的!

其他的注解就没什么难度了,来看个栗子就都知道了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@Aspect
public calss Audience {

// 把 Performance 的 perform 方法定义为名为 performance 的切点
@Pointcut("execution(** concert.Performance.perform(..))")
public void performance() {}

// 表演之前输出
@Before("performance()")
public void silencePhones() {
System.out.println("Siliencing phones.");
}

// 表演之后输出
@AfterReturning("performance()")
public void clap() {
System.out.println("Claping.");
}

// 表演失败(抛出异常)输出
@AfterThrowing(“Performance()”)
public void refund() {
System.out.println("Demanding a refund");
}
}

详细的理论知识补充可以去参考 Spring中的AOP 一文

对JDBC的支持

在刚开始的时候已经看到了,Spring 中有个模块就是 Spring DAO,说明了 Spring 对 JDBC 的支持还是很好的,;例如体现在对 C3P0 连接池的支持上,甚至比 Hibernate 都好,并且有 JdbcTemplate 模板工具类(类似 DBUtils)。
下面配置 dataSource 的方式应该都比较熟悉了,使用 C3P0:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
<property name="driverClass" value="org.gjt.mm.mysql.Driver"/>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/ssh?useUnicode=true&amp;characterEncoding=UTF-8"/>
<property name="user" value="root"/>
<property name="password" value="123456"/>
<!--初始化时获取的连接数,取值应在minPoolSize与maxPoolSize之间。Default: 3 -->
<property name="initialPoolSize" value="1"/>
<!--连接池中保留的最小连接数。-->
<property name="minPoolSize" value="1"/>
<!--连接池中保留的最大连接数。Default: 15 -->
<property name="maxPoolSize" value="300"/>
<!--最大空闲时间,60秒内未使用则连接被丢弃。若为0则永不丢弃。Default: 0 -->
<property name="maxIdleTime" value="60"/>
<!--当连接池中的连接耗尽的时候c3p0一次同时获取的连接数。Default: 3 -->
<property name="acquireIncrement" value="5"/>
<!--每60秒检查所有连接池中的空闲连接。Default: 0 -->
<property name="idleConnectionTestPeriod" value="60"/>
</bean>

<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>


<bean id="userDao" class="com.curd.spring.impl.UserDAOImpl">
<property name="jdbcTemplate" ref="jdbcTemplate"></property>
</bean>

并且顺便把 JdbcTemplate 也进行实例化了,它需要一个数据域,可以 set 方法指定,也可以构造函数给它;
至于它怎么使用,看个栗子就都知道了,会感觉很熟悉吧….

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
public class UserDAOImpl extends JdbcDaoSupport implements IUserDAO {

public void addUser(User user) {
String sql = "insert into user values(?,?,?)";
this.getJdbcTemplate().update(sql, user.getId(), user.getUsername(),
user.getPassword());
}

public void deleteUser(int id) {
String sql = "delete from user where id=?";
this.getJdbcTemplate().update(sql, id);

}

public void updateUser(User user) {
String sql = "update user set username=?,password=? where id=?";
this.getJdbcTemplate().update(sql, user.getUsername(),
user.getPassword(), user.getId());
}

public String searchUserName(int id) {// 简单查询,按照ID查询,返回字符串
String sql = "select username from user where id=?";
// 返回类型为String(String.class)
return this.getJdbcTemplate().queryForObject(sql, String.class, id);

}

public List<User> findAll() {// 复杂查询返回List集合
String sql = "select * from user";
return this.getJdbcTemplate().query(sql, new UserRowMapper());

}

public User searchUser(int id) {
String sql="select * from user where id=?";
return this.getJdbcTemplate().queryForObject(sql, new UserRowMapper(), id);
}

class UserRowMapper implements RowMapper<User> {
     //rs为返回结果集,以每行为单位封装着
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;
}
}
}

用户自己编写DAO 只需要继承 JdbcDaoSupport, 就可以注入 JdbcTemplate,下面就可以直接用了;JdbcTemplate 基本就可以满足一些日常需求了

关于事务

事务可分为两种,编程式事务和声明式事务;
编程式的我们都用过,比如 JDBC 中的 conn.setAutoCommite(false),或者 Hibernate 中的 session.beginTransaction();好处是操作比较灵活,能够实现细粒度的控制(但在 Spring 中一般不提倡使用);
声明式事务就是字面意思,先声明后面直接用,如果不需要细粒度的事务控制那么可以选择,在 Spring 中只需要在配置文件中配置一下就可以了,无侵入性、无耦合的,是基于 AOP 的;
下面主要说说声明式事务,基于 AOP 那也就可以得出只能对方法进行事务控制,对方法内的几行实现事务控制是做不到的,可以使用两种方式进行配置,XML 或者 注解;

XML配置方式

XML 的话这样配:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<!-- 定义事务管理器(声明式的事务) -->    
<bean id="transactionManager"
class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
<!-- Hibernate版(上)JDBC版(下) -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>

<!-- 配置事务增强(如何管理事务) -->
<!-- 将我们想要施加在事务中的语义封装在<tx:advice/>中,
其中默认的设置为:事务性传播设置是REQUIRED;隔离级别为DEFAULT;事务是读/写;
事务超时默认是依赖于事务系统的,或者事务超时没有被支持;
任何 RuntimeException 将触发事务回滚,但是任何 checked Exception 将不触发事务回滚。-->
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<tx:method name="save*" propagation="REQUIRED" read-only="false"/>
<tx:method name="add*" propagation="REQUIRED"/>
<tx:method name="delete*" propagation="REQUIRED"/>
<tx:method name="test*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>

<!-- 设置一个 pointcut 确保由 "txAdvice" bean定义的事务通知在应用中合适的点来执行,
然后使用一个通知器(advisor)将该切面与 txAdvice 绑定到一起,
其中 expression 的属性是织入点语法 -->
<aop:config>
<aop:pointcut expression="execution(public * com.niu.service..*.*(..))" id="transactionAop"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="transactionAop"/>
</aop:config>

<!-- *******************分割线************************ -->
<!--指定异常回滚类型-->
<tx:method name="save*" propagation="REQUIRED"
rollback-for="NoProductInStockException"/>

<!--即使遇到没有经过处理的InstrumentNotFoundException异常,也不要回滚事务-->
<tx:method name="save*" propagation="REQUIRED"
no-rollback-for="InstrumentNotFoundException"/>

除了上面列出的两种事务管理器,其实还有 JpaTransactionManager、JtaTransactionManager 等,就不介绍了,声明事务管理并不是这一种,更多的可以参考最后一个参考连接
分割线下的不设置也可以,大概……. ;这里的 txManager 就相当于是个切面了
<tx:method>设置:

属性是否需要默认值描述
name与事务属性关联的方法名。
propagationREQUIRED事务传播行为
isolationDEFAULT事务隔离级别
timeout-1事务超时时间,以秒为单位
readonlyfalse事务是否只读
rollback-for将被触发回滚的Exception,以逗号隔开
no-rollback-for不被触发回滚的Exception,以逗号隔开

注解方式

使用注解的方式还是要配下 XML 文件,起码要设置开启扫描、事务管理器之类的:

1
2
3
4
5
6
7
<bean id="txManager" class="org.springframework.orm.hibernate5.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
<!--启动注解版事务管理开关-->
<tx:annotation-driven transaction-manager="txManager"/>

<context:component-scan base-package="com.bfchengnuo" />

然后就可以使用 @Transactional 注解进行设置了,关于这个注解前面文章中讲过(Spring 中的注解

参考&资料

AOP 系列:https://my.oschina.net/itblog/blog/208067
https://jacksmiththu.github.io/2017/06/26/Spring%E4%B8%AD%E7%9A%84AOP/
https://www.ibm.com/developerworks/cn/education/opensource/os-cn-spring-trans/index.html
http://www.cnblogs.com/hellojava/archive/2012/11/21/2780694.html

喜欢就请我吃包辣条吧!

评论框加载失败,无法访问 Disqus

你可能需要魔法上网~~