Spring5框架-学习总结(结合个人理解)
Spring框架
ps:这个是我看狂神spring教程时,一边看老师的,一边结合自己的思想写的一篇总结。方便日后回顾用.
1.Spring
1.1 简介
Spring : 春天 —->给软件行业带来了春天
2002年,Rod Jahnson首次推出了Spring框架雏形interface21框架。
2004年3月24日,Spring框架以interface21框架为基础,经过重新设计,发布了1.0正式版。
很难想象Rod Johnson的学历 , 他是悉尼大学的博士,然而他的专业不是计算机,而是音乐学。
Spring理念 : 使现有技术更加实用 . 本身就是一个大杂烩 , 整合现有的框架技术
官网 : http://spring.io/
官方下载地址 : https://repo.spring.io/libs-release-local/org/springframework/spring/
GitHub : https://github.com/spring-projects
1.2 优点
这是一个免费的开源的框架
spring是一个轻量级(本身很小,包下下来就能用了)、非入侵式(你引入spring不会改变你代码原来的任何情况,而且你用了它会更加方便)的框架 PS:入侵式 就是你为了一个jar包,导进项目之后导致你项目不能用了,类把你原来的部署的配置给干掉了,就很不好!
核心:控制反转(IOC:inversion of control)和面向切面编程(AOP:Aspect Oriented Programming)
支持事务的处理,对框架整合的支持!(即市面上多有的java框架spring都能整合进去)
总结:spring就是一个轻量级的控制反转(IOC)和面向切片编程(AOP)的框架!
1.3 组成
组成 Spring 框架的每个模块(或组件)都可以单独存在,或者与其他一个或多个模块联合实现。每个模块的功能如下:
- 核心容器:核心容器提供 Spring 框架的基本功能。核心容器的主要组件是
BeanFactory
,它是工厂模式的实现。BeanFactory
使用控制反转(IOC) 模式将应用程序的配置和依赖性规范与实际的应用程序代码分开。 - Spring 上下文:Spring 上下文是一个配置文件,向 Spring 框架提供上下文信息。Spring 上下文包括企业服务,例如 JNDI、EJB、电子邮件、国际化、校验和调度功能。
- Spring AOP:通过配置管理特性,Spring AOP 模块直接将面向切面的编程功能 , 集成到了 Spring 框架中。所以,可以很容易地使 Spring 框架管理任何支持 AOP的对象。Spring AOP 模块为基于 Spring 的应用程序中的对象提供了事务管理服务。通过使用 Spring AOP,不用依赖组件,就可以将声明性事务管理集成到应用程序中。
- Spring DAO:JDBC DAO 抽象层提供了有意义的异常层次结构,可用该结构来管理异常处理和不同数据库供应商抛出的错误消息。异常层次结构简化了错误处理,并且极大地降低了需要编写的异常代码数量(例如打开和关闭连接)。Spring DAO 的面向 JDBC 的异常遵从通用的 DAO 异常层次结构。
- Spring ORM:Spring 框架插入了若干个 ORM 框架,从而提供了 ORM 的对象关系工具,其中包括 JDO、Hibernate 和 iBatis SQL Map。所有这些都遵从 Spring 的通用事务和 DAO 异常层次结构。
- Spring Web 模块:Web 上下文模块建立在应用程序上下文模块之上,为基于 Web 的应用程序提供了上下文。所以,Spring 框架支持与 Jakarta Struts 的集成。Web 模块还简化了处理多部分请求以及将请求参数绑定到域对象的工作。
- Spring MVC 框架:MVC 框架是一个全功能的构建 Web 应用程序的 MVC 实现。通过策略接口,MVC 框架变成为高度可配置的,MVC 容纳了大量视图技术,其中包括 JSP、Velocity、Tiles、iText 和 POI。
1.4 拓展
Spring Boot与Spring Cloud
- Spring Boot 是 Spring 的一套快速配置脚手架,可以基于Spring Boot 快速开发单个微服务;
- Spring Cloud是基于Spring Boot实现的;
- Spring Boot专注于快速、方便集成的单个微服务个体,Spring Cloud关注全局的服务治理框架;
- Spring Boot使用了约束优于配置的理念,很多集成方案已经帮你选择好了,能不配置就不配置 , Spring Cloud很大的一部分是基于Spring Boot来实现,Spring Boot可以离开Spring Cloud独立使用开发项目,但是Spring Cloud离不开Spring Boot,属于依赖的关系。
- SpringBoot在SpringClound中起到了承上启下的作用,如果你要学习SpringCloud必须要学习SpringBoot。
- 总结:Spring ==> SpringMVC ==> SpringBoot ==>SpringCloud
1.5 入门
pom.xml环境配置
<!--pom.xml配置,如果配置报红,只需要选中pom.xml右击选中maven->Reload project等待会即可--><dependencies><dependency><groupId>org.springframework</groupId><artifactId>spring-webmvc</artifactId><version>5.2.0.RELEASE</version></dependency></dependencies>
1.创建一个实体类
这里创建的是Hello类,之后其实创建的是Dao类和DaoImpl类
package com.kuang.pojo;/*** @author <作者>* @version <版本>* @since <JDK版本>*/
public class Hello {private String str;public String getStr() {return str;}public void setStr(String str) {this.str = str;}@Overridepublic String toString() {return "Hello{" +"str='" + str + '\'' +'}';}
}
2.创建一个Beans.xml
其实按照官方的写法应该叫做:applicationContext.xml,这里为了简化用了Beans作为名称
<?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"><!--使用spring来创建对象,这些在spring中都称为bean标签,就是实体类的感觉解析:id:代表是new出来的对象名class:就是你new 的那个类的字节码文件--><bean id="hello" class="com.kuang.pojo.Hello"><!--解析:name:就是你实体类中属性的名字,即成员变量名value:就是你为成员变量赋的值,就相当于执行了hello.setStr("helloSpring!!!")方法--><property name="str" value="helloSpring!!!"></property></bean></beans>
3.创建一个"测试类"
注意:我这里并不是真正的测试类,就是一个普通的main方法,为了简化点操作
import com.kuang.pojo.Hello;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;/*** @author <作者>* @version <版本>* @since <JDK版本>*/
public class MyTest {public static void main(String[] args) {//这里new就已经是创建了一个对象了.获取applicationContext,拿到spring容器ApplicationContext applicationContext = new ClassPathXmlApplicationContext("Beans.xml");//获取hello对象,注意:需要强转,默认是Object类型的helloHello hello = (Hello) applicationContext.getBean("hello");//因为在xml中,已经为str成员变量设置值了为 "helloSpring!!!",所以直接获取即可!String str = hello.getStr();System.out.println(str);//helloSpring!!!System.out.println(hello);//Hello{str='helloSpring!!!'}}
}
1.6 思考
Hello 对象是谁创建的 ? 【 hello 对象是由Spring创建的 】
Hello 对象的属性是怎么设置的 ? 【hello 对象的属性是由Spring容器设置的 】
这个过程就叫控制反转 :
- 控制 : 谁来控制对象的创建 , 传统应用程序的对象是由程序本身控制创建的 , 使用Spring后 , 对象是由Spring来创建的
- 反转 : 程序本身不创建对象 , 而变成被动的接收对象 .
依赖注入 : 就是利用set方法来进行注入的.
IOC是一种编程思想,由主动的编程变成被动的接收
可以通过newClassPathXmlApplicationContext去浏览一下底层源码 .
1.7 实例
我们来创建一个之前黑马中Dao、Service、及其Impl实现类的创建与调用
参考spring-study==>spring-01-ioc1是挺全的
2.IOC创建对象的方式
2.1.通过无参构造方法来创建
User.java
public class User {private String name;public User() {System.out.println("user无参构造!");}public void setName(String name) {this.name = name;}public void show(){System.out.println("name="+ name );}
}
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/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd"><!--spring帮我们创建一个叫user的User类对象--><bean id="user" class="com.kuang.pojo.User"><!--帮我们为name成员变量赋值为"韩进论"了,就相当于调用了user.setName("韩进论")--><property name="name" value="韩进论"/></bean>
</beans>
测试类
@Test
public void test(){//普通创建对象方式:User user = new User();//打印:User无参构造!//1.获取spring容器,这一步就是创建出来对象了,就调用了无参构造了ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");//打印:User无参构造!//2.从spring容器中获取user对象User u = (User) context.getBean("user");//3.调用user对象的show方法u.show();
}
2.2.通过有参构造方法来创建(一共有三种)
2.2.1通过形参下标来进行赋值
User.java
package com.kuang.pojo;/*** @author <作者>* @version <版本>* @since <JDK版本>*/
public class User {private String name = "默认姓名";private int age = 0;public User() {System.out.println("User无参构造!");}public User(String name,int age) {System.out.println("User有参构造!");this.name = name;this.age = age;}public void setName(String name) {this.name = name;}public void setAge(int age) {this.age = age;}public void show(){System.out.println("name="+ name );System.out.println("age="+ age );}
}
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"><!--spring帮我们创建一个叫user的User类对象--><bean id="user" class="com.kuang.pojo.User"><!--有参构造,通过下标赋值,index=0代表有参构造第一个形参,value就是第一个形参的值--><constructor-arg index="0" value="老烟斗鬼"/><!--注意!这个第二个形参是int类型的,但你传入参数依然要加双引号--><constructor-arg index="1" value="21"/></bean>
</beans>
MyTest.java
import com.kuang.pojo.User;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;/*** @author <作者>* @version <版本>* @since <JDK版本>*/
public class MyTest {public static void main(String[] args) {//普通创建对象方式:User user = new User();//打印:User无参构造!//1.获取spring容器,这一步就是创建出来对象了,就调用了无参构造了ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");//打印:User无参构造!//2.从spring容器中获取user对象User u = (User) context.getBean("user");//3.调用user对象的show方法u.show();}}
2.2.2通过形参类型来进行赋值
ps:其他两个文件并没有什么差别,所以就不写了
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"><!--spring帮我们创建一个叫user的User类对象--><bean id="user" class="com.kuang.pojo.User"><!--2.1有参构造,下标赋值,index=0代表有参构造第一个形参,value就是第一个形参的值--><!--<constructor-arg index="0" value="老烟斗鬼"/>注意!这个第二个形参是int类型的,但你传入参数依然要加双引号<constructor-arg index="1" value="21"/>--><!--2.2有参构造(不推荐),通过形参的类型进行赋值,如果是引用类型的就需要手写导包--><constructor-arg type="java.lang.String" value="张三"/><constructor-arg type="int" value="42"/></bean>
</beans>
2.2.3通过形参名称来进行赋值
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"><!--spring帮我们创建一个叫user的User类对象--><bean id="user" class="com.kuang.pojo.User"><!--property用setter注入,constructor-arg用构造方法注入--><!--1.无参构造--><!--帮我们为name成员变量赋值为"韩进论"了,就相当于调用了user.setName("韩进论")--><!--<property name="name" value="韩进论"/>--><!--2.1有参构造,下标赋值,index=0代表有参构造第一个形参,value就是第一个形参的值--><!--<constructor-arg index="0" value="老烟斗鬼"/>注意!这个第二个形参是int类型的,但你传入参数依然要加双引号<constructor-arg index="1" value="21"/>--><!--2.2有参构造(不推荐),通过形参的类型进行赋值,如果是引用类型的就需要手写导包--><!--<constructor-arg type="java.lang.String" value="张三"/><constructor-arg type="int" value="42"/>--><!--2.3有参构造(推荐),通过形参的名称来进行赋值,最为直观--><constructor-arg name="name" value="李四"/><constructor-arg name="age" value="24"/></bean>
</beans>
2.3 观察多次getBean创建对象是否同一个
import com.kuang.pojo.User;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;/*** @author <作者>* @version <版本>* @since <JDK版本>*/
public class MyTest {public static void main(String[] args) {//普通创建对象方式:User user = new User();//打印:User无参构造!//1.获取spring容器,这一步就是创建出来对象了,就调用了无参构造了//因为在获取ApplicationContext对象的时候 所有对象都会被创建,所以也会调用UserTwo的无参构造ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");//2.从spring容器中获取user对象User u = (User) context.getBean("user");//3.调用user对象的show方法u.show();/*注意;spring创建对象的顺序也是根据xml中bean的创建顺序来依次创建的结果:User有参构造! 解析:spring中先调用在xml中先创建的是User类中的有参构造,所以打印出来UserTwo被创建出来了 再调用UserTwo类的无参构造name=李四 这就是User类中的show方法age=24*/User u2 = (User) context.getBean("user");System.out.println(u == u2);//输出:true,说明getBean中的对象是同一个}}
2.4总结:
无参构造和有参构造之间的改写就是实现在Beans.xml上,你只要改Beans.xml即可,这就是方便之处了
3.spring配置
3.1. 别名
alias 设置别名 , 为bean设置别名 , 可以设置多个别名
<!--设置别名:在获取Bean的时候可以使用别名获取,也可以通过原来的名字来获取-->
<alias name="userTwo" alias="userNew"/>
用途:一般是用于一些长类名可以通过首字母来缩减类名,如findUserById(假设这就是个类)就可以写成
fubi,当然一般不是这样写的.有更规范的写法
3.2. Bean的配置
<!--bean就是java对象,由Spring创建和管理-->
<!--
id 是bean的标识符,要唯一,如果没有配置id,
name就是默认标识符,如果一个xml里面id重复,则下面的覆盖上面的
如果配置id,又配置了name,那么name是别名
name可以设置多个别名,可以用逗号,分号,空格隔开
如果不配置id和name,可以根据applicationContext.getBean(.class)获取对象;
class是bean的全限定名=包名+类名-->
<bean id="hello" name="hello2 h2,h3;h4" class="com.kuang.pojo.Hello"><property name="name" value="Spring"/>
</bean>
在这里,我们可以看到,我们的name属性的作用和alias标签的作用是一样的,都是给类取别名,但是相比之下,name略显高级,因为它可以取多个别名。
3.3. import
团队的合作通过import来实现 ,它可以将多个配置文件,导入合并为一个
假设:现在项目中多人开发,这三个人负责不同的类的开发,不同的类需要注册到不同的bean中,这时我们最后整合的时候就可以通过import来导入了 ,例如最后整合名就叫applicaitonContest.xml
<!--applicaitonContest中导入以下这些,这样在myTest中ApplicationContext context = new ClassPathXmlApplicationContext("applicationContest.xml");
写入这句,就可以getBean以下三个配置文件中的类了。
-->
<import resource="beans1.xml"/>
<import resource="beans2.xml"/>
<import resource="beans3.xml"/>
4.依赖注入
4.1 构造方式注入
这个前面已经说过了 ,那些无参,有参(参数类型注入、参数下标注入、参数名字注入)构造。
4.2 Set方法注入【重点】
依赖注入:Set注入!
依赖:bean对象的创建依赖于容器
注入:bean对象的所有属性,由容器来注入!
1. 编写所需的pojo类(Address,Student)
package com.kuang.pojo;/*** @author <作者>* @version <版本>* @since <JDK版本>*/ public class Address {private String address;public Address(){}public Address(String address) {this.address = address;}public String getAddress() {return address;}public void setAddress(String address) {this.address = address;}@Overridepublic String toString() {return "Address{" +"address='" + address + '\'' +'}';} }
Student类
package com.kuang.pojo;import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set;/*** @author <作者>* @version <版本>* @since <JDK版本>*/ public class Student {private String name;private Address address;private String[] books;private List<String> hobbies;/*** 学生卡*/private Map<String, String> card;private Set<String> games;private String wife;private Properties info;public String getName() {return name;}public void setName(String name) {this.name = name;}public Address getAddress() {return address;}public void setAddress(Address address) {this.address = address;}public String[] getBooks() {return books;}public void setBooks(String[] books) {this.books = books;}public List<String> getHobbies() {return hobbies;}public void setHobbies(List<String> hobbies) {this.hobbies = hobbies;}public Map<String, String> getCard() {return card;}public void setCard(Map<String, String> card) {this.card = card;}public Set<String> getGames() {return games;}public void setGames(Set<String> games) {this.games = games;}public String getWife() {return wife;}public void setWife(String wife) {this.wife = wife;}public Properties getInfo() {return info;}public void setInfo(Properties info) {this.info = info;}@Overridepublic String toString() {return "Student{" +"name='" + name + '\'' +", address=" + address.toString() +", books=" + Arrays.toString(books) +", hobbies=" + hobbies +", card=" + card +", games=" + games +", wife='" + wife + '\'' +", info=" + info +'}';} }
2. 编写bean.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="address" class="com.kuang.pojo.Address"/><bean id="student" class="com.kuang.pojo.Student"><property name="name" value="张三"/><!--基本类型用value,引用类型用ref--><!--此处的name是决定Address类中的那个参数,ref是指bean配置文件中的bean名称--><property name="address" ref="address"/><!--Array--><property name="books"><array><value>Java进阶</value><value>Python进阶</value><value>JavaScript进阶</value></array></property><!--List--><property name="hobbies"><list><value>编程</value><value>健身</value><value>听歌 </value></list></property><!--Map--><property name="card"><map><entry key="ID" value="1"/><entry key="Password" value="020625"/></map></property><!--Set--><property name="games"><set><value>COD</value><value>CSGO</value><value>CF</value></set></property><!--默认为空值--><property name="wife"><null/></property><!--Properties格式 :key = valuekey = valuekey = valuevalue就是写在中间的--><property name="info"><props><prop key="学号:">0251200506</prop><prop key="性别:">男</prop><prop key="姓名:">张三</prop></props></property></bean>
</beans>
3. 编写测试类
import com.kuang.pojo.Student;
import org.springframework.context.support.ClassPathXmlApplicationContext;/*** @author <作者>* @version <版本>* @since <JDK版本>*/
public class MyTest {public static void main(String[] args) {//1.创建一个spring容器,去创建对应的对象ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");//2.获取学生对象Student student = (Student) context.getBean("student");//3.调用方法String name = student.getName();//张三System.out.println(name);String[] books = student.getBooks();for (int i = 0; i < books.length; i++) {/*Java进阶Python进阶JavaScript进阶*/System.out.println(books[i]);}/*Student{name='张三',address=Address{address='null'},books=[Java进阶, Python进阶, JavaScript进阶],hobbies=[编程, 健身, 听歌 ],card={ID=1, Password=020625},games=[COD, CSGO, CF], wife='null',info={性别:=男, 学号:=0251200506, 姓名:=张三}}*/System.out.println(student);}
}
4.3 通过扩展方式进行注入
环境搭建:实体类User
package com.kuang.pojo; /*** @author <MrDai>* @version <1.0>* @since <JDK8.0>*/ public class User {private String name;private Integer age;public User() {}public User(String name, Integer age) {this.name = name;this.age = age;}public String getName() {return name;}public void setName(String name) {this.name = name;}public Integer getAge() {return age;}public void setAge(Integer age) {this.age = age;}@Overridepublic String toString() {return "User{" +"name='" + name + '\'' +", age=" + age +'}';} }
1. 通过p: 命名空间注入
<!--
需要在beans中添加以下
导入p命名空间
xmlns:p="http://www.springframework.org/schema/p"
-->
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:p="http://www.springframework.org/schema/p"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"><!-- p命名空间注入:直接注入对应的属性值就是property属性的简写,自动去识别对象中的age和name成员变量名,并直接为它赋值 --><bean id="user" class="com.kuang.pojo.User" p:age="14" p:name="哒哒"/></beans>
@Test
public void TestP(){ApplicationContext context = new ClassPathXmlApplicationContext("userbeans.xml");User user = context.getBean("user", User.class);//User{name='哒哒', age=14}System.out.println(user);
}
ps:User.class讲解
User user = context.getBean(“user”, User.class);
加上User.class就是为了不用在强转了
2. 通过c: 命名空间注入
注意:这里如果实体类中没有有参构造的话,你是无法通过c:来构造注入的,所以必须要写入有参构造和无参构造
<!--
需要在beans中添加以下
导入c命名空间
xmlns:c="http://www.springframework.org/schema/c"
-->
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:c="http://www.springframework.org/schema/c"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"><!--c:命名空间注入:通过构造器construct注入--><bean id="user2" class="com.kuang.pojo.User" c:age="15" c:name="大典"/>
</beans>
@Test
public void TestC(){ApplicationContext context = new ClassPathXmlApplicationContext("userbeans.xml");User user2 = context.getBean("user2", User.class);// User{name='大典', age=15}System.out.println(user2);
}
4.4 Bean作用域
在Spring中,那些组成应用程序的主体及由Spring IoC容器所管理的对象,被称之为bean。简单地讲,bean就是由IoC容器初始化、装配及管理的对象
几种作用域中,request、session作用域仅在基于web的应用中使用(不必关心你所采用的是什么web应用框架),只能用在基于web的Spring ApplicationContext环境。
Singleton(单例模式)
默认就是单例模式
当一个bean的作用域为Singleton,那么Spring IoC容器中只会存在一个共享的bean实例,并且所有对bean的请求,只要id与该bean定义相匹配,则只会返回bean的同一实例。Singleton是单例类型,就是在创建起容器时就同时自动创建了一个bean的对象,不管你是否使用,他都存在了,每次获取到的对象都是同一个对象。注意,Singleton作用域是Spring中的缺省作用域。要在XML中将bean定义成singleton,可以这样配置:
<bean id="user2" class="com.kuang.pojo.User" c:age="15" c:name="大典" scope="singleton"/>
测试:
@Test public void TestScope(){ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("userbeans.xml");User user1 = context.getBean("user2", User.class);User user2 = context.getBean("user2", User.class);/**输出* 单例模式:true*/System.out.println(user1 == user2); }
小结:也就是说在单例模式下,你获得的同个id的对象,都是同一个
Prototype(原型模式)
当一个bean的作用域为Prototype,表示一个bean定义对应多个对象实例。Prototype作用域的bean会导致在每次对该bean请求(将其注入到另一个bean中,或者以程序的方式调用容器的getBean()方法)时都会创建一个新的bean实例。Prototype是原型类型,它在我们创建容器的时候并没有实例化,而是当我们获取bean的时候才会去创建一个对象,而且我们每次获取到的对象都不是同一个对象。根据经验,对有状态的bean应该使用prototype作用域,而对无状态的bean则应该使用singleton作用域。在XML中将bean定义成prototype,可以这样配置:
<!--c:命名空间注入:通过构造器construct注入scope:prototype:原型模式,即每次getBean同一个id的对象时,获取的对象都是单独的,不相同的。 --> <bean id="user2" class="com.kuang.pojo.User" c:age="15" c:name="大典" scope="prototype"/>
测试:
@Test public void TestScope(){ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("userbeans.xml");User user1 = context.getBean("user2", User.class);User user2 = context.getBean("user2", User.class);/**输出 * 单例模式:true * 原型模式:false * */System.out.println(user1 == user2); }
小结:也就是说在原型模式下,你获得的同个id的对象,都会重新创建出来。即你每次从容器中getBean的时候,都会产生一个新对象。
注意:其余的request、session只能在web开发中使用到!
Request
当一个bean的作用域为Request,表示在一次HTTP请求中,一个bean定义对应一个实例;即每个HTTP请求都会有各自的bean实例,它们依据某个bean定义创建而成。该作用域仅在基于web的Spring ApplicationContext情形下有效。考虑下面bean定义:
<bean id="loginAction" class=cn.csdn.LoginAction" scope="request"/>
针对每次HTTP请求,Spring容器会根据loginAction bean的定义创建一个全新的LoginAction bean实例,且该loginAction bean实例仅在当前HTTP request内有效,因此可以根据需要放心的更改所建实例的内部状态,而其他请求中根据loginAction bean定义创建的实例,将不会看到这些特定于某个请求的状态变化。当处理请求结束,request作用域的bean实例将被销毁。
Session
当一个bean的作用域为Session,表示在一个HTTP Session中,一个bean定义对应一个实例。该作用域仅在基于web的Spring ApplicationContext情形下有效。考虑下面bean定义:
<bean id="userPreferences" class="com.foo.UserPreferences" scope="session"/>
针对某个HTTP Session,Spring容器会根据userPreferences bean定义创建一个全新的userPreferences bean实例,且该userPreferences bean仅在当前HTTP Session内有效。与request作用域一样,可以根据需要放心的更改所创建实例的内部状态,而别的HTTP Session中根据userPreferences创建的实例,将不会看到这些特定于某个HTTP Session的状态变化。当HTTP Session最终被废弃的时候,在该HTTP Session作用域内的bean也会被废弃掉。
5.Bean自动装配
- 自动装配是Spring满足bean依赖的一种方式!
- Spring会在上下文中自动寻找,并自动给bean装配属性!
在spring中有三种装配的方式
- 在xml中显式配置
- 在java中显式配置
- 隐式的自动装备bean【重要】
5.1 测试
1、新建一个项目
2、新建两个实体类,Cat Dog 都有一个叫的方法
public class Cat {public void shout() {System.out.println("miao~");}
}
public class Dog {public void shout() {System.out.println("wang~");}
}
3、新建一个用户类 User
public class User {private Cat cat;private Dog dog;private String str;
}
4、编写Spring配置文件
<?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 id="dog" class="com.kuang.pojo.Dog"/><bean id="cat" class="com.kuang.pojo.Cat"/><bean id="user" class="com.kuang.pojo.User"><property name="cat" ref="cat"/><property name="dog" ref="dog"/><property name="str" value="qinjiang"/></bean>
</beans>
5、测试
public class MyTest {@Testpublic void testMethodAutowire() {ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");User user = (User) context.getBean("user");user.getCat().shout();user.getDog().shout();}
}
结果正常输出,环境OK
5.2 byName自动装配
autowire byName (按名称自动装配)
由于在手动配置xml过程中,常常发生字母缺漏和大小写等错误,而无法对其进行检查,使得开发效率降低。
采用自动装配将避免这些错误,并且使配置简单化。
测试:
1、修改bean配置,增加一个属性 autowire=“byName”
<bean id="cat" class="com.kuang.dao.Cat"/>
<bean id="dog" class="com.kuang.dao.Dog"/>
<!--autowire="byName":原理:会自动在此容器上下文中查找和people对象set方法后面的值(去掉set并且将首字母小写)对应的bean id比如:此时,找到了cat这个bean id,然后找people对象中有setCat==>cat这个值,然后就可以去自动进行装配.缺点:如果bean id不和set方法后面首字母转为小写的值相同,那么就会报空指针异常为什么?因为你根本没有赋值,所以people对象里面的cat成员变量就为null调用shout方法,当然就会报空指针异常了。这时候就与了byType来自动装配-->
<bean id="people" class="com.kuang.dao.People" autowire="byName"><!--这一步,就相当与执行了setName("张三")--><property name="name" value="张三"/><!--ref是针对dog对象来说的,也就是赋值进去的是一个对象我们可以看到,name="dog"指是叫dog的成员变量,需要被设置值为Dog类型的dog对象即setDog(dog);这就很多余,因为我们知道它肯定是要被赋值为Dog类型的,所以想让spring自动装配。--><!--<property name="dog" ref="dog"/><property name="cat" ref="cat"/>--></bean>
2、再次测试,结果依旧成功输出!
3、我们将 cat 的bean id修改为 catXXX
4、再次测试, 执行时报空指针java.lang.NullPointerException。因为按byName规则找不对应set方法,真正的setCat就没执行,对象就没有初始化,所以调用时就会报空指针错误。
小结:
当一个bean节点带有 autowire byName的属性时。
将查找其类中所有的set方法名,例如setCat,获得将set去掉并且首字母小写的字符串,即cat。
去spring容器中寻找是否有此字符串名称id的对象。
如果有,就取出注入;如果没有,就报空指针异常。
5.3 byType自动装配
autowire byType (按类型自动装配)
使用autowire byType首先需要保证:同一类型的对象,在spring容器中唯一。如果不唯一,会报不唯一的异常。
NoUniqueBeanDefinitionException测试:
<bean class="com.kuang.dao.Cat"/><bean class="com.kuang.dao.Dog"/><!--autowire="byType":会自动在此容器上下文中查找和people对象中属性类型相同的bean就是根据people对象中属性的类型找和此容器中class相同的好处:因为是根据类型进行查找的,所以bean 中的id名(本来就是为了明确而定义的)写错根本没有关系。甚至你把id删除都没有关系。不足:因为是根据类型而查找的,所以你不能添加多个相同类型的bean--><bean id="people" class="com.kuang.dao.People" autowire="byType"><!--这一步,就相当与执行了setName("张三")--><property name="name" value="张三"/></bean>
5.2、5.3总结:
- byName的时候,需要保证所有bean的id唯一,并且这个bean需要和自动注入的属性的set方法的值相同!
- byType的时候,需要保证所有bean的class唯一,并且这个bean需要和自动注入的属性的类型相同!
5.4 使用注解自动装配
jdk1.5开始支持注解,spring2.5开始全面支持注解。
准备工作:利用注解的方式注入属性。
1. 在spring配置文件中引入context文件头
xmlns:context="http://www.springframework.org/schema/context"http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
<!--完整格式-->
<?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:context="http://www.springframework.org/schema/context"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsd"></beans>
2. 开启属性注解支持!
<context:annotation-config/><bean id="cat" class="com.kuang.dao.Cat"/>
<bean id="dog" class="com.kuang.dao.Dog"/>
<bean id="people" class="com.kuang.dao.People"/>
<!--完整的bean.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:context="http://www.springframework.org/schema/context"xmlns:aop="http://www.springframework.org/schema/aop"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop.xsd"><!--=====================注解自动装配===========================--><context:annotation-config/><bean id="cat" class="com.kuang.dao.Cat"/><bean id="dog" class="com.kuang.dao.Dog"/><bean id="people" class="com.kuang.dao.People"/></beans>
3. 给成员变量加上@Autowired/@Qualifier
(1) @Autowired
package com.kuang.dao;import org.springframework.beans.factory.annotation.Autowired;/*** @author <MrDai>* @version <1.0>* @since <JDK8.0>*/
public class People {@Autowiredprivate Cat cat;@Autowiredprivate Dog dog;private String name;public People() {System.out.println("人无参被调用");}public People(Cat cat, Dog dog, String name) {this.cat = cat;this.dog = dog;this.name = name;}public Cat getCat() {return cat;}public Dog getDog() {return dog;}public String getName() {return name;}@Overridepublic String toString() {return "People{" +"cat=" + cat +", dog=" + dog +", name='" + name + '\'' +'}';}
}
好处:
1.使用@Autowired注解就不用我们再在实体类里面添加set方法了,不过你添上也没事(更推荐加上)
2.bean.xml中你就只要配置好对应的bean就好了
3.因为此注解的原理是先根据类型判断,所以同样的你的id写错了或者不写,自动注入都不会有问题 @Autowired执行原理:@Autowired注解是先按照类型(byType)装配依赖对象。类型相同,根据名字来租入,如果我们直接想使用按照名称 (byName)来装配,可以结合**@Qualifier注解**一起使用.
也就是@Autowired会先根据类型进行注入,如果容器中有个多个满足类型的实例,就会根据bean id进行注入,并不是 单纯的只根据类型注入。
(2) @Qualifier
package com.kuang.dao;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.lang.Nullable;/*** @author <MrDai>* @version <1.0>* @since <JDK9.0>*/
public class People {/*** @Autowried细节:* 解析:* 其实还是一样,就是先按照byType来注入,如果beans.xml里面有多个类型相同的bean* 那么就根据名称来注入,比如beans.xml有以下几个bean* <bean id="cat" class="com.kuang.dao.Cat"/>* <bean id="dog" class="com.kuang.dao.Dog"/>** <bean id="cat2" class="com.kuang.dao.Cat"/>* <bean id="dog2" class="com.kuang.dao.Dog"/>* 这个时候我们这个类如下并不会报错* \ @Autowired(required = false)* private Cat cat;* \ @Autowired* private Dog dog;* 因为我们知道因为有多个相同类型的bean,所以不是byType了,而是byName* 根据你属性的名称去查找beans.xml中是否有相同名字的bean id。* 所以找到了不会报错。* =======================================* @Autowried细节2:* 解析:* 现在我们改变下beans.xml中的id* <bean id="cat1" class="com.kuang.dao.Cat"/>* <bean id="dog1" class="com.kuang.dao.Dog"/>** <bean id="cat2" class="com.kuang.dao.Cat"/>* <bean id="dog2" class="com.kuang.dao.Dog"/>* 现在看到以下代码在cat,dog处报红了* Could not autowire. There is more than one bean of 'Cat和Dog' type.** \ @Autowired(required = false)* private Cat cat;* \ @Autowired* private Dog dog;* 因为我们这个时候找不到名称相同的bean id了,这个时候怎么办呢?* 这个时候就需要@Qualifier注解来帮忙了~* =======================================* @Qualifier注解细节:* 解析:* 就是专门用于byName来自动注入。需要执行value的值,* value的值就是你想自动转配的bean id,* 这注解会自动在beans.xml去寻找有没有和value值相同的bean id** 1.如果bean id所对应的类型和我们这类中的成员变量的类型不同还是会报错** 2.如果value属性你指定的bean id是完全不存在的,不会报错* =======================================* 总结:* 1.默认先用Autowired注解,如果beans.xml中的装配环境比较复杂* 有多个相同类型的bean,这个时候Autowired就不好使了* 因为autowired先根据类型来查找(byType)* 有类型相同的就根据bean id名字来查找有没有和成员变量名相同的(byName)** 2.但是如果名字也没有相同的* 那就要通过Qualifier的value属性来手动指配想要注入的bean id了* @Autowired查找候选者:* 按类型找->通过限定符@Qualifier过滤->@Primary->@Priority->根据名称找(字段名称或者方法名称)* =======================================* 扩展:* required:必需的* required = false:说明bean.xml里面你没有定义这个bean也不会报错* 在执行的时候报了java.lang.NullPointerException** 如果没有这个,直接就在这个类中报错* Could not autowire. No beans of 'Cat' type found.* 在beans.xml中找不到对应的bean*/@Autowired(required = false)@Qualifier(value = "cat1")private Cat cat;@Autowired@Qualifier(value = "dog1")private Dog dog;private String name;public People() {System.out.println("人无参被调用");}/*** 加了@NUllable就是说这个name可以为空,也不会报错* @param name*/public People(@Nullable String name) {this.name = name;}public People(Cat cat, Dog dog, String name) {this.cat = cat;this.dog = dog;this.name = name;}public Cat getCat() {return cat;}public Dog getDog() {return dog;}public String getName() {return name;}@Overridepublic String toString() {return "People{" +"cat=" + cat +", dog=" + dog +", name='" + name + '\'' +'}';}
}
课外科普:
1. @Nullable
以下为spring的源码,大致可以参考
public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh, @Nullable ApplicationContext parent) throws BeansException {super(parent);this.setConfigLocations(configLocations);if (refresh) {this.refresh();}
}
//当我们把上面的@NULLable删除了,就会报错
public FileSystemXmlApplicationContext(String... configLocations) throws BeansException {// here warning this(configLocations, true, (ApplicationContext)nul);
}
2. @Autowired(required = false)
解析:代表如果找不到装配的bean不会抛出异常,正常情况下找不到转给的bean对象会直接抛出异常
package com.kuang.dao;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.lang.Nullable;/*** @author <MrDai>* @version <1.0>* @since <JDK8.0>*/
public class People {/*** required:必需的* required = false:说明bean.xml里面你没有定义这个bean也不会报错* 比如我把beans.xml里面的cat的bean删除* 这只是在执行的时候报了java.lang.NullPointerException,这时候你可以选择抛出异常,这样就相当于无事发生了,见下代码,可以看到只要抓取了异常,可以'正常运行'/!*try {/!*获取applicationContext,拿到spring容器,这时候就已经依次创建好猫,狗,人的对象了(根据beans.xml的顺序来调用无参构造)*!/ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");People people = context.getBean("people", People.class);/!*beans.xml没有cat Bean的情况的结果狗无参被调用人无参被调用wang!*!/people.getDog().shout();people.getCat().shout();} catch (Exception e) {}*!/** 如果没有required = false,直接就在这个类中报错* Could not autowire. No beans of 'Cat' type found.* 在beans.xml中找不到对应的bean*/@Autowired(required = false)private Cat cat;@Autowiredprivate Dog dog;private String name;public People() {System.out.println("人无参被调用");}/*** 加了@NUllable就是说这个name可以为空,也不会报错* @param name*/public People(@Nullable String name) {this.name = name;}public People(Cat cat, Dog dog, String name) {this.cat = cat;this.dog = dog;this.name = name;}public Cat getCat() {return cat;}public Dog getDog() {return dog;}public String getName() {return name;}@Overridepublic String toString() {return "People{" +"cat=" + cat +", dog=" + dog +", name='" + name + '\'' +'}';}
}
3. @Resource
ps:为什么要放在课外的原因是在JDK8之后就不再支持这个注解了,所以为了以防在工作中遇到这个注解,还是记下好,但是我idea是9.0的不想在麻烦下额jdk8.0了所以就口头描述为多
解析:
1.与@Autowired的差别主要是@Resource是先按照byName进行注入的,如果bean id和类的成员变量名没有一样的,==注意:如果bean id和成员变量名相同也要保证类型是相同的。==那么就按照类型进行自动注入。
而@Autowred的就是先byType如果存在多类型的bean,再是byName,如果bean id和成员变量名没有一样的则加上@Qualifier注解指定bean id 2.如果有多个类型的话,这就是另一个区别了,它有个name属性,就是来指定bean id的,就相当于@Qualifier注解的作用
4.总结:
精简版总结:@Resource=@Autowired+@Qualifier
详细版总结:
@Autowired与@Resource异同: 1、@Autowired与@Resource都可以用来装配bean。都可以写在字段上,或写在setter方法上。
2、@Autowired默认按类型装配(属于spring规范),默认情况下必须要求依赖对象必须存在,如果要允许null 值,可以设置它的required属性为false,如:@Autowired(required=false) ,如果我们想使用名称装配可以结合@Qualifier注解进行使用
3、@Resource(属于J2EE复返),默认按照名称进行装配,名称可以通过name属性进行指定。如果没有指定name属性,当注解写在字段上时,默认取字段名进行按照名称查找,如果注解写在setter方法上默认取属性名进行装配。当找不到与名称匹配的bean时才按照类型进行装配。但是需要注意的是,如果name属性一旦指定,就只会按照名称进行装配。
它们的作用相同都是用注解方式注入对象,但执行顺序不同。@Autowired先byType,@Resource先byName。
6.使用注解开发
须知:我们之前都是使用 bean 的标签进行bean注入,但是实际开发中,我们一般都会使用注解!但有些注解我们一般实际项目中应该不会经常用到,它一般都是应用于简单的属性配置
注意:在spring4之后,想要使用注解形式,必须得要引入aop的包
<!--在配置文件当中,还得要引入一个context约束-->
<?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:context="http://www.springframework.org/schema/context"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsd"><!--指定要扫描的包,这个包下的注解就会生效了--><context:component-scan base-package="com.kuang.dao"/><context:annotation-config/>
</beans>
6.1 bean实现
实体类注入
/*** @author <MrDai>* @version <1.0>* @since <JDK9.0>* @Component:* 这个bean id默认就是这个类的首字母小写* 等价于<bean id = "user" class = "com.kuang.pojo.User"/>*/
@Component
public class User {public String name = "张三";public void setName(String name) {this.name = name;}@Overridepublic String toString() {return "User{" +"name='" + name + '\'' +'}';}
}
测试类
@Test
public void Test01(){ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");User user = context.getBean("user", User.class);//张三System.out.println(user.name);
}
6.2 属性如何注入
实体类
package com.kuang.pojo;import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;/*** @author <MrDai>* @version <1.0>* @since <JDK9.0>* @Component:* 这个bean id默认就是这个类的首字母小写* 等价于<bean id = "user" class = "com.kuang.pojo.User"/>*/
@Component
public class User {public String name = "张三";/*** 等价于<property name="name" value="李四"/>* 缺点:写死了,没法根据开发中用户名的不同而改变,所以一般用于程序中固定的地方* @param name*/@Value("李四")public void setName(String name) {this.name = name;}@Overridepublic String toString() {return "User{" +"name='" + name + '\'' +'}';}
}
6.3 衍生的注解
我们这些注解,就是替代了在配置文件当中配置步骤而已!更加的方便快捷!在web开发中,我们会按照mvc三层架构分层
@Component三个衍生注解
为了更好的进行分层,Spring可以使用其它三个注解,功能一样,目前使用哪一个功能都一样。
- @Controller:web层相当与servlet层
- @Service:service层
- @Repository:dao层
写上这些注解,就相当于将这个类交给Spring管理装配了!
6.4 自动装配
这个就是我们之前5.4总说过的。这里就简要概况了
精简版总结:@Resource=@Autowired+@Qualifier(共同注意:如果bean id和成员变量名相同也要保证类型是相同的,才能进行注入)- @Resource是先按照byName进行注入的,如果bean id和类的成员变量名没有一样的, 那么就按照类型进行自动注入。- @Autowred是先按照byType进行注入的,如果存在多个相同类型的bean,再是byName注入, 如果bean id和成员变量名没有一样的则加上@Qualifier注解指定bean id
6.5 作用域
@scope
- singleton:默认的,Spring会采用单例模式创建这个对象。关闭工厂 ,所有的对象都会销毁。
- prototype:多例模式。关闭工厂 ,所有的对象不会销毁。内部的垃圾回收机制会回收
@Controller("user")
@Scope("prototype")
public class User {@Value("秦疆")public String name;
}
6.6 小结
XML与注解比较
- XML可以适用任何场景 ,结构清晰,维护方便
- 注解不是自己提供的类使用不了,开发简单方便
xml与注解整合开发 :
最佳实际开发方法
- xml管理Bean
- 注解只负责完成属性的注入
- 使用过程中, 可以不用扫描,扫描是为了类上的注解
回顾:
<context:annotation-config/>
作用:
进行注解驱动注册,从而使注解生效
用于激活那些已经在spring容器里注册过的bean上面的注解,也就是显示的向Spring注册
如果不扫描包,就需要手动配置bean
如果不加注解驱动,则注入的值为null!
<context:component-scan base-package="com.kuang" />
作用:
- 指定要扫描的包,这个包下的注解就会生效
7.使用Java的方式配置Spring
我们现在要完全不适用spring的xml配置,全权交给java来实现!
这时候就需要javaConfig来做了
7.1 什么是javaConfig
javaConfig原本是Spring的一个子项目,在Spring4之后,它成为了一个核心功能。
7.2 作用
它通过 Java 类的方式提供 Bean 的定义信息
7.3 实例
与之前的不同的是,我们需要在kuang文件夹中新建一个config文件夹,来存放配置类。
方法一: 配置类:@Configuration+@Bean,实体类不用@Component
实体类User
package com.kuang.pojo;import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;/**注意(这里讨论是针对配置类中加了@Bean的情况,如果没有加@Bean可以选择看,加了的需要看看):* 这里@Component重复了,可以删除* 它和我们的配置类重复了,但我们没有xml来开启注解扫描,所以只有配置类的注解生效了* 我们在config类中已经对User对象进行声明bean操作即@Bean* 这里能用完全是依赖@Bean的!跟@Component无关!** 也就是说没了这个@Component,这个类也会交给spring容器去管理了,所以去掉@Component也不会报错!* 但是如果config里面你没有@Bean就肯定会报错了,报No bean named 'user' available* @author <MrDai>* @version <1.0>* @since <JDK9.0>* @Component:(注意:这里没有被component-scan扫描到,所以是不会生效的,这里能运行,主要是有@Bean* 这个bean id默认就是这个类的首字母小写* 等价于<bean id = "user" class = "com.kuang.pojo.User"/>*/
@Component
public class User {@Value("小呆比")private String name;public String getName() {return name;}public void setName(String name) {this.name = name;}@Overridepublic String toString() {return "User{" +"name='" + name + '\'' +'}';}
}
配置类
package com.kuang.config;import com.kuang.pojo.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;/*** @Configuration:* 在一个类上加了这个注解就相当于在applicationContext.xml中加上了<beans></beans>* @author <MrDai>* @version <1.0>* @since <JDK9.0>*/
@Configuration
/*** 自动去扫描@Component并生成bean*/
//@ComponentScan("com.kuang.pojo")
public class applicationConfig {/*** @Bean:* 详解:* 就是相当于注册了一个bean,即<bean id = "user" class = "com.kuang.pojo.User"/>* 重点1.这里的方法名就是我们的bean中的id,也就是对象名,所以在测试类中我们getBean的名字必须和此方法名相同* 重点2.这里的返回值就是我们在bean中的class,也就是类型,所以测试类中的返回值类型就是我们这的返回值** @return User*/@Beanpublic User user(){return new User();}
}
方法二: 实体类:@Component,配置类:@Configuration+@ComponentScan(“实体类的包路径”)
实体类User
package com.kuang.pojo;import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;/**注意(这里讨论是针对配置类中加了@Bean的情况,如果没有加@Bean可以选择看,加了的需要看看):* 这里@Component重复了* 它和我们的配置类重复了,但我们没有xml来开启注解扫描,所以只有配置类的注解生效了* 我们在config类中已经对User对象进行声明bean操作即@Bean* 这里能用完全是依赖@Bean的!跟@Component无关!** 也就是说没了这个@Component,这个类也会交给spring容器去管理了,所以去掉@Component也不会报错!* 但是如果config里面你没有@Bean就肯定会报错了,报No bean named 'user' available* @author <MrDai>* @version <1.0>* @since <JDK9.0>* @Component:(注意:这里没有被component-scan扫描到,所以是不会生效的,这里能运行,主要是有@Bean* 这个bean id默认就是这个类的首字母小写* 等价于<bean id = "user" class = "com.kuang.pojo.User"/>*/
@Component
public class User {@Value("小呆比")private String name;public String getName() {return name;}public void setName(String name) {this.name = name;}@Overridepublic String toString() {return "User{" +"name='" + name + '\'' +'}';}
}
配置类
package com.kuang.config;import com.kuang.pojo.User;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;/*** @Configuration:* 在一个类上加了这个注解就相当于在applicationContext.xml中加上了<beans></beans>* @author <MrDai>* @version <1.0>* @since <JDK9.0>*/
@Configuration
/*** 自动去扫描@Component并生成bean*/
@ComponentScan("com.kuang.pojo")
public class applicationConfig {/*** @Bean:* 详解:* 就是相当于注册了一个bean,即<bean id = "user" class = "com.kuang.pojo.User"/>* 重点1.这里的方法名就是我们的bean中的id,也就是对象名,所以在测试类中我们getBean的名字必须和此方法名相同* 重点2.这里的返回值就是我们在bean中的class,也就是类型,所以测试类中的返回值类型就是我们这的返回值** @return User*///@Beanpublic User user(){return new User();}
}
方法一和方法二共同有的测试类
import com.kuang.config.applicationConfig;
import com.kuang.pojo.User;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;/*** @author <MrDai>* @version <1.0>* @since <JDK9.0>*/
public class MyTest {@Testpublic void Test01(){AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(applicationConfig.class);User getUser = context.getBean("user",User.class);String name = getUser.getName();//小呆比System.out.println(name);}
}
8.AOP
8.1 代理模式【重点】
为什么要学习代理模式?
1.因为AOP的底层机制就是动态代理!而代理的核心是多态
2.面试常问
-SpringAOP【我们这次学的就是这个】
-SpringMVC
代理模式分类
- 静态代理
- 动态代理
这是代理模式的一个比方
这是代理模式的真实模型
代理角色分析
- 抽象对象【进行租房交易】 : 一般使用接口或者抽象类来实现
- 真实对象【房东】 : 被代理的角色
- 代理对象【中介】: 代理真实角色 ; 代理真实角色后 , 一般会做一些附属的操作
- 客户 【租房客】 : 使用代理角色来进行一些操作(如:租房),访问代理对象的人。
8.1.1 静态代理
静态代理的好处:
- 可以使得我们的真实角色更加纯粹 . 不再去关注一些公共的事情 .
- 公共的业务由代理来完成 . 实现了业务的分工 ,
- 公共业务发生扩展时变得更加集中和方便 .
静态代理的缺点:
- 类多了 , 多了代理类 , 工作量变大了 .
- 一个真实对象就会产生一个代理角色。代码量就会多很多, 开发效率降低
我们想要静态代理的好处,又不想要静态代理的缺点,所以 , 就有了动态代理 !
实现静态代理
创建接口(抽象对象)
package com.kuang.demo01;/*** 租房事件*/ public interface Rent {public abstract void Rent(); }
真实对象
package com.kuang.demo01;/**租客* @author <MrDai>* @version <1.0>* @since <JDK9.0>*/ public class Client {public static void main(String[] args) {/*** 通过代理去和房东沟通,但是中介也需要有他的事情要干,* 不单单是一个租房事情,不然还要中介干嘛*/Proxy proxy = new Proxy(new Host());/*** 这句代码就实现了你不用去管房东,客户直接找中介即可* 结果:* 房东要出租房子* 中介带客户看房* 签租赁合同* 收中介费* 可以看到,只有一个方法,但是这方法里面实现了多个方法* 就像是一个中间的横向扩展* 你添加新的功能不会改变真实对象的代码* 真实对象(客户)只用做专一的事情即可,*/proxy.Rent();} }
代理对象
package com.kuang.demo01;/*** @author <MrDai>* @version <1.0>* @since <JDK9.0>*/ public class Proxy implements Rent{private Host host;public Proxy() {}public Proxy(Host host) {this.host = host;}@Overridepublic String toString() {return "Proxy{" +"host=" + host +'}';}public void SeeHouse(){System.out.println("中介带客户看房");}public void contract(){System.out.println("签租赁合同");}public void Fare(){System.out.println("收中介费");}@Overridepublic void Rent() {host.Rent();SeeHouse();contract();//代理也要帮房东出租房子,所以要调用出租事件Fare();} }
客户端访问代理对象
package com.kuang.demo01;/** 房东* @author <MrDai>* @version <1.0>* @since <JDK9.0>*/ public class Host implements Rent {@Overridepublic void Rent() {System.out.println("房东要出租房子");} }
静态代理详解
练习步骤:
1、创建一个抽象角色,比如咋们平时做的用户业务,抽象起来就是增删改查!
//抽象角色:增删改查业务
public interface UserService {void add();void delete();void update();void query();
}
2、我们需要一个真实对象来完成这些增删改查操作
//真实对象,完成增删改查操作的人
public class UserServiceImpl implements UserService {public void add() {System.out.println("增加了一个用户");}public void delete() {System.out.println("删除了一个用户");}public void update() {System.out.println("更新了一个用户");}public void query() {System.out.println("查询了一个用户");}
}
3、需求来了,现在我们需要增加一个日志功能,怎么实现!
- 思路1 :在实现类上增加代码 【麻烦!】
- 思路2:使用代理来做,能够不改变原来的业务情况下,实现此功能就是最好的了!
4、设置一个代理类来处理日志!代理角色
package com.kuang.demo02;/*** @author <MrDai>* @version <1.0>* @since <JDK9.0>*/
public class UserServiceProxy implements UserService{private UserService userService;public void setUserService(UserService userService) {this.userService = userService;}/*** 解析:* 有可能会有疑问,这和之前加了输出语句有什么区别* 其实,这个例子不太好,因为我们整个是将简单的复杂化了,看起来不直观* 但是我们需要明白,我们这里只是设置了方法里面的参数* 而在实际开发中如果不用代理的话你就是需要在每个方法里面写一些代码了* 所以:我们这样的目的就是不用再去改在UserServiceImpl中的代码了* 好处:* 耦合度更低,不会触及源码,可以在基础上自由拓展***/@Overridepublic void add() {log("add");userService.add();}@Overridepublic void del() {log("del");userService.del();}@Overridepublic void update() {log("update");userService.update();}@Overridepublic void query() {log("query");userService.query();}//日志方法public void log(String msg){System.out.println("[DEBUG]:使用了"+msg+"方法");}
}
5、测试访问类:
package com.kuang.demo02;import com.kuang.demo02.Impl.UserServiceImpl;/*** @author <MrDai>* @version <1.0>* @since <JDK9.0>*/
public class Client {public static void main(String[] args) {UserService userService = new UserServiceImpl();UserServiceProxy proxy = new UserServiceProxy();proxy.setUserService(userService);proxy.add();/**输出结果:* [DEBUG]:使用了add方法* 增加了一个用户!* 详解:* 我们原来的add方法本来是只输出'增加了一个用户!'的,* 但是我们的领导想让我们在add方法里面增添一个功能* 比如就是增加一个日志输出,我们就还要在Impl的原有代码基础上写吗?* 不用了,直接在代理上增添一些功能即可。* 问:* 为什么不能在原有代码上改?* 答:* 1.改动了原有的业务代码,在公司中是大忌!* (如果别人写的好好的,你一改全崩了,你又忘记改了哪些小地方* 那不就完蛋了吗,只能通过数据备份来恢复了)* 如果上司想要个日志方法,你还要跑到接口中增加一个日志方法* 实现类里也要改,如果实现类一多,混了怎么办?* 现在增加了代理,要实现一些额外的业务,就只要改代理的代码就好了* 原来的实体类中的东西保持不变。** 再重申一遍:可能现在会觉的麻烦,多此一举,但是在以后复杂环境* 中开发是十分必要的!** 2.在实际开发中,原有代码不只是你一个人去调用,其他人的业务中* 也会调用,其他业务如果不想用这个新增的日志功能呢?那不就需要* 代理来实现你想要的特有的功能吗.*/}
}
8.1.2 动态代理
什么是动态代理?
我们上面说过,静态代理是有一个缺点的,就是每当你多一个接口(业务需求)的时候,如果你想要再去实现一个日志增加,又要写一个代理类,这样十分繁琐,所以为了解决这个问题就出现了动态代理
动态代理的角色和静态代理的一样 .
动态代理的代理类是动态生成的 . 静态代理的代理类是我们提前写好的
动态代理分为两类 : 一类是基于接口动态代理 , 一类是基于类的动态代理
- 基于接口的动态代理----JDK动态代理
- 基于类的动态代理–cglib
- 现在用的比较多的是 javasist 来生成动态代理 . 百度一下javasist
- 我们这里使用JDK的原生代码来实现,其余的道理都是一样的!
JDK的动态代理需要了解两个类
核心 : InvocationHandler 和 Proxy , 打开JDK帮助文档看看
1.【InvocationHandler:调用处理程序】
Object invoke(Object proxy, 方法 method, Object[] args); //参数 //proxy - 调用该方法的代理实例 //method -所述方法对应于调用代理实例上的接口方法的实例。方法对象的声明类将是该方法声明的接口,它可以是代理类继承该方法的代理接口的超级接口。 //args -包含的方法调用传递代理实例的参数值的对象的集合,或null如果接口方法没有参数。原始类型的参数包含在适当的原始包装器类的实例中,例如java.lang.Integer或java.lang.Boolean 。
2.【Proxy : 代理】
//生成代理类 public Object getProxy(){return Proxy.newProxyInstance(this.getClass().getClassLoader(),rent.getClass().getInterfaces(),this); }
代码实现
抽象角色和真实角色和之前的一样!
Rent . java 即抽象角色
//抽象角色:租房 public interface Rent {public void rent(); }
Host . java 即真实角色
//真实角色: 房东,房东要出租房子 public class Host implements Rent{public void rent() {System.out.println("房屋出租");} }
ProxyInvocationHandler. java 即代理角色
public class ProxyInvocationHandler implements InvocationHandler {private Rent rent;public void setRent(Rent rent) {this.rent = rent;}//生成代理类,重点是第二个参数,获取要代理的抽象角色!之前都是一个角色,现在可以代理一类角色public Object getProxy(){return Proxy.newProxyInstance(this.getClass().getClassLoader(),rent.getClass().getInterfaces(),this);}// proxy : 代理类 method : 代理类的调用处理程序的方法对象.// 处理代理实例上的方法调用并返回结果@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {seeHouse();//核心:本质利用反射实现!Object result = method.invoke(rent, args);fare();return result;}//看房public void seeHouse(){System.out.println("带房客看房");}//收中介费public void fare(){System.out.println("收中介费");}}
Client . java
//租客 public class Client {public static void main(String[] args) {//真实角色Host host = new Host();//代理实例的调用处理程序ProxyInvocationHandler pih = new ProxyInvocationHandler();pih.setRent(host); //将真实角色放置进去!Rent proxy = (Rent)pih.getProxy(); //动态生成对应的代理类!proxy.rent();}}
核心:一个动态代理 , 一般代理某一类业务 , 一个动态代理可以代理多个类,代理的是接口!
深化思考
我们可以想下,这样就弥补了静态代理的缺点了吗?很显然并没有,因为我们这样还是只是针对具体一个接口而言的,要是其他业务也想实现这个代理角色里的功能,那还不是要写一个?
因为ProxyInvocationHandler中,我们并没有针对任何类写,而是针对Rent类而写的private Rent rent,所以我们想要真正实现动态代理,我们就想让这个代理类为Object类型。
我们来使用动态代理实现代理我们后面写的UserService!
我们可以编写一个通用的动态代理实现的类!
package com.kuang.demo03;import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;/**我们来创建一个万能的共有的代理类,就是你用了这个类就可以动态去实现代理* 即:这个类就是来帮我们自动生成代理类的!** 解释其作用:* ProxyInvocationHandler:生成动态代理实例的* InvocationHandler: 调用处理程序,并返回结果的** @author <MrDai>* @version <1.0>* @since <JDK9.0>*/
public class ProxyInvocationHandler implements InvocationHandler {/*** 这两串代码注意是为了告诉JVM用户调用的是哪个类,想要实现那个类的代理** target就是一个被代理的接口*/public Object target;public void setTarget(Object target) {this.target = target;}/*** 得到代理实例,返回任意类型的对象,需要用于反射* 以便之后用户要获得他们所想要的各种成员变量,方法名等等。* @return*/public Object getProxy(){return Proxy.newProxyInstance(this.getClass().getClassLoader(),target.getClass().getInterfaces(),this);}/*** 重点:处理代理实例,并且返回对应的结果.* @param proxy 就是获取输入的对象的实例类(host)* @param method 就是利用反射机制得到用户输入的类的方法集(如add,del....)* @param args 用户用于输入的参数集合(我们这里方法都没有参数,所以都是为null)* @return 用户想要的输出结果类* @throws Throwable 异常处理*/@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {/*** 真正意义上实现了全动态,动态获取了用户输入的方法名* 完全不用我们去自己手打了*/String methodName = method.getName();log(methodName);/*** 用户想通过方法映射集得到的结果,* 反射:* 就是通过获取到一个对象,来反向获取此对象的各种信息* ====================================================* 反射Method讲解* * Method:方法对象* * 执行方法:* * Object invoke(Object obj, Object... args)* 第一个参数:即这个类创建出来的对象(查看下面的例子,这个类指的不太准确)* 第二个参数:即调用这个方法的方法对象的形参值** 例如:* //在一个类中* Person p = new Person();* // 方法名 方法对应的形式参数列表的类型的字节码文件* Method sleep = personClass.getMethod("sleep", String.class);//先要获取该方法对象* //方法名.invoke(对象,方法的参数);* sleep.invoke(p,"戴学医");//戴学医在睡觉~* //在Person类中的方法* public void sleep(String name){* System.out.println(name + "在睡觉~");* }* 获取方法名称:* String getName:获取方法名* ====================================================*/Object invoke = method.invoke(target, args);return invoke;}/*** 比如我们上级又想让我们来实现打印日志的效果* 但是我们现在又了反射机制,我们就可以通过反射来动态获取对应的方法名* 不用我们在一个个打什么 log("update"); 特别麻烦* 我们这时可以用getName来获取*/public void log(String msg){System.out.println("[DEBUG]实现了" + msg + "方法");}
}
Client.java
package com.kuang.demo03;import com.kuang.demo02.Impl.UserServiceImpl;
import com.kuang.demo02.UserService;/*** @author <MrDai>* @version <1.0>* @since <JDK9.0>*/
public class Client {public static void main(String[] args) {//创建一个真实对象UserService service = new UserServiceImpl();//创建一个代理对象ProxyInvocationHandler proxyInvocationHandler = new ProxyInvocationHandler();//告诉JVM要代理的是哪个类proxyInvocationHandler.setTarget(service);/*** 动态生成一个代理实例* 这里是一个多态的写法* 左边是一个接口,而返回来的实际是一个代理类,这代理类中就实现了你想要添加的log方法*/UserService proxy = (UserService) proxyInvocationHandler.getProxy();proxy.del();}
}
8.2 AOP简介
1. 什么是AOP
AOP(Aspect Oriented Programming)意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
2.Aop在Spring中的作用
提供声明式事务;允许用户自定义切面
以下名词需要了解下:
- 横切关注点:跨越应用程序多个模块的方法或功能。即是,与我们业务逻辑无关的,但是我们需要关注的部分,就是横切关注点。如日志 , 安全 , 缓存 , 事务等等 …
- 切面(ASPECT):横切关注点 被模块化 的特殊对象。即,它是一个类。
- 通知(Advice):切面必须要完成的工作。即,它是类中的一个方法。
- 目标(Target):被通知对象。
- 代理(Proxy):向目标对象应用通知之后创建的对象。
- 切入点(PointCut):切面通知 执行的 “地点”的定义。
- 连接点(JointPoint):与切入点匹配的执行点。
SpringAOP中,通过Advice定义横切逻辑,Spring中支持5种类型的Advice:
即 Aop 在 不改变原有代码的情况下 , 去增加新的功能(如日志)
8.3 使用Spring实现Aop
Spring有两种方式来实现AOP
1.使用Spring的APi接口
2.自定义来实现AOP
3.使用注解实现
【重点】使用AOP织入,需要导入一个依赖包!
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId><version>1.9.4</version>
</dependency>
第一种方式:通过 Spring API 实现
实现方式:StringAPI:就是一些接口例如: MethodBeforeAdvice、AfterReturningAdvice
首先编写我们的业务接口和实现类
public interface UserService {public void add();public void delete();public void update();public void search();}
//
public class UserServiceImpl implements UserService{@Overridepublic void add() {System.out.println("增加用户");}@Overridepublic void delete() {System.out.println("删除用户");}@Overridepublic void update() {System.out.println("更新用户");}@Overridepublic void search() {System.out.println("查询用户");}
}
然后去写我们的增强类 , 我们编写两个 , 一个前置增强 一个后置增强
package com.kuang.Log;import org.springframework.aop.MethodBeforeAdvice;import java.lang.reflect.Method;/** 在执行代理方法前插入日志消息* @author <MrDai>* @version <1.0>* @since <JDK9.0>*/
public class Log implements MethodBeforeAdvice {/**** @param method 要执行的目标对象的方法(如目标对象为UserServiceImpl的add方法)* @param args 方法中的参数(如我们的add方法:我们没有参数所以为null)* @param target 目标对象(如:我们的UserServiceImpl)* @throws Throwable*/@Overridepublic void before(Method method, Object[] args, Object target) throws Throwable {String className = target.getClass().getName();String methodName = method.getName();System.out.println("[DEBUG] "+className +"的"+methodName+"方法被执行了!");}}
//========================================================================
package com.kuang.Log;import org.springframework.aop.AfterReturningAdvice;import java.lang.reflect.Method;/** 后置的日志增强类* @author <MrDai>* @version <1.0>* @since <JDK9.0>*/
public class AfterLog implements AfterReturningAdvice {/**** @param returnValue 返回值* @param method 要执行的目标对象的方法(如目标对象为UserServiceImpl的add方法)* @param args 方法中的参数(如我们的add方法:我们没有参数所以为null)* @param target 目标对象(如:我们的UserServiceImpl)* @throws Throwable*/@Overridepublic void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {System.out.println("[DEBUG] 执行了"+method.getName()+"方法,返回值为:"+returnValue);}
}
最后去spring的文件中注册 , 并实现aop切入实现 , 注意导入约束 【重点】
<?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"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop.xsd"><!--注册Bean--><bean id="service" class="com.kuang.service.UserServiceImpl"/><bean id="log" class="com.kuang.Log.Log"/><bean id="afterLog" class="com.kuang.Log.AfterLog"/><!--配置aop--><aop:config><!--目的:设置一个切入点:就是我们想在哪个地方去执行我们的spring方法(如我们的add方法)参数:id:就是为你的切入点命一个名字expression:表达式表达式:execution(* com.kuang.service.Impl.UserServiceImpl.*(..))讲解:execution():这是一个规定死的写法* com.kuang.service.Impl.UserServiceImpl.*(..):对应:权限修饰符 返回值 类的方法名(参数)代码含义:这里权限修饰符和方法名都用通配符*来表示,参数用..来表示都是代表任意的意思!!!实际含义:也就是说,它支持切入返回值为UserServiceImpl的并且任意权限修饰符的任意方法名,参数也是任意即:这里它就是能支持切入到UserServiceImpl为返回值类型的所有方法就是这个UserServiceImpl中的所有方法都能被切入,增加一些方法详解:execution(public * *(..)) 所有的公共方法execution(* set*(..)) 以set开头的任意方法execution(* com.kuang.service.UserServiceImpl.*(..)) com.kuang.service.UserServiceImpl类中的所有的方法execution(* com.kuang.service.*.*(..)) com.cj.service包中的所有的类的所有的方法execution(* com.kuang.service..*.*(..)) com.cj.service包及子包中所有的类的所有的方法execution(* com.kaung.spring.aop..*.*(String,?,Integer)) com.kuang.spring.aop包及子包中所有的类的有三个参数且第一个参数为String,第二个参数为任意类型,第三个参数为Integer类型的方法--><aop:pointcut id="pointcut" expression="execution(* com.kuang.service.UserServiceImpl.*(..))"/><!--执行增加环绕日志:前置日志:我们这句代码的意思就是讲bean id为log的类增加到切入点叫pointcut的地方后置日志:也是一样的道理,这样一前一后我们就能实现环绕日志了--><aop:advisor advice-ref="log" pointcut-ref="pointcut"/><aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/></aop:config>
</beans>
测试
import com.kuang.service.UserService;
import org.springframework.context.support.ClassPathXmlApplicationContext;/*** @author <MrDai>* @version <1.0>* @since <JDK9.0>*/
public class MyTest {public static void main(String[] args) {ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");/*** 注意:动态代理,代理是一个接口,如果你的字节码写的是实现类的那就会报错!* UserService service = context.getBean("service", UserServiceImpl.class);* 原因:* 返回的是被创建出来的代理类(增加了前后log的),是新的实现类* 代理类和被代理类都实现的是UserService接口* 所以新生成的代理类只能向上转型为接口类型,不能同等级强转给UserServiceImpl*/UserService service = (UserService) context.getBean("service");/*结果[DEBUG] com.kuang.service.UserServiceImpl的add方法被执行了!增加了一个用户![DEBUG] 执行了add方法,返回值为:null*/service.add();}
}
Aop的重要性 : 很重要 . 一定要理解其中的思路 , 主要是思想的理解这一块 .
Spring的Aop就是将公共的业务 (日志 , 安全等) 和领域业务结合起来 , 当执行领域业务时 , 将会把公共业务加进来 . 实现公共业务的重复利用 . 领域业务更纯粹 , 程序猿专注领域业务 , 其本质还是动态代理 .
第二种方式:通过自定义类来实现Aop
实现方式:自定义类:就是我们任意想要的一个任意增强方法,主要就是面向切片定义
目标业务类不变依旧是userServiceImpl
第一步 : 写我们自己的一个切入类
public class DiyPointcut {public void before(){System.out.println("---------方法执行前---------");}public void after(){System.out.println("---------方法执行后---------");}}
去spring中配置
<!--方式二:自定义来实现spring好处:简单易懂缺点:功能较小,没有多少能实现的方法
-->
<!--第一步:配置Bean-->
<bean id="diy" class="com.kuang.DIY.DiyPointCut"/>
<!--第二部:配置AOP-->
<aop:config><!--aop:aspect:代表切面,即模块化的方法的集合。就是这个类中都是非核心代码,用来增强的--><aop:aspect ref="diy"><!--定义切入点:就是你想将这些方法切入到哪个方法中或者说允许哪些方法可以切入--><aop:pointcut id="pointcut" expression="execution(* com.kuang.service.UserServiceImpl.*(..))"/><!--aop:after中的就不是我们在after类中定义的方法了,而是他切面中本来就定义好了的含义:就是在我们的切入点之前执行该方法属性method:里面的方法就是我们方法里面定义的方法名了属性pointcut-ref:就是我们定义的切入点位置了--><aop:after method="after" pointcut-ref="pointcut"/><aop:before method="before" pointcut-ref="pointcut"/></aop:aspect>
</aop:config>
测试:
import com.kuang.service.UserService;
import org.springframework.context.support.ClassPathXmlApplicationContext;/*** @author <MrDai>* @version <1.0>* @since <JDK9.0>*/
public class MyTest {public static void main(String[] args) {ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");/*** 注意:动态代理,代理是一个接口,如果你的字节码写的是实现类的那就会报错!* UserService service = context.getBean("service", UserServiceImpl.class);* 原因:* 返回的是被创建出来的代理类(增加了前后log的),是新的实现类* 代理类和被代理类都实现的是UserService接口* 所以新生成的代理类只能向上转型为接口类型,不能同等级强转给UserServiceImpl*/UserService service = context.getBean("service", UserService.class);/*springAPI方式实现AOP结果:[DEBUG] com.kuang.service.UserServiceImpl的add方法被执行了!增加了一个用户![DEBUG] 执行了add方法,返回值为:null自定义类:=====方法执行前=====增加了一个用户!=====方法执行后=====*/service.add();}
}
第三种方式:通过注解的方式实现AOP
新增一个有注解实现的增强类
package com.kuang.Annotation;import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;/** 方式三:使用注解的方式实现AOP* @Aspect:标记这个类是一个切面* 就不用像自定义切面那样,在xml里面去定义某个类是一个切面了* @author <MrDai>* @version <1.0>* @since <JDK9.0>*/
@Aspect
public class AnnotationPointCut {/**@Before:* 就是在xml中配置* <aop:before method="before" pointcut-ref="pointcut"/>* 是一样的,声明为前置日志的作用* 参数作用:* 就是标记你的切入点在哪里* 也就是说标记哪些类允许设置为切入点,可以切入这个before方法** 我们这里就是说明是在service包下返回值类型为* UserServiceImpl类型的所有方法名都是切入点*/@Before("execution(* com.kuang.service.UserServiceImpl.*(..))")public void before(){System.out.println("=====方法执行前=====");}@After("execution(* com.kuang.service.UserServiceImpl.*(..))")public void after(){System.out.println("=====方法执行后=====");}@Around("execution(* com.kuang.service.UserServiceImpl.*(..))")public void Around(ProceedingJoinPoint pj) throws Throwable {System.out.println("执行前...");//执行目标方法proceedObject proceed = pj.proceed();Signature signature = pj.getSignature();/*输出这个类的一些基本信息*/System.out.println(signature);System.out.println(proceed);System.out.println("执行后...");}
}
applicationContext .xml配置
<!--方式三:通过注解来实现AOP好处:方便简洁不用再在xml中说明切入点的位置,哪个是切面,哪个是通知(方法)即<aop:after method="after" pointcut-ref="pointcut"/>因为我们在AnnotationPointCut类里面已经用注解都说明了缺点:难以维护-->
<bean id="annotationPointCut" class="com.kuang.Annotation.AnnotationPointCut"/>
<!--开启注解支持-->
<aop:aspectj-autoproxy/>
aop:aspectj-autoproxy:说明
通过aop命名空间的<aop:aspectj-autoproxy />声明自动为spring容器中那些配置@aspectJ切面的bean创建代理,织入切面。当然,spring 在内部依旧采用AnnotationAwareAspectJAutoProxyCreator进行自动代理的创建工作,但具体实现的细节已经被<aop:aspectj-autoproxy />隐藏起来了<aop:aspectj-autoproxy />有一个proxy-target-class属性,默认为false,表示使用jdk动态代理织入增强,当配为<aop:aspectj-autoproxy poxy-target-class="true"/>时,表示使用CGLib动态代理技术织入增强。不过即使proxy-target-class设置为false,如果目标类没有声明接口,则spring将自动使用CGLib动态代理。JDK和Cglib的区别小结:区别是jdk只能代理接口实现类,而cglib可以代理没有实现接口的类
AOP核心思想:
无非就是将核心代码和非核心代码进行分离,在核心代码中切入非核心代码,主要项目中就是完成事务和日志的记录的分离
9.整合Mybatis
9.1 依赖整合步骤
1、导入相关jar包
junit
<dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version>
</dependency>
mybatis
<dependency><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId><version>3.5.2</version>
</dependency>
mysql-connector-java
<dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.47</version>
</dependency>
spring相关
<dependency><groupId>org.springframework</groupId><artifactId>spring-webmvc</artifactId><version>5.1.10.RELEASE</version>
</dependency>
<dependency><groupId>org.springframework</groupId><artifactId>spring-jdbc</artifactId><version>5.1.10.RELEASE</version>
</dependency>
aspectJ AOP 织入器
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId><version>1.9.4</version>
</dependency>
mybatis-spring整合包 【重点】
<dependency><groupId>org.mybatis</groupId><artifactId>mybatis-spring</artifactId><version>2.0.2</version>
</dependency>
配置Maven静态资源过滤问题!
<build><resources><resource><directory>src/main/java</directory><includes><include>**/*.properties</include><include>**/*.xml</include></includes><filtering>true</filtering></resource></resources>
</build>
2、编写配置文件
3、代码实现
9.2 回忆Mybatis
编写pojo实体类
package com.kuang.pojo;public class User {private int id; //idprivate String name; //姓名private String pwd; //密码
}
实现mybatis的配置文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configurationPUBLIC "-//mybatis.org//DTD Config 3.0//EN""http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration><typeAliases><package name="com.kuang.pojo"/></typeAliases><environments default="development"><environment id="development"><transactionManager type="JDBC"/><dataSource type="POOLED"><property name="driver" value="com.mysql.jdbc.Driver"/><property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=utf8"/><property name="username" value="root"/><property name="password" value="123456"/></dataSource></environment></environments><mappers><package name="com.kuang.dao"/></mappers>
</configuration>
UserDao接口编写
public interface UserMapper {public List<User> selectUser();
}
接口对应的Mapper映射文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.kuang.dao.UserMapper"><select id="selectUser" resultType="User">select * from user</select></mapper>
测试类
@Test
public void selectUser() throws IOException {String resource = "mybatis-config.xml";InputStream inputStream = Resources.getResourceAsStream(resource);SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);SqlSession sqlSession = sqlSessionFactory.openSession();UserMapper mapper = sqlSession.getMapper(UserMapper.class);List<User> userList = mapper.selectUser();for (User user: userList){System.out.println(user);}sqlSession.close();
}
9.3 Mybatis-Spring
引入Spring之前需要了解mybatis-spring包中的一些重要类;
什么是 MyBatis-Spring?
MyBatis-Spring 会帮助你将 MyBatis 代码无缝地整合到 Spring 中。
知识基础
在开始使用 MyBatis-Spring 之前,你需要先熟悉 Spring 和 MyBatis 这两个框架和有关它们的术语。这很重要
MyBatis-Spring 需要以下版本:
MyBatis-Spring | MyBatis | Spring 框架 | Spring Batch | Java |
---|---|---|---|---|
2.0 | 3.5+ | 5.0+ | 4.0+ | Java 8+ |
1.3 | 3.4+ | 3.2.2+ | 2.1+ | Java 6+ |
如果使用 Maven 作为构建工具,仅需要在 pom.xml 中加入以下代码即可:
<dependency><groupId>org.mybatis</groupId><artifactId>mybatis-spring</artifactId><version>2.0.2</version>
</dependency>
要和 Spring 一起使用 MyBatis,需要在 Spring 应用上下文中定义至少两样东西:一个 SqlSessionFactory 和至少一个数据映射器类。
在 MyBatis-Spring 中,可使用SqlSessionFactoryBean来创建 SqlSessionFactory。要配置这个工厂 bean,只需要把下面代码放在 Spring 的 XML 配置文件中:
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"><property name="dataSource" ref="dataSource"/><!--绑定Mybatis配置文件绑定好了之后,mybatis的核心配置文件和我们的bean sqlSessionFactory完全连接起来了,也就是说核心配置里面的那些取别名,绑定操作都可以在这里完成这样,bean可以在这里面完成,配置也可以在这里完成这不就完成了整合吗--><property name="configLocation" value="classpath:Mybatis-config.XML"/><!--整合了绑定Mapper,就是注册xml文件步骤--><property name="mapperLocations" value="classpath:com/kuang/Mapper/*.xml"/>
</bean>
注意:SqlSessionFactory需要一个 DataSource(数据源)。这可以是任意的 DataSource,只需要和配置其它 Spring 数据库连接一样配置它就可以了。
在基础的 MyBatis 用法中,是通过 SqlSessionFactoryBuilder 来创建 SqlSessionFactory 的。而在 MyBatis-Spring 中,则使用 SqlSessionFactoryBean 来创建。
在 MyBatis 中,你可以使用 SqlSessionFactory 来创建 SqlSession。一旦你获得一个 session 之后,你可以使用它来执行映射了的语句,提交或回滚连接,最后,当不再需要它的时候,你可以关闭 session。
SqlSessionFactory有一个唯一的必要属性:用于 JDBC 的 DataSource。这可以是任意的 DataSource 对象,它的配置方法和其它 Spring 数据库连接是一样的。
一个常用的属性是 configLocation,它用来指定 MyBatis 的 XML 配置文件路径。它在需要修改 MyBatis 的基础配置非常有用。通常,基础配置指的是 < settings> 或 < typeAliases>元素。
需要注意的是,这个配置文件并不需要是一个完整的 MyBatis 配置。确切地说,任何环境配置(),数据源()和 MyBatis 的事务管理器()都会被忽略。SqlSessionFactoryBean 会创建它自有的 MyBatis 环境配置(Environment),并按要求设置自定义环境的值。
SqlSessionTemplate 是 MyBatis-Spring 的核心。作为 SqlSession 的一个实现,这意味着可以使用它无缝代替你代码中已经在使用的 SqlSession。
模板可以参与到 Spring 的事务管理中,并且由于其是线程安全的,可以供多个映射器类使用,你应该总是用 SqlSessionTemplate 来替换 MyBatis 默认的 DefaultSqlSession 实现。在同一应用程序中的不同类之间混杂使用可能会引起数据一致性的问题。
可以使用 SqlSessionFactory 作为构造方法的参数来创建 SqlSessionTemplate 对象。
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate"><constructor-arg index="0" ref="sqlSessionFactory" />
</bean>
现在,这个 bean 就可以直接注入到你的 DAO bean 中了。你需要在你的 bean 中添加一个 SqlSession 属性,就像下面这样:
public class UserDaoImpl implements UserDao {private SqlSession sqlSession;public void setSqlSession(SqlSession sqlSession) {this.sqlSession = sqlSession;
}public User getUser(String userId) {return sqlSession.getMapper...;
}
}
按下面这样,注入 SqlSessionTemplate:
<bean id="userDao" class="org.mybatis.spring.sample.dao.UserDaoImpl"><property name="sqlSession" ref="sqlSession" />
</bean>
9.4 第一种整合方式
1、引入Spring配置文件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/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd">
2、配置数据源替换mybaits的数据源
<!--DataSource:使用spring1的数据源替代Mybatis的配置数据源有很多,如:C3P0,druid...我们这里用spring提供的JDBC:org.springframework.jdbc.datasource.DriverManagerDataSource-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"><!--这里和mybatis的核心配置文件下的环境是一样的因为想要整合mybatis,这些连接数据库的操作是必不可少的--><property name="driverClassName" value="com.mysql.jdbc.Driver"/><property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=false&useUnicode=true&characterEncoding=utf8"/><property name="username" value="root"/><property name="password" value="12345678"/>
</bean>
3、配置SqlSessionFactory,关联MyBatis
<!--sqlSessionFactory-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"><property name="dataSource" ref="dataSource"/><!--绑定Mybatis配置文件绑定好了之后,mybatis的核心配置文件和我们的bean sqlSessionFactory完全连接起来了,也就是说核心配置里面的那些取别名,绑定操作都可以在这里完成这样,bean可以在这里面完成,配置也可以在这里完成这不就完成了整合吗--><property name="configLocation" value="classpath:Mybatis-config.XML"/><!--整合了绑定Mapper,就是注册xml文件步骤--><property name="mapperLocations" value="classpath:com/kuang/Mapper/*.xml"/>
</bean>
4、[核心]注册sqlSessionTemplate,关联sqlSessionFactory;
<!--sqlSessionTemplate:就是去整合我们的sqlSession就是之前MybatisUtils都可以不用了,由spring来干-->
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate"><!--通过有参构造注入sqlSessionFactory,因为它没有set方法就是相当于执行了这个语句SqlSessionFactory sqlSessionFactory = builder.build(InputResource);--><constructor-arg index="0" ref="sqlSessionFactory"/>
</bean>
5、增加Dao接口的实现类;私有化sqlSessionTemplate
package com.kuang.Mapper;import com.kuang.pojo.User;
import org.mybatis.spring.SqlSessionTemplate;import java.util.List;/*** 为什么需要一个实现类* 答:* 因为我们现在是在spring中整合了mybatis* 所以我们需要在spring中拿sqlSession,然后操作接口,来操作数据库** 详细解释:* 就是说,你调用方法,是不是用SqlSession的getMapper获得一个mapper,然后调用方法嘛* 然后,如果是 service 层要调用这个方法的话,那是不是要得到 SqlSession ?* 但是按理 service 层是不能直接调用它的,service 层只能直接调用 dao 层的方法* 而 service 层也不可能直接调用接口吧,因为那也是没有通过 sqlSession 的一个普通接口而已* 所以 service 层要调用方法的话,需要在 dao 层先创一个实现类,再被调用** 就是这个层将来会被service层调用* @author <MrDai>* @version <1.0>* @since <JDK9.0>*/
public class UserMapperImpl implements UserMapper{/*** 原来:我们的所有操作,都使用sqlSession来执行* 现在:我们都使用SqlSessionTemplate来执行* @return*/private SqlSessionTemplate sqlSession;/*** 注入sqlSession来连接数据库,* bean中要为sqlSession设置值就必须要有对应的set方法* @param sqlSession*/public void setSqlSession(SqlSessionTemplate sqlSession) {this.sqlSession = sqlSession;}@Overridepublic List<User> selectUser() {//开始正常流程/*这里的sqlSession已经在spring-dao进行配置了所以直接拿来用就好了*/UserMapper mapper = sqlSession.getMapper(UserMapper.class);List<User> users = mapper.selectUser();return users;}
}
6、注册bean实现
<!--建造出sqlSession并且进行配置想一想:为什么这里的class是UserMapperImpl?回答:因为我们的实现类里面有一个私有成员变量sqlSession,它还没有被赋值,所以想真正的去实现它就要去设置值,那么这个值从哪里来呢?当然就是从上一个bean sqlSession中来,再赋值到我们实现类中的sqlSession中去,实现一些方法,如查询用户,查询好了将结果返回给测试类(将来开发中的Service层)--><bean id="userMapper" class="com.kuang.Mapper.UserMapperImpl"><!--将上一个bean中创建好的sqlSession传入userMapper(就是实现类)中的sqlSession成员变量--><property name="sqlSession" ref="sqlSession"/></bean>
7、测试(实际开发中的service层)
@Testpublic void SpringSelectUser(){ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring-dao.xml");UserMapper userMapper = context.getBean("userMapper", UserMapper.class);/*这里调用的selectUser就是我们在实现类的方法*/List<User> users = userMapper.selectUser();for (User user : users) {/*** User{name='狂神', id=1, pwd='123456'}* User{name='张三', id=2, pwd='abcdef'}* User{name='李四', id=3, pwd='987654'}*/System.out.println(user);}}
结果成功输出!现在我们的Mybatis配置文件的状态!发现都可以被Spring整合!
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configurationPUBLIC "-//mybatis.org//DTD Config 3.0//EN""http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration><!--给一个实体类写别名,方便开发--><typeAliases><typeAlias type="com.kuang.pojo.User" alias="user"/></typeAliases><!--整合了mybatis之后,我们一般就在配置文件中写两样东西(虽然spring都可以整合掉)一个是别名,另一个就是配置(现在没有什么好配的,所以注释了)--><!--<settings><setting name="" value=""/></settings>-->
</configuration>
9.5 第二种整合方式
这一种就相对简单了,只要在Impl中继承一个类,执行些方法,这个类就会帮您创建SqlSessionTemplate
mybatis-spring1.2.3版以上的才有这个 .
官方文档截图 :
dao继承Support类 , 直接利用 getSqlSession() 获得 , 然后直接注入SqlSessionFactory . 比起方式1 , 不需要管理SqlSessionTemplate , 而且对事务的支持更加友好 . 可跟踪源码查看
测试:
1、将我们上面写的UserDaoImpl修改一下
package com.kuang.Mapper;import com.kuang.pojo.User;
import org.mybatis.spring.support.SqlSessionDaoSupport;import java.util.List;/*** @author <MrDai>* @version <1.0>* @since <JDK9.0>*/
public class UserMapperImplTwo extends SqlSessionDaoSupport implements UserMapper{@Overridepublic List<User> selectUser() {//自动获得sqlSessionFactory,从中拿取一个sqlSession/*SqlSession sqlSession = getSqlSession();UserMapper mapper = sqlSession.getMapper(UserMapper.class);return mapper.selectUser();*///我们一行解决return getSqlSession().getMapper(UserMapper.class).selectUser();}
}
2、修改bean的配置
<!--第二种方式就只要在spring-dao中配置数据源和sqlSessionFactory即可不用配置sqlSessionTemplate了因为UserMapperImplTwo的父类SqlSessionDaoSupport帮我们干了-->
<bean id="userMapperTwo" class="com.kuang.Mapper.UserMapperImplTwo"><!--注入sqlSessionFactory这个对象涉及到了Spring的嵌套注入SqlSessionFactory需要注入的对象SqlSessionTemplate这个类的构造需要注入一个sqlSessionFactory对象--><property name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>
3、测试
@Testpublic void SpringSelectUserTwo(){ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");UserMapper userMapperTwo = context.getBean("userMapperTwo", UserMapper.class);List<User> users = userMapperTwo.selectUser();for (User user : users) {/** 输出:* User{name='狂神', id=1, pwd='123456'}* User{name='张三', id=2, pwd='abcdef'}* User{name='李四', id=3, pwd='987654'}*/System.out.println(user);}}
10.声明式事务
10.1 回顾事务
- 把一组业务当成一个业务来做,要么都成功,要么都失败
- 事务在项目开发过程非常重要,涉及到数据的一致性的问题,不容马虎!
- 事务管理是企业级应用程序开发中必备技术,用来确保数据的完整性和一致性。
事务就是把一系列的动作当成一个独立的工作单元,这些动作要么全部完成,要么全部不起作用。
事务四个属性ACID
原子性(atomicity)
- 事务是原子性操作,由一系列动作组成,事务的原子性确保动作要么全部完成,要么完全不起作用
一致性(consistency)
- 一旦所有事务动作完成,事务就要被提交。数据和资源处于一种满足业务规则的一致性状态中
隔离性(isolation)
- 可能多个事务会同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏
持久性(durability)
- 事务一旦完成,无论系统发生什么错误,结果都不会受到影响。通常情况下,事务的结果被写到持久化存储器中
测试:
将上面的代码拷贝到一个新项目中
在之前的案例中,我们给userMapper接口新增两个方法,删除和增加用户;
//添加一个用户
int addUser(User user);//根据id删除用户
int deleteUser(int id);
mapper文件,我们故意把 deletes 写错,测试!
<insert id="addUser" parameterType="com.kuang.pojo.User">
insert into user (id,name,pwd) values (#{id},#{name},#{pwd})
</insert><delete id="deleteUser" parameterType="int">
deletes from user where id = #{id}
</delete>
编写接口的实现类,在实现类中,我们去操作一波
public class UserDaoImpl extends SqlSessionDaoSupport implements UserMapper {//增加一些操作public List<User> selectUser() {User user = new User(4,"小明","123456");UserMapper mapper = getSqlSession().getMapper(UserMapper.class);mapper.addUser(user);mapper.deleteUser(4);return mapper.selectUser();}//新增public int addUser(User user) {UserMapper mapper = getSqlSession().getMapper(UserMapper.class);return mapper.addUser(user);}//删除public int deleteUser(int id) {UserMapper mapper = getSqlSession().getMapper(UserMapper.class);return mapper.deleteUser(id);}}
测试
@Test
public void test2(){ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");UserMapper mapper = (UserMapper) context.getBean("userDao");List<User> user = mapper.selectUser();System.out.println(user);
}
报错:sql异常,delete写错了
结果 :用户插入成功!
没有进行事务的管理;我们想让他们都成功才成功,有一个失败,就都失败,我们就应该需要事务!
以前我们都需要自己手动管理事务,十分麻烦!
但是Spring给我们提供了事务管理,我们只需要配置即可;
10.2 spring中的事务管理
- 声明式事务: AOP
- 编程式事务: 需要在代码中进行事务的管理 [了解即可]
Spring在不同的事务管理API之上定义了一个抽象层,使得开发人员不必了解底层的事务管理API就可以使用Spring的事务管理机制。Spring支持编程式事务管理和声明式的事务管理。
编程式事务管理
- 将事务管理代码嵌到业务方法中来控制事务的提交和回滚
- 缺点:必须在每个事务操作业务逻辑中包含额外的事务管理代码
声明式事务管理
- 一般情况下比编程式事务好用。
- 将事务管理代码从业务方法中分离出来,以声明的方式来实现事务管理。
- 将事务管理作为横切关注点,通过aop方法模块化。Spring中通过Spring AOP框架支持声明式事务管理。
使用Spring管理事务,注意头文件的约束导入 : tx
xmlns:tx="http://www.springframework.org/schema/tx"http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
事务管理器
- 无论使用Spring的哪种事务管理策略(编程式或者声明式)事务管理器都是必须的。
- 就是 Spring的核心事务管理抽象,管理封装了一组独立于技术的方法。
JDBC事务
<!--配置声明式事务-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"><!--给事务类的构造器中传入我们的数据源,告诉它我们需要管理哪个事务--><!--<constructor-arg ref="dataSource"/>--><!--当然了,我们这个DataSourceTransactionManager事务管理类也有专门的这个属性和这个属性的set方法--><property name="dataSource" ref="dataSource"/>
</bean>
配置好事务管理器后我们需要去配置事务的通知
<!--结合AOP来实现事务的织入--><!--配置事务的类(即通知)--><tx:advice id="txAdvice" transaction-manager="transactionManager"><!--给我们接口中的方法配置事务配置事务的传播(propagation):Spring中七种Propagation类的事务属性详解:REQUIRED:支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择(默认的)SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行(就不给他事务)。MANDATORY:支持当前事务,如果当前没有事务,就抛出异常。REQUIRES_NEW:新建事务,如果当前存在事务,把当前事务挂起。NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。NESTED:支持当前事务,如果当前事务存在,则执行一个嵌套事务,如果当前没有事务,就新建一个事务。--><tx:attributes><!--注意:这些add,delete都是要和我们mapper接口中的方法名相同才行除非你的name设置为通配符*,那么你就不用管了--><tx:method name="add" propagation="REQUIRED"/><tx:method name="delete" propagation="REQUIRED"/><tx:method name="update" propagation="REQUIRED"/><!--只要有方法凡是query带头的,它就只能读取,不能进行其他操作当然这只是为了权限更加紧密而已,你如果查询--><tx:method name="query" read-only="true"/><tx:method name="*" propagation="REQUIRED"/></tx:attributes></tx:advice>
spring事务传播特性:
事务传播行为就是多个事务方法相互调用时,事务如何在这些方法间传播。spring支持7种事务传播行为:
- propagation_requierd:如果当前没有事务,就新建一个事务,如果已存在一个事务中,加入到这个事务中,这是最常见的选择。
- propagation_supports:支持当前事务,如果没有当前事务,就以非事务方法执行。
- propagation_mandatory:使用当前事务,如果没有当前事务,就抛出异常。
- propagation_required_new:新建事务,如果当前存在事务,把当前事务挂起。
- propagation_not_supported:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
- propagation_never:以非事务方式执行操作,如果当前事务存在则抛出异常。
- propagation_nested:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与propagation_required类似的操作
Spring 默认的事务传播行为是 PROPAGATION_REQUIRED,它适合于绝大多数的情况。
假设 ServiveX#methodX() 都工作在事务环境下(即都被 Spring 事务增强了),假设程序中存在如下的调用链:Service1#method1()->Service2#method2()->Service3#method3(),那么这 3 个服务类的 3 个方法通过 Spring 的事务传播机制都工作在同一个事务中。
就好比,我们刚才的几个方法存在调用,所以会被放在一组事务当中!
配置AOP
前提:导入aop的头文件!
<!--aop配置事务切入-->
<aop:config><!--说明:支持Mapper包下的所有类的所有方法(的任意参数)都可以被切入--><aop:pointcut id="txPointCut" expression="execution(* com.kuang.Mapper.*.*(..))"/><!--将我们的txAdvice事务织入到txPointCut中去--><aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>
</aop:config>
进行测试
UserMapperImpl类
package com.kuang.Mapper;import com.kuang.pojo.User;
import org.apache.ibatis.session.SqlSession;
import org.mybatis.spring.support.SqlSessionDaoSupport;import java.util.List;/*** 为什么需要一个实现类* 答:* 因为我们现在是在spring中整合了mybatis* 所以我们需要在spring中拿sqlSession,然后操作接口,来操作数据库** 详细解释:* 就是说,你调用方法,是不是用SqlSession的getMapper获得一个mapper* 然后调用方法吗?* 然后,如果是 service 层要调用这个方法的话,那是不是要得到 SqlSession ?* 但是按理 service 层是不能直接调用它的,service 层只能直接调用 dao 层的方法* 而 service 层也不可能直接调用接口吧,因为那也是没有通过 sqlSession 的一个普通接口而已* 所以 service 层要调用方法的话,需要在 dao 层先创一个实现类,再被调用** 就是这个层将来会被service层调用** 这个实现类只是交给spring进行托管的,顺便执行getMapper方法* Mybatis还是注册的是接口* @author <MrDai>* @version <1.0>* @since <JDK9.0>*/
public class UserMapperImpl extends SqlSessionDaoSupport implements UserMapper{@Overridepublic List<User> selectUser() {SqlSession sqlSession = getSqlSession();UserMapper mapper = sqlSession.getMapper(UserMapper.class);List<User> users = mapper.selectUser();User user = new User(5,"王五","194123");//以下两个方法只是为了测试,实际开发不会有这么离谱的关联,一般插入和查询关联mapper.insertUser(user);mapper.deleteUser(4);return users;}@Overridepublic void insertUser(User user) {SqlSession sqlSession = getSqlSession();UserMapper mapper = sqlSession.getMapper(UserMapper.class);mapper.insertUser(user);}@Overridepublic void deleteUser(Integer id) {SqlSession sqlSession = getSqlSession();UserMapper mapper = sqlSession.getMapper(UserMapper.class);mapper.deleteUser(id);}}
删掉刚才插入的数据,再次测试!
@Testpublic void selectUser(){ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");UserMapper userMapper = context.getBean("userMapper", UserMapper.class);List<User> users = userMapper.selectUser();for (User user : users) {/**如果在 UserMapper.xml中有数据库代码写错,那么就不会查询成功,插入成功和删除成功当然,也不知UserMapper写错才会有这效果,其他地方写错都这样。达到了原子性.只要全部代码写对,那么就一起运行。*/System.out.println(user);}}
思考问题
为什么需要配置事务?
- 如果不配置,就需要我们手动提交控制事务;
- 事务在项目开发过程非常重要,涉及到数据的一致性的问题,不容马虎
<tx:method name="update" propagation="REQUIRED"/><!--只要有方法凡是query带头的,它就只能读取,不能进行其他操作当然这只是为了权限更加紧密而已,你如果查询--><tx:method name="query" read-only="true"/><tx:method name="*" propagation="REQUIRED"/></tx:attributes>
</tx:advice>
**spring事务传播特性:**事务传播行为就是多个事务方法相互调用时,事务如何在这些方法间传播。spring支持7种事务传播行为:> - propagation_requierd:如果当前没有事务,就新建一个事务,如果已存在一个事务中,加入到这个事务中,这是最常见的选择。
> - propagation_supports:支持当前事务,如果没有当前事务,就以非事务方法执行。
> - propagation_mandatory:使用当前事务,如果没有当前事务,就抛出异常。
> - propagation_required_new:新建事务,如果当前存在事务,把当前事务挂起。
> - propagation_not_supported:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
> - propagation_never:以非事务方式执行操作,如果当前事务存在则抛出异常。
> - propagation_nested:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与propagation_required类似的操作Spring 默认的事务传播行为是 PROPAGATION_REQUIRED,它适合于绝大多数的情况。假设 ServiveX#methodX() 都工作在事务环境下(即都被 Spring 事务增强了),假设程序中存在如下的调用链:Service1#method1()->Service2#method2()->Service3#method3(),那么这 3 个服务类的 3 个方法通过 Spring 的事务传播机制都工作在同一个事务中。就好比,我们刚才的几个方法存在调用,所以会被放在一组事务当中!**配置AOP** 前提:导入aop的头文件!```xml
<!--aop配置事务切入-->
<aop:config><!--说明:支持Mapper包下的所有类的所有方法(的任意参数)都可以被切入--><aop:pointcut id="txPointCut" expression="execution(* com.kuang.Mapper.*.*(..))"/><!--将我们的txAdvice事务织入到txPointCut中去--><aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>
</aop:config>
进行测试
UserMapperImpl类
package com.kuang.Mapper;import com.kuang.pojo.User;
import org.apache.ibatis.session.SqlSession;
import org.mybatis.spring.support.SqlSessionDaoSupport;import java.util.List;/*** 为什么需要一个实现类* 答:* 因为我们现在是在spring中整合了mybatis* 所以我们需要在spring中拿sqlSession,然后操作接口,来操作数据库** 详细解释:* 就是说,你调用方法,是不是用SqlSession的getMapper获得一个mapper* 然后调用方法吗?* 然后,如果是 service 层要调用这个方法的话,那是不是要得到 SqlSession ?* 但是按理 service 层是不能直接调用它的,service 层只能直接调用 dao 层的方法* 而 service 层也不可能直接调用接口吧,因为那也是没有通过 sqlSession 的一个普通接口而已* 所以 service 层要调用方法的话,需要在 dao 层先创一个实现类,再被调用** 就是这个层将来会被service层调用** 这个实现类只是交给spring进行托管的,顺便执行getMapper方法* Mybatis还是注册的是接口* @author <MrDai>* @version <1.0>* @since <JDK9.0>*/
public class UserMapperImpl extends SqlSessionDaoSupport implements UserMapper{@Overridepublic List<User> selectUser() {SqlSession sqlSession = getSqlSession();UserMapper mapper = sqlSession.getMapper(UserMapper.class);List<User> users = mapper.selectUser();User user = new User(5,"王五","194123");//以下两个方法只是为了测试,实际开发不会有这么离谱的关联,一般插入和查询关联mapper.insertUser(user);mapper.deleteUser(4);return users;}@Overridepublic void insertUser(User user) {SqlSession sqlSession = getSqlSession();UserMapper mapper = sqlSession.getMapper(UserMapper.class);mapper.insertUser(user);}@Overridepublic void deleteUser(Integer id) {SqlSession sqlSession = getSqlSession();UserMapper mapper = sqlSession.getMapper(UserMapper.class);mapper.deleteUser(id);}}
删掉刚才插入的数据,再次测试!
@Testpublic void selectUser(){ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");UserMapper userMapper = context.getBean("userMapper", UserMapper.class);List<User> users = userMapper.selectUser();for (User user : users) {/**如果在 UserMapper.xml中有数据库代码写错,那么就不会查询成功,插入成功和删除成功当然,也不知UserMapper写错才会有这效果,其他地方写错都这样。达到了原子性.只要全部代码写对,那么就一起运行。*/System.out.println(user);}}
思考问题
为什么需要配置事务?
- 如果不配置,就需要我们手动提交控制事务;
- 事务在项目开发过程非常重要,涉及到数据的一致性的问题,不容马虎
Spring5框架-学习总结(结合个人理解)相关推荐
- Spring5框架学习
Spring5框架学习 备注:视频来源于尚硅谷 - Spring 5 框架最新版教程(idea版):https://www.bilibili.com/video/BV1Vf4y127N5 Spring ...
- Spring5框架(上) IOC
Spring5框架 IOC 前言 Spring框架概述 IOC容器 底层原理 Bean管理XML方式(创建对象和set注入属性) 注入集合类型属性1 IOC操作Bean管理 Bean管理(工厂Bean ...
- jQuery框架学习第二天:jQuery中万能的选择器
jQuery框架学习第一天:开始认识jQuery jQuery框架学习第二天:jQuery中万能的选择器 jQuery框架学习第三天:如何管理jQuery包装集 jQuery框架学习第四天:使用jQu ...
- Android接口和框架学习
Android接口和框架学习 缩写: HAL:HardwareAbstraction Layer,硬件抽象层 CTS:CompatibilityTest Suite,兼容性测试套件 Android让你 ...
- TLD(Tracking-Learning-Detection)学习与源码理解之(三)
TLD(Tracking-Learning-Detection)学习与源码理解之(三) zouxy09@qq.com http://blog.csdn.net/zouxy09 下面是自己在看论文和这些 ...
- [Embeding-2]文本表示学习-词嵌入入门理解
转载自Scofield Phil: http://www.scofield7419.xyz/2017/09/25/文本表示学习-词嵌入入门理解/ 之前一段时间,在结合深度学习做NLP的时候一直有思考一 ...
- mybatis框架--学习笔记(上)
使用JDBC操作数据库的问题总结: (1)数据库连接,使用时创建,不使用时立即释放,对数据库进行频繁连接开启和关闭,造成数据库资源浪费,影响数据库性能. 设想:使用数据库连接池管理数据库连接. (2) ...
- Java框架学习顺序是哪些?
Java编程是互联网行业不可或缺的一门编程语言,也是世界第一编程语言,Spring.Struts.Hibernate是经典中的经典,最常用的框架类型.下面小编就为大家详细的介绍一下Java框架学习顺序 ...
- spring boot框架学习学前掌握之重要注解(2)-通过java的配置方式进行配置spring
本节主要内容: 1:通过代码演示实现零XML配置spring 2:使用重点注解理解 声明: 本文是<凯哥陪你学系列-框架学习之spring boot框架学习>中spring boot框架学 ...
最新文章
- eclipse 导入hadoop2.2源代码
- spss相关性分析看结果_利用spss做Pearson相关性分析步骤详解
- 为什么只需要一个eden而需要两个survivor?
- (chap7 确保WEB安全的HTTPS) HTTPS通信步骤
- 见微知著(一):解析ctf中的pwn--Fast bin里的UAF
- 阿里巴巴宣布架构调整;英伟达放大招!重磅发布 ​TensorRT 7 ,支持超千种计算变换;苹果、谷歌和亚马逊罕见结盟……...
- Leetcode 1.两数之和
- centos mysql-5.5.20_mysql-5.5.20+CentOS 6.2 编译安装全过程详解(2)
- 【linux】安装python依赖库confluent_kafka
- 解析oracle sqllder日志,sqlloader 参数
- 10种受欢迎的前后端相关开发工具
- fast无线路由器设置服务器,迅捷(FAST)FW300R无线路由器怎么设置
- spring security 结合cas单点登录退出后的返回地址
- Android之水滴落下的下拉刷新
- 第二单元 用python学习微积分(十五)微分方程和分离变量法
- 当酒品牌遇上爱“微醺”的年轻人,会擦出怎样的火花?
- 原形网络(Prototypical Networks)基于PyTorch的实现
- VsCode与Sublime编辑器优缺点对比
- 【计算机基础】 --- LSB、MSB与大/小端字节序
- 使用GRUB2制作多重系统引导程序
热门文章
- XXL-Job 分布式任务调度(一)本地执行
- flutter自定义分页器
- 钉钉发群通知报{“errcode“:310000,“errmsg“:“keywords not in content“}解决办法
- 了解行业信息的数据渠道
- 在线词云制作生成 tagxedo
- lettuce执行原理
- 总有起风的清晨,总有绚烂的黄昏,总有流星的夜晚
- 【想进阿里的小菜鸟】有趣的哥德巴赫猜想
- 腾讯云发布全新非关系型数据库KeeWiDB 搭载基于英特尔傲腾技术的全自研存储引擎
- virsh 关机_kvm 虚拟化 virsh shutdown 无法关闭客户机