什么是Spring框架?
Spring框架是一种开发框架,它的使用有助于提升开发人员的开发效率以及系统的可维护性。它由很多模块组成,框架示意图如下:
Spring AOP:提供面向切面的编程实现。
Spring ORM:用于支持Hibernate等ORM(关系规则映射)工具。
Spring DAO:提供事务支持,JDBC支持等与数据库相关。
Spring Web:提供面向切面的编程
Spring Context与Spring Web MVC:为Web应用提供支持
Spring Core:基础,可以说 Spring 其他所有的功能都需要依赖于该类库。主要提供 IoC 依赖注入功能。
Spring的核心功能
Spring的核心功能主要是IOC(Inverse of Control 控制反转)与AOP(Aspect-Oriented Programming 面向切面编程),本篇博文也是主要介绍这两个功能。
IOC介绍
IOC是一种设计思想,就是将对象的创建与管理交给Spring框架完成。IOC在其他语言中也存在,并非Spring特有的。 IoC 容器是 Spring 用来实现 IoC 的载体, IoC 容器实际上就是个Map(key,value),Map 中存放的是各种对象。
提出IOC的目的:如同手表中的齿轮,当其中一个齿轮出现问题就会影响其他齿轮的工作,进而影响整个手表的工作,对象与对象之间也存在这种关系,称为耦合关系,这是无法避免的,也是必需的,开发者的目标就是尽量减少对象之间的耦合。基于此,软件专家Michael Mattson提出了IOC理论,用来实现对象之间的“解耦”。
现在,我们来看看IOC提出的过程,举例进行说明:需求:User类中有add方法,现要在其他类中调用其add方法,则有:
//User类
public class User {
public void add() {
System.out.println("User的add方法");
}
//在Test类中调用User的add方法
public class Test {
public void test() {
User user = new User();
user.add();
}
public static void main(String[] args) {
Test test = new Test();
test.test();
}
}
我们会发现,这种方法虽然可以实现我们的需求,但对象之间耦合太高,一旦User类中出现问题,那么Test类中相关的功能也会受到限制,如何解决呢,我们又提出了利用工厂类实例化对象来实现。
//User类
public class User {
public void add() {
System.out.println("User的add方法");
}
//利用工厂类实例化对象
public class Factory {
public static User getBean() {
return new User();
}
}
//在Test类中调用User的add方法
public class Test {
public void test() {
User user = Factory.getBean();
user.add();
}
public static void main(String[] args) {
Test test = new Test();
test.test();
}
}
如此一来,解决了User类的对象与Test对象间的耦合关系,但是,工厂类对象与User类对象的耦合性又太高了。此时,我们为了解耦,利用IOC来进行,后面我们会介绍Spring IOC的两种方式(XML方式和注解方式)。
什么是IOC?
IOC理论提出的观点大体是这样的:借助于“第三方”实现具有依赖关系的对象之间的解耦,如下图:
IOC容器就是“第三方”,它的引进使对象A,B,C,D之间没有了耦合,将控制权交给了第三方(IOC容器),所以IOC容器成为了整个系统的关键核心,那么为什么称这种过程为控制反转呢?
软件系统在没有引入IOC容器之前,对象A依赖于对象B,那么对象A在初始化或者运行到某一点的时候,自己必须主动去创建对象B或者使用已经创建的对象B。无论是创建还是使用对象B,控制权都在自己手上。
软件系统在引入IOC容器之后,这种情形就完全改变了,由于IOC容器的加入,对象A与对象B之间失去了直接联系,所以,当对象A运行到需要对象B的时候,IOC容器会主动创建一个对象B注入到对象A需要的地方。
通过前后的对比,我们不难看出来:对象A获得依赖对象B的过程,由主动行为变为了被动行为,控制权颠倒过来了,这就是“控制反转”这个名称的由来。
IOC的实现
Spring IOC主要有两种实现方式:xml配置方式和注解方式。
1、使用XML配置方式实现IOC:
实现步骤:
1、通过maven管理依赖核心jar包
<!--Spring 基本核心jar包-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>4.1.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>4.1.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.1.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>4.1.7.RELEASE</version>
</dependency>
<!--log4j日志-->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
2、创建类、在类中创建方法
public class User {
public void add(){
System.out.println("User.add...");
}
}
3、创建springXML文件来管理user,使用的约束spring-beans-3.0.xsd
<?xml version="1.0" encoding="UTF-8"?>
<!--根标签-->
<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-3.0.xsd">
将User对象的实例化交给Spring管理
<bean id="user" class="com.tulun.bean.User"/>
</beans>
4、测试代码创建对象
public static void main(String[] args) {
//加载Spring的配置文件,创建对象
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
//得到实例对象user
User user = (User) context.getBean("user");
System.out.println(user);
user.add();
}
可以看到,Spring IOC的上述实现过程中创建对象是通过XML配置文件进行的,通过XML配置创建对象也分为三种方法(通过无参构造,通过静态工厂,通过一般工厂),上述过程就是通过无参构造创建对象的,下面我们介绍另外两种方法。
使用静态工厂创建对象(在配置文件中添加)
<!--通过静态工厂创建对象-->
<bean id="user1" class="com.tulun.foctory.StaticFactory" factory-method="getBean"/>
class属性值为工厂类的全路径名
factory-method属性值对应的是工厂类提供的实例化对象方法
工厂类:
public class StaticFactory {
//通过工厂方法创建对象
public static User getBean() {
return new User();
}
}
使用一般工厂创建对象
<!--通过普通实例工厂创建对象-->
<!--实例化普通工厂-->
<bean id="factory" class="com.tulun.foctory.Factory2"/>
<bean id="user2" factory-bean="factory" factory-method="getBean"/>
工厂类:
public class Factory2 {
public User getBean() {
return new User();
}
}
bean标签常用属性
- id属性 名称,不能包含特殊字符,根据id属性来获取对象
- name属性:功能和id相同,但是可以包含特殊字符
- class属性:创建对象所在类的全路径
- scope属性:
-
-singleton:默认值,单例
-
-prototype:多例
-
-request:创建对象将对象放到request域里面
-
-session:创建的对象放到session域里面
-
-globalSession:创建的对象放到globalSession域里面
IOC的别名:依赖注入(DI)
2004年,Martin Fowler探讨了同一个问题,既然IOC是控制反转,那么到底是“哪些方面的控制被反转了呢?”,经过详细地分析和论证后,他得出了答案:“获得依赖对象的过程被反转了”。控制被反转之后,获得依赖对象的过程由自身管理变为了由IOC容器主动注入。于是,他给“控制反转”取了一个更合适的名字叫做“依赖注入(Dependency Injection)”。他的这个答案,实际上给出了实现IOC的方法:注入。所谓依赖注入,就是由IOC容器在运行期间,动态地将某种依赖关系注入到对象之中。
依赖注入(DI)和控制反转(IOC)是从不同的角度的描述的同一件事情,就是指通过引入IOC容器,利用依赖关系注入的方式,实现对象之间的解耦。
依赖注入方式
依赖注入的方式有三种:set方法 有参构造 接口注入,下面将进行一一介绍:首先创建接口UserDao以及对象的实现类UserDaoImpl:
public interface userDao{
public void add(String name)
}
public class UserDaoImpl implements userDao {
private String name;
public void add(String name){
this.name = name;
}
}
Spring提供属性注入方式
(1)通过set方法注入属性
<!--通过set方法注入对象属性-->
<bean id="user3" class="com.tulun.bean.User">
<property name="name" value="tulun"/>
<property name="sex" value="nan"/>
</bean>
(2)有参构造
<!--通过有参构造函数注入对象属性-->
<bean id="user4" class="com.tulun.bean.User">
<constructor-arg name="name" value="lisi"/>
<constructor-arg name="sex" value="niu"/>
</bean>
2、通过注解方式实现Spring IOC
创建对象(注解形式)注解的写法: @注解名称(属性名称=属性值),注解可以使用在类、方法、属性上面
1、引入依赖jar(核心的四个jar包外,还要包含以下spring-aop的注解jar包)
<!--注解相关jar包-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>4.1.7.RELEASE</version>
</dependency>
2、创建bean类,添加注解Component
@Component(value = "student")
public class Student1 {
public void show() {
System.out.println("Student1.show...");
}
}
3、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: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">
<!--<bean id="" class=" "/>-->
<!--开启注解扫描
(1)到包里面扫描类方法、属性上是否有注解
-->
<context:component-scan base-package="com.tulun"></context:component-scan>
<!--
扫描属性上面的注解(不建议使用)
-->
<!--<context:annotation-config></context:annotation-config>-->
</beans>
4、使用student1对象
ApplicationContext context = new ClassPathXmlApplicationContext("spring-annotation.xml");
Student1 student = (Student1)context.getBean("student");
student.show();
注:
Spring创建对象的注解:4个注解
1、@Component
2、@Controller (web层)
3、@Service (业务层)
4、Register (持久层)
4个注解的功能是一样的,都是来创建对象
属性注入(注解方式)
@Autowired
//通过注解的形式注入属性,set方法不用在写了
private Student1 student;
@Resource(name = "student")(使用比较多)
name属性值写的是通过注解创建的dao对象的value值
3、XML与注解配合使用(xml创建对象,注解属性注入)
首先:创建StudentDao\StudentService\Student,提供show方法
1、创建三个对象StudentDao\StudentService\Student对象
2、在controller层使用service层,即在controller层添加属性studentService
3、在StudentService层使用dao层,即在StudentService层添加属性student
public class Student {
public void show() {
System.out.println("student.show");
}
}
public class StudentService {
// //通过配置文件的set方法注入属性
@Autowired
public Student student;
public void show() {
student.show();
}
}
public class StudentDao {
@Autowired
private StudentService studentService;
public void show() {
studentService.show();
}
}
在注解的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: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">
<!--开启注解扫描
(1)到包里面扫描类方法、属性上是否有注解
-->
<context:component-scan base-package="MavenLG"></context:component-scan>
<!--
扫描属性上面的注解(不建议使用)
-->
<!--<context:annotation-config></context:annotation-config>-->
<!--通过无参构造创建对象-->
<bean id="studentService" class="MavenLG.service.StudentService"/>
<bean id="studentDao" class="MavenLG.dao.StudentDao"/>
<bean id="student" class="MavenLG.bean.Student"/>
</beans>
测试类进行测试:
public class TestStudent {
@Test
public void testAdd(){
//利用xml创建对象,注解进行属性注入
//读取配置文件(加载Spring上下文 )
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring-zhujie.xml");
//获取 bean
StudentDao studentDao = (StudentDao) context.getBean("studentDao");
System.out.println(studentDao);
studentDao.show();//使用
}
}
总结
IOC容器的技术剖析
IOC中最基本的技术就是“反射(Reflection)”编程,有关反射的概念和用法,通俗来讲就是根据给出的类名(字符串方式)来动态地生成对象。这种编程方式可以让对象在生成时才决定到底是哪一种对象。我们可以把IOC容器的工作模式看做是工厂模式的升华,可以把IOC容器看作是一个工厂,这个工厂里要生产的对象都在配置文件中给出定义,然后利用编程语言的的反射编程,根据配置文件中给出的类名生成相应的对象。从实现来看,IOC是把以前在工厂方法里写死的对象生成代码,改变为由配置文件来定义,也就是把工厂和对象生成这两者独立分隔开来,目的就是提高灵活性和可维护性。
使用IOC框架应该注意什么
使用IOC框架产品能够给我们的开发过程带来很大的好处,但是也要充分认识引入IOC框架的缺点:
第一、软件系统中由于引入了第三方IOC容器,生成对象的步骤变得有些复杂,本来是两者之间的事情,又凭空多出一道手续,所以,我们在刚开始使用IOC框架的时候,会感觉系统变得不太直观。所以,引入了一个全新的框架,就会增加团队成员学习和认识的培训成本,并且在以后的运行维护中,还得让新加入者具备同样的知识体系。
第二、由于IOC容器生成对象是通过反射方式,在运行效率上有一定的损耗。如果你要追求运行效率的话,就必须对此进行权衡。
第三、具体到IOC框架产品(比如:Spring)来讲,需要进行大量的配制工作,比较繁琐,对于一些小的项目而言,客观上也可能加大一些工作成本。
第四、IOC框架产品本身的成熟度需要进行评估,如果引入一个不成熟的IOC框架产品,那么会影响到整个项目,所以这也是一个隐性的风险。
参考自:
https://www.cnblogs.com/wang-meng/p/5597490.html
https://www.zhihu.com/question/23277575/answer/169698662