Spring
spring是一个容器,它帮助我们管理java对象。它的核心思想是Inversion of Control,意思是把对象的创建,赋值,管理工作都交给代码之外的容器实现,这样做的好处就是解耦合。
那么解耦合的好处又是什么呢?当我们需要对代码的一些部分进行修改时(比如将数据库从mysql换成oracle),只需要修改spring配置文件即可,不光降低了修改的工作量,更为重要的一点是热插拔(改变代码要生效必须重启服务器,而该配置文件不需要)。servlet其实就是IOC的一个体现,它是由Tomcat容器创建。
实现IOC的方式有多种,DI(Dependency Injection)是用的最广泛的一个,spring实现IOC的方式就是DI,它的底层是通过反射来创建对象。
【什么对象应该放到spring容器中,让它帮我们管理?】
dao类,service类,controller类和工具类。
【什么对象不应该放到spring容器中呢?】
实体类对象,因为这些对象来自数据库。另外servlet、listener以及filter这些对象已经放在tomcat容器中了,因此也不需要放入spring容器中。
1. 初步使用
首先创建一个接口以及它的实现类(比如MyService接口,实现类名为MyServiceImple)。
1.1 创建spring配置文件
在resources目录下创建【applicationContext.xml文件】(spring配置文件的规范名称)
配置文件内容如下:
1 |
|
1.2 创建容器对象ApplicationContext
1 | ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");//从classes文件夹中读取配置文件,创建spring容器,【spring容器创建完毕后,会为所有bean创建对象,并保存在容器中】 |
可以调用ac.getBeanDefinitionCount();
获取当前spring容器中的对象总数。ac.getBeanDefinitionNames();
可以获取spring容器中所有对象的名称数组。
1.3 使用spring创建非自定义类对象
比如想让spring创建一个日期类
把日期类的bean注册到spring容器
1
<bean id="mydate" class="java.util.Date"/>
获取日期类对象
1
2ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");//读取配置文件,创建spring容器
Date d = (Date) ac.getBean("mydate");
1.4 用配置文件给spring创建的对象赋值(需要经常改动的部分用此法)
该方法最大的优点,就是代码和赋值完全是分开的,改代码的值不用重启服务器。
1.4.1 设值set注入
其原理就是Spring调用了对象的set方法给对象的属性赋值,使用该方法的前提是对象有set方法,且【属性名】和set方法的【参数名】一致。
在bean标签中用property标签对属性逐个赋值。
1 | <bean id="MyService" class="com.aaron.service.impl.MyServiceImple"> |
【赋值引用类型属性】
将要赋值的引用类型的bean标签注册beans标签中
利用ref赋值引用类型属性
1 | <bean id="SomeObject" class="com.aaron.service.impl.SomeObject"/> |
1.4.2 构造注入
如同它的名字,其原理是spring调用对象的构造方法给对象属性赋值。注意,和设值注入一样,构造方法的参数名要和其对应的属性名保持一致,这样才方便spring通过标签中的name定位到对应的属性。
1 | <bean> |
1.4.3 自动注入
对象某一属性类型为引用类型时,可用此法赋值
【byName】
添加一个对应的引用类型的bean,该bean的id与对象中该引用类型的属性名相同,这时使用autowired绑定即可自动给引用类型对象赋值。
1 | <bean id="Student" class="com.aaron.service.impl.MyServiceImple" autowire="byName"> |
【byType】
添加一个对应的引用类型的bean,该bean的class与引用类型为同源关系时
1 | <bean id="Student" class="com.aaron.service.impl.MyServiceImple" autowire="byType"> |
同源是指:
- 两个类型完全一致
- 两个类型为父子关系
- 两个类型为实现关系
2. 多配置文件
小项目使用一个spring配置文件够了,但是一旦项目规模上来,spring配置文件的信息太多久会造成读取缓慢。另外一般一个项目是由多人共同开发的,大家一起往一个配置文件读写就会存在冲突问题,因此最好使用多配置文件。
多文件分配方案有多种,我们以一个功能模块一个配置文件为例
为每一个模块创建一个sprint-xxx.xml配置文件(它们必须位于同一目录),比如spring-module1.xml,spring-module2.xml
创建一个主配置文件applicationContext.xml,它的作用就是包含其他部分配置文件
1
2
3
4
5
6
7
8
9
10
11
12
13
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<import resource="classpath:spring-module1.xml"/>
<import resource="classpath:spring-module2.xml"/>
<!--或者使用更方便的通配符-->
<import resource="classpath:spring-*.xml"/>
</beans>
3. 使用注解创建对象以及属性赋值(不需要经常改动的部分用此法,常用)
【注意】spring版本大于3.2.3才支持java1.8,报错的话要么升级spring版本,要么降低java版本。
- 必须先用maven加入spring-aop依赖。
- 给类加上不同功能的spring注解
1 | //这个value就相当于配置文件中bean的id |
- 在spring配置文件中加入组件扫描器标签,说明注解在项目中的位置
1 |
|
使用,跟之前一样
1
2
3ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
MyServiceImple obj = (MyServiceImple) ac.getBean("MyServiceImple");
obj.speak();
3.1 不同功能的spring注解
@Repository()
用于创建dao层对象@Service()
用于创建service对象@Controller()
用于创建controller对象
它们的使用与@Component()
相同,不过都有专门针对某层的额外功能。而@Component()
是用于除了以上三种情况的其他任何情况。
3.2 属性赋值
3.2.1 基本类型赋值
直接在实现类中,属性的上方利用注解:@value(value = "xxx")
即可赋值,此时不需要set方法。
1 |
|
或者也可以在set方法上注解。
3.2.2 引用类型赋值
先要给引用类型的类定义加上注解(代表将它的bean注册到spring容器中),然后在其他类中,属性为该引用类型的上方加上autowired(默认byType)即可。
1 |
|
要指定byName的话,按如下方式
1 |
|
默认的byType有时会产生歧义,这时加上byName即可解决。
4. AOP
想要给当前已经存在类或者方法增加额外(非主要)功能时,或者要给项目中的多个类或方法增加相同的功能时,用AOP。
面向切面编程(Aspect Oriented Programming),底层原理是动态代理(若目标类有接口用jdk动态代理,若没接口用cglib的动态代理),因为动态代理的使用方式比较灵活,它所做的就是提供动态代理使用的规范。Aspect指的是【非业务】功能,即不影响主业务正常进行的辅助性功能,比如计算某个业务方法的运行时间,打印某个业务处理的日志信息,这一类【非业务】功能可以理解为对原功能的增强,通过动态代理来实现这类功能,可以在不改变原业务方法的基础上增加功能,满足开闭原则。
常见的切面功能有:日志、事务、统计信息,参数检查,权限验证
切面三要素:
- JoinPoint(连接点),将切面功能和业务功能连接起来的位置,实际上就是业务层的方法(即调用切面功能的方法)。
- PointCut(切入点),连接点的集合。
- Advice,表示切面功能执行的时机,比如是在业务层方法执行前还是执行后。
spring虽然内置了aop框架,但是比较笨重,所以我们一般使用另外一个专门的aop框架: aspectJ,spring中也集成了这个框架。
4.1 aspectJ的使用
一般使用注解的方式(也可以通过xml文件配置)。
4.1.1 切面的执行时间:Advice
1 | //目标类方法执行后切面方法执行 |
4.1.2 切面执行的位置(切入点pointCut)
使用切入点表达式,它的原型为
1 | execution( |
其中?代表可选。
该表达式声明了哪些包中的哪些类中的哪些方法要增加切面功能,表达式中的参数都是pattern,也就是说他们可以用通配符来表示
“*
”代表0~多个任意字符,
“..
”用在方法参数中代表任意多个参数,用在包名后代表当前包及其子包路径
“+
”用在类名后表示当前类及其子类,用在接口后表示当前接口及其实现类
举例:
execution(public * *(..))
将切入点指定为任意public方法
execution(* set*(..))
将切入点指定为函数名称以“set”开头的所有方法
execution(* com.xyz.service.*.*(..))
4.2 基于注解的AOP使用示例
4.2.1 JoinPoint
在切面方法中,可以利用joinPoint参数(必须是第一个参数)获取到该切面方法附着的主方法【在执行时】的全部信息。
1 |
|
4.2.2 before
在spring配置文件中声明自动代理生成器
<aop:aspectj-autoproxy/>
确定要使用切面方法的函数(假定要在People类中speak函数执行前调用切面方法),这个函数也叫做joinPoint
1 |
|
- 创建切面类(在普通类的上面加上注解
@Aspect
),并在切面类中写切面功能方法,这些方法的上面要加上advice注解比如@before
,和切入点表达式execution
1 |
|
切面类和目标类都要注册为bean
通过spring获取到目标类对象,调用对应的方法后就可看到效果(本例中在speak之前打印了当前时间,说明切面方法执行成功)
4.2.3 AfterReturning
1 |
|
4.2.4 ProceedingJoinPoint(最强)
1 |
|
环绕通知经常用来做事务,目标方法启动前开启事务,关闭后提交事务。
4.2.5 AfterThrowing
在目标方法抛出异常时就会执行该切面方法。
1 |
|
4.2.6 After
如果说AfterThrowing相当于catch语句,After就相当于finally语句。
1 |
|
4.2.7 PointCut
如果一个目标方法(pointCut)要绑定多个切面方法,那么可以给该pointCut起个别名。
1 |
|
然后其他切面方法都可以用别名更方便的关联到该pointCut。
1 |
|
5. Spring集成MyBatis
利用spring的ioc技术,将MyBatis框架中的对象交给spring统一管理,这样开发人员几乎无需知道MyBatis的具体使用方法,只需要会一个spring框架即可。
回忆MyBatis的使用过程,先要配置主配置文件和mapper文件,然后创建SqlSessionFactory对象,通过它获取到SqlSession,然后再通过SqlSession的getMapper方法获取到对应的dao对象。之前主配置文件中有定义一个连接池,该连接池功能比较弱,spring可以帮我们替换掉它。
因此,我们需要spring帮我们创建以下对象:
- 创建连接池对象(我们使用阿里的druid连接池)
- SqlSessionFactory对象
- dao对象
5.1 使用步骤
5.1.1 MyBatis部分
5.1.1.1 创建实体类和其对应的dao接口
5.1.1.2 创建对应的mapper文件
5.1.1.3 创建mybatis主配置文件
mybatis.xml放在resources目录下
1 |
|
可以看到现在mybatis主配置文件中没有配置连接数据库的相关信息,这是因为我们要更换其他的数据源,要在其他地方配置。
5.1.2 spring配置
5.1.2.1 配置DataSource
将其他数据源(如阿里的druid)依赖导入,在spring的配置文件中加入
1 | <!--声明数据源--> |
5.1.2.2 配置SqlSessionFactoryBean
1 | <!--声明mybatis提供的SqlSessionFactory类--> |
5.1.2.3 配置dao对象bean
1 | <!--创建dao对象,该配置内部调用getMapper()生成每一个dao接口的代理对象。【该bean不用id】--> |
【重点】这一步结束后,spring容器中就会存在所有的dao对象,它们的bean id为接口名字首字母小写(比如dao接口名为UserDao,那么spring帮我们创建的实现类名叫userDao)。
5.1.3 service层
一般项目中是service层对象调用dao层对象访问数据库。
5.1.3.1 创建service接口及其实现类
接口的实现类放到子包impl中。
5.1.3.2 将service实现类注册到spring
只需在service接口实现类中添加两个注解
1 |
|
5.1.3.3 通过service类调用dao对象完成数据库操作
1 |
|
【重点】spring整合mybatis后,事务自动提交,不需要我们手动调commit方法了。
5.2 配置文件分离
将属性配置文件和spring配置文件分开,方便维护。如之前数据库信息部分可以改为
1 | <!--把数据库的配置信息写到独立的文件jdbc.properties中,并将该文件位置告诉spring--> |
然后创建一个jdbc.properties文件,将数据库信息写进去
1 | jdbc.url=jdbc:mysql://localhost:3306/test |
6. 事务
什么是事务?
事务是一组sql语句,它们要不都执行,要不都不执行。
不同数据库sql语法不一样,事务相关的语句逻辑也不一样,spring做的就将它们全部统一了。实际开发中,事务一般放在service层的方法上,因为只有业务方法中才会调用多个dao方法,执行多个sql语句,需要用到事务。
spring把每个数据库访问技术对应的事务处理类都创建好了:
- DataSourceTransactionManager代表mybatis
- HibernateTransactionManager代表hibernate
我们只需要声明要使用的事务处理类的bean即可,比如用mybatis:<bean id="xx" class="...DataSourceTransactionManager">
6.1 配置事务
说明某个方法需要什么样的事务,即配置事务属性
事务的隔离级别
read_uncommitted,read_committed,repeatable_read,serializable
mysql默认为repeatable_read,oracle默认为read_committed
事务的传播行为
propagation_required,表示本方法必须在事务内执行(spring默认)
propagation_requires_new,
propagation_supports
事务提交/回滚的时机
a. 业务方法执行成功,没有异常抛出,spring会提交事务
b. 业务方法抛出【运行时异常】,spring调用回滚
c. 业务方法抛出【非运行时异常】,提交事务
6.2 使用事务
声明对应数据库访问技术的事务管理器对象
开始事务注解驱动(告诉spring我们采用注解的方式管理事务)
spring会对所有被@Transactional注解的方法使用事务,其实现原理就是aop的Around advice,在被注解的方法执行前开启事务,执行后关闭事务。
1 | <!--spring事务--> |
在要用的方法(必须为public)加上事务注解。这一步完成后,UserServiceImpl中所有方法都使用事务了。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class UserServiceImpl implements UserService {
private UserDao userDao;
//事务注解
public int addUser(User user) { //返回本次sql影响的行数
int re = userDao.insertUser(user);
return re;
}
public List<User> queryALlUsers() {
List<User> userList = userDao.selectUser();
return userList;
}
}@Transactional也可以加在类上面,效果是该类中所有的方法都会开启事务。
6.3 使用aspectj配置事务(针对大型项目)
该法是完全在xml文件中配置事务,达到了事务和代码完全分离,因此适合大型项目。
- 加入spring-aspects依赖
- 声明事务管理器对象(与上一小节相同)
- 配置事务属性(隔离级别,传播行为,超时等)
- 配置aop,指定哪些类要创建代理
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<!--spring事务,aspectj实现-->
<!--1. 声明事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--将数据源指定为当前连接的数据库-->
<property name="dataSource" ref="myDataSource"/>
</bean>
<!--2. 配置业务方法的事务属性,其中transaction-manager为上面事务管理器对象的id-->
<tx:advice id="transactionInterceptor" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="buy" isolation="DEFAULT" read-only="false"/> <!--每一个该标签代表一个方法的事务配置,name就是方法的名称(可用通配符,非全限定名称,就是方法本名)-->
<tx:method name="sell" isolation="DEFAULT" read-only="true"/> <!--只指定方法名,不指定全限定名称,那有重名方法怎么办?下面还要配置解决-->
</tx:attributes>
</tx:advice>
<!--3. 配置aop-->
<aop:config>
<!--配置切入点表达式:指定在哪些包中使用事务-->
<!--id为切入点表达式的名称,expression为切入点表达式,指定哪些类要使用事务-->
<aop:pointcut id="servicePt" expression="execution(* *..service..*.*(..))"/> <!--execution中表达式代表:所有名称带service的包中的所有类中的所有方法-->
<!--配置增强器,将pointCut和advice关联起来-->
<!--advice-ref的值为第2步tx advice的id-->
<!--pointcut-ref的值为本步骤中pointcut的id-->
<aop:advisor advice-ref="transactionInterceptor" pointcut-ref="servicePt"/>
</aop:config>
7. web项目使用spring
可以在servlet的service方法中通过ApplicationContext从spring容器中获取想要的对象
1 | protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { |
但是ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
相当于创建对象然后放到容器中,那么每次网页请求都要重新创建对象放到容器中,这显然是不科学的。显然ApplicationContext这个对象在整个项目的运行过程中只需要创建一次,然后将其放到全局作用域ServletContext中就够了,
可以用监听器来实现。当全局作用域对象被创建时,创建ApplicationContext并将其存入全局作用域对象,(spring框架提供了一个监听器,本例我们使用它)。
加入spring-web依赖,里面有监听器
将监听器注册到web.xml
1
2
3<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>修改监听器默认加载spring配置文件位置
监听器默认/WEB-INF/applicationContext.xml为spring配置文件位置,我们将其改掉,同样在web.xml下配置:
1
2
3
4<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value> <!--会找到resources下的applicationContext.xml-->
</context-param>
这就完成了,后面我们的代码中不需要写ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
这句话了,每次web容器启动时,会自动读取applicationContext.xml并创建所有bean声明过的对象放到spring容器中。
1 | protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { |
最终版spring配置文件
1 |
|
bug解决
1. invalid bound statement
这是因为mapper文件没有被编译(一般是由于mapper文件没有放在resources目录中,而是放到dao包内),在pom.xml的build标签下加上
1 | <resources> |