目录
参考
Spring框架教程
https://blog.youkuaiyun.com/One_L_Star/article/details/100924963
Spring框架
https://www.w3cschool.cn/wkspring/
Spring框架 · 语雀课程目标理解Spring框架的两大核心掌握Spring...
https://www.yuque.com/neuedu/campus/zkd7cg
一:简单介绍Spring:
Johnson 在其著作Expert One-On-One J2EE Development and Design中阐述的部分理念和原型衍生而来。它是为了解决企业应用开发的复杂性而创建的。框架的主要优势之一就是其分层架构,分层架构允许使用者选择使用哪一个组件,同时为 J2EE 应用程序开发提供集成的框架。Spring使用基本的JavaBean来完成以前只可能由EJB完成的事情。然而,Spring的用途不仅限于服务器端的开发。从简单性、可测试性和松耦合的角度而言,任何Java应用都可以从Spring中受益。Spring的核心是控制反转(IoC)和面向切面(AOP)。
其中最核心的就是ioc和aop技术
二:spring家族:
三:spring的核心技术之一:控制反转(IoC)
(一):目前项目中的问题:
当前项目中会出现耦合度很高的代码,而代码最好的实现就是高内聚,低耦合。耦合度降低,使得代码效率安全更好。
比如:现在的dao层需要对应的impl ,需要new对象,回调的时候,需要一一对应,这样就闲的代码耦合度很高,如果实现层方面改变了,就需要重新new一个对象。
而spring就解决了这样的问题,使用对象的时候,在程序中不要主动使用new产生对象,转换为由外部提供对象,这时候就引出来了ioc的概念。
(二)Bean,DI,IOC,IOC容器的概念
IOC(Inversion of Control):控制反转,就是说之前需要new产生一个对象,现在不需要了,直接由外部提供的对象,就可以直接实现,这种思想就叫做控制反转。
IOC容器:对于那个外部,Spring就提供了一个容器,就是IOC容器,就是充当了IOC思想中的外部。IOC容器就是负责对象的创建,初始化等一系列工作,其中包含了各种类的对象,而这些类的对象,创建的或者被管理的对象,就成为Bean。IOC容器中放的就是一个个bean对象,需要用的时候,就可以直接提供出来。
DI(Dependency Injection):依赖注入,在容器中,建立了bean与bean之间的关系,就是注入依赖。比如说service的业务层就要和dao层的数据操作层建立起来依赖关系。
总结一下:
IOC: 控制反转,控制反转的是对象的创建权DI: 依赖注入,绑定对象与对象之间的依赖关系Spring 创建了一个容器用来存放所创建的对象,这个容器就叫 IOC 容器容器中所存放的一个个对象就叫Bean或Bean对象
(三):案例基础实现IOC和DI
1.需要导入依赖:
<dependencies><dependency><groupId> org.springframework </groupId><artifactId> spring-context </artifactId><version> 5.2.10.RELEASE </version></dependency><dependency><groupId> junit </groupId><artifactId> junit </artifactId><version> 4.12 </version><scope> test </scope></dependency></dependencies>
2.配置好文件:
在resource下面创建spring Config的xml
提醒一下,需要先写了依赖才能出来spring config。
<?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/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd" ><!--bean 标签标示配置 beanid 属性标示给 bean起名字class 属性表示给 bean 定义类型--><bean id = "bookDao" class = "com.blue.dao.impl.BookDaoImpl" /><bean id = "bookService" class = "com.blue.service.impl.BookServiceImpl" /></beans>
其中需要特别主义的就是,那个class后面的全路径报名,不能对应接口,因为很简单,接口不能创建对象,因为这个bean其实替代的就是一个对象。
3获取IOC容器以及执行相应的对象:
public static void main ( String [] args ) {// 获取 IOC 容器ApplicationContext ctx = new ClassPathXmlApplicationContext ( "applicationContext.xml" );}
其中那个后面的applicationContext.xml 就填你自己的springconfig 的配置文件的名字,一一对应就好。
public static void main(String[] args) {
//获取IOC容器
ApplicationContext ctx = new
ClassPathXmlApplicationContext("applicationContext.xml");
// BookDao bookDao = (BookDao) ctx.getBean("bookDao");
// bookDao.method();
BookService bookService = (BookService) ctx.getBean("bookService");
bookService.method();
}
这样就不会出现了new对象的情况,耦合度降低了。
但是这时候会发现,在service中还存在着new daoimpl的操作,这时候就需要了依赖注入。
将一些东西在配置文件之中就对应好关系,这样就会进一步降低了耦合度。
4简单应用DI依赖注入:
public class BookServiceImpl implements BookService {
//删除业务层中使用new的方式创建的dao对象
private BookDao bookDao;
public void save() {
System.out.println("book service save ...");
bookDao.save();
}
//提供对应的set方法
public void setBookDao(BookDao bookDao) {
this.bookDao = bookDao;
}
}
配置文件改为这样:
<bean id="bookDao" class="com.blue.dao.impl.BookDaoImpl"/>
<bean id="bookService" class="com.blue.service.impl.BookServiceImpl">
<!--配置server与dao的关系-->
<!--property标签表示配置当前bean的属性
name属性表示配置哪一个具体的属性
ref属性表示参照哪一个bean
-->
<property name="bookDao" ref="bookDao"/>
</bean>
详细说明一下这里的 name里面的 bookDao 就是service里面通过set方法找到的对应的 bookDao
而ref就是对应的这个配置文件中的某一个具体已经对应好的bean对象。这样就使得两者之间建立了详细的联系。
总结一下几个注意事项:
1.NoSuchBeanDefinitionException 说明那个bean在配置文件里面没有。或者你名字写错了。
2.bean对象 除了可以直接用id获取,name同样可以获取,name就是别名,可以添加很多个值。id必须是唯一的,bean与bean之间的id尽量还是不要重复。
3scope:可以控制bean对象的创建是否是单例的,
<bean id="bookDao" name="dao" class="com.blue.dao.impl.BookDaoImpl"scope="prototype"/>
其中singleton默认是单例的,protoype就是非单例的。
单例的好处:
4.
(四):bean实例化:
提供三种方法:构造方法,静态工厂,实例工厂
(1)构造方法:这种方法就是什么都不要干,静静的写一个配置文件就好了
public interface BookDao {
public void save();
}
public class BookDaoImpl implements BookDao {
public void save() {
System.out.println("book dao save ...");
}
}
<bean id="bookDao" class="com.blue.dao.impl.BookDaoImpl"/>
public class AppForInstanceBook {
public static void main(String[] args) {
ApplicationContext ctx = new
ClassPathXmlApplicationContext("applicationContext.xml");
BookDao bookDao = (BookDao) ctx.getBean("bookDao");
bookDao.save();
}
}
此时就是一个非常简单的无参构造方法进行了创建bean,也是之前开始案例使用的
public class BookDaoImpl implements BookDao {
public BookDaoImpl() {
System.out.println("book dao constructor is running ....");
}
public void save() {
System.out.println("book dao save ...");
}
}
这样可以检测出来使用通过的无参构造方法 创建的bean对象。
(2)静态工厂:
与之前不同的就是 在创建一个新的类:DaoFactory并提供一个静态方法。
所以叫做静态工厂。
//静态工厂创建对象
public class DaoFactory {
public static Dao getDao(){
return new DaoImpl();
}
}
配置文件加上
<bean id="orderDao" class="com.blue.factory.OrderDaoFactory" factory-method="getOrderDao"/>
其实看似这个静态工厂没啥太大用处,就是多了一个类,在获得这个dao层的对象,但是换个角度想,既然又创建了这个类,那么是不是就可以在返回这个对象之前进行一些操作,比如初始化一些东西,加载一些东西。
//静态工厂创建对象
public class DaoFactory {
public static Dao getDao(){
System.out.println("Hello nihaoakeke");
return new DaoImpl();
}
}
这样的话就是在执行getdao最后返回值之前,会输出一句话:就是 Hello nihaoakek。
这就是单独创建一个工厂类的好处。
(3)实例工厂:
就是改成public,相比较static静态不能之前加载,需要提前创建一个bean,然后进行对应。
public class UserDaoFactory {
public UserDao getUserDao(){
return new UserDaoImpl();
}
}
所以配置文件:需要提前有一个userFactory的bean
<bean id = "userFactory" class = "com.blue.factory.UserDaoFactory" /><bean id = "userDao" factory-method = "getUserDao" factory-bean = "userFactory" />
到这里,实例化工厂的方式就已经介绍完了,配置的过程还是比较复杂,所以spring为了简化这种配置方式就提供了一种叫做factorybean的方式来简化。
(4)factortybean的使用:
就是直接创建一个userdaofactorybean的类,实现factorybean接口,重写接口方法
public class UserDaoFactoryBean implements FactoryBean<UserDao> {
//代替原始实例工厂中创建对象的方法
public UserDao getObject() throws Exception {
return new UserDaoImpl();
}
//返回所创建类的Class对象
public Class<?> getObjectType() {
return UserDao.class;
}
}
在配置文件中:
<bean id = "userDao" class = "com.blue.factory.UserDaoFactoryBean" />
其中
default boolean isSingleton () {return true ;}
这个方法是处理 是否单例化的。
总结一下:
在这三种方法中,重点需要掌握的是构造方法和factorybean就可以了。
需要注意的一点是,构造方法在类中默认会提供,但是如果重写了构造方法,默认的就会消失,在使用的过程中需要注意,如果需要重写构造方法,最好把默认的构造方法也重写下。
(五):bean的生命周期:
就是从创建到消亡的完整过程。
public class BookDaoImpl implements BookDao {
public void save() {
System.out.println("book dao save ...");
}
//表示bean初始化对应的操作
public void init(){
System.out.println("init...");
}
//表示bean销毁前对应的操作
public void destory(){
System.out.println("destory...");
}
}
配置文件:
<bean id = "bookDao" class = "com.blue.dao.impl.BookDaoImpl" init-method = "init"destroy-method = "destory" />
这时候会发现这里的destory方法没有执行,因为在执行完了main方法之后,jvm已经关闭,destory方法还没有来的及就以己经关闭了。
所以可以用:
ClassPathXmlApplicationContext ctx = newClassPathXmlApplicationContext ( "applicationContext.xml" );ctx.close();//关闭//ctx.registerShutdownHook(); 也可以
其实,这里的方法实现起来会很复杂,为了简化,又出现了新的方式:
public class BookServiceImpl implements BookService, InitializingBean,
DisposableBean {
private BookDao bookDao;
public void setBookDao(BookDao bookDao) {
this.bookDao = bookDao;
}
public void save() {
System.out.println("book service save ...");
bookDao.save();
}
public void destroy() throws Exception {
System.out.println("service destroy");
}
public void afterPropertiesSet() throws Exception {
System.out.println("service init");
}
}
其中注意到setBookDao先执行,后执行init(); 如果在setBookDao中有东西,则会先执行。
四:依赖注入DI
setter 注入:简单类型引用类型构造器注入:简单类型引用类型
(一)setter注入:
public interface BookDao {
public void save();
}
public class BookDaoImpl implements BookDao {
public void save() {
System.out.println("book dao save ...");
}
}
public interface UserDao {
public void save();
}
public class UserDaoImpl implements UserDao {
public void save() {
System.out.println("user dao save ...");
}
}
public interface BookService {
public void save();
}
public class BookServiceImpl implements BookService {
private BookDao bookDao;
private UserDao userDao;
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
public void setBookDao(BookDao bookDao) {
this.bookDao = bookDao;
}
public void save() {
System.out.println("book service save ...");
bookDao.save();
userDao.save();
}
}
public class AppForDISet {
public static void main( String[] args ) {
ApplicationContext ctx = new
ClassPathXmlApplicationContext("applicationContext.xml");
BookService bookService = (BookService) ctx.getBean("bookService");
bookService.save();
}
}
<bean id="bookDao" class="com.blue.dao.impl.BookDaoImpl"/>
<bean id="userDao" class="com.blue.dao.impl.UserDaoImpl"/>
<bean id="bookService" class="com.blue.service.impl.BookServiceImpl">
<property name="bookDao" ref="bookDao"/>
<property name="userDao" ref="userDao"/>
</bean>
public class BookDaoImpl implements BookDao {
private String databaseName;
private int connectionNum;
public void setConnectionNum(int connectionNum) {
this.connectionNum = connectionNum;
}
public void setDatabaseName(String databaseName) {
this.databaseName = databaseName;
}
public void save() {
System.out.println("book dao save
..."+databaseName+", "+connectionNum);
}
}
<bean id = "bookDao" class = "com.blue.dao.impl.BookDaoImpl" ><property name = "databaseName" value = "mysql" /><property name = "connectionNum" value = "10" /></bean><bean id = "userDao" class = "com.blue.dao.impl.UserDaoImpl" /><bean id = "bookService" class = "com.blue.service.impl.BookServiceImpl" ><property name = "bookDao" ref = "bookDao" /><property name = "userDao" ref = "userDao" /></bean>
注意:注入类型值得时候,value得值类型要和name对应的类型一样。因为它是自动类型转换的,不然就会出现报错。
(2)构造器进行注入注入引用类型:
把setter方法删除,并且附加上带有引用类型的构造器方法
private BookDao bookDao ;private UserDao userDao ;public BookServiceImpl ( BookDao bookDao , UserDao userDao ) {this . bookDao = bookDao ;this . userDao = userDao ;}
配置文件
<bean id = "bookDao" class = "com.blue.dao.impl.BookDaoImpl" /><bean id = "userDao" class = "com.blue.dao.impl.UserDaoImpl" /><bean id = "bookService" class = "com.blue.service.impl.BookServiceImpl" ><constructor-arg name = "bookDao" ref = "bookDao" /><constructor-arg name = "userDao" ref = "userDao" /></bean>
同样的问题,导入普通的数据类型
private String databaseName ;private int connectionNum ;public BookDaoImpl ( String databaseName , int connectionNum ) {this . databaseName = databaseName ;this . connectionNum = connectionNum ;}
配置文件中:
<bean id = "bookDao" class = "com.blue.dao.impl.BookDaoImpl" ><constructor-arg name = "databaseName" value = "mysql" /><constructor-arg name = "connectionNum" value = "666" /></bean><bean id = "userDao" class = "com.blue.dao.impl.UserDaoImpl" /><bean id = "bookService" class = "com.blue.service.impl.BookServiceImpl" ><constructor-arg name = "bookDao" ref = "bookDao" /><constructor-arg name = "userDao" ref = "userDao" /></bean>
综合上述的情况,发现耦合度依然很高,name的名字依然需要和之前那个位置的名字一一对一个
有两种方式解决这个问题:
方法一:删除name属性,添加type的属性,按照类型进行注入
<bean id = "bookDao" class = "com.blue.dao.impl.BookDaoImpl" ><constructor-arg type = "int" value = "10" /><constructor-arg type = "java.lang.String" value = "mysql" /></bean>
方法二:删除type,添加index属性
<bean id = "bookDao" class = "com.blue.dao.impl.BookDaoImpl" ><constructor-arg index = "1" value = "100" /><constructor-arg index = "0" value = "mysql" /></bean>
注意:如果构造器注入,构造器里面的类型必须全部给值,但是setter方法用一个给一个很方便。
(三)自动装配
按照类型进行装配:
把property标签删除,加入autowire属性,
<bean class = "com.blue.dao.impl.BookDaoImpl" /><!--autowire 属性:开启自动装配,通常使用按类型装配 --><bean id = "bookService" class = "com.blue.service.impl.BookServiceImpl"autowire = "byType" />
注意事项:
需要注入属性的类中对应属性的 setter 方法不能省略被注入的对象必须要被 Spring 的 IOC 容器管理按照类型在 Spring 的 IOC 容器中如果找到多个对象,会报 NoUniqueBeanDefinitionException
如果IOC中有多个对象,则用byName的方式
<bean class = "com.blue.dao.impl.BookDaoImpl" /><!--autowire 属性:开启自动装配,通常使用按类型装配 --><bean id = "bookService" class = "com.blue.service.impl.BookServiceImpl"autowire = "byName" />
byname就是根据的set后面的名字进行的匹配注入,耦合度较高不推荐使用。
自动装配适合于引用类型的注入,不能对简单类型进行注入。
集合注入:
注入数组类型
<property name = "array" ><array><value> 100 </value><value> 200 </value><value> 300 </value></array></property>
注入list类型
<property name = "list" ><list><value> itcast </value><value> itheima </value><value> boxuegu </value><value> chuanzhihui </value></list></property>
注入set类型
<property name = "set" ><set><value> itcast </value><value> itheima </value><value> boxuegu </value><value> boxuegu </value></set></property>
注入map类型
<property name = "map" ><map><entry key = "country" value = "china" /><entry key = "province" value = "henan" /><entry key = "city" value = "kaifeng" /></map></property>
注入properties类型数据:
<property name = "properties" ><props><prop key = "country" > china </prop><prop key = "province" > henan </prop><prop key = "city" > kaifeng </prop></props></property>
特别注意在加入这些数据的时候,需要放在bean与bean之间
<bean id = "bookDao" class = "com.blue.dao.impl.BookDaoImpl" >//注入</bean>