1.IOC控制反转16
●控制反转是一种思想。
●控制反转是为了降低程序耦合度,提高程序扩展性,实现OCP原则和DIP原则。
●什么是控制反转?
○将对象的创建权交给第三方容器负责。
○交出对象与对象关系的维护权,交给第三方容器负责。
●如何实现控制反转的思想?
○DI(DependencyInjection):依赖注入
2.依赖注入16依靠注入来控制逆转的想法。
Spring依靠注入来完成Bean管理。
Bean管理说:Bean对象的创建,以及Bean对象中属性的赋值(或Bean对象之间关系的维护)。
依赖注入:
●依赖是指对象与对象之间的关系。
●注入是指通过注入使对象与对象产生关系的数据传输行为。
依赖注入有两种常见的实现方法:
●第一种:set注入
●二是结构注入
2.1set注入177基于set方法实现的set注入,底层会通过反射机制调用属性对应的set方法,然后赋值属性。这种方法要求属性必须向外界提供set方法。
package com.powernode.spring6.dao;import org.slf4j.Logger;import org.slf4j.LoggerFactory;/** * Bean 17 **/public class UserDao { private static final Logger logger = LoggerFactory.getLogger(UserDao.class); public void insert(){ //System.out.println(”数据库正在保存用户信息。"); // 使用log4j2日志框架 logger.info(”数据库正在保存用户信息。"); // 使用log4j2日志框架 logger.info(数据库正在保存用户信息。); }}
package com.powernode.spring6.service;import com.powernode.spring6.dao.UserDao;/** * Bean 17 **/public class UserService { private UserDao userDao;// // 如果set注入,必须提供set方法。 // Spring容器将调用此set方法赋值userdao属性。 // Spring容器将调用此set方法赋值userdao属性。 // 在不使用IDEA工具的情况下,我自己写一个set方法。不符合javabean规范。 // 至少这种方法是从set单词开始的。前三个字母不能随便写,必须是“set" /* public void setMySQLUserDao(UserDao xyz){ this.userDao = xyz; }*/ // 这种set方法是由符合javabean规范的IDEA工具生成的。 public void setUserDao(UserDao userDao) { this.userDao = userDao; } public void saveUser(){ // 将用户信息保存到数据库 userDao.insert(); //vipDao.insert(); }}
@Test public void testSetDI(){ ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml"); UserService userService = applicationContext.getBean("userServiceBean", UserService.class); userService.saveUser(); }
spring.xml
<!--配置dao--> <bean id="userDaoBean" class="com.powernode.spring6.dao.UserDao"/> <!--配置service--> <bean id="userServiceBean" class="com.powernode.spring6.service.UserService"> <!-- 要使Spring调用相应的set方法,需要配置property标签 17--> <!-- name属性如何指定值:set方法的方法名,去掉set,然后将剩余的单词首字母变成小写,写在这里。--> <!-- ref翻译为引用。英语单词:references。ref后面指定的是要注入的beanid。--><!-- <property name="mySQLUserDao" ref="userDaoBean"/>--> <!--在命名set方法时,不要为难自己,按照规范来。所以一般来说,在name位置写属性名是可以的。--> <property name="userDao" ref="userDaoBean"/> <!--<property name="vipDao" ref="vipDaoBean"/>--><!-- <property name="abc" ref="vipDaoBean"/>--> </bean>
关键内容是什么原则:
<?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"> <bean id="userDaoBean" class="com.powernode.spring6.dao.UserDao"/> <bean id="userServiceBean" class="com.powernode.spring6.service.UserService"> <property name="userDao" ref="userDaoBean"/> </bean></beans>
2.1.1实现原理:17属性名通过property标签获得:userDao
set方法名通过属性名推断出来:setUserDao
通过反射机制调用setuserdao()给属性赋值
name是property标签的属性名。
property标签的ref是要注入bean对象的id。(bean最简单的组装方式就是通过ref属性完成bean的组装。组装是指创建系统组件之间关联的动作)
我们再写一个VipDao,故意把set方法写成setabc,然后在spring.property标签在xml文件中的name是属性名故意写成vipDao
再次测试:
package com.powernode.spring6.dao;import org.slf4j.Logger;import org.slf4j.LoggerFactory;/*** dao 17 **/public class VipDao { private static final Logger logger = LoggerFactory.getLogger(VipDao.class); public void insert(){ logger.info(”Vip信息正在保存!!!!!"); }}
private VipDao vipDao; public void setAbc(VipDao vipDao){ this.vipDao = vipDao; }
<bean id="vipDaoBean" class="com.powernode.spring6.dao.VipDao"/> <bean id="userServiceBean" class="com.powernode.spring6.service.UserService"> <property name="vipDao" ref="vipDaoBean"/> </bean>
2.1.2结论:<property name=" " ref=""/>
property标签的name是set方法后面的字母名(首字母小写后)
例如
property标签的name是:setUserDao()方法名的演变。演变的规律是:
●setUsername()演变为username
●setPassword()演变为pasword
●setUserDao()演变为userDaoo
●setUserService()演变为userservice
property标签的ref是注入bean对象的id,通常是clas=“标签中的值”
set注入的核心实现原理:通过反射机制调用set法赋值属性,使两个对象之间产生关系。
2.2构造注入18核心原理:通过调用结构法赋值属性。
beans.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" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="xxxx" class="com.powernode.spring6.dao.UserDao"/> <bean id="yyyy" class="com.powernode.spring6.dao.VipDao"/> <bean id=“csbean3” class="com.powernode.spring6.service.CustomerService"> <!--不指定下标,也不指定参数名,让spring自己做类型匹配。--不指定下标,也不指定参数名,让spring自己做类型匹配。--> <!--这种方法实际上是根据类型注入的。spring将根据类型自动判断将ref注入哪个参数。--> <constructor-arg ref="yyyy"/> <constructor-arg ref="xxxx"/> </bean> <bean id=“csbean2” class="com.powernode.spring6.service.CustomerService"> <!--根据结构方法参数的名称注入。--> <constructor-arg name="vipDao" ref="yyyy"/> <constructor-arg name="userDao" ref="xxxx"/> </bean> <!--<bean id="csBean" class="com.powernode.spring6.service.CustomerService"><!--<bean id="csBean" class="com.powernode.spring6.service.CustomerService"><!– 构造输入 18–> <!– Index属性指定参数下标,第一个参数为0,第二个参数为1,第三个参数为2,以此类推。 ref属性用于指定注入bean的id –><!– 指定结构方法的第一个参数为0–> <constructor-arg index="0" ref="xxxx"/><!– 指定结构方法的第二个参数为1–> <constructor-arg index="1" ref="yyyy"/> </bean>--></beans>
com.powernode.spring6.service
CustomerService
package com.powernode.spring6.service;import com.powernode.spring6.dao.UserDao;import com.powernode.spring6.dao.VipDao;/** * Bean 构造注入 18 **/public class CustomerService { private UserDao userDao; private VipDao vipDao; public CustomerService(UserDao userDao, VipDao vipDao) { this.userDao = userDao; this.vipDao = vipDao; } /*public void setUserDao(UserDao userDao) { this.userDao = userDao; } public void setVipDao(VipDao vipDao) { this.vipDao = vipDao; }*/ public void save(){ userDao.insert(); vipDao.insert(); }}
///结构注入 18 @Test public void testConstructorDI(){ ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");// CustomerService customerService = applicationContext.getBean("csBean", CustomerService.class);// customerService.save();// CustomerService csbean2 = applicationContext.getBean("csbean2", CustomerService.class);// csbean2.save(); CustomerService csbean3 = applicationContext.getBean("csbean3", CustomerService.class); csbean3.save(); }
2.2.1结论18通过测试得知,注入结构方法时:
●可通过下标
●可通过参数名
●也可以自动推断类型,无需指定下标和参数名。
Spring在装配上还是比较强壮的。
3.set注入专题1193.19注入外部Bean以前使用的案例是注入外部Bean的方式。
OrderDao
package com.powernode.spring6.dao;import org.slf4j.Logger;import org.slf4j.LoggerFactory;///set注入专题,订单dao 19public class OrderDao { private static final Logger logger = LoggerFactory.getLogger(OrderDao.class); public void insert(){ logger.info(”订单正在生成。。。。。"); }}
OrderService
package com.powernode.spring6.service;import com.powernode.spring6.dao.OrderDao;///set注入专题 19public class OrderService { private OrderDao orderDao; ////通过set赋值属性 19 public void setOrderDao(OrderDao orderDao) { this.orderDao = orderDao; } /////生成订单的业务方法 19 public void generate(){ orderDao.insert(); }}
set-di.xml
<!-- 声明定义外部beann 19--> <bean id="orderDaoBean" class="com.powernode.spring6.dao.OrderDao"></bean> <bean id="orderServiceBean" class="com.powernode.spring6.service.OrderService"><!-- 使用ref属性进行介绍,这就是注入外部beann 19--> <property name="orderDao" ref="orderDaoBean"/> </bean> <bean id=“orderserviceBean2” class="com.powernode.spring6.service.OrderService"> <property name="orderDao"><!-- 嵌套bean标签用于property标签,这就是内部bean 19--> <bean class="com.powernode.spring6.dao.OrderDao"></bean> </property> </bean>
测试
///set注入专题,之 外部bean和内部bean 19 @Test public void testsetDI2(){ ApplicationContext applicationContext = new ClassPathXmlApplicationContext("set-di.xml"); OrderService orderServiceBean = applicationContext.getBean("orderServiceBean", OrderService.class); orderServiceBean.generate(); OrderService orderserviceBean = applicationContext.getBean("orderserviceBean", OrderService.class); orderserviceBean.generate(); }
3.2set注入专题简单类型20需要特别注意的是,如果给简单类型赋值,则使用value属性或value标签。而不是ref。
set-di.xml
<!-- set注入专题的简单类型 20--> <bean id="userBean" class="com.powernode.spring6.bean.User"> <!--重点:如果给简单类型赋值,就不能使用ref。value需要使用。--> <property name="username" value="张三"/> <property name="password" value="123"/> <property name="age" value="20"/> </bean>
package com.powernode.spring6.bean;///简单类型的普通Java///set注入专题 20public class User { private String username; private String password; private int age; public void setUsername(String username) { this.username = username; } public void setPassword(String password) { this.password = password; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "User{" + "username='" + username + '\'' + ", password='" + password + '\'' + ", age=" + age + '}'; }}
///set注入专题的简单类型 20 @Test public void testSimpleTypeSet(){ ApplicationContext applicationContext = new ClassPathXmlApplicationContext("set-di.xml"); User user = applicationContext.getBean("userBean", User.class); System.out.println(user);// SimpleValueType svt = applicationContext.getBean("svt", SimpleValueType.class);// System.out.println(svt); }
3.2.哪种类型是简单类型21可以看到源码阅读
/** * Check if the given type represents a "simple" value type: a primitive or * primitive wrapper, an enum, a String or other CharSequence, a Number, a * Date, a Temporal, a URI, a URL, a Locale, or a Class. * {@code Void} and {@code void} are not considered simple value types. * @param type the type to check * @return whether the given type represents a "simple" value type * @see #isSimpleProperty(Class) */public static boolean isSimpleValueType(Class type) {return (Void.class != type && void.class != type &&(ClassUtils.isPrimitiveOrWrapper(type) ||Enum.class.isAssignableFrom(type) ||charSequencece.class.isAssignableFrom(type) ||Number.class.isAssignableFrom(type) |||Date.class.isAssignableFrom(type) ||Temporal.class.isAssignableFrom(type) ||URI.class == type ||URL.class == type |||Locale.class == type ||Class.class == type));}
3.2.1.1根据源码分析,简单类型包括:22需要注意的是:
●如果将Date视为简单类型,则不能随意编写日期字符串格式。格式必须符合Date的tostring()方法格式。显然,这是鸡肋。如果我们提供这样一个日期字符串:2010-10-11,我们不能在这里赋予Date类型的属性。
●Spring6后,当注入URL时,将检测URL字符串的有效性。如果它是一个存在的URL,那就没问题了。如果没有,请报告错误。
●基本数据类型
●基本数据类型对应的包装类型
●String或其他CharSequence子类
●Number子类
●Date子类23
如果你坚持把Date作为一个简单的类型,使用value赋值,这个日期字符串格式需要在实际开发中,我们通常不把Date作为一个简单的类型,尽管它是一个简单的类型。ref通常用于赋值Date类型的属性。
●Enum子类
●URI
●URL
●Temporal子类
●Locale
●Class
●还包括上述简单值类型对应的数组类型。
package com.powernode.spring6.bean;/** * 这是一枚枚举 22 **/public enum Season { SPRING, SUMMER, AUTUMN, WINTER}
com.powernode.spring6.bean
SimpleValueType
package com.powernode.spring6.bean;import java.util.Date;/** * 简单类型的测试 22 **/public class SimpleValueType { /* 包括以下简单类型 22 public static boolean isSimpleValueType(Class type) {return (Void.class != type && void.class != type &&(ClassUtils.isPrimitiveOrWrapper(type) ||Enum.class.isAssignableFrom(type) ||charSequencece.class.isAssignableFrom(type) ||Number.class.isAssignableFrom(type) |||Date.class.isAssignableFrom(type) || java.util.简单类型的Temporalate.class.isAssignableFrom(type) || Temporal是Java8提供的时间和时区类型URI.class == type ||URL.class == type |||Locale.class == type || Locale是语言类的,也是一种简单的类型。Class.class == type));} */ private int age; private Integer age2; private boolean flag; private Boolean flag2; private char c; private Character c2; private Season season; private String username; private Class clazz; // 生日 private Date birth; public void setBirth(Date birth) { this.birth = birth; } @Override public String toString() { return "SimpleValueType{" + "age=" + age + ", age2=" + age2 + ", flag=" + flag + ", flag2=“” + flag2 + ", c=" + c + ", c2=" + c2 + ", season=" + season + ", username='" + username + '\'' + ", clazz=" + clazz + ", birth=" + birth + '}'; } public void setAge(int age) { this.age = age; } public void setAge2(Integer age2) { this.age2 = age2; } public void setFlag(boolean flag) { this.flag = flag; } public void setFlag2(Boolean flag2) { this.flag2 = flag2; } public void setC(char c) { this.c = c; } public void setC2(Character c2) { this.c2 = c2; } public void setSeason(Season season) { this.season = season; } public void setUsername(String username) { this.username = username; } public void setClazz(Class clazz) { this.clazz = clazz; }}
<!--测试哪些类型的简单类型? 22--> <bean id="svt" class="com.powernode.spring6.bean.SimpleValueType"> <property name="age" value="20"/> <property name="age2" value="20"/> <property name="username" value="zhangsan"/> <property name="season" value="SPRING"/> <property name="flag" value="false"/> <property name=“flag2” value="true"/> <property name="c" value="男"/> <property name="c2" value="女"/> <property name="clazz" value="java.lang.String"/> <!--报错了,说字符串1970-10-11不能转换成java.util.Date类型。--报错了,说字符串1970-10-11不能转换成java.util.Date类型。 23--> <!--<property name="birth" value="1970-10-11"/>--> <!--如果您坚持使用value赋值Date作为一种简单的类型,则需要此日期字符串格式--> <!--尽管Date是一种简单的类型,但在实际开发中,我们通常不会将Date视为一种简单的类型。 ref通常用于赋值Date类型的属性。--> <property name="birth" value="Wed Oct 19 16:28:13 CST 2022"/> </bean>
///set注入专题的简单类型 20 @Test public void testSimpleTypeSet(){ ApplicationContext applicationContext = new ClassPathXmlApplicationContext("set-di.xml");// User user = applicationContext.getBean("userBean", User.class);// System.out.println(user); //测试简单类型2 22 SimpleValueType svt = applicationContext.getBean("svt", SimpleValueType.class); System.out.println(svt); }
3.2.24简单类型在实际开发中的应用经典案例:数据源属性注入值:
假设我们现在必须自己写一个数据源,我们都知道所有的数据源都必须实现javax.sql.DataSource接口,数据源中应该有连接数据库的信息,例如:driver、url、username、password等。
com.powernode.spring6.jdbc
MyDataSource
package com.powernode.spring6.jdbc;import javax.sql.DataSource;import java.io.PrintWriter;import java.sql.Connection;import java.sql.SQLException;import java.sql.SQLFeatureNotSupportedException;import java.util.logging.Logger;// 在实际开发中应用简单类型 Java规范应实现24///所有数据源:javax.sql.DataSource///什么是数据源:可以为您提供Conection对象,都是数据源。public class MyDataSource implements DataSource { // Spring容器可以管理数据源。 private String driver; private String url; private String username; private String password; public void setDriver(String driver) { this.driver = driver; } public void setUrl(String url) { this.url = url; } public void setUsername(String username) { this.username = username; } public void setPassword(String password) { this.password = password; } @Override public String toString() { return "MyDataSource{" + "driver='" + driver + '\'' + ", url='" + url + '\'' + ", username='" + username + '\'' + ", password='" + password + '\'' + '}'; } @Override public Connection getConnection() throws SQLException { // 在获取数据库连接对象时,需要四个信息:driver url username password 24 return null; } @Override public Connection getConnection(String username, String password) throws SQLException { return null; } @Override public PrintWriter getLogWriter() throws SQLException { return null; } @Override public void setLogWriter(PrintWriter out) throws SQLException { } @Override public void setLoginTimeout(int seconds) throws SQLException { } @Override public int getLoginTimeout() throws SQLException { return 0; } @Override public Logger getParentLogger() throws SQLFeatureNotSupportedException { return null; } @Override public T unwrap(Class iface) throws SQLException { return null; } @Override public boolean isWrapperFor(Class iface) throws SQLException { return false; }}
<!-- 在实际开发中应用简单类型 让spring管理我们的数据源 24--> <bean id="myDataSource" class="com.powernode.spring6.jdbc.MyDataSource"> <property name="driver" value="com.mysql.cj.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/spring6> <property name="username" value="root"/> <property name="password" value="lzl"/> </bean>
///简单类型在实际开发中的应用 24 @Test public void testMyDataSource(){ ApplicationContext applicationContext = new ClassPathXmlApplicationContext("set-di.xml"); MyDataSource myDataSource = applicationContext.getBean("myDataSource", MyDataSource.class); System.out.println(myDataSource); }