关于整个三月份,我为什么都在学习ssm的三大老框架,为什么我称其为老框架呢?从没两三年就会诞生出顺应用户需要与企业生产与开发需求的新型框架,虽然近年来的boot与cloud框架一个负责微服务的分布式架构开发,一个负责云服务之类的开发,但是他们都是spring家族的一部分。为了更好的从大学的编程环境与编程语言的基础知识所形成的习惯性学生式编程思维定式里转换到更好的为企业,为用户开发的需求业务式编程思维。我花费了一个月的时间学习ssm这三个基础框架,当然前提是我们不能只跟着学习怎么用框架,而是了解源码与原理,了解spring的核心原理:控制反转与面向切面编程的逻辑与核心实现方法。了解spring-mvc的运行逻辑,各组件之间的请求与响应,数据的流向等等。如果只是一味的从代码的角度看,学习是一项没有意思的活动,但是如能够从中发现一些有趣的联想与创造,会发现很有意思。
spring框架
我认识的spring框架其实没有官方那么复杂,是一个人类社会的实际模拟,不知道是不是编程语言与编程框架都是人类自己开发的缘故,我在学习一些语言的过程中都会莫名的觉得这个特性,这个处理机制好像我们人类的思维与活动习惯的那种关联性。所以在比如学习java中的方法堆,类堆与栈存储的时候我有时候就会想到,分重要程度,分种类,分使用时间好像都是人类的活动思维习惯和约束吧?我们都知道java对象实例化之后存储的位置其实在堆里,它的局部变量也在它的堆内存中,这说明了一个物件本来所拥有的结构实际上存储的过程中,它们还是一体的,只是会对内部也会进行二级以及三级分类,这样才能有效率的存储与使用。像极了我们会把包饺子需要的材料都放在冰箱,但是我们又会把饺馅与饺皮分开存放,放在一起不分类那不就乱套了?不说这些联想性的思考了,我们回过头来看spring,其实spring包含的两个最核心的技术:IOC(控制反转)与AOP(面向切面编程),尽管后面还有很多封装的接口与类家族来实现这两个技术,我们实际上要掌握的就是这两个技术与在业务逻辑上它们的实际运用方法。
springIOC控制反转
控制反转,有没有剧情突然反转的狗血感觉?这里的控制反转很好理解,来使用联想与类比的方法进行理解,这时一个不恰当的例子在我的脑海里浮现了,通常我在家里想吃红烧肉的时候,我都是自己想好怎么去做,准备各项材料,亲自对材料进行处理,然后烹饪得到美食。可是现在我有了一个框架,也就是我的家长,出于在家里的地位考虑,姑且认为我家长会同意我的正常请求,那么我只需要告诉我妈怎么做就行了(这是一个对于制造的原生信息的传递过程),其他的处理材料呀,烹饪之类的工作都不需要我去做了,框架就会直接为我们创造好我们想要的东西并将它放在框架专属的一个盒子里(我们习惯叫它spring容器),可是我们真的什么都不需要做吗?当然不是,我们需要告诉家长怎么去做?也就是我需要提供一个制造的原生信息。这个信息就是java类。而框架为我们做的就是通过xml配置文件(极其重要)的映射为我们实例化这个类,我们才会得到这个实例化对象。而我们提供这个制造的原生信息的工作就是实现IOC技术的一个重要过程DI(依赖注入)。
DI依赖注入
这里我们需要先提到xml文件,也需要规范一下这个制造原生信息的官方名称Javabean,也可以叫做Bean。
以下代码段是spring的配置文件的待使用的初始样式:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework/schema/beans" xmlns:xsi="http://www.w3/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework/schema/beans http://www.springframework/schema/beans/spring-beans.xsd"> </beans>
我们看到<beans>这个标签,<bean>是我们需要注入的内容标签,所以这个文件头<beans>在没有进行其他的修改之前我们只能够进行bean的注入,后面会添加别的命名空间,会多出很多的配置与功能。
现在我有一个java实体类:
package com.hlc.pojo; public class Role { private long id; private String roleName; private String roleDesc; //出于篇幅考虑,setter方法与getter方法省略 }
那么我们之前使用它的时候一般就是用一句:
Role role = new Role();
来进行对象的实例化,这样的过程就是我拿着制造的原生信息(实际上就是java类)自己去处理得到实例对象。
控制反转就是框架来帮我们实例化java类的一个技术。
现在我们需要在xml文件里的<beans>标签下注入这个java类,让它成为我们的javabean:
<bean id="role" class="com.hlc.pojo.Role"> <property></property>或是<constructor-arg></constructor-arg>需要看需求以及注入依赖的方式 </bean> //这里的id是我们随意取的一个名称,而后面的class则是这个java类的全限定名(可以相关于项目下的源路径)
那么我们来想想java类的构造方法会不会影响这个bean的注入内容变化?基本来看运用最多的是有参数构造与无参数构造,无参构造就相当于你按照场景与业务变化给我做出来就行,而有参构造是你需要我给出详细的需求信息来确定做出来的结果样式,所以DI(依赖注入)技术对这个java类的构造方法的不同也会有不同的注入方式。
setter注入
setter注入方式更多的用在bean与bean之间的互调方式里。
java实体类:
public class Student { private int sno; private String clazz; private String name; //setter注入的方式必须含有set方法 public void setName(String name) { this.name = name; } public void setClazz(String clazz) { this.clazz = clazz; } public void setSno(String sno) { this.name = name; } }
xml文件对应的javabean的内容:
<bean id="student" class="com.hlc.pojo.Student"> <property name="sno" value="18"/> <property name="name" value="黄林春"/> <property name="clazz" value="计科193班"/> </bean>
需要注意的是set注入时<bean>标签中的<property>这个属性标签的作用一般并不是用来直接注入成员变量值的,实现业务类之间互调的一种标签。
构造器注入
构造器注入的方式有很大的耦合性,注入之后的bean是一种属性一致的bean,内容与属性被定住,没有无参那么灵活。
java实体类:
public class User { private String id; private String name; Date brith; private String phone; public User(String id, String name, Date brith, String phone) { this.id = id; this.name = name; this.brith = brith; this.phone = phone; } }
xml文件对应的javabean的内容:
<bean id="user" class ="com.hla.pojo.User"> <!--constructor-arg标签name特性是指向构造方法中的形参,value特性则是直接给出形参的值,ref特性则是指向javabean,注意这里的javabean的必须对应构造方法中某个参数的类型,这里指的是brith参数--> <constructor-arg name="id" value="01"/> <constructor-arg name="name" value="黄林春"/> <constructor-arg name="brith" ref="brithDate"/> <constructor-arg name="phone" value="15136701189"/> </bean> <bean id="brithDate" class="java.util.Date"/>
一定注意这里的constructor-arg与property的用法与区别。
补充:bean标签中的List与Map以及properties三种标签,在java类里定义的变量类型如果为List、Map、properties的话,后续bean里注入属性值就要用到相应的标签,当然我个人理解测试时运用Map的键值对,会有比较好的实用性,带着ref指向性就更好。
容器与bean实例化的方法
在没有学习springmvc之前我们只能手动的实例化bean,这里就必须要掌握下面的测试代码:
@Test public void test1() { //这里的app相当于spring容器访问的客户端,用接口ApplicationContext定义,我们将会利用它去获取bean对象 ApplicationContext app = new ClassPathXmlApplicationContext(""); //app实例化bean采用对象接收 bean指向的java类名 对象名 = app.getBean("bean的id"); //如果java类里写了tostring方法的话可以直接查看注入属性是否成功!没写的话就是标识名了,这里推荐测试一下在利用容器实例化一个bean对象,去看看这两个bean对象的标识名是否一样?就能确定框架爸爸在默认情况下做的红烧肉是不是每天的味道都一样? System.out.println(对象名); }
补充:
-
ApplicationContext是spring框架中接口家族的一个子接口。它的具体实现有三种分别是ClassPathXMLApplicationContext(".xml文件名") 、FileSystemPathApplicationContext("磁盘绝对路径,如C://User/stu/h.xml")、以及注解里的实现方法,后续会提到。目前用的最多的就是第一个。
-
getBean()方法:通过java反射原理,它的两种实现第一种是getBean("id");具体说一说第二种getBean("id所属bean标签指向的全限定名代表的java类名".class);通过字节码文件反射,需要注意的是字节码文件是jvm类加载机制里实现一次编译多次执行的重要翻译过程的产物,也就是说编译之后的字节码文件不会更改,一次翻译多次使用,所以在bean实例化如果采取scope=“prototype”时(下面的scope就会补充到相关),字节码文件没变,但是你却要它产生多例bean对象,就会出冲突错误。
scope作用域
作为比较受关注的属性,scope的两个值prototype与singleton的区别就很重要了,它的使用是在xml文件bean标签里的。
设置bean的scope属性:
<bean id="user" class="com.hlc.pojo.User" scope="prototype"或是"singleton">
首先说当scope=“prototype”时实例化出来的对象的特点,定义这个值的时候每一次使用getBean方法之后(这里比较有特点,就是说scope等于prototype的时候,容器app实例化之后对象并没有创建,而是在运行getBea方法之后才能得到bean的实例化对象,属于半自动化了,我写多少条getBean代码就会产生多少个对象)得到的对象并不是同一个对象,也就是说它每次都会创建新的对象,销毁的机制就有些像java垃圾回收机制GC对于一些自定义类的回收方法,长时间不用自动回收。其次当scope=“singleton”的时候,实例化出来的对象都是同一个对象,无论你写多少行getBean代码,对象都是一个对象。有意思的是这种方式下创建对象的时间和spring容器实例化的时间一致,那么它的回收肯定与spring容器一起了。
配置数据源
我们发现,前面的注入都是简单的对Java实体类的耦合性注入,直接把写好的内容注入进去了,实际上在开发中并没有什么用处。我们需要数据库的链接与使用,数据从数据库出来由我们接收并使用才最合理。老款的java-jdbc链接数据库的方式不适合spring框架的项目开发,我们采用数据池化思想,先来试试数据源配置的过程。与jdbc链接数据库的过程有相同之处,逃不掉的四大内容:驱动,url,用户与密码。
c3p0数据源的配置
测试代码
@Test // 测试c3p0数据源 public void test1() throws Exception{ // new一个c3p0数据源 ComboPooledDataSource dataSource = new ComboPooledDataSource(); // 这里设置驱动会抛出异常,当然用try与catch环绕也可以,我在这里抛出最大的异常就比较懒。 dataSource.setDriverClass("com.mysql.jdbc.Driver"); // 设置数据源地址,本地3306。 dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test"); // 设置用户与密码 dataSource.setUser("root"); dataSource.setPassword("123456"); // 获取资源进行输出地址测试 Connection connection = dataSource.getConnection(); System.out.println(connection); // 使用之后关闭 connection.close(); }
Druid数据源的配置
测试代码
@Test // 测试Druid数据源,我比较在意它与c3p0的区别。特别是set跟的属性名,后续在测试spring容器帮我们配置数据源的时候 // 对<prototype name="set后属性名头字母小写形式"/>有不同的要求,需要注意。 public void test2(){ DruidDataSource dataSource = new DruidDataSource(); dataSource.setDriverClassName("com.mysql.jdbc.Driver"); dataSource.setUrl("jdbc:mysql://localhost:3306/test"); dataSource.setUsername("root"); dataSource.setPassword("123456"); // 这里获取资源的语句会抛出异常,我添加try与catch环绕吧,不能太懒。 try { Connection connection = dataSource.getConnection(); System.out.println(connection); connection.close(); } catch (SQLException e) { e.printStackTrace(); } }
使用properties文件解耦合
jdbc.properties文件
jdbc.driver = com.mysql.jdbc.Driver jdbc.url = jdbc:mysql://localhost:3306/test jdbc.username = root jdbc.password = 123456
测试代码
@Test // 测试数据库properties配置文件的读取 // 说一下为什么要采用读取配置文件的方式来创建与使用数据源呢?个人理解像上述的在java文件中直接 // 设置参数创建的方式在后期由于jvm特性都会变成class字节码文件,再想进行debug以及修改的话返工 // 的可能性比较大,采用配置文件的话,修改就很简单,直接修改配置文件就可以完成,也有一定的解耦作用。 public void test3() throws Exception{ // ResourceBundle专门用来对付jdbc.properties这类文件的读取,所以不需要跟后缀,直接写文件名就行 ResourceBundle resourceBundle = ResourceBundle.getBundle("jdbc"); String driver = resourceBundle.getString("jdbc.driver"); String url = resourceBundle.getString("jdbc.url"); String username = resourceBundle.getString("jdbc.username"); String password = resourceBundle.getString("jdbc.password"); // 这里是测试一下是否正确获取jdbc.properties文件里的内容 // System.out.println(driver); // System.out.println(url); // System.out.println(username); // System.out.println(password); // 创建数据源datasource ComboPooledDataSource dataSource = new ComboPooledDataSource(); // 这里会抛出异常,和上述的两个测试一样采用抛出大异常吧 dataSource.setDriverClass(driver); dataSource.setJdbcUrl(url); dataSource.setUser(username); dataSource.setPassword(password); // 获取资源connection并测试输出 Connection connection = dataSource.getConnection(); System.out.println(connection); connection.close(); }
spring容器配置数据源
测试代码
@Test // 测试spring配置c3p0数据源,关于sprig配置Druid数据源的代码就不敲了哈哈哈哈 public void test4(){ ApplicationContext app = new ClassPathXmlApplicationContext("DataSource.xml"); ComboPooledDataSource dataSource = (ComboPooledDataSource) app.getBean("dataSource"); DataSource dataSource = app.getBean(DataSource.class); try { Connection connection = dataSource.getConnection(); System.out.println(connection); connection1.close(); } catch (SQLException e) { e.printStackTrace(); } }
xml文件
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="com.mysql.jdbc.Driver"/> <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test"/> <property name="user" value="root"/> <property name="password" value="123456"/> </bean>
这里没有结合上面的properties文件的使用,大家可以试试,解耦合性可以更好。
利用bean的互调实现简单业务
通常,在项目开发里,会将数据接收,业务封装,视图控制三种功能的java类或接口分层编写,这样的话功能明确,模块化适用于管理,dao层负责数据持久与接收,主要负责连接数据库编写增删改查的sql语句并将数据传给service层,service层负责业务封装,具备桥接作用,将数据进行传递给controller层,controller层负责视图控制,主要负责API与数据的对应,利用mvc特有的注解进行请求的接收与响应。(当然我们目前的spring框架并不具备真正的web控制,所以只测试前两层的数据流动)如下图:
那么实现这些数据的传递以及实例与实例之间的调用到底是怎么实现的呢?
之前提到,IOC容器会将你在xml文件里配置的bean全部都实例化放在容器独有的内存里,那么你就可以放心大胆的使用了,这里提供的调用方法有两种,一种是在bean中对需要调用的bean进行set方法注入,另一种是在java类里利用注解开发的方式对需要调用的另一个类进行调用。
目前还没有学习可以直接操作数据库的模板或者框架,出于测试数据传递的过程考虑,咱们就不用创建实体类,直接建dao层,当然这里的dao层并没有传递数据的作用,方法的互调也可以模拟数据的流动。
ReloDao
public interface RoleDao { void test(); }
RoleDaoImpl.java
public class RoleDaoImpl implements RoleDao{ public void test(){ System.out.println("run...."); } }
利用setter注入的互调
既然是setter注入的调用,那么一定要在xml文件里有所体现:
<!-- 配置roleService的bean--> <bean id="roleService" class="com.hlc.service.RoleServiceImpl"> <property name="roleDao" ref="roleDao"/> </bean> <!-- 配置roleDao的bean--> <bean id="roleDao" class="com.hlc.dao.RoleDaoImpl"> </bean>
很显然这里的bean中含有setter注入方式的标志性标签<property>,但是真正的标志不是这个,而是java代码中的set方法。
RoleService
public interface RoleService { void test(); }
RoleServiceImpl.java
import com.hlc.dao.RoleDao; public class RoleServiceImpl implements RoleService{ private RoleDao roleDao; public void setRoleDao(RoleDao roleDao) { this.roleDao = roleDao; } public void test(){ roleDao.test(); } }
利用注解@Autowird的互调
一般的ssm开发,会默认在dao层与service采用这样的setter注入达成的bean互调,在controller层则采用有意思的注解互调。
RoleController
import com.hlc.service.RoleService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; @Controller public class RoleController { @Autowired //xml文件中配置好相应的bean之后可以通过spring提供的注解实现bean的互调。 private RoleService roleService; public void test(){ roleService.test(); } }
当然这个@Controller注解目前还没有说明,我们只需要先记住这个注解就是自动注入的意思,之所以叫这个名字是为了提高分层的辨识度。
注解开发
常用注解
-
@component("id")组件,注在想要注入的类上面(相当于bean标签含id,含class的功能),它的三个孩子和它一样的作用,不过名字不一样:@Repository(dao层)@Service(Service层)@controller(controller层);
-
@Autowired + @Qualifier("id") == @Resource("id") 在与id对应的调用bean字段上根据类型进行依赖注入的时候使用。@Autowired单独使用也可,默认id对应。
-
@value("${变量名}")注入普通数据类型的变量时使用(常用的地方:${变量名},比如:${jdbc.driver})
-
@Scope('"prototype")还记得Scope的值是来决定bean的作用域的,决定实例化的bean对象是单个对象还是多个对象
-
@PostConstruct:初始化方法注解;@PreDestroy:销毁方法注解。
注解使用测试
UserService
public interface UserService { public void save(); }
UserDao
public interface UserDao { void save(); void init(); void destroy(); }
UserServiceImpl
@Service("userService") @Scope("prototype") public class UserServiceImpl implements UserService{ //普通数据类型注入采用Value注解,常用的地方:${变量},标注一下:${jdbc.driver} //jdbc.driver=com.mysql.jdbc.driver这句应该出现在jdbc.propetries文件中 @Value(value = "${jdbc.driver}") private String ServiceName; //在对应的调用bean字段上根据类型依赖注入的时候需要加上这个Autowired注解,加上后spring容器会将与 //注解字段匹配的bean注入,所以这里就是去掉@Qualifier结果也会产生。但是如果属于字段类型的 //bean有多个,就会冲突了。 @Autowired //结合Autowired适用于根据id进行依赖注入,一定要结合Autowired一起用。 @Qualifier("userDao") // @Resource(name = "userDao")功能相当于上述两个注解一起用 private UserDao userDao; public void setUserDao(UserDao userDao) { this.userDao = userDao; } public void save() { System.out.println(ServiceName); userDao.save(); userDao.init(); userDao.destroy(); } }
UserDaoImpl
@Repository("userDao") //这里的Component的作用就相当于xml文件中的<bean id="userDao" class="com.hlc.dao.UserDaoImpl"/> //都是为了实例化bean对象做服务的,相当于给容器一张图纸信息,容器反射获取并生成,这就是IOC技术的体现。 public class UserDaoImpl implements UserDao{ public void save() { System.out.println("save......"); } //这里是初始化方法注解 @PostConstruct public void init(){ System.out.println("init..."); } //这里是销毁方法注解 @PreDestroy public void destroy(){ System.out.println("destroy...."); } }
xml文件
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework/schema/beans" xmlns:context="http://www.springframework/schema/context" xmlns:xsi="http://www.w3/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework/schema/beans http://www.springframework/schema/beans/spring-beans.xsd http://www.springframework/schema/context http://www.springframework/schema/context/spring-context.xsd"> <!--由于我在采取注解的方式获取bean,所以这里要针对基础包为com.hlc配置component组件扫描这里说明一下每一层的component组件都有自己的注解名以便区别,分别是repository与service以及controller--> <!-- 其次要想使用context这个标签,还需要对context命名空间进行设置,见xml头的变化--> 组件扫描 <context:component-scan base-package="com.hlc"/> </beans>
今天就写到这里,用到的代码都是我平时测试练习的时候码出来的,有改动,所以大家看个原理就行,加深印象也不是坏事。这周还有两天的时间,我会补完三大框架的总结,boot的进度下周再更啦。
更多推荐
spring框架浅结1
发布评论