官方文档:https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html
Spring是一个轻量级的控制反转(IOC)和面向切面编程(AOP)的框架
Spring 框架是一个分层架构,由 7 个定义良好的模块组成。Spring 模块构建在核心容器之上,核心容器定义了创建、配置和管理 bean 的方式
组成 Spring 框架的每个模块都可以单独存在,或者与其他一个或多个模块联合实现。每个模块的功能如下:
传统的new对象方法:当UserDao类的路径和方法改变时,UserService类也要跟着相应变化。
public interface UserDao { public void getUser();
}
public class UserDaoImpl implements UserDao { @Override public void getUser() { System.out.println("获取用户数据"); }
}
public interface UserService { public void getUser();
}
public class UserServiceImpl implements UserService {private UserDao userDao = new UserDaoImpl(); @Override public void getUser() { userDao.getUser(); }
}
@Test
public void test(){ UserService service = new UserServiceImpl(); service.getUser();
}
当把Userdao的实现类增加一个时
public class UserDaoMySqlImpl implements UserDao { @Override public void getUser() { System.out.println("MySql获取用户数据"); }
}
紧接着我们要去使用MySql的话 , 我们就需要去service实现类里面修改对应的实现 .
public class UserServiceImpl implements UserService {private UserDao userDao = new UserDaoMySqlImpl();@Overridepublic void getUser() {userDao.getUser();}
}
假设我们再增加一个Userdao的实现类 ,再修改service实现类
再添加再修改……一直这样下去。最后……
这种方式耦合性太高,牵一发而动全身
如何解决
我们可以在需要用到他的地方 , 不去实现它 , 而是留出一个接口 , 利用set , 我们去代码里修改下 .
public class UserServiceImpl implements UserService {private UserDao userDao;// 利用set实现public void setUserDao(UserDao userDao) {this.userDao = userDao;}@Overridepublic void getUser() {userDao.getUser();}
}
@Test
public void test(){UserServiceImpl service = new UserServiceImpl();service.setUserDao( new UserDaoMySqlImpl() );service.getUser();//那我们现在又想用Oracle去实现呢service.setUserDao( new UserDaoOracleImpl() );service.getUser();
}
从程序控制创建对象 到 自行控制创建对象 ,把主动权交给了调用者 . 程序不用去管怎么创建,怎么实现了 . 它只负责提供一个接口 .
这种思想 , 从本质上解决了问题 , 我们程序员不再去管理对象的创建了 , 更多的去关注业务的实现 . 耦合性大大降低 . 这也就是IOC的原型 !
其中涉及到迪米特原则(LoD):又叫作最少知识原则(The Least Knowledge Principle),一个类应当对其它类有尽可能少的了解,类与类之间的了解的越多,关系越密切,耦合度越大,当一个类发生改变时,还有一个类也可能发生变化。
迪米特法则的初衷在于降低类之间的耦合。由于每个类尽量减少对其他类的依赖,因此,很容易使得系统的功能模块功能独立,相互之间尽可能少存在依赖关系。
迪米特法则不希望类之间建立直接的联系。如果真的有需要建立联系,也希望能通过它的友元类来转达。
控制反转IoC(Inversion of Control),是一种设计思想,DI(依赖注入)是实现IoC的一种方法。没有IoC的程序中 , 我们使用面向对象编程 , 对象的创建与对象间的依赖关系完全硬编码在程序中,对象的创建由程序自己控制,控制反转后将对象的创建转移给第三方,个人认为所谓控制反转就是:获得依赖对象的方式反转了。
IoC是Spring框架的核心内容,使用多种方式完美的实现了IoC,可以使用XML配置,也可以使用注解,新版本的Spring也可以零配置实现IoC。
Spring容器在初始化时先读取配置文件,根据配置文件或元数据创建与组织对象存入容器中,程序使用时再从Ioc容器中取出需要的对象。
采用XML方式配置Bean的时候,Bean的定义信息是和实现分离的,而采用注解的方式可以把两者合为一体,Bean的定义信息直接以注解的形式定义在实现类中,从而达到了零配置的目的。
控制反转是一种通过描述(XML或注解)并通过第三方去生产或获取特定对象的方式。在Spring中实现控制反转的是IoC容器,其实现方法是依赖注入(Dependency Injection,DI)。
IOC底层原理:xml解析、工厂模式、反射
注 : spring 需要导入commons-logging进行日志记录 . 利用maven 自动下载对应的依赖项 .
org.springframework spring-webmvc 5.1.10.RELEASE
编写一个Hello实体类
public class Hello {private String name;public String getName() {return name;}public void setName(String name) {this.name = name;}public void show(){System.out.println("Hello,"+ name );}
}
编写spring文件 , 命名为beans.xml
测试
@Test
public void test(){//解析beans.xml文件 , 生成管理相应的Bean对象(获取Spring容器)ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");//getBean : 参数即为spring配置文件中bean的id .Hello hello = (Hello) context.getBean("hello");hello.show();
}
这个过程就叫控制反转 :
依赖注入 : 就是利用set方法来进行注入的.
IOC是一种编程思想,由主动的编程变成被动的接收
上面一通依赖注入操作,不知道在写啥玩意!!!转头扎进了百度……
问题一:为什么不用new而用xml
如:连接mysql数据库后,想要换一个数据库,就得把所有new的全部改一遍
引入工厂模式
工厂模式的实质是“定义一个创建对象的接口,让子类决定实例化哪个类。”
通过工厂模式来管理对象
例: 为了创建不同的类型的车,需要三个不同的工厂去创建,把创建的任务交给工厂去完成.
/*** @Project: spring * @description: 造车工厂的接口* @author: sunkang* @create: 2018-08-30 22:33* @ModificationHistory who when What**/
public interface Factory {Car getCar();
}/***生产奥迪的工厂*/
public class AudiFactory implements Factory {@Overridepublic Car getCar() {return new Audi();}
}/***生产奔驰的工厂*/
public class BenzFactory implements Factory {@Overridepublic Car getCar() {return new Benz();}
}/*** 生产宝马的工厂*/
public class BmwFactory implements Factory {@Overridepublic Car getCar() {return new Bmw();}
}/*** 工厂方法的测试类*/
public class FactoryTest {public static void main(String[] args) {//1.首先先创建一个奥迪工厂出来Factory factory = new AudiFactory();//2.然后根据工厂得到奥迪车,具体的造车工厂交给工厂来完成System.out.println(factory.getCar());factory = new BmwFactory();System.out.println(factory.getCar());}
}
将对象创建提取到工厂类中,由工厂类负责创建对象,调用者无需考虑对象的创建,只管从工厂中拿,在修改被调用者是也无需改动太多的代码,只需要修改工厂类。(原来的两个依赖类解耦,但和工厂类耦合)
但是,工厂模式只是将创建对象的责任分离到工厂实体上,对工厂产生了依赖,并未解除依赖,只是有多个依赖转为一个“工厂”的依赖,创建对象还是不够灵活。
比如:制造一辆电动车时,需要轮胎,轮胎需要轮毂,若干螺丝,外胎,内胎,刹车…… , 那么创建车的对象时,工厂就需要先创建一系列依赖的对象。但当我要生产汽车时,轮胎就得换掉了,总不能把电动车的轮胎装在汽车上面吧,这时我们就得重新开一个工厂做汽车。我又想做其他的,那又得搞个工厂……(无情的工厂制造机)
引入依赖注入DI
在依赖注入的情况下,Object只知道依赖关系,它对容器或工厂一无所知。
看到一篇 依赖倒转 的文章,写的非常清晰,我转载过来了,希望不要介意,大佬实在太厉害,终于看懂一点IOC了,推荐看看大佬的文章。
之前设计汽车的的思想:要创建汽车对象,就得先创建依赖对象。即先设计轮子,然后根据轮子大小设计底盘,接着根据底盘设计车身,最后根据车身设计好整个汽车。这里就出现了一个“依赖”关系:汽车依赖车身,车身依赖底盘,底盘依赖轮子。
这种设计可维护性低,如依赖对象改变时,导致依赖该对象的所有类都需要修改。
假设我们需要改动一下轮胎(Tire)类,把它的尺寸变成动态的,而不是一直都是30。我们需要这样改:
由此我们可以看到,仅仅是为了修改轮胎的构造函数,这种设计却需要修改整个上层所有类的构造函数!在软件工程中,这样的设计几乎是不可维护的——在实际工程项目中,有的类可能会是几千个类的底层,如果每次修改这个类,我们都要修改所有以它作为依赖的类,那软件的维护成本就太高了。
我们现在换一种思路。**假设所有依赖对象都已经创建好了,要用的时候直接调用就行了。**我们先设计汽车的大概样子,然后根据汽车的样子来设计车身,根据车身来设计底盘,最后根据底盘来设计轮子。这时候,依赖关系就倒置过来了:轮子依赖底盘, 底盘依赖车身, 车身依赖汽车。
这时候,如果要改动轮子的设计,我们就只需要改动轮子的设计,而不需要动底盘,车身,汽车的设计了。
这就是依赖倒置原则——把原本的高层建筑依赖底层建筑“倒置”过来,变成底层建筑依赖高层建筑。高层建筑决定需要什么,底层去实现这样的需求,但是高层并不用管底层是怎么实现的。这样就不会出现前面的“牵一发动全身”的情况。
所以我们需要进行控制反转(IoC),及上层控制下层,而不是下层控制着上层。我们用依赖注入(Dependency Injection)这种方式来实现控制反转。所谓依赖注入,就是把底层类作为参数传入上层类,实现上层类对下层类的“控制”。这里我们用构造方法传递的依赖注入方式重新写车类的定义:
这里我们再把轮胎尺寸变成动态的,同样为了让整个系统顺利运行,我们需要做如下修改:
这里**我只需要修改轮胎类就行了,不用修改其他任何上层类。这显然是更容易维护的代码。不仅如此,在实际的工程中,这种设计模式还有利于不同组的协同合作和单元测试:**比如开发这四个类的分别是四个不同的组,那么只要定义好了接口,四个不同的组可以同时进行开发而不相互受限制;而对于单元测试,如果我们要写Car类的单元测试,就只需要Framework类传入Car就行了,而不用把Framework, Bottom, Tire全部new一遍再来构造Car。
控制反转容器
上面的例子中,对车类进行初始化的那段代码发生的地方,就是控制反转容器。
因为采用了依赖注入,在初始化的过程中就不可避免的会写大量的new。这里IoC容器就解决了这个问题。这个容器可以自动对你的代码进行初始化,你只需要维护一个Configuration(可以是xml可以是一段代码),而不用每次初始化一辆车都要亲手去写那一大段初始化的代码。这是引入IoC Container的第一个好处。
IoC Container的第二个好处是:**我们在创建实例的时候不需要了解其中的细节。**在上面的例子中,我们自己手动创建一个车的实例时候,是从底层往上层new的:
这个过程中,我们需要了解整个Car/Framework/Bottom/Tire类构造函数是怎么定义的,才能一步一步new/注入。
而IoC Container在进行这个工作的时候是反过来的,它先从最上层开始往下找依赖关系,到达最底层之后再往上一步一步new(有点像深度优先遍历):
这里IoC Container可以直接隐藏具体的创建实例的细节,在我们来看它就像一个工厂:
我们就像是工厂的客户。我们只需要向工厂请求一个Car实例,然后它就给我们按照Config创建了一个Car实例。我们完全不用管这个Car实例是怎么一步一步被创建出来。
再次重申:IOC是一种编程思想,由主动的编程变成被动的接收
这个是大佬总结的spring ioc导图,很棒!!!
xml配置文件,配置创建的对象dao
有service类和dao类,创建工厂类
class UserFactory {public static UserDao getDao() {String classValue = "com.xiong.UserDao"; //xml解析, 得到class属性值Class clazz = Class.forName(classValue); //通过反射创建对象return (UserDao)clazz.newInstance();}
}
当改变路径时,只需要改变配置文件的值
IOC思想基于IOC容器完成,IOC容器底层就是对象工厂
Spring提供IOC容器实现两种方式:(两个接口)
BeanFactory:IOC容器基本实现,是Spring内部的使用接口,不提供开发人员进行使用
加载配置文件时候不会创建对象,在获取对象(使用)才去创建对象
ApplicationContext:BeanFactory接口的子接口,提供更多更强大的功能,一般由开发人员进行使用
加载配置文件时候就会把在配置文件对象进行创建
BeanFactory使用时创建耗时响应慢但节省资源,ApplicationContext先创建耗费资源到响应快
bean就是java对象
Bean管理:
(1)Spring创建对象
(2)Spring注入属性
基于xml配置文件方式实现
基于注解方式实现
依赖注入DI:
// 配置User对象创建
(1)在spring配置文件中,使用bean标签,标签里面添加对应属性,就可以实现对象创建
在bean标签有很多属性,介绍常用的属性:
id属性:唯一标识
class属性:类全路径(包类路径)
(2)创建对象时,默认也是执行无参数构造方法完成对象创建
//1.创建类,定义属性和对应的set方法
public class Book { //创建属性 private String bname; private String bauthor; //创建属性对应的set方法 public void setBname(String bname) { this.bname = bname; } public void setBauthor(String bauthor) {this.bauthor = bauthor; } public void show(){System.out.println("name="+ bname );}
}
//测试
@Test
public void test(){ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");//在执行getBean的时候, book已经创建好了 , 通过无参构造Book book = (Book) context.getBean("book");//调用对象的方法book.show();
}
//1. 创建类,定义属性,创建属性对应有参数构造方法
public class Orders {//属性private String oname; private String address; //有参数构造 public Orders(String oname,String address) {this.oname = oname; this.address = address; } public void show(){System.out.println("name="+ oname );}
}
//测试
@Test
public void testT(){ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");Orders orders = (Orders) context.getBean("orders");Orders.show();
}
xml 注入其他类型属性
1、字面量
(1) null值
(2) 属性值包含特殊符号
>]]>
2、外部bean
(1) 创建两个类 service类和dao类
(2) 在service调用dao里面的方法
(3) 在spring配置文件中进行配置
public class UserService { //创建UserDao类型属性,生成set方法 private UserDao userDao; public void setUserDao(UserDao userDao) {this.userDao = userDao; } public void add() {System.out.println("service add..............."); userDao.update(); }
}
3、内部bean
(1)一对多关系:部门和员工
一个部门有多个员工,一个员工属于一个部门;
(2)在实体类之间表示一对多关系,员工表示所属部门,使用对象类型属性进行表示
//部门类
public class Dept { private String dname; public void setDname(String dname) {this.dname = dname; }
}//员工类
public class Emp {private String ename; private String gender; //员工属于某一个部门,使用对象形式表示 private Dept dept;public void setDept(Dept dept) {this.dept = dept; } public void setEname(String ename) {this.ename = ename; } public void setGender(String gender) {this.gender = gender;}
}
(3)在spring配置文件中进行配置
4、级联赋值
写法一
写法二
private Dept dept;
//生成dept的get方法
public Dept getDept() {return dept;
}
public Dept setDept() {this.dept = dept;
}
//(1)创建类,定义数组、list、map、set类型属性,生成对应set方法
public class Stu {private String[] courses; private List list; private Map maps; private Set sets; public void setSets(Set sets) {this.sets = sets; } public void setCourses(String[] courses) {this.courses = courses; } public void setList(List list) {this.list = list; }public void setMaps(Map maps) { this.maps = maps; }
}
java课程 数据库课程 张三 小三
MySQL Redis
在集合里面设置对象类型值
20190604 男 小明
Spring 有两种类型 bean,一种普通 bean,另外一种工厂 bean(FactoryBean)
//第一步 创建类,让这个类作为工厂 bean,实现接口 FactoryBean
//第二步 实现接口里面的方法,在实现的方法中定义返回的 bean 类型
public class MyBean implements FactoryBean {//定义返回 bean@Overridepublic Course getObject() throws Exception {Course course = new Course();course.setCname("abc");return course;}@Overridepublic Class> getObjectType() {return null;}@Overridepublic boolean isSingleton() {return false;}
}@Test
public void test3() {ApplicationContext context = new ClassPathXmlApplicationContext("bean3.xml");Course course = context.getBean("myBean", Course.class);System.out.println(course);
}
在Spring中,那些组成应用程序的主体及由Spring IoC容器所管理的对象,被称之为bean。简单地讲,bean就是由IoC容器初始化、装配及管理的对象 。默认情况下,bean 是单实例对象
几种作用域中,request、session作用域仅在基于web的应用中使用(不必关心你所采用的是什么web应用框架),只能用在基于web的Spring ApplicationContext环境。
如何设置单实例还是多实例
(1)在 spring 配置文件 bean 标签里面有属性(scope)用于设置单实例还是多实例
(2)scope 属性值
第一个值 默认值,singleton,表示是单实例对象
第二个值 prototype,表示是多实例对象
区别:设置 scope 值是 singleton 时候,加载 spring 配置文件时候就会创建单实例对象。 设置 scope 值是 prototype 时候,不是在加载 spring 配置文件时候创建 对象,而是在调用 getBean 方法时候创建多实例对象。根据经验,对有状态的bean应该使用prototype作用域,而对无状态的bean则应该使用singleton作用域
当一个bean的作用域为Request,表示在一次HTTP请求中,一个bean定义对应一个实例;即每个HTTP请求 都会有各自的bean实例,它们依据某个bean定义创建而成。该作用域仅在基于web 的Spring ApplicationContext情形下有效。考虑下面bean定义:
针对每次HTTP请求,Spring容器会根据loginAction bean的定义创建一个全新的LoginAction bean实例,且该loginAction bean实例仅在当前HTTP request内有效,因此可以根据需要放心的更改所建实例的内部状态,而其他请求中根据loginAction bean定义创建的实例,将不会看到这些特定于某个请求的状态变化。当处理请求结束,request作用域的bean实例将被销毁。
当一个bean的作用域为Session,表示在一个HTTP Session中,一个bean定义对应一个实例。该作用域仅在基于web的Spring ApplicationContext情形下有效。考虑下面bean定义:
针对某个HTTP Session,Spring容器会根据userPreferences bean定义创建一个全新的userPreferences bean实例,且该userPreferences bean仅在当前HTTP Session内有效。与request作用域一样,可以根据需要放心的更改所创建实例的内部状态,而别的HTTP Session中根据userPreferences创建的实例,将不会看到这些特定于某个HTTP Session的状态变化。当HTTP Session最终被废弃的时候,在该HTTP Session作用域内的bean也会被废弃掉。
从对象创建到对象销毁的过程:
(1)通过构造器创建 bean 实例(无参数构造)
(2)为 bean 的属性设置值和对其他 bean 引用(调用 set 方法)
(3)调用 bean 的初始化的方法(需要进行配置初始化的方法)
(4)bean 可以使用了(对象获取到了)
(5)当容器关闭时候,调用 bean 的销毁的方法(需要进行配置销毁的方法)
public class Orders {//无参数构造 public Orders() {System.out.println("第一步 执行无参数构造创建bean实例"); } private String oname; public void setOname(String oname) {this.oname = oname; System.out.println("第二步 调用set方法设置属性值"); } //创建执行的初始化的方法 public void initMethod() {System.out.println("第三步 执行初始化的方法"); } //创建执行的销毁的方法 public void destroyMethod() { System.out.println("第五步 执行销毁的方法"); }
}@Test
public void testBean3() {// ApplicationContext context = // new ClassPathXmlApplicationContext("bean4.xml"); ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean4.xml"); Orders orders = context.getBean("orders", Orders.class);System.out.println("第四步 获取创建bean实例对象");System.out.println(orders);//手动让bean实例销毁 context.close();
}
bean的后置处理器,bean生命周期有七步
//(1)创建类,实现接口BeanPostProcessor,创建后置处理器
public class MyBeanPost implements BeanPostProcessor {@Overridepublic Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {System.out.println("在初始化之前执行的方法");return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {System.out.println("在初始化之后执行的方法"); return bean; }
}
Spring中bean有三种装配机制:
自动化的装配bean
手动装配过程
新建两个实体类
public class Cat {public void shout() {System.out.println("miao~");}
}public class Dog {public void shout() {System.out.println("wang~");}
}
新建一个用户类 User
public class User {private Cat cat;private Dog dog;private String str;
}
编写Spring配置文件
测试
public class MyTest {@Testpublic void testMethodAutowire() {ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");User user = (User) context.getBean("user");user.getCat().shout();user.getDog().shout();}
}
自动装配
由于在手动配置xml过程中,常常发生字母缺漏和大小写等错误,而无法对其进行检查,使得开发效率降低。采用自动装配将避免这些错误,并且使配置简单化。
autowire byName (按名称自动装配)
修改bean配置,增加一个属性 autowire=”byName”
当我们将 cat 的bean id修改为 catXXX,再次测试, 执行时报空指针java.lang.NullPointerException。因为按byName规则找不对应set方法,真正的setCat就没执行,对象就没有初始化,所以调用时就会报空指针错误。
小结: 当一个bean节点带有 autowire byName的属性时。将查找其类中所有的set方法名,例如setCat,获得将set去掉并且首字母小写的字符串,即cat, 去spring容器中寻找是否有此字符串名称id的对象, 如果有,就取出注入;如果没有,就报空指针异常。
autowire byType (按类型自动装配)
使用autowire byType首先需要保证:同一类型的对象,在spring容器中唯一。如果不唯一,会报不唯一的异常。
NoUniqueBeanDefinitionException
测试:
autowire="byType"
测试,报错:NoUniqueBeanDefinitionException, 删掉cat2,将cat的bean名称改掉!测试!因为是按类型装配,所以并不会报异常,也不影响最后的结果。甚至将id属性去掉,也不影响结果。
准备工作: 利用注解的方式注入属性。
在spring配置文件中引入context文件头
xmlns:context="http://www.springframework.org/schema/context"
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
开启属性注解支持!
@Autowired
测试:将User类中的set方法去掉,使用@Autowired注解
public class User {@Autowiredprivate Cat cat;@Autowiredprivate Dog dog;private String str;public Cat getCat() {return cat;}public Dog getDog() {return dog;}public String getStr() {return str;}
}
此时配置文件内容
@Autowired(required=false) 说明: false,对象可以为null;true,对象必须存对象,不能为null。
//如果允许对象为null,设置required = false,默认为true
@Autowired(required = false)
private Cat cat;
@Qualifier
测试实验步骤:
配置文件修改内容,保证类型存在对象。且名字不为类的默认名字!
没有加Qualifier测试,直接报错
在属性上添加Qualifier注解
@Autowired
@Qualifier(value = "cat2")
private Cat cat;
@Autowired
@Qualifier(value = "dog2")
private Dog dog;
测试,成功输出!
@Resource
实体类
public class User {//如果允许对象为null,设置required = false,默认为true@Resource(name = "cat2")private Cat cat;@Resourceprivate Dog dog;private String str;
}
beans.xml
配置文件2:beans.xml , 删掉cat2
实体类上只保留注解
@Resource
private Cat cat;
@Resource
private Dog dog;
成功
结论:先进行byName查找,失败;再进行byType查找,成功。
@Value
注入普通类型属性
@Value(value = "abc")
private String name;
@Autowired与@Resource异同:
它们的作用相同都是用注解方式注入对象,但执行顺序不同。@Autowired先byType,@Resource先byName。
注:在spring4之后,想要使用注解形式,必须得要引入aop的包, 在配置文件当中,还得要引入一个context约束
我们之前都是使用 bean 的标签进行bean注入,但是实际开发中,我们一般都会使用注解!
开启组件扫描,配置扫描哪些包下的注解,如果扫描多个包,多个包使用逗号隔开
在指定包下编写类,增加注解
//在注解里面value属性值可以省略不写,
//默认值是类名称,首字母小写
@Component("user")
// 相当于配置文件中
public class User {public String name = "秦疆";
}
测试
@Test
public void test(){ApplicationContext applicationContext =new ClassPathXmlApplicationContext("beans.xml");User user = (User) applicationContext.getBean("user");System.out.println(user.name);
}
使用注解注入属性
可以不用提供set方法,直接在直接名上添加@value(“值”)
@Component("user")
// 相当于配置文件中
public class User {@Value("秦疆")// 相当于配置文件中 public String name;
}
如果提供了set方法,在set方法上添加@value(“值”);
@Component("user")
public class User {public String name;@Value("秦疆")public void setName(String name) {this.name = name;}
}
注解,就是替代了在配置文件当中配置步骤而已!更加的方便快捷!
@Component三个衍生注解
为了更好的进行分层,Spring可以使用其它三个注解,功能一样,目前使用哪一个功能都一样。(只是用来做区分)
写上这些注解,就相当于将这个类交给Spring管理装配了!
@scope
@Controller("user")
@Scope("prototype")
public class User {@Value("秦疆")public String name;
}
XML与注解比较
xml与注解整合开发 :
作用:
JavaConfig 原来是 Spring 的一个子项目,它通过 Java 类的方式提供 Bean 的定义信息,在 Spring4 的版本, JavaConfig 已正式成为 Spring4 的核心功能 。
测试:
编写一个实体类Dog
@Component //将这个类标注为Spring的一个组件,放到容器中!
public class Dog {public String name = "dog";
}
新建一个config配置包,编写一个MyConfig配置类
@Configuration //代表这是一个配置类,替代xml配置文件
@ComponentScan("com.xiong.pojo")
public class MyConfig {@Bean //通过方法注册一个bean,这里的返回值就Bean的类型,方法名就是bean的id!public Dog getDog(){return new Dog();}
}
@Test
public void test2(){ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyConfig.class);Dog dog = (Dog) applicationContext.getBean("getDog");System.out.println(dog.name);
}
注:Component+ComponentScan注解应和Configration+Bean分开用,前者和后者都能实现Bean的注入。
@ComponentScan(“com.xiong.pojo”)扫描了包,默认id为Dog类的小写dog,等同于xml里面添加 context:component-scan base-package=“xxx.xxx”.
如果去掉包扫描, 这id必须一样!!!即 Dog dog = (Dog) applicationContext.getBean(“getDog”); 对应上方的bean的id
AOP(Aspect Oriented Programming)意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
下面是选自博客中的一段解释
AOP的本质
AOP是实现分散关注的编程方法,将关注封装在切面中。如何分散关注呢?将需求功能从不相关的类中分离出来,同时使多个类共用一个行为,一旦行为发生变化,不必修改多个类,只修改行为即可。
AOP将软件系统划分为两个部分:核心关注点、横切关注点,业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的特点是经常发生在核心关注点的多个位置,而且它们功能基本相似。AOP的作用在于分离系统中的各个关注点,将核心关注点和横切关注点分离开来。
AOP技术利用一种称为“横切”的技术,剖解开封装对象的内部,将影响多个类的公共行为封装到一个可重用的模块中,并将其命名为Aspect
切面。所谓的切面,简单来说就是与业务无关,却为业务模块所共同调用的逻辑,将其封装起来便于减少系统的重复代码,降低模块的耦合度,有利用未来的可操作性和可维护性。
补充
OOP面向对象编程,主要是针对业务处理过程中的实体属性和行为的抽象与封装,以获得更加清晰高效地逻辑单元。
AOP面向切面编程,针对业务处理过程中的切面进行提取,它所面对的是处理过程中某个步骤或阶段,以获得逻辑过程中各部分之间低耦合性的隔离效果。
AOP是OOP的补充和完善,OOP引入封装、继承、多态等概念建立了一种对象层次结构,用来模拟公共行为的一个集合。当需要为分散的对象引入公共行为的时候,OOP显得无能为力,也就是说,OOP允许定义从上到下的关系,但并不适合定义从左到右的关系。
提供声明式事务;允许用户自定义切面
简单解释一下上面的术语:
目标对象:即要被代理的类 , save()为对象中的方法(实现逻辑)
连接点:被代理类中哪些方法可以被增强,这些方法称为连接点
切入点:实际被增强的方法
通知: 即增强,实际增强的逻辑部分,包括多种类型:
切面:指把通知应用到切入点的过程
切入点表达式
切入点表达式作用:知道对哪个类里面的哪个方法进行增强
语法结构:语法结构: execution([权限修饰符] [返回类型] [类全路径] [方法名称]([参数列表]) )
举例1:对com.atguigu.dao.BookDao类里面的add()方法进行增强
//权限修饰符默认public, * 表示返回类型任意, ..表示参数任意
execution(* com.atguigu.dao.BookDao.add(..))
举例2:对com.atguigu.dao.BookDao类里面的所有的方法进行增强
//后面的 * 表示类下的所有方法
execution(* com.atguigu.dao.BookDao.* (..))
举例3:对com.atguigu.dao包里面所有类,类里面所有方法进行增强
execution(* com.atguigu.dao.*.* (..))
【重点】使用AOP,需要导入一个依赖包!
org.aspectj aspectjweaver 1.9.4
方式一:注解
public class User {public void add() {System.out.println("add......."); }
}
创建增强类(编写增强逻辑)
(1)在增强类里面,创建方法,让不同方法代表不同通知类型
//增强的类
public class UserProxy {//前置通知public void before(){System.out.println("---------方法执行前---------");}public void after(){System.out.println("---------方法执行后---------");}
}
进行通知的配置
(1)在spring配置文件中,开启注解扫描
(2)使用注解创建User和UserProxy对象
//被增强的类
@Component
public class User {}//增强的类
@Component
public class UserProxy {}
(3)在增强类上面添加注解 @Aspect
//增强的类
@Component
@Aspect
//生成代理对象
public class UserProxy {}
(4)在spring配置文件中开启生成代理对象
//增强的类
@Component
@Aspect
//生成代理对象
public class UserProxy { //前置通知 //@Before注解表示作为前置通知 @Before(value = "execution(* com.atguigu.spring5.aopanno.User.add(..))") public void before() {System.out.println("before.........");} //后置通知(返回通知) @AfterReturning(value = "execution(* com.atguigu.spring5.aopanno.User.add(..))") public void afterReturning() {System.out.println("afterReturning........."); } //最终通知 @After(value = "execution(* com.atguigu.spring5.aopanno.User.add(..))") public void after() { System.out.println("after........."); } //异常通知 @AfterThrowing(value = "execution(* com.atguigu.spring5.aopanno.User.add(..))") public void afterThrowing() { System.out.println("afterThrowing........."); } //环绕通知@Around(value = "execution(* com.atguigu.spring5.aopanno.User.add(..))")public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {System.out.println("环绕之前........."); //被增强的方法执行 proceedingJoinPoint.proceed(); System.out.println("环绕之后........."); }
}
打印测试结果:
方法存在异常结果:
//相同切入点抽取
@Pointcut(value = "execution(* com.atguigu.spring5.aopanno.User.add(..))")
public void pointdemo() {
}
//前置通知
//@Before注解表示作为前置通知
@Before(value = "pointdemo()")
public void before() { System.out.println("before.........");
}
@Component
@Aspect
@Order(1)
public class PersonProxy{}
补充:配置类
@Configuration
@ComponentScan(basePackages = {"com.atguigu"})
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class ConfigAop { }
方式二:配置文件
事务是数据库操作最基本单元,逻辑上一组操作,要么都成功,如果有一个失败所有操作都失败
典型场景:银行转账,不能单方面成功或失败
事务是原子性操作,由一系列动作组成,事务的原子性确保动作要么全部执行,要么全部不执行。只要 其中一个指令执行失败,所有的指令都执行失败,数据进行回滚,回到执行指令前的数据状态。
事务的执行使数据从一个状态转换为另一个状态,但是对于整个数据的完整性保持稳定。
隔离性是当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被 其他事务的操作所干扰,多个并发事务之间要相互隔离。
对于任意两个并发的事务T1和T2,在事务T1看来,T2要么在T1开始之前就已经结束,要么在T1结束之后才 开始,这样每个事务都感觉不到有其他事务在并发地执行。
当事务正确完成后,它对于数据的改变是永久性的。
1、创建数据库表,添加记录
2、创建service,搭建dao,完成对象创建和注入关系
(1)service注入dao,在dao注入JdbcTemplate,在JdbcTemplate注入DataSource
@Service
public class UserService {//注入dao@Autowired private UserDao userDao;
} @Repository
public class UserDaoImpl implements UserDao { @Autowiredprivate JdbcTemplate jdbcTemplate;
}
3、在dao创建两个方法:多钱和少钱的方法,在service创建方法(转账的方法)
@Repository
public class UserDaoImpl implements UserDao {@Autowired private JdbcTemplate jdbcTemplate;//lucy转账100给mary//少钱 @Override public void reduceMoney() {String sql = "update t_account set money=money-? where username=?"; jdbcTemplate.update(sql,100,"lucy"); }//多钱 @Override public void addMoney() { String sql = "update t_account set money=money+? where username=?"; jdbcTemplate.update(sql,100,"mary"); }
}@Servicepublic class UserService { //注入dao@Autowiredprivate UserDao userDao; //转账的方法 public void accountMoney() {//lucy少100userDao.reduceMoney();//mary多100userDao.addMoney(); }
}
上面代码,如果正常执行没有问题的,但是如果代码执行过程中出现异常,有问题
当出现异常时,
reduceMoney()
执行了,接着出现异常,导致后续代码都未执行,最终结果是一边钱少了,但另一边钱未增加。(银行说这个操作我喜欢……)
如何解决上述问题: 使用事务进行解决
既然会出现异常导致代码执行出现问题,那么我们将会出现异常的代码使用try
catch
捕获,内部实现事务
注:事务添加到JavaEE三层结构里面Service层(业务逻辑层)
在Spring进行事务管理操作有两种方式:
编程式事务管理:上面使用的方法,每开启一个事务按照上述四个步骤来写,代码冗余,修改不便
声明式事务管理
在Spring进行声明式事务管理,底层使用AOP原理 ,
Spring事务管理API
提供一个接口,代表事务管理器,这个接口针对不同的框架提供不同的实现类
事务操作(注解声明式事务管理)
(1)在spring配置文件引入名称空间 tx
(2)开启事务注解
(1)@Transactional,这个注解添加到类上面,也可以添加方法上面
(2)如果把这个注解添加类上面,这个类里面所有的方法都添加事务
(3)如果把这个注解添加方法上面,为这个方法添加事务
@Service
@Transactional
public class UserService {
}
在service类上面添加注解@Transactional,在这个注解里面可以配置事务相关参数
引用文章:带你读懂Spring 事务——事务的传播机制
简单的理解就是多个事务方法相互调用时,事务如何在这些方法间传播。
举个栗子,方法A是一个事务的方法,方法A执行过程中调用了方法B,那么方法B有无事务以及方法B对事务的要求不同都会对方法A的事务具体执行造成影响,同时方法A的事务对方法B的事务执行也有影响,这种影响具体是什么就由两个方法所定义的事务传播类型所决定。
例:
dao层两个方法A和B(不同类下,同一类下要使用注入,不能直接使用this.方法名调用(这个是对象内部方法,不会通过Spring管理)),方法A执行会在A表插入一条数据,方法B执行会在B表插入一条数据
//将传入参数a存入A表
pubilc void A(a){insertIntoATable(a);
}//将传入参数b存入B表
public void B(b){insertIntoBTable(b);
}
Service层测试
@Transactional(propagation = Propagation.REQUIRED)
public void testMain(){A(a1); //调用A入参a1testB(); //调用testB
}
@Transactional(propagation = Propagation.REQUIRED)
public void testB(){B(b1); //调用B入参b1i = 10 / 0; //模拟异常B(b2); //调用B入参b2
}
结果:数据库没有插入新的数据,数据库还是保持着执行testMain方法之前的状态,没有发生改变。
testMain上声明了事务,在执行testB方法时就加入了testMain的事务(当前存在事务,则加入这个事务),在执行testB方法抛出异常后事务会发生回滚,又testMain和testB使用的同一个事务,所以事务回滚后testMain和testB中的操作都会回滚,也就使得数据库仍然保持初始状态
public void testMain(){A(a1); //调用A入参a1testB(); //调用testB
}@Transactional(propagation = Propagation.REQUIRED)
public void testB(){B(b1); //调用B入参b1i = 10 / 0; //模拟异常B(b2); //调用B入参b2
}
结果:数据a1存储成功,数据b1和b2没有存储。
由于testMain没有声明事务,testB有声明事务且传播行为是REQUIRED,所以在执行testB时会自己新建一个事务(如果当前没有事务,则自己新建一个事务),testB抛出异常则只有testB中的操作发生了回滚,也就是b1的存储会发生回滚,但a1数据不会回滚,所以最终a1数据存储成功,b1和b2数据没有存储.
当前存在事务,则加入当前事务,如果当前没有事务,就以非事务方法执行
public void testMain(){A(a1); //调用A入参a1testB(); //调用testB
}
@Transactional(propagation = Propagation.SUPPORTS)
public void testB(){B(b1); //调用B入参b1i = 10 / 0; //模拟异常B(b2); //调用B入参b2
}
这种情况下,执行testMain的最终结果就是,a1,b1存入数据库,b2没有存入数据库。由于testMain没有声明事务,且testB的事务传播行为是SUPPORTS,所以执行testB时就是没有事务的(如果当前没有事务,就以非事务方法执行),则在testB抛出异常时也不会发生回滚,所以最终结果就是a1和b1存储成功,b2没有存储。
当前存在事务,则加入当前事务
@Transactional(propagation = Propagation.REQUIRED)
public void testMain(){A(a1); //调用A入参a1testB(); //调用testB
}
@Transactional(propagation = Propagation.SUPPORTS)
public void testB(){B(b1); //调用B入参b1i = 10 / 0; //模拟异常 B(b2); //调用B入参b2
}
testMain上声明事务且使用REQUIRED传播方式的时候,满足当前存在事务,则加入当前事务,在testB抛出异常时事务就会回滚,最终结果就是a1,b1和b2都不会存储到数据库。
创建一个新事务,如果存在当前事务,则挂起该事务。
可以理解为设置事务传播类型为REQUIRES_NEW的方法,在执行时,不论当前是否存在事务,总是会新建一个事务。
@Transactional(propagation = Propagation.REQUIRED)
public void testMain(){A(a1); //调用A入参a1testB(); //调用testBi = 10 / 0; //模拟异常
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void testB(){B(b1); //调用B入参b1B(b2); //调用B入参b2
}
这种情形的执行结果就是a1没有存储,而b1和b2存储成功,因为testB的事务传播设置为REQUIRES_NEW,所以在执行testB时会开启一个新的事务,testMain中发生的异常时在testMain所开启的事务中,所以这个异常不会影响testB的事务提交,testMain中的事务会发生回滚,所以最终a1就没有存储,而b1和b2就存储成功了。
与这个场景对比的一个场景就是testMain和testB都设置为REQUIRED,那么上面的代码执行结果就是所有数据都不会存储,因为testMain和testMain是在同一个事务下的,所以事务发生回滚时,所有的数据都会回滚.
事务有特性成为隔离性,多事务操作之间不会产生影响。不考虑隔离性产生很多问题
有三个读问题:脏读、不可重复读、幻读
脏读:一个未提交事务读取到另一个未提交事务的数据
**不可重复读:**一个事务两次读取同一行的数据,结果得到不同状态的结果,中间正好另一个事务更新了该数据,两次数据内容不一致,不可被信任。针对update操作
幻读:一个事务执行两次查询,第二次结果集包含第一次中没有或某些行已经被删除的数据,造成两次结果不一致,只是另一个事务在这两次查询中间插入或删除了数据造成的。幻读是事务非独立执行时发生的一种现象。前后多次读取,数据总量不一致。 针对insert和delete操作
解决:设置事务隔离级别
READ UNCOMMITTED(读未提交)
该隔离级别表示一个事务可以读取另一个事务修改但还没有提交的数据。该级别不能防止脏读和不可重复读,因此很少使用该隔离级别。
READ_COMMITTED (读提交)
该隔离级别表示一个事务只能读取另一个事务已经提交的数据。该级别可以防止脏读,这也是大多数情况下的推荐值。
REPEATABLE_READ (可重复读)
该隔离级别表示一个事务在整个过程中可以多次重复执行某个查询,并且每次返回的记录都相同。即使在多次查询之间有新增的数据满足该查询,这些新增的记录也会被忽略。该级别可以防止脏读和不可重复读。
SERIALIZABLE (串行化)
所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。 在该隔离级别下事务都是串行顺序执行的,MySQL 数据库的 InnoDB 引擎会给读操作隐式加一把读共享锁,从而避免了脏读、不可重读复读和幻读问题。
(1)事务需要在一定时间内进行提交,如果不提交进行回滚
(2)默认值是 -1 ,设置时间以秒单位进行计算
(1)读:查询操作,写:添加修改删除操作
(2)readOnly默认值false,表示可以查询,可以添加修改删除操作
(3)设置readOnly值是true,设置成true之后,只能查询
rollbackFor:回滚
设置出现哪些异常进行事务回滚
noRollbackFor:不回滚
设置出现哪些异常不进行事务回滚
在spring配置文件中进行配置
1. 配置事务管理器
1. 配置通知
1. 配置切入点和切面
@Configuration
//配置类
@ComponentScan(basePackages = "com.atguigu")
//组件扫描
@EnableTransactionManagement
//开启事务
public class TxConfig { //创建数据库连接池 @Beanpublic DruidDataSource getDruidDataSource() {DruidDataSource dataSource = new DruidDataSource(); dataSource.setDriverClassName("com.mysql.jdbc.Driver"); dataSource.setUrl("jdbc:mysql:///user_db"); dataSource.setUsername("root"); dataSource.setPassword("root"); return dataSource; } //创建JdbcTemplate对象 @Beanpublic JdbcTemplate getJdbcTemplate(DataSource dataSource) { //到ioc容器中根据类型找到dataSourceJdbcTemplate jdbcTemplate = new JdbcTemplate();//注入dataSourcejdbcTemplate.setDataSource(dataSource);return jdbcTemplate; } //创建事务管理器 @Bean public DataSourceTransactionManager getDataSourceTransactionManager(DataSource dataSource) {DataSourceTransactionManager transactionManager = new DataSourceTransactionManager(); transactionManager.setDataSource(dataSource); return transactionManager; }
}
间内进行提交,如果不提交进行回滚
(2)默认值是 -1 ,设置时间以秒单位进行计算
(1)读:查询操作,写:添加修改删除操作
(2)readOnly默认值false,表示可以查询,可以添加修改删除操作
(3)设置readOnly值是true,设置成true之后,只能查询
rollbackFor:回滚
设置出现哪些异常进行事务回滚
noRollbackFor:不回滚
设置出现哪些异常不进行事务回滚
在spring配置文件中进行配置
1. 配置事务管理器
1. 配置通知
1. 配置切入点和切面
@Configuration
//配置类
@ComponentScan(basePackages = "com.atguigu")
//组件扫描
@EnableTransactionManagement
//开启事务
public class TxConfig { //创建数据库连接池 @Beanpublic DruidDataSource getDruidDataSource() {DruidDataSource dataSource = new DruidDataSource(); dataSource.setDriverClassName("com.mysql.jdbc.Driver"); dataSource.setUrl("jdbc:mysql:///user_db"); dataSource.setUsername("root"); dataSource.setPassword("root"); return dataSource; } //创建JdbcTemplate对象 @Beanpublic JdbcTemplate getJdbcTemplate(DataSource dataSource) { //到ioc容器中根据类型找到dataSourceJdbcTemplate jdbcTemplate = new JdbcTemplate();//注入dataSourcejdbcTemplate.setDataSource(dataSource);return jdbcTemplate; } //创建事务管理器 @Bean public DataSourceTransactionManager getDataSourceTransactionManager(DataSource dataSource) {DataSourceTransactionManager transactionManager = new DataSourceTransactionManager(); transactionManager.setDataSource(dataSource); return transactionManager; }
}
以上很多地方学的不是很透彻,写的有点饶,后面有时间在修改!!!