spring初体验
关于spring![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/3f6d6921ec801427d50f82d39e04513b.gif)
在学习spring前,让我们带着这些着问题来学习spring吧。
第一个问题:什么是spring,有什么特点?
Spring是一个开放源代码的设计层面框架,他解决的是业务逻辑层和其他各层的松耦合问题,因此它将面向接口的编程思想贯穿整个系统应用。Spring是于2003年兴起的一个轻量级的Java 开发框架,由Rod Johnson创建。简单来说,Spring是一个分层的JavaSE/EE full-stack(一站式) 轻量级开源框架。(来源度娘)
重要思想:
控制反转——Spring通过一种称作控制反转(IoC)的技术促进了低耦合。当应用了IoC,一个对象依赖的其它对象会通过被动的方式传递进来,而不是这个对象自己创建或者查找依赖对象。你可以认为IoC与JNDI相反——不是对象从容器中查找依赖,而是容器在对象初始化时不等对象请求就主动将依赖传递给它。(来源度娘)
面向切面——Spring提供了面向切面编程的丰富支持,允许通过分离应用的业务逻辑与系统级服务(例如审计和事务管理)进行内聚性的开发。应用对象只实现它们应该做的——完成业务逻辑——仅此而已。它们并不负责(甚至是意识)其它的系统级关注点,例如日志或事务支持。
第二个问题:为什么要使用spring?
1.因为控制反转,依赖注入:我喜欢这个大佬婚介所的例子:他把spring容器比作婚介所,然后每一个类比作想寻偶的人。当他想寻偶时,他需要向婚介所提出寻偶要求(向容器按条件请求对象),然后婚介所按照他的要求给他介绍了这样条件的人(就是返回按条件查找出来的对象)。其实婚介所里面有多少人(多少对象),他们什么时候加入的(创建),什么时候退出(销毁)的我们都不需要关心。我们只要和这个介绍过来的对象完成事务就好了。
http://www.php.cn/java-article-368875.html
2.,面向切面切面:
所有的类都会在spring容器中登记,告诉spring你是个什么东西,你需要什么东西,然后spring会在系统运行到适当的时候,把你要的东西主动给你,同时也把你交给其他需要你的东西。所有的类的创建、销毁都由spring来控制,也就是说控制对象生存周期的不再是引用它的对象,而是spring。
开发环境:
JDK 1.8.0,Eclipse oxygen,数据库 Mysql+Navicat
开始我们的spring工程
我们前部分是讲解控制反转(IOC),后面会提及到面向切面(aop)。
第一步:新建一个java web项目
工程架构如下:
第二步:导入jar包
接下来在把所有要用到的jar包复制到webContent-> WEB-INF->lib文件下(如果没有lib文件夹的童鞋就自己照着这个路径新建一个就好)。然后选中这些所有的包右键选择Build Path->add to Bulid path,这样你就导入所有的包啦。
第三步:新建Spring配置的xml文件
这里我们使用官方的推荐,命名为applicationContext.xml,将它放在src文件下(如果放在WebContent下,会报找不到配置文件的错误,不过也有解决办法哟)。首先我们在新建的applicationContext.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"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">
</beans>
和上一节课配置mybatis中配置大同小异,当你还需要其他的标签时在其中的位置添加网站就好。
什么是bean?
https://www.cnblogs.com/wuchanming/p/5426746.html 里面图讲解非常好,大家可以看看。
第四步:怎么创建一个bean
在Spring中使用xml方式来配置bean的方法有三种:
第1种:使用无参构造函数创建bean:
我们在pojo类下新建一个PeoplePojo的类,他张这个样子:
public class PeoplePojo {
private int cid;
private String name;
private int age;
private String sex;
public PeoplePojo() {
System.out.println("大家好,我是PeoplePojo的无参构造方法,我被执行啦");
}
public int getCid() {
return cid;
}
public void setCid(int cid) {
this.cid = cid;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
@Override
public String toString() {
return "PeoplePojo [cid=" + cid + ", name=" + name + ", age=" + age + ", sex=" + sex + "]";
}
}
我们要用无参的构造函数来创建bean,下面我们去配置文件applicationContext.xml下的<beans></beans>中添加一个<bean><bean>标签或者<bean/>便签。现在我需要填写两个属性一个是id,另外一个是class。
<bean id="poeple" class="com.hg.pojo.PeoplePojo"/>
第一个id是一会儿我们通过bean创建对象是要用到的属性,class需要填写该类的所在路径(这里有个小窍门就是在java文件中,将光标放到类名上,右键 - Copy Qualified Name),自己填写的话会容易出错哟。
到这里我们的第一个spring的bean就配置好啦。我们来测试一下吧。
这次我们将会使用到junit测试,我在刚导包时已经导入了Junit的包了,所以我就简单讲讲他是怎么使用的吧。首先我们新建一个test的package包,在该包下新建一个test的类。接下来我们建立一个我们的测试方法,第一步我们要建一个public的方法,无返回值填写void,最后我们在写好的方法上加上一个@Test的注解,我们的测试方法就配置好了。
@Test
public void 方法名() {
}
接下来可开始我们的测试,由于加载配置文件在 每次测试都会用到,所以我把它用静态的方法加载在类中了。
public class MyTest {
// 加载Spring配置文件,把配置文件中的对象进行创建
private static ApplicationContext context = null;
static {
context = new ClassPathXmlApplicationContext("applicationContext.xml");
}
// 1.创建对象
// 1-11.使用无参构造函数,创建对象
@Test
public void test1() {
// 根据配置文件的id得到PeoplePojo对象
PeoplePojo peoplePojo = (PeoplePojo) context.getBean("poeple");
System.out.println(peoplePojo.toString());
}
}
步骤是不是很简单,只需要一个context,getbean(“刚刚在bean的id名”),我们就能获取到对象,不过需要强制转化下。
思考:context.getBean()获得对象是属于什么类呢?
因为我们刚刚在无参构造函数中加了一句:大家好,我是PeoplePojo的无参构造方法,我被执行啦,所以执行结果就是:
第2种:使用静态工厂(了解)
首先我们还是新建一个类,名字就叫AClass::
public class AClass {
public AClass() {
System.out.println("大家好,我是AClass的无参构造方法,我被执行啦");
}
}
既然使用的静态工厂的方法来创建类,那么我们在factoryd的包下新建一个MyFactory的类:
public class MyFactory {
public static AClass getAClass()
{
System.out.println("我使用了静态工厂的方法创建了AClass");
return new AClass();
}
}
注意这里MyFactory 中方法一定是加上static的静态方法。最后我们回到配置文件中添加上bean是我们的AClass:
<bean id="Aclass" class="com.hg.factory.MyFactory" factory-method="getAClass"/>
最后回到我们的测试类中:
// 1-12.使用静态工厂(了解)
@Test
public void test2() {
AClass aClass = (AClass) context.getBean("Aclass");
System.out.println(aClass.toString());
}
结果:
思考:这个也用了new class()的方法,spring这样设计的目的是?
第3种:使用实例工厂(了解)
哈哈还是新建一个BClass类:
public class BClass {
public BClass() {
System.out.println("大家好,我是BClass的无参构造方法,我被执行啦");
}
}
然后创建一个工厂,这次工厂创建对象不需要静态方法了:
public class MyFactory1 {
public BClass getBClass()
{
System.out.println("我使用实例工厂的方法创建了BClass");
return new BClass();
}
}
最后回到配置文件中,与刚刚创建静态方法有点点不同:这次需要配置两个bean分别是factory的bean和我们BClass的bean;
<!-- 1先创建工厂对象 -->
<bean id="MyFactory1" class="com.hg.factory.MyFactory1"/>
<!-- 2再使用工厂对象创建BClass对象 -->
<bean id="BClass" factory-bean="MyFactory1" factory-method="getBClass"/>
看看我们的测试吧:
// 1-13.使用实例工厂(了解)
@Test
public void test3() {
BClass bClass = (BClass) context.getBean("BClass");
System.out.println(bClass.toString());
}
测试结果为:
思考:相信聪明到这已经发现了一个问题,我们在运行test3()的是否发现运行台输出的结果是:
为什么即使我不实例化一个类,也会创建一个类,在下面我们会找到答案的。
第五步: Spring中Bean的属性注入
这你也同样有三种方法可以供我们使用,方别是构造方法的方式注入属性,set方法的方式注入属性,对象类型的注入。
第一种:构造方法的方式注入属性
首先我们新建一个DClass,代码如下:
public class DClass {
private int id;
public DClass() {
System.out.println("大家好,我是DClass的无参构造方法,我被执行啦");
}
public DClass(int id) {
super();
System.out.println("大家好,我是DClass的有参构造方法,我的id是" + id);
this.id = id;
}
}
在applicationContext.xml中需要这么配置:
<bean id="DClass" class="com.hg.pojo.DClass">
<constructor-arg name="id" value="123456"/>
</bean>
测试:
@Test
public void test5() {
DClass dClass = (DClass) context.getBean("DClass");
System.out.println(dClass.toString());
}
结果:
第二种:set方法的方式注入属性
新建一个EClass类,代码如下:
public class EClass {
private int id;
public EClass() {
System.out.println("大家好,我是EClass的无参构造方法,我被执行啦");
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
}
在applicationContext.xml中需要这么配置:
<bean id="EClass" class="com.hg.pojo.EClass">
<property name="id" value="01234"></property>
</bean>
测试代码:
@Test
public void test6() {
EClass eClass = (EClass) context.getBean("EClass");
System.out.println(eClass.getId());
}
结果:
第三种:对象类型的注入
这个实力我们就来模拟一下实际工程中的dao层和service层的工作原理吧。首先我们新建一个实体类StudentPojo:
public class StudentPojo{
private int uid;
private String username;
private String psw;
public int getUid() {
return uid;
}
public void setUid(int uid) {
this.uid = uid;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPsw() {
return psw;
}
public void setPsw(String psw) {
this.psw = psw;
}
@Override
public String toString() {
return "StudentPojo [uid=" + uid + ", username=" + username + ", psw=" + psw + "]";
}
}
然后我们模拟操作他的数据操作层(dao层),在dao的package中新建一个StudentDao的类:
public class StudentDao {
public void insertStudent(StudentPojo pojo) {
System.out.println("我现在在dao层,向学生表中插入一名学生!");
}
}
接下来我们实现一个StudentService的接口,代码如下:
public interface StudentService{
public void add(StudentPojo StudentPojo);
}
好了再来一个实现类:
public class StudentServiceImpl implements StudentService{
private StudentDao dao;
public StudentDao getDao() {
return dao;
}
public void setDao(StudentDao dao) {
this.dao = dao;
}
public void add(StudentPojo StudentPojo) {
System.out.println("我现在在serviceImpl层,执行dao层的insert操作!");
dao.insertStudent(StudentPojo);
}
}
}
到现在我们就可以去测试了。现在假设我们已经从前端拿到了一个关于要插入的学生信息了。现在我们把信息交到了StudentService的add中来处理,在add方法中我们再调用了dao层的StudentDao 中的insertStudent。好啦至此一个“简单”的数据插入工作就完成了。配置文件如下:
<!--2-13 1.注册studentPojo-->
<bean name="StudentPojo" class="com.hg.pojo.StudentPojo" />
<!--2-13 2.注册student dao层-->
<bean id="StudentDao" class="com.hg.dao.StudentDao"/>
<!--2-13 1.注册student serviceImpl层-->
<bean id="StudentServiceImpl" class="com.hg.service.impl.StudentServiceImpl">
<property name="dao" ref="StudentDao"></property>
</bean>
看到了吗我们在StudentServiceImpl中加入一个关于dao层中的set注入,所以我们在bean中加入了<property name=“dao” ref=“StudentDao”>,我们使用ref引用已经创建的bean,这个才是我们真正要演示的效果 。好啦回到我们模拟的学生插入效果的测试中:
// 2-13对象类型的注入
@Test
public void test7() {
StudentPojo studentPojo = (StudentPojo) context.getBean("StudentPojo");
StudentService service = (StudentServiceImpl) context.getBean("StudentServiceImpl");
service.add(studentPojo);
}
结果:
思考:其实结果对我们并不重要,大家有发现这个问题吗?StudentService service = (StudentServiceImpl) context.getBean(“StudentServiceImpl”);我们用一个接口去实现了一个对象的实例化,java允许吗?其实java不仅允许,而且在条件允许的情况下,java鼓励大家这么做。
哈哈哈想知道原因的同学可以到这两个博客中找原因Java中到底是应该用接口类型还是实现类的类类型去引用对象?+面向对象之多态(向上转型与向下转型)。
其实相比于传统的xml配置,现在使用注解的方式更加简便和大大减少了代码量。为什么我们还要费劲的学习以上的内容,可能是出于我的个人经验吧,在一开始就使用注解的我,在练习的过程中对很多东西到搞不懂,所以我觉得是有必要学习一下xml配置再去学习简单的注解方式。好啦废话不多说继续我们的spring注解的开发中,你会从中真正感受到矿建注解提供的便利性:
spring注解
这次我决定换个方式来演示一下使用注解的过程。
我们新建一个工程,然后还是像上面一样新建一个applicationContext.xml。
整个工程如下:
注意到了吗这次我们多导入了一个包,好啦让我们看看applicationContext.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"
xmlns:util="http://www.springframework.org/schema/util"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.hg.pojo"></context:component-scan>
</beans>
我们先来看看配置文件中多了两句话
因为添加这两句话,我们就可以使用下面的标签了。这句话的意思是扫描com.hg.pojo下所有类型。这样就可以让pojo包下的类都注册成为bean了吗?还差一步就可以了,我们需要在要成为bean还要类前加入一个@Component的标签就像这样:
@Component("peo")
public class PeoplePojo {
private String name;
private String sex;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public PeoplePojo() {
System.out.println("我是人类");
}
}
在这里提醒一下大家,我在@Component(“peo”)指定了一个id,当然也允许不指定id.那么默认的id就是类的首字母小写。在后面引用的对象名要和id一致(不过我测试别的注解时就没有怎么严格要求那,如果有明白的提醒麻烦告诉我下是怎么回事)。
测试类代码:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({"classpath:applicationContext.xml"})
public class MyTest {
@Resource
private PeoplePojo peo;
@Test
public void test1()
{
peo.setAge(10);
System.out.println(peo.getAge());
}
}
我们看到了在测试类前加入两个新的注解,RunWith意思使用所有注释前必须使用@RunWith(SpringJUnit4ClassRunner.class),让测试运行于Spring测试环境,ContextConfiguration的意思是加载applicationContext.xml文件。对于想了解classpath的同学可以点开链接看看。我们在测试类中私有化了一个PeoplePojo 的对象,我们在前面加入了一个@Resource,其实如果我们使用@Autowired也可以达到目的,想知道有什么区别的的同学可以点开链接看看区别。让我们来看看测试结果吧:
好了简单的注释加载bean就完成了,可能有的同学有几个问题。让我来猜猜大家想问些什么。
第一个问题:只能扫描pojo包,怎么把其他包一起扫描了?
当然可以扫描所有的包,我们只要轻轻的改动一下配置文件就可以:
<context:component-scan base-package="com.hg"/>
这样就可以扫描到所有的包啦。
第二个问题:我们现在扫描的包要注册的类太多了,会不会不利于开发?
对的,如果我们都只用@Component注解,其实对项目的结构层次还是很模糊的。其实还有@Controller,@Controller,@ Repository等等注解,这几个最常用的注解。他们的作用都是一样的,spring用他们来区分每个模块间的作用:
@Component:是所有受Spring 管理组件的通用形式,@Component注解可以放在类的头上,@Component不推荐使用
@Controller:@Controller对应表现层的Bean,可以用是我们接下来要学的spring mvc。对于spring mvc,我的理解就是传统的servlet。
@ Service:是业务层,处理业务。
@ Repository:可以使用在数据访问层dao层。
第三个问题:我们在每个类中都要加载配置文件吗?
答案是不需要,仅仅在测试类中,我们需要加载配置文件。在其他类中我们已经使用了扫描器了,所有添加了类的注解的类会被扫描进spring容器中,我们只要在test以外的包以外使用,不需要加载配置文件。
第四个问题:为什么注解可以直接声明对象,怎么实现的?
@Autowired注解就是spring可以自动帮你把bean里面引用的对象的setter/getter方法省略,它会自动帮你set/get对象。当你在一个类中引用时一个对象时,其实和我们在对象类型的注入时的例子相似。
public class StudentServiceImpl implements StudentService{
private StudentDao dao;
public StudentDao getDao() {
return dao;
}
public void setDao(StudentDao dao) {
this.dao = dao;
}
}
<bean id="StudentServiceImpl" class="com.hg.service.impl.StudentServiceImpl">
<property name="dao" ref="StudentDao"></property>
</bean>
等于
public class StudentServiceImpl implements StudentService{
@Autowired
private StudentDao dao;
}
第五个问题:spring的作用域怎么划定?
参见Scope的作用域:https://www.cnblogs.com/lonecloud/p/5745902.html
第六个问题:spring是怎么实现控制反转的,为什么这样设计?
这里会有你的答案:https://www.cnblogs.com/czhfuture/p/3572334.html+https://blog.csdn.net/weixin_42072322/article/details/80254143
我把这次spring体验的两个项目都上传到了github上了,如果有想了解源码或者下载jar包的同学可以去下载。第一个使用xml配置spring的项目:https://github.com/tony-bryant/MySpring01,第二个使用的注解spring项目地址:https://github.com/tony-bryant/MySpring02
(多啰嗦一句,我在第二个项目中不仅仅是应用注解使用spring,更是增加了一些我对多态的一些理解,以及正规开发的一些流程。再多啰嗦一句,在我以往几乎没用到过继承和接口,即使在用,也不是很清楚其中的原理是什么。在编写这个项目时,我开始思考:在我们设计一个类时,为什么要把所有的属性私有了,但是提供公共的set/get方法来操作属性。比如为什么要向上转型等等:通过查阅发现这其中的答案要用到java面向对象的思想,java的处理机制,设计模式原理等等学科知识,才发现原来一个java中的知识浩如烟海,自己还要不停的学习下去才对)。
好了相信你对spring的反转控制和依赖注入有了一定的理解了,
让我们来看看spring的AOP(Aspect Oriented Programming),即面向切面编程:
如果你和我一样对aop有什么用存在疑问,那么推荐你去看看https://blog.csdn.net/baidu_33403616/article/details/70304051
advice一共有五种:
有了原先的spring基础我们在这就可以提速了哟,这次我们先做一个只有前置通知和后置通知的小项目。
添加上这次桃用的jar包:
再来到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"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.2.xsd">
<bean id="UserDao" class="com.hg.dao.UserDao"/>
<bean id="TimeHandler" class="com.hg.aspect.TimeHandler"/>
</beans>
可以看到增加了aop的标签,然后我们这次模拟我们在dao层进行操作时,在开始操作前计时,在完成计时的小功能。
新建一个dao层的UserDao
public class UserDao {
public void insertUser()
{
System.out.println("插入一个学生");
}
public void deleteUser()
{
System.out.println("删除一个学生");
}
}
好了再来设置一下我要做的aop的advice.
新建一个TimeHandler代码如下:
public class TimeHandler {
public void beforeprintTime()
{
System.out.println("开始时间 = " + System.currentTimeMillis());
}
public void afterprintTime()
{
System.out.println("结束时间 = " + System.currentTimeMillis());
}
}
最后回到配置文件中,添加如下代码:
<aop:config>
<aop:aspect id="time" ref="TimeHandler">
<aop:pointcut id="addAllMethod" expression="execution(* com.hg.dao.UserDao.*(..))" />
<aop:before method="beforeprintTime" pointcut-ref="addAllMethod" />
<aop:after method="afterprintTime" pointcut-ref="addAllMethod" />
</aop:aspect>
</aop:config>
添加我们的测试:
public class MyTest {
static ApplicationContext context;
static {
context=new ClassPathXmlApplicationContext("AopTest.xml");
}
@Test
public void test1()
{
UserDao dao=(UserDao)context.getBean("UserDao");
dao.deleteUser();
dao.insertUser();
}
}
测试结果如下:
到此我也不想在赘述spring的注解实现aop的方法了,如果用注解的话只会更简单。感兴趣的同学可以去我的github下载查看。使用xml配置文件+使用注解实现AOP.
总结
通过这次对spring的复习,总结。让我收获了spring的使用有了更多的心得,一开始就使用注解开发的话,容易在注解之中忽略到spring真正的工作原理。通过xml的配置让我了解了其中的一些原理(并不是全部。。。)。还有一个最大的收获是我想我该重新学习一下关于面向对象的思想,感觉自己的基础还是很不好。所以在理解框架时什么的,并没有一种茅塞顿开的感觉,也没有惊叹到框架设计的精妙之处,反而总感觉有一种多此一举的感觉。童年过这次让我整理spring的知识,同时也了解到了面向对象的精妙之处(下去好好学习学习)。
如果在文中如果有什么错误希望大家也可以及时帮我指出来,讲的不好的地方还望大家多多海涵,谢谢。