Spring
框架
Spring
框架是由于软件开发的复杂性而创建的。
Spring
使用的是基本的
JavaBean
来完成以前只可能由
EJB
完成的事情。然而,
Spring
的用途不仅仅限于服务器端的开发。从简单性、可测试性和松耦合性角度而言,绝大部分
Java
应用都可以从
Spring
中受益。
◆目的:解决企业应用开发的复杂性
◆功能:使用基本的
JavaBean
代替
EJB
,并提供了更多的企业应用功能
◆范围:任何
Java
应用
Spring
是一个轻量级控制反转
(IoC)
和面向切面
(AOP)
的容器框架。
spring
框架的核心机制:核心机制是以
BeanFactory
为基础
,
管理
bean
与
benn
之间的依赖的
.
spring
以
bean
组织管理
Java
应用中的各组件
,
组件之间的依赖关系和耦合
.
这依赖与
spring
德核心机制
:
依赖注入
.Spring
使用
BeanFactory
作为应用中负责生产和管理各种组件的工厂
,
同事也是组件运行的容器
.BeanFactory
根据配置的文件确定容器中
bean
的实现
.
管理
bean
之间的关系
依赖注入可以称之为
控制反转(IOC)
不管是控制反转还是依赖注入,他们都可以这样理解:当某个
Java
实例(调用者)需要另一个
Java
实例(被调用者)时,在传统的程序设计过程中,通常有调用者来创建被调用者的实例。但是在依赖注入
/
控制反转模式下,创建被调用者的工作不再是有调用者来完成,而是由
spring
容器来完成,然后注入调用者。
依赖注入通常有如下两种:
1
、
设置注入:
IoC
容器使用属性的
setter
方法来注入被依赖的实例。
2
、
构造注入:
IoC
容器使用构造器来注入被依赖的实例。
依赖注入的体现:
1.设置注入
下面是Person接口,该接口定义了一个Person规范。
public
interface
Person {
//
定义使用斧子的方法
public
void
useAxe();
}
Axe
接口:
public
interface
Axe {
//Axe
接口里面有个砍的方法
public
String chop();
}
Person
的实现类:
public
class
Chinese
implements
Person {
private
Axe axe;
private
String name;
//
设值注入所需的
setter
方法
public
void
setAxe(Axe axe) {
this
.axe = axe;
}
public
void
setName(String name) {
this
.name = name;
}
//
实现
Person
接口的
userAxe
方法
public
void
useAxe() {
//
调用
axe
的
chop
方法,表明
Person
对象依赖于
Axe
对象
System.out.println(
"
我是
"
+name+
"
用
"
+axe.chop());
}
}
上面的代码实现了
Person
接口的
userAxe()
方法,实现该方法时调用了
axe
的的
chop()
方法,这就是典型的依赖关系。
在这里
Spring
容器的作用就是已松耦合的方式来管理这种调用关系。在上面的
Chinese
类中,
Chinese
类并不知道它要调用的
axe
实例在哪里,也不知道
axe
实例是如何实现的,它只是需要调用一个
axe
实例,这个
Axe
实例将由
Spring
容器负责注入。
public
class
StoneAxe
implements
Axe{
public
String chop() {
return
"
石斧砍柴好慢啊
!!!"
;
}
}
直到这里,程序依然不知道
Chinese
类和
Axe
实例耦合,
Spring
也不知道!实际上,
Spring
需要使用
XML
配置文件来指定实例之间的依赖关系。
Spring
采用了
XML
文件作为配置文件。
对于本应用的
XML
配置文件如下:
1.
<?xml
version
=
"1.0"
encoding
=
"UTF-8"
?>
2.
<beans
xmlns:xsi
=
"http://www.w3.org/2001/XMLSchema-instance"
3.
xmlns
=
"http://www.springframework.org/schema/beans"
4.
xsi:schemaLocation
="http://www.springframework.org/schema/beans
5.
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"
>
<!--
配置
Chinese
实例,其实现类是
Chinese -->
6.
<bean
id
=
"chinese"
class
=
"com.spring.service.impl.Chinese"
>
7.
<!--
将
StoneAxe
注入给
axe
属性
-->
8.
<property
name
=
"axe"
ref
=
"stoneAxe"
/>
9.
<property
name
=
"name"
value
=
"
孙悟空
"
/>
10.
</bean>
11.
<!--
配置
stoneAxe
实例
-->
12.
<bean
id
=
"stoneAxe"
class
=
"com.spring.service.impl.StoneAxe"
/>
13.
</beans>
在配置文件中,
Spring
配置
Bean
实例通常会指定两个属性:
id
:指定该
Bean
的唯一标识,程序会通过
id
属性值来访问该
Bean
实例。
class
:指定该
Bean
的实现类,此处不可再用接口,必须是实现类,
Spring
容器会使用
XML
解析器读取该属性值,并利用反射来创建该实现类的实例。
从上面可以看出
Bean
于
Bean
之间的依赖关系放在配置文件里组织,而不是写在代码里。通过配置文件的指定,
Spring
能够精确地为每个
Bean
注入属性。因此,配置文件里的
<bean…/>
元素的
class
属性值不能是接口,而必须是真正的实现类。
Spring
会自动接管每个
<bean…/>
定义里的
<property …/>
元素定义,
Spring
会在调用无参数的构造器、创建默认的
Bean
实例后,调用相应的
setter
方法为程序注入属性值。
<property…/>
定义的属性值将不再有该
Bean
来主动设置、管理,而是接受
Spring
的注入。
每个
Bean
的
id
属性是该
Bean
的唯一标识,程序通过
id
属性访问
Bean
,
Bean
与
Bean
的依赖关系也是通过
id
属性关联。
Bean
与
Bean
之间的依赖关系有
Spring
管理,
Spring
采用
setter
方法为目标
Bean
注入所依赖的
Bean
,这种方式被称之为设值注入。
总结:
从上面的实例我们可以看出,依赖注入以配置文件管理
Bean
实例之间的耦合,让
Bean
实例之间的耦合从代码层次分离出来。
Spring IOC
容器有如下
3
个基本要点:
1
、
应用程序的各个组件面向接口编程。面向接口编程可以将各个组件的耦合提升到接口层次,从而有利于项目后期的扩展。
2
、
应用程序的各组件不再由程序主动产生,而是由
Spring
容器来负责产生,并初始化。
3
、
Spring
采用配置文件、或者
Annotation
来管理
Bean
的实现类、依赖关系,
Spring
容器则根据配置文件,利用反射机制来创建时间,并为之注入依赖关系(
IOC
)。
二、
构造注入
1.
public class Japanese implements Person{
2.
3.
private
Axe axe;
4.
//
默认构造器
5.
public
Japanese(){
6.
7.
}
8.
9.
//
构造注入所需的带参数构造器
10.
public
Japanese(Axe axe){
11.
this
.axe = axe;
12.
}
13.
14.
public
void
useAxe() {
15.
System.out.println(axe.chop());
16.
}
上面的
Chinese
类并没有
setter
方法,仅仅只是提供了一个带
Axe
属性的构造器,
Spring
将通过该构造器为
Chinese
注入所依赖的
Bean
实例。
构造注入的配置文件需要做一些修改。为了使用构造注入,使用
<constructor-arg…/>
元素来指定构造器的参数。如下
1.
<?xml
version
=
"1.0"
encoding
=
"UTF-8"
?>
2.
<beans
xmlns:xsi
=
"http://www.w3.org/2001/XMLSchema-instance"
3.
xmlns
=
"http://www.springframework.org/schema/beans"
4.
xsi:schemaLocation
="http://www.springframework.org/schema/beans
5.
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"
>
6.
<!--
配置
Japanese
实例
-->
7.
<bean
id
=
"japanese"
class
=
"com.spring.service.impl.Japanese"
>
8.
<!--
使用构造注入
,为
Japanese
实例注入
SteelAxe
实例
-->
9.
<constructor-arg
ref
=
"stoneAxe"
/>
10.
</bean>
11.
12.
<!--
配置
stoneAxe
实例
-->
13.
<bean
id
=
"stoneAxe"
class
=
"com.spring.service.impl.StoneAxe"
/>
14.
</beans>
------------------------------------------------------------------------
如果没有看懂上面,可以看下面这一段最简单的控制反转,控制反转跟
spring
的依赖注入其实差不了多少,差不多就是一个意思。
如我定义了一个规范的Dao接口类UserDao。
public interface
UserDao
{
public void add(Users users);
}
但我有两个不同的去实现这个Dao接口的实现类。
public class
UserDaoImp1
implements UserDao{
@Override
public void add(Users users) {
System.out.println("这是userDao的第一个实现类"+users.getName());
}
}
public class
UserDaoImp2
implements UserDao{
@Override
public void add(Users users) {
System.out.println("这是userDao的第二个实现类"+users.getName());
}
}
现在我定义了一个接口类型的Servlce层,包括实现类。
//Servilce
接口类
public interface
UserService
{
public void save(Users users);
}
//Servilce
实现类
public class
UserServiceImp
implements UserService{
private UserDao userDao;
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
@Override
public void save(Users users) {
userDao.add(users);
}
}
现在我写了一个测试类去调用service层得到结果。
public class Test {
public static void main(String[] args) {
//
现在创建一个
BeanFactory
工厂
BeanFactory factory=new ClassPathXmlApplicationContext("applicationContext.xml");
//
通过这个工厂去获取
servece
层
UserService service=(UserService) factory.getBean("userSer");
service.save(new Users(1,"老项"));
}
}
现在我准备实现第一个Dao层的实现类。
<!-- 通过设置一个变量指向UserDao接口类的具体路径,我们可以通过不同的指定路径得到不同的结果,如 -->
<bean id="userDao" class="com.pro.dao.UserDaoImp1"></bean>
<!-- 通过设置一个变量指向userSer接口类的具体路径,在property 标签中,指定userSer接口类中的userDao的指向路径为上条语句的路径,ref的值为上条语句的Id -->
<bean id="userSer" class="com.pro.service.UserServiceImp">
<property name="userDao" ref="userDao"></property>
</bean>
假如我又不想实现第一个
Dao
层的实现类,想实现第二个,那么我只需要更改配置文件中的
userDao
指向路径类就可以实现。
-----------------------------------------------------------------------------
Aop的理解:
假设你有一个保存用户信息的方法如下:
public
void
save
(UserInfo user){
this
.userService.save(user); }
问题来了,我是一个事儿多的头头,我现在给你个新要求,你给我做个校验,如果
user.getUserName() == "fuck"
时,不保存,并抛出异常。
ok
,这时候你可能想,简单啊,我来,于是代码修改为:
public
void
save
(UserInfo user){
if
(user.getUserName() ==
"fuck"
){
throw
new
Exception(
"Fuck you"
);
}
this
.userService.save(user);
}
代码很简单对不对?
事儿逼又来了,现在发现这个方法常出错,我希望记下日志,方便日后查到底是哪个用户出错了,怎么改?这时候你又来了,如下:
public
void
save
(UserInfo user){
logger.log(
"Saving user :"
+ user);
if
(user.getUserName() ==
"fuck"
){
throw
new
Exception(
"Fuck you"
);
}
this
.userService.save(user);
}
注意到问题了么?你每次都在修改保存用户的源码,关键是修改的内容还都和主业务逻辑无关,那么问题来了,万一哪次你手残把原有逻辑改坏了肿么办?或者说,这个方法被加了一大堆和主业务无关的东西,现在让你测试这个方法,怎么测好呢?
这个时候
aop
来救世了,我们通过面相切面的思维方式来考虑这个问题,正好
spring
又可以很好的使用
aop
,那么问题就解决了。只要通过一些配置,就可以指定在
save
方法执行前
/
后执行一些与主业务无关,但又需要的内容,开不开心?
代码实现方式
的解释:
@Aspect
public class MyAsp {
public void say(){
System.out.println("我是日志");
System.out.println("---------");
}
}
通过在
Spring
的配置文件中去指定它的位置,在
aop
的配置中,利用
aop:pointcut
标签的
expression
去指定它这个
ASP
类要在哪个包下去执行,通过
aop:before
或
aop:after
去指定它要在类运行前
/
后去运行
<bean id="asp" class="com.pro.myasp.MyAsp"></bean>
<aop:config>
<aop:pointcut expression="execution(* com.pro.service..*.*(..))" id="all"/>
<aop:aspect ref="asp">
<!-- 代表先执行 -->
<aop:before method="say" pointcut-ref="all"/>
<!-- 代表后执行 -->
<aop:after method="say" pointcut-ref="all"/>
</aop:aspect>
</aop:config>
通过配置,可以得到一个结果。假如在AOP中,写入了代表先执行的AOP。那么我在调用com.pro.service下的类方法时,如调用一个方法,方法内写了一条
System.out.println("111")输出语句。程序在执行这条之前会先去执行配置文件中的
配置的com.pro.myasp.MyAsp类下的say方法。
结果会是
我是日志
---------
111