认识 Spring 框架
Spring 框架是 Java 应用最广的框架,它的成功来源于理念,而不是技术本身,它的理念包括 IoC (Inversion of Control,控制反转) 和 AOP(Aspect Oriented Programming,面向切面编程)。
什么是 Spring:
1.Spring 是一个轻量级的 DI / IoC 和 AOP 容器的开源框架,来源于 Rod Johnson 在其著作《Expert one on one J2EE design and development》中阐述的部分理念和原型衍生而来。
2.Spring 提倡以“最少侵入”的方式来管理应用中的代码,这意味着我们可以随时安装或者卸载 Spring
*适用范围:任何 Java 应用
*Spring 的根本使命:简化 Java 开发
耦合与解耦
什么是耦合:耦合性、。指软件系统结构中各模块间相互联系紧密程度的一种度量。模块之间联系越紧密,其耦合性就越强,模块的独立性则越差。模块间耦合高低取决于模块间接口的复杂性、调用的方式及传递的信息。
解耦:
解除类之间的直接关系,将直接关系转换成间接关系,一般情况下发现耦合需要解耦,避免强耦合,这样的话一改则全部都要改,不利于程序进行
Spring的IOC控制反转和依赖注入
控制反转即IoC (Inversion of Control),它把传统上由程序代码直接操控的对象的调用权交给外部容器,通过容器来实现对象组件的装配和管理。所谓的“控制反转”概念就是组件对象的控制权转移了,从程序代码本身转移到了外部容器。
IoC(Inversion of Control)是近年来兴起的一种思想,不仅仅是编程思想。主要是协调各组件间相互的依赖关系,同时大大提高了组件的可移植性,组件的重用机会也变得更多。在传统的实现中,由程序内部代码来控制程序之间的关系。我们经常使用new关键字来实现两对象组件间关系的组合,这种实现的方式会造成组件之间耦合(一个好的设计,不但要实现代码重用,还要将组件间关系解耦)。IoC很好的解决了该问题,它将实现组件间关系从程序内部提到外部容器来管理。也就是说由容器在运行期将组件间的某种依赖关系动态的注入到组件中。控制程序间关系的实现交给了外部的容器来完成。即常说的好莱坞原则“Don’t call us, we’ll call you”(你呆着别动,到时我会找你)。
我们知道,如果Class A中用到了Class B的对象b,一般情况下,需要在A的代码中显式的new一个B的对象。采用依赖注入技术之后,A的代码只需要定义一个私有的B对象,不需要直接new来获得这个对象,而是通过相关的容器控制程序来将B对象在外部new出来并注入到A类里的引用中。而具体获取的方法、对象被获取时的状态由配置文件(如XML)来指定。
IoC实现方法
实现控制反转主要有两种方式:依赖注入和依赖查找。两者的区别在于,前者是被动的接收对象,在类A的实例创建过程中即创建了依赖的B对象,通过类型或名称来判断将不同的对象注入到不同的属性中,而后者是主动索取响应名称的对象,获得依赖对象的时间也可以在代码中自由控制。IoC的实现与语言无关。用各种语言,如C++, Java, C#等都可以。这里主要以Java为例。
依赖注入有如下实现方式:
-
基于接口。实现特定接口以供外部容器注入所依赖类型的对象。接口中定义要注入依赖对象的方法。 基于setter方法。实现特定属性的public
-
set方法,来让外部容器调用,以传入所依赖类型的对象。如Spring Framework,WebWork/XWork。
-
基于构造函数。实现特定参数的构造函数,在新建对象时传入所依赖类型的对象。如PicoContainer,HiveMind。
-
基于注解。基于Java的注解功能,在私有变量前加“@Autowired”等注解,不需要显式的定义以上三种代码,便可以让外部容器传入对应的对象。该方案相当于定义了public的set方法,但是因为没有真正的set方法,从而不会为了实现依赖注入导致暴露了不该暴露的接口(因为set方法只想让容器访问来注入而并不希望其他依赖此类的对象访问)。
-
依赖查找更加主动,在需要的时候通过调用框架提供的方法来获取对象,获取时需要提供相关的配置文件路径、key等信息来确定获取对象的状态。这主要是通过JNDI或ServiceManager等获得依赖对象
-
依赖注入
依赖注入的基本原则是:应用组件不应该负责查找资源或者其他依赖的协作对象。配置对象的工作应该由IoC容器负责,“查找资源”的逻辑应该从应用组件的代码中抽取出来,交给IoC容器负责。
实例:
构建一个maven工程,加入spring依赖。
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<spring.version>4.3.13.RELEASE</spring.version>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
</dependency>
</dependencies>
UserDao.java
package com.rimi.springtext.dao;
public class UserDao {
public String get(String name) {
return "hello,"+name;
}
}
Userserver.java
package com.rimi.springtext.service;
package com.rimi.springtext.dao.UserDao;
public class UserService {
private UserDao userDao;
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
public String get(String name) {
return userDao.get(name);
}
}
spring.xml
<bean id="userDao" class="com.rimi.springtext.dao.UserDao"></bean>
<bean id="userService" class="com.rimi.springtext.service.UserService">
<property name="userDao" ref="userDao"/>
</bean>
测试类
package com.rimi.springtext;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.rimi.springtext.service.UserService;
public class SpringMain {
@SuppressWarnings("resource")
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring.xml");
UserService userService = context.getBean(UserService.class);
String hello = userService.get("lx");
System.out.println(hello);
}
}
这个演示了一个原始的spring作为一个ioc容器的例子,很简单,我们对UserDao,UserService实体做了bean配置,然后需要的时候,通过context.getBean()就可以获取了,很方便,但是随着业务的扩展,这种bean的配置会越来越多,而且bean的依赖也会做配置,这时候,注解就是用来做多编码,少做配置的。
spring注解提供了@Component、@Repository、@Service、@Controller等注解,用于配置在实体bean上,表示这是一个组件,会被spring容器扫描并实例化。
@Resource、@Autowired注解,用于配置在实体的属性上,表示这个属性可以通过依赖注入的方式给注入到实体中。这样,有了实体,有了依赖,我们就可以通过spring获取一个bean对象,并执行相关的方法。实体的实例化和依赖注入,全部通过注解来实现了,但是有一点,虽然实体bean和属性不用在xml配置文件里面配置了,但是我们还需要配置一个包扫描路径,告诉spring容器,我们的实体bean在哪里,让他去指定的包路径下扫描并实例化和注入。
因此,改过之后的spring示例,代码会是这个样子的。
UserDao.java
package com.rimi.springtext.dao;
import org.springframework.stereotype.Repository;
@Repository
public class UserDao {
public String get(String name) {
return "hello,"+name;
}
}
UserService.java
package com.rimi.springtext.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.rimi.springtext.dao.UserDao;
@Service
public class UserService {
@Autowired
private UserDao userDao;
public String get(String name) {
return userDao.get(name);
}
}
spring.xml
<context:component-scan base-package="com.rimi.springtext" />
这下子spring配置文件简单了很多,就一个配置。这里我们测试,使用单元测试,因为可以使用注解@Resource、@Autowire来做依赖注入,所以UserService不用通过context.getBean()的方式来获取了。
test.java
package com.rimi.springtext;
import javax.annotation.Resource;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import com.rimi.springtext.service.UserService;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations= {"classpath:spring.xml"})
public class Test1 {
@Resource
private UserService userService;
@Test
public void test2() {
String name = "lx";
String res = userService.get(name);
System.out.println(res);
}
}
这个实例里面,我们对UserDao,UserService实体采用了@Repository,@Service注解,表示这是一个实体bean,我们可以考虑去掉这些注解,同样可以实现实体bean的实例化,就是xml配置需要做一些修改。
UserDao.java
package com.rimi.springtext.dao;
public class UserDao {
public String get(String name) {
return "hello,"+name;
}
}
UserService.java
package com.rimi.springtext.service;
import org.springframework.beans.factory.annotation.Autowired;
import com.rimi.springtext.dao.UserDao;
public class UserService {
@Autowired
private UserDao userDao;
public String get(String name) {
return userDao.get(name);
}
}
spring.xml
<context:component-scan base-package="com.rimi.springtext">
<context:include-filter type="regex" expression="com\.rimi\.springtext\.dao\..*"/>
<context:include-filter type="regex" expression="com\.rimi\.springtext\.service\..*"/>
</context:component-scan>
对于扫描包配置,我们做进一步细化,使用context:include-filter来指定了具体的包下面的所有实体。这样我们的实体bean上就不需要设置注解了。