SpringFramework之IOC(控制反转)详解
发布日期:2021-06-29 15:52:00 浏览次数:2 分类:技术文章

本文共 18558 字,大约阅读时间需要 61 分钟。

文章目录

Spring介绍

在这里插入图片描述

SpringFramework是Spring框架的核心,其他模块都依赖于SpringFramework

Spring两大特点:Ioc(控制反转)和Aop(面向切面编程)
虽然SpringMVC,SpringBoot等使用了Spring的两大特性,但是Spring这两个特点不仅仅局限于Web应用开发

在这里插入图片描述

前期知识

编译期依赖

编译期依赖即在编译期就发生依赖关系,如果出错,编译期会提示出错

示例:jdbc

使用maven创建一个工程

pom.xml

mysql
mysql-connector-java
8.0.15

Main.java

public class Main {
public static void main(String[] args) throws Exception{
DriverManager.registerDriver(new com.mysql.cj.jdbc.Driver());// Class.forName("com.mysql.cj.jdbc.Driver"); ... } }}

加载jdbc驱动时,可以使用两种方式

方式1: new一个Driver对象

DriverManager.registerDriver(new com.mysql.cj.jdbc.Driver());

方式2:使用反射机制

Class.forName("com.mysql.cj.jdbc.Driver");

这时,如果将pom.xml文件中的jdbc依赖去掉,方式1立马会报错,而方式2不会立即报错,而是在程序运行时抛出了异常,将方式1的编译期依赖转移到了运行期。

工厂模式解耦

普通案例

先看一个普通的业务层和持久化层的例子

业务层

接口

public interface IAccountService {
/** * 模拟业务层调用保存账户 */ void saveAccount();}

实现类

public class AccountServiceImp implements IAccountService{
IAccountDao accountDao=new AccoutDaoImp(); /** * 模拟业务层调用保存账户 */ @Override public void saveAccount() {
accountDao.saveAccount(); }}

持久层

接口

public interface IAccountDao {
/** * 模拟账户类的持久化层 */ public void saveAccount();}

实现类

public class AccoutDaoImp implements IAccountDao {
/** * 模拟账户类的持久化层 */ @Override public void saveAccount() {
System.out.println("账户信息已保存"); }}

客户端

public static void main(String[] args) {
IAccountService accountService=new AccountServiceImp(); accountService.saveAccount();}

可以发现,在业务层实例化了一个持久层的对象,客户端也实例化了一个业务层对象,需要进一步改进

改进的要点

1.使用配置文件

2.反射机制

依赖关系

在这里插入图片描述

使用工厂模式解耦

  • 添加配置文件bean.properties
accountService=com.console.prelearn.service.imp.AccountServiceImpaccountDao=com.console.prelearn.dao.imp.AccountDaoImp
  • 添加BeanFactory
public class BeanFactory {
private static Properties properties; private static Map
beans; static {
properties=new Properties(); try {
InputStream inputStream = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties"); properties.load(inputStream); } catch (IOException e) {
e.printStackTrace(); throw new ExceptionInInitializerError("初始化配置文件错误"); } beans = new HashMap<>(); } public static Object getBean(String beanName){
Object bean=null; bean=beans.get(beanName); if (bean!=null){
return bean; } //如果bean是第一次使用,则创建新的对象,并保存在beans中 String beanPath = properties.getProperty(beanName); try {
bean = Class.forName(beanPath).getDeclaredConstructor().newInstance(); }catch (Exception e){
e.printStackTrace(); } beans.put(beanName,bean); return bean; }}
  • 将原始案例中new的对象换成BeanFactory.getBean()

至此,原始案例中的编译期依赖通过工厂模式实现了转移

依赖关系图

在这里插入图片描述

Ioc(Inversion of control,控制反转)引入

IAccountService accountService =new AccountServiceImp(); IAccountService accountService= (IAccountService) BeanFactory.getBean("accountService");

控制反转,是面向对象编程中的一种设计原则,通过控制反转,对象在被创建的时候,由一个调控系统内所有对象的外界实体将其所依赖的对象的引用传递给它。

之所以叫控制反转,是因为一个对象把本该由自己完全控制的依赖交给了Ioc容器,失去了主动权,所以叫“控制反转”

在这里插入图片描述

spring ioc实现控制反转

案例

将工厂模式解耦案例使用springframework实现

项目pom配置

org.springframework
spring-context
5.2.3.RELEASE

springframework的依赖关系图

在这里插入图片描述

添加配置文件

resources/bean.xml

程序中获取bean

ApplicationContext context=new ClassPathXmlApplicationContext("bean.xml");IAccountService accountService = (IAccountService) context.getBean("accountService");

ApplicationContext的实现类

ApplicationContext三种实现类

  • ClassPathXmlApplicationContext 可以加载类路径下的配置文件
  • FileSystemXmlApplicationContext 可以加载磁盘任意路径的配置文件
  • AnnotationConfigApplicationContext 用于读取注解创建的容器

与ApplicationContext类似,还有一个BeanFactory接口可以实现同样的功能

但是 ApplicationContext立即加载

BeanFactory是使用Bean的时候才会加载

Spring对Bean的管理

创建Bean的三种方式

使用默认构造函数创建

举例

使用 id 和 class 标签,没有其他标签

类必须要有默认无参数构造函数

使用某个类的方法返回值创建

举例

BeanFactory.java中方法

public Object getAccountService(){
return new AccountServiceImp();}

配置文件

配置文件中有两个Bean,第一个Bean(factory)是工厂类的Bean,第二个Bean(accountService)使用第一个Bean中的getAccountService来创建

使用静态方法获取

举例

BeanFactory.java中方法

public static Object getAccountService(){
return new AccountServiceImp();}

配置文件

Bean对象的作用范围

Bean的作用范围使用 scope标签确定

  • singlton 单例
  • prototype 多例
  • request 请求
  • session 会话
  • global-session 全局会话(集群环境下,没有集群和session相同)

Bean对象的生命周期

Bean的生命周期与其实单例对象还是多例对象有关

单例 多例
创建 容器创建时 第一次使用对象时
运行 与容器相同 只要对象在使用,就一直存在
销毁 容器销毁时 java垃圾回收

依赖注入(Dependency Injection)

概述

依赖关系的维护叫做依赖注入

能注入的类型:

  • 基本类型和string
  • Bean(在配置文件或者注解中配置过的bean)
  • 复杂类型/集合类型

注入的方式

  • 构造函数
  • set方法
  • 注解

通过构造函数注入

使用的标签

User.java

public class User {
private String name; private Integer age; private Date birthday; public User(String name, Integer age, Date birthday) {
this.name = name; this.age = age; this.birthday = birthday; } @Override public String toString() {
return "User{" + "name='" + name + '\'' + ", age=" + age + ", birthday=" + birthday + '}'; }}

bean.xml

通过set方法注入

需要使用的标签

这里的 name 标签对应类声明里的 setXxx()方法,不需要和类属性对应

并且将首字母大写字母变换小写类型

User.java

public class User {
private String name; private Integer age; private Date birthday; public User(){
} public User(String name, Integer age, Date birthday) {
this.name = name; this.age = age; this.birthday = birthday; } public void setName(String name) {
this.name = name; } public void setAge(Integer age) {
this.age = age; } public void setBirthday(Date birthday) {
this.birthday = birthday; } @Override public String toString() {
return "User{" + "name='" + name + '\'' + ", age=" + age + ", birthday=" + birthday + '}'; }}

bean.xml

复杂类型的注入

测试Bean添加以下属性和对应的set方法

private String[] array;private List
list;private Map
map;private Properties properties;

bean.xml

123
234
134
123
234
134
1234
3445

注意:可以给array赋值标签除了array还有list、set

即:格式相同的标签可以互换

使用注解注入

注入Bean @Component

user.java

@Componentpublic class User {
...}

配置文件

除了@Component注解之外,还有@Repository,@Service和@Controller三个注解有类似的功能

不过@Repository用在持久化层

@Service用在服务层

@Controller用在视图层

@Component用在不属于这三层的bean

注入值

@Autowired

  • 可以用在方法和成员属性上
  • 可以自动注入容器中类型一致其唯一的对象
  • 当容器中同类型的Bean对象有多个时,使用变量名匹配
  • 变量名和类型都匹配不上时,抛出异常

示例

AccountImp1.java

@Repository("accountDao1")public class AccountDaoImp1 implements IAccountDao {
/** * 模拟账户类的持久化层 */ @Override public void saveAccount() {
System.out.println("账户信息1已保存"); }}

AccountDao2.java

@Repository("accountDao2")public class AccountDaoImp2 implements IAccountDao {
/** * 模拟账户类的持久化层 */ @Override public void saveAccount() {
System.out.println("账户信息2已保存"); }}

AccountServiceImp.java

@Service("accountService")public class AccountServiceImp implements IAccountService{
@Autowired private IAccountDao accountDao1; @Autowired private IAccountDao accountDao2; public AccountServiceImp(){
} /** * * 模拟业务层调用保存账户 */ @Override public void saveAccount() {
accountDao.saveAccount(); accountDao2.saveAccount(); }}

Autowired的问题

  • 对Bean的要求严格,使用范围不广
  • 解决方法 和 @Qualifier配合使用

@Qualifier

  • 在按照类型注入时,同时按照id注入
  • 类的属性注解时不能单独使用
  • 给方法参数注入时可以单独使用、
  • 属性
    • value: 指定注入Bean的id
@Autowired@Qualifier("accountDao2")private IAccountDao accountDao;@Autowired@Qualifier("accountDao")private IAccountDao accountDao2;

@Resource

按照Bean的id注入,使用 name属性指定Bean的id

可以代替Autowired 和Qualifier

@Resource(name="accountDao2")private IAccountDao accountDao;@Resource(name="accountDao")private IAccountDao accountDao2;

注:

这个注解在javax.annotation包下面,如果无法引入,则需要添加pom依赖

javax.annotation
javax.annotation-api
1.3.1
  • @Autowired@Qualifier和@Resource只能用于现有Bean的注入,无法注入基本类型和String类型
  • 数组列表多等类型注入只能通过xml

@Value

注入基本类型和String类型

使用value属性

可以使用 Spring 中SpEL表达式 ${}

改变作用范围@Scope

@Scope

使用属性value指定作用范围

  • singleton 单例
  • prototype 多例

Spring默认是单例

生命周期

@PostConstruct 初始化

@PreDestroy 销毁

@PostConstructpublic void init(){System.out.println(this.getClass().getName()+" 初始化");}@PreDestroypublic void destroy(){System.out.println(this.getClass().getName()+" 销毁");}

综合案例

使用SpringIoc实现一个crud操作

持久化层选用 dbutil

pom.xml

mysql
mysql-connector-java
8.0.15
org.springframework
spring-context
5.2.3.RELEASE
javax.annotation
javax.annotation-api
1.3.1
commons-dbutils
commons-dbutils
1.4
c3p0
c3p0
0.9.1.2
junit
junit
4.10
test

方案1 xml配置文件注入

user.java

public class User {
private int id; private String name; private int age; private String gender; getter&setter()...}

UserDao.java

import com.console.ioc.demo.domain.User;import org.apache.commons.dbutils.QueryRunner;import org.apache.commons.dbutils.handlers.BeanHandler;import org.apache.commons.dbutils.handlers.BeanListHandler;import java.sql.SQLException;import java.util.List;public class UserDao {
//持久层对象 private QueryRunner runner; /** * set注入QueryRunner对象,供SpringIoc使用xml注入使用 * @param runner */ public void setRunner(QueryRunner runner) {
this.runner = runner; } public List
allUser(){
try {
return runner.query("select * from user",new BeanListHandler
(User.class)); } catch (SQLException throwable) {
throw new RuntimeException(throwable); } } public User getById(int id){
try {
return runner.query("select * from user where id=?",new BeanHandler
(User.class),id); } catch (SQLException throwable) {
throw new RuntimeException(throwable); } } public void deleteById(int id){
try {
runner.update("delete from user where id=?",id); } catch (SQLException throwable) {
throw new RuntimeException(throwable); } } public void update(User user){
try {
runner.update("update user set name=?,age=?,gender=? where id=?",user.getName(),user.getAge(),user.getGender(),user.getId()); } catch (SQLException throwable) {
throw new RuntimeException(throwable); } } public void add(User user){
try {
runner.update("insert into user(name,age,gender) values(?,?,?)",user.getName(),user.getAge(),user.getGender()); } catch (SQLException throwable) {
throw new RuntimeException(throwable); } }}

UserService.java

import com.console.ioc.demo.dao.UserDao;import com.console.ioc.demo.domain.User;import java.util.List;public class UserService {
UserDao userDao; /** * set注入UserDao对象,供SpringIoc使用xml注入使用 * @param userDao */ public void setUserDao(UserDao userDao) {
this.userDao = userDao; } public List
allUser(){
return userDao.allUser(); } public User getById(int id){
return userDao.getById(id); } public void deleteById(int id){
userDao.deleteById(id); } public void update(User user){
userDao.update(user); } public void add(User user){
userDao.add(user); }}

demo.xml

UserService.java

import com.console.ioc.demo.domain.User;import org.junit.After;import org.junit.Before;import org.junit.Test;import org.springframework.context.support.ClassPathXmlApplicationContext;public class UserServiceTest {
private UserService userService; @Before public void setUp() throws Exception {
ClassPathXmlApplicationContext context=new ClassPathXmlApplicationContext("demo1.xml"); userService= (UserService) context.getBean("userService"); } @After public void tearDown() throws Exception {
System.gc(); } @Test public void allUser() {
System.out.println(userService.allUser()); } @Test public void getById() {
System.out.println(userService.getById(1)); } @Test public void deleteById() {
userService.deleteById(2); } @Test public void update() {
User user = userService.getById(3); user.setName("white"); user.setAge(20); userService.update(user); System.out.println(userService.allUser()); } @Test public void add() {
User user=new User(); user.setAge(18); user.setName("mike"); user.setGender("男"); userService.add(user); System.out.println(userService.allUser()); }}

方案2 部分注解注入

user.java

public class User {
private int id; private String name; private int age; private String gender; getter&setter()...}

UserDao.java

@Repository("userDao")public class UserDao {
/** * 持久层对象 */ @Resource(name="queryRunner") private QueryRunner runner; ...和方法1相同}

UserService.java

@Service("userService")public class UserService {
@Resources(name="userDao") UserDao userDao; ......}

demo.xml

UserService.java

import com.console.ioc.demo.domain.User;import org.junit.After;import org.junit.Before;import org.junit.Test;import org.springframework.context.support.ClassPathXmlApplicationContext;public class UserServiceTest {
private UserService userService; //这里不能使用@Resource注解注入 @Before public void setUp() throws Exception {
ClassPathXmlApplicationContext context=new ClassPathXmlApplicationContext("demo.xml"); userService= (UserService) context.getBean("userService"); } // .....和方法1相同}

方案3 全部使用注解

使用的注解

@Configuratin

@ComponentScan({“com.console.ioc.demo”})

Bean(“id”)

@Scope(“prototype”)

对比方法2,可以发现,方法2中只有自定义的类使用了注解,而其他包里的类无法使用注解,并且还得使用xml配置文件

下面介绍如何去掉配置文件

  • 将配置文件编程配置类
/** * @ClassName BeanConfiguration 配置类 */@Configuration@ComponentScan({
"com.console.ioc.demo"})public class BeanConfiguration {
@Bean(name = "queryRunner") public QueryRunner createQueryRunner(DataSource dataSource){
return new QueryRunner(dataSource); } @Bean(name = "dataSource") @Scope("prototype") public DataSource createDataSource(){
ComboPooledDataSource dataSource = new ComboPooledDataSource(); try {
dataSource.setDriverClass("com.mysql.cj.jdbc.Driver"); dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test?serverTimezone=GMT%2B8"); dataSource.setUser("root"); dataSource.setPassword("caozhijian.19971023"); } catch (PropertyVetoException e) {
e.printStackTrace(); } return dataSource; }}

新的注解

  • @Configuration 作用在类上,说明该类是配置类
    • 当Configuration类作为AnnotationConfigApplicationContext()构造函数的参数时,可以不写@Configuration注解
  • @ComponentScan({“com.console.ioc.demo”}) 作用在类,说明扫描有注解配置的包
  • @Bean(“id”) 作用在方法上,将方法的返回值加入到Ioc容器中
  • @ Scope(“prototype”)作用在方法上,说明待添加Bean的Scope属性
  • 当有@Bean注解的方法有参数时,自动将Ioc容器中的Bean和参数类型对比,类似与@Autowired

测试文件中获取Ioc容器的方法发生变化

public class UserServiceTest {
private UserService userService; @Before public void setUp() throws Exception {
ApplicationContext context= new AnnotationConfigApplicationContext(BeanConfiguration.class); userService= (UserService) context.getBean("userService",UserService.class); }

这里使用 AnnotationConfigApplicationContext类获取Ioc使用注解的容器

方案4 多配置类

使用的注解

@Import() 将其他配置类导入主配置类

@Configuration()

当有多个配置文件时,可以使用如下几种解决方案

  1. 使用全部配置类构造AnnotationConfigApplicationContext对象

    1. ApplicationContext context= new AnnotationConfigApplicationContext(BeanConfiguration.class, JdbcConfiguration.class)
    2. 此时,配置类可以不用@Configuration

  2. 使用一个类(主配置类)构造AnnotationConfigApplicationContext对象,其他类使用@Configuration注解,并且在主配置类中指定扫描

  3. 使用一个类(主配置类)构造AnnotationConfigApplicationContext对象,其他配置类使用@Import注解导入主配置类

方案5 使用配置文件配置jdbc

使用到的注解

@PropertySource(“classpath:jdbc.properties”)

指定配置文件的位置

@Value()

注入基本类型和String类型

resources/jdbc.properties

jdbc.driver=com.mysql.cj.jdbc.Driverjdbc.url=jdbc:mysql://localhost:3306/test?serverTimezone=GMT%2B8jdbc.user=rootjdbc.password=xxxxxxxxx

JdbcConfiguration

@Configuration@PropertySource("classpath:jdbc.properties")public class JdbcConfiguration {
@Value("${jdbc.driver}") private String driver; @Value("${jdbc.url}") private String url; @Value("${jdbc.user}") private String user; @Value("${jdbc.password}") private String password; @Bean(name = "dataSource") @Scope("prototype") public DataSource createDataSource(){
ComboPooledDataSource dataSource = new ComboPooledDataSource(); try {
dataSource.setDriverClass(driver); dataSource.setJdbcUrl(url); dataSource.setUser(user); dataSource.setPassword(password); } catch (PropertyVetoException e) {
e.printStackTrace(); } return dataSource; }}

转载地址:https://console.blog.csdn.net/article/details/107750572 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:jdbc连接数据库两个常见问题及解决方法
下一篇:centos8磁盘整体扩容(不是挂载)

发表评论

最新留言

表示我来过!
[***.240.166.169]2024年04月15日 06时25分16秒