1、IOC思想(充分解耦)
-
Inversion of Control,控制反转,将JavaWeb中实现的new对象构建方法简化。
-
由主动new产生对象转换为由外部(IOC容器)提供对象,此过程中对象创建控制权由程序转移到外部
-
IOC容器,负责对象的创建、初始化等工作,被创建或被管理的对象在IOC容器中统称为Bean!
思考为什么在同一个IOC容器中依然可以实现service包调用dao包?
-
这里运用的就是DI(Dependency Injection)依赖注入(用于绑定关系)
-
在容器中建立bean与bean之间的依赖关系的整个过程,称为依赖注入
-
2、入门Spring
1. 文件的导入
-
在pom.xml文件中,加入spring-context的坐标,id为org.springframework
-
在resources目录中,创建applicationContext.xml文件
-
配置bean(通过application的接口实现类去调用方法)
-
//dao接口的实现类 package com.mao.dao.impl; import com.mao.dao.BookDao; public class BookDaoImpl implements BookDao { @Override public void save() { System.out.println("book dao save"); } } //dao的接口 package com.mao.dao; public interface BookDao { public void save(); } //Service接口的实现类 package com.mao.service.impl; import com.mao.dao.BookDao; import com.mao.dao.impl.BookDaoImpl; import com.mao.service.BookService; public class BookServiceImpl implements BookService { private BookDao bookDao = new BookDaoImpl();//service中应先调用dao层,实现其接口 @Override public void save() { System.out.println("book service save"); bookDao.save(); } } //Service接口,不可创建对象,所以需要new其实现类 package com.mao.service; public interface BookService { public void save(); } //测试类 import com.mao.dao.BookDao; import com.mao.dao.impl.BookDaoImpl; import com.mao.service.BookService; import com.mao.service.impl.BookServiceImpl; public class App { public static void main(String[] args) { //通过先调用service层,后service层中包含调用dao层,故产生两次输出 //接口不可以创建对象,所以需要new它的实现类!!!! BookService bookService = new BookServiceImpl(); bookService.save(); System.out.println("======================="); //调用dao层 BookDao bookDao = new BookDaoImpl(); bookDao.save(); } }
第二种测试类:获取IOC容器、获取Bean调用方法
public class App2 { public static void main(String[] args) { //3.导入pom文件以及配置Bean后,现获取IOC容器(接口不可new对象,需要实例化其接口) ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml"); //4.获取IOC容器后,获取Bean(需要强转类型、引号中的内容为配置Bean的id) BookDao bookDao = (BookDao) ctx.getBean("bookDao"); //获取bean后,可以调用其方法 bookDao.save(); System.out.println("=================="); BookService bookService = (BookService) ctx.getBean("bookService"); bookService.save(); } }
2. 通过注入IOC及DI注释来调用方法
<?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.xsd"> <!-- 1.导入spring-context的坐标--> <!-- 2.配置bean--> <!-- bean标签表示配置bean id属性表示给bean起名字(不可重复) name属性表示bean的别名,用空格分隔开,可能有多个别名,同时在获取bean时,引号中可以使用(name的范围很广,ref中也可以引用) class表示给bean定义类型 --> <!-- <bean id="bookDao" class="com.mao.dao.impl.BookDaoImpl"></bean> <bean id="bookService" class="com.mao.service.impl.BookServiceImpl"></bean>--> <!-- 7.配置service和dao的关系--> <bean id="bookDao" class="com.mao.dao.impl.BookDaoImpl"></bean> <bean id="bookService" class="com.mao.service.impl.BookServiceImpl"> <!-- property标签表示配置当前bean的属性 name属性表示配置哪一个具体的属性(在Service接口实现类中定义的属性,private BookDao bookDao) ref属性表示参照哪一个bean,这里对应的是上面第一个配置的bean的id --> <!-- 若这部分注释掉,则DI注入失败,也就是service和dao层的关系不明确,service层无法调用dao层的方法--> <property name="bookDao" ref="bookDao"></property> </bean> </beans>
对应的是第五步、第六步
package com.mao.service.impl; import com.mao.dao.BookDao; import com.mao.dao.impl.BookDaoImpl; import com.mao.service.BookService; public class BookServiceImpl implements BookService { /* private BookDao bookDao = new BookDaoImpl();//service中应先调用dao层,实现其接口 @Override public void save() { System.out.println("book service save"); bookDao.save(); }*/ //5.删除业务层中使用new的方式创建的dao对象 private BookDao bookDao; @Override public void save() { System.out.println("book service save"); bookDao.save(); } //6.提供对应的set方法 public void setBookDao(BookDao bookDao) { this.bookDao = bookDao; } }
3. Bean的取别名及作用域(单例多例)
import com.mao.dao.BookDao; import com.mao.service.BookService; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class App2 { public static void main(String[] args) { //3.导入pom文件以及配置Bean后,现获取IOC容器(接口不可new对象,需要实例化其接口) ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml"); //4.获取IOC容器后,获取Bean(需要强转类型、引号中的内容为配置Bean的id) BookDao bookDao = (BookDao) ctx.getBean("dao"); //获取bean后,可以调用其方法 bookDao.save(); System.out.println("=================="); //这里的引号中,使用的是bean的别名引入 BookService bookService = (BookService) ctx.getBean("service"); bookService.save(); //测试bookDao是否为单例对象(若不在bean配置中加scope,则默认是单例即singleton,则所有对象都是同一个地址) BookDao bookDao2 = (BookDao) ctx.getBean("dao"); System.out.println(bookDao); System.out.println(bookDao2); } }
<!-- 7.配置service和dao的关系--> <bean id="bookDao" name="dao" class="com.mao.dao.impl.BookDaoImpl" scope="prototype"></bean> <bean id="bookService" name="service" class="com.mao.service.impl.BookServiceImpl" scope="prototype"> <!-- property标签表示配置当前bean的属性 id属性表示配置哪一个具体的属性(在Service接口实现类中定义的属性,private BookDao bookDao) ref属性表示参照哪一个bean,这里对应的是上面第一个配置的bean的id --> <!-- 若这部分注释掉,则DI注入失败,也就是service和dao层的关系不明确,service层无法调用dao层的方法--> <property name="bookDao" ref="bookDao"></property> </bean>
4. 实例化Bean的三种方式
4.1 空参构造
//实例化Bean的方式(空参构造)不需要调用,默认访问空参构造(与访问域无关) public BookDaoImpl() { System.out.println("dao constructor is running..."); }
4.2 静态工厂模式
<!-- Bean的实例化方式2 需要加factory-method才可以使用工厂模式 使用静态工厂实例化bean --> <bean id="orderDao" class="com.mao.factory.OrderDaoFactory" factory-method="getOrderDao"></bean>
//OrderDao接口的实现类 package com.mao.dao.impl; import com.mao.dao.OrderDao; public class OrderDaoImpl implements OrderDao { @Override public void save() { System.out.println("OrderDao save ... "); } } //OrderDao接口 package com.mao.dao; public interface OrderDao { public void save(); } //测试类,用于测试静态工厂实例化Bean import com.mao.dao.OrderDao; import com.mao.dao.impl.BookDaoImpl; import com.mao.dao.impl.OrderDaoImpl; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class App3 { public static void main(String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml"); OrderDao orderDao = (OrderDao) ctx.getBean("orderDao"); orderDao.save(); } }
4.3 实例化工厂模式(FactoryBean)
package com.mao.factory; import com.mao.dao.UserDao; import com.mao.dao.impl.UserDaoImpl; import org.jspecify.annotations.Nullable; import org.springframework.beans.factory.FactoryBean; public class UserDaoFactoryBean implements FactoryBean<UserDao> { //判断是否为单例,true为单例,false为多类型 @Override public boolean isSingleton() { return true; } //代替原始实例工厂中创建对象的方法 @Override public UserDao getObject() throws Exception { return new UserDaoImpl(); } //返回类型对象为UserDao的字节码 @Override public Class<?> getObjectType() { return UserDao.class; } }
public interface UserDao { public void save(); } //接口的实现类 public class UserDaoImpl implements UserDao { @Override public void save() { System.out.println("UserDaoImpl.save"); } } //测试类,测试FactoryBean实例化Bean public class App3 { public static void main(String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml"); /* OrderDao orderDao = (OrderDao) ctx.getBean("orderDao"); orderDao.save();*/ UserDao userdao1 = (UserDao) ctx.getBean("userdao"); UserDao userdao2 = (UserDao) ctx.getBean("userdao"); userdao1.save(); System.out.println(userdao1); System.out.println(userdao2); } }
<!-- 方式四:FactoryBean模式实例化bean--> <bean id="userdao" class="com.mao.factory.UserDaoFactoryBean"></bean>
5. setter注入
<bean id="bookDao" name="dao" class="com.mao.dao.impl.BookDaoImpl" scope="prototype"> <property name="connectionNum" value="10"></property> <property name="databaseName" value="mysql"></property> </bean> <bean id="bookService" name="service" class="com.mao.service.impl.BookServiceImpl" scope="prototype"> <!-- id属性表示配置哪一个具体的属性(在Service接口实现类中定义的属性,private BookDao bookDao) name属性表示bean的别名,用空格分隔开,可能有多个别名,同时在获取bean时,引号中可以使用(name的范围很广,ref中也可以引用) --> <!-- 若这部分注释掉,则DI注入失败,也就是service和dao层的关系不明确,service层无法调用dao层的方法 property标签表示配置当前bean的属性 name则表示是类中的对象名 ref属性表示参照哪一个bean,这里对应的是上面第一个配置的bean的id --> <!--空参构造的注入--> <property name="bookDao" ref="bookDao"></property> <!-- 配置userdao的bean--> <property name="userDao" ref="userDao"></property> </bean> <!--构造器的注入--> <property name="bookDao" ref="bookDao"></property> <!--setter的注入--> <property name="bookDao" value="bookDao"></property>
//引用类型注入(调用的话需要在xml文件中导入bean),同时提供set方法 private UserDao userDao; public void setUserDao(UserDao userDao) { this.userDao = userDao; } //定义简单类型 private int connectionNum; private String databaseName; @Override public void save() { System.out.println("book dao save" + "," + connectionNum + "," + databaseName); }
value:注入简单值(value会自动转换所需的类型),而ref则是注入引用类型,引入其他bean
6. 数据源对象管理
测试类,获取id为datasource的bean
public class App { public static void main(String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml"); DataSource datasource = (DataSource) ctx.getBean("datasource"); System.out.println(datasource); } }
导入几个依赖
<dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.2.24</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.32</version> </dependency> <dependency> <groupId>c3p0</groupId> <artifactId>c3p0</artifactId> <version>0.9.1.2</version> </dependency>
分别交换id,用于测试,一个是Druid、一个是c3p0
<bean class="com.alibaba.druid.pool.DruidDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver"></property> <property name="url" value="jdbc:mysql://localhost:3306/smbms"></property> <property name="username" value="root"></property> <property name="password" value="123456"></property> </bean> <bean id="datasource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="com.mysql.jdbc.Driver"></property> <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/smbms"></property> <property name="user" value="root"></property> <property name="password" value="123456"></property> </bean>
导入新的命名空间(加载制定的properties文件)
<!--引入新的命名空间,context--> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" 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 http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd" > <!-- 使用新的命名空间 context (可实现动态获取)--> <context:property-placeholder location="jdbc.properties"></context:property-placeholder> <!--动态获取jdbc的属性,使用占位符${属性名} --> <bean class="com.alibaba.druid.pool.DruidDataSource"> <property name="driverClassName" value="${jdbc.driver}"></property> <property name="url" value="${jdbc.url}"></property> <property name="username" value="${jdbc.username}"></property> <property name="password" value="${jdbc.password}"></property> </bean> <!-- 引入BookDao,用于测试是否正确访问--> <bean id="bookDao" class="com.mao.dao.impl.BookDaoImpl"> <property name="name" value="${jdbc.driver}"></property> </bean>
引入数据库信息
jdbc.driver=com.mysql.cj.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/smbms jdbc.username=root jdbc.password=123456
测试类
public class App { public static void main(String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml"); // DataSource datasource = (DataSource) ctx.getBean("datasource"); // System.out.println(datasource); BookDao bookDao = (BookDao) ctx.getBean("bookDao"); bookDao.save(); } }
dao层
public interface BookDao { public void save(); } public class BookDaoImpl implements BookDao { // 用于测试是否生效,访问jdbc的文件 private String name; public void setName(String name) { this.name = name; } @Override public void save() { System.out.println("save ..."+name); } }
对context命名空间进行优化
<!-- 加载所有的properties文件 system-properties-mode设置为永不访问系统的同名--> <context:property-placeholder location="classpath*:*.properties" system-properties-mode="NEVER"></context:property-placeholder>
7. 注解开发定义bean
扫描组件
<context:component-scan base-package="com.mao" ></context:component-scan>
//@Component("bookService") //@Service public class BookServiceImpl implements BookService { private BookDao bookDao; // private BookDao bookDao = new BookDaoImpl();//引入dao层(可以不写,在bean里引入即可!) public void setBookDao(BookDao bookDao) { this.bookDao = bookDao; } @Override public void save() { System.out.println("bookService save..."); //前面已经定义了bookDao,调用了dao层,那么就可以直接调用其方法了 // bookDao.save(); } }
//@Component("bookDao") @Repository("bookDao") public class BookDaoImpl implements BookDao { @Override public void save() { System.out.println("bookDao save..."); } }
public class App { public static void main(String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml"); // DataSource datasource = (DataSource) ctx.getBean("datasource"); // System.out.println(datasource); BookDao bookDao = (BookDao) ctx.getBean("bookDao"); bookDao.save(); //BookService bookService = (BookService)ctx.getBean("bookService"); BookService bookService = ctx.getBean(BookService.class); bookService.save(); } }
7. 纯注解开发
这里的ClassPath加载被注解加载所替代:即AnnotationConfigApplicationContext,后面加载Java类的字节码文件,获取配置
public class AppForAnnotation { public static void main(String[] args) { ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class); BookDao bookDao = (BookDao) ctx.getBean("bookDao"); bookDao.save(); BookService bookService = ctx.getBean(BookService.class); bookService.save(); } }
将applicationContext.xml文件改成java类来实现注解!
@Configuration则是bean的内容
@ComponentScan("com.mao")则是context命名空间的内容
package com.mao.config; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; @Configuration @ComponentScan("com.mao") public class SpringConfig { }
8. bean的作用范围和生命周期
@Repository @Scope("prototype") public class BookDaoImpl implements BookDao { @Override public void save() { System.out.println("bookDao save..."); } @PostConstruct public void init() { System.out.println("init..."); } @PreDestroy public void destory() { System.out.println("destory..."); } }
public class AppForAnnotation { public static void main(String[] args) { AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class); BookDao bookDao1 = ctx.getBean(BookDao.class); BookDao bookDao2 = ctx.getBean(BookDao.class); System.out.println(bookDao1); System.out.println(bookDao2); ctx.close(); System.out.println(ctx.isClosed()); // BookService bookService = ctx.getBean(BookService.class); // bookService.save(); } }
9. 注解开发依赖注入
@Repository("bookDao") @Scope("prototype") public class BookDaoImpl implements BookDao { @Value("${name}") private String name; @Override public void save() { System.out.println("bookDao save..."+name); } }
package com.mao.service.impl; import com.mao.dao.BookDao; import com.mao.dao.impl.BookDaoImpl; import com.mao.service.BookService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import org.springframework.stereotype.Service; @Service public class BookServiceImpl implements BookService { //自动装配 @Autowired private BookDao bookDao; // private BookDao bookDao = new BookDaoImpl();//引入dao层(可以不写,在bean里引入即可!) public void setBookDao(BookDao bookDao) { this.bookDao = bookDao; } @Override public void save() { System.out.println("bookService save..."); //前面已经定义了bookDao,调用了dao层,那么就可以直接调用其方法了 bookDao.save(); } }
public class AppForAnnotation { public static void main(String[] args) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class); BookService bookService = context.getBean(BookService.class); bookService.save(); } }
@Configuration @ComponentScan("com.mao") @PropertySource("jdbc.properties") public class SpringConfig { } name=com.mysql.cj.jdbc.Driver