目录
Spring5 核心容器支持函数式风格 GenericApplicationContext
一、Spring 概述
1、Spring 是一个 轻量级的开源的JavaEE框架
2、Spring 可以解决企业应用开发的复杂性
3、Spring 有俩个核心部分:IOC 容器 和 AOP
IOC :控制反转,把创建对象的过程交给 Spring 进行管理
AOP:面向切面,不修改源代码进行功能增强
4、Spring 的特点:
- 方便解耦,简化开发
- 支持AOP编程
- 方便和其他框架进行整合
- 方便进行事务操作
- 降低 API 的开发程度
二、Spring 插件下载
- 进入官网:spring.io
三、入门案例
导入以下相关的jar 包:
使用 Spring 创建对象:
1、创建普通类
public class Test_01 {
public void doSome(){
//普通创建方式
// Test_01 test_01 = new Test_01();
System.out.println("doSome!");
}
}
2、创建 Spring Config
class :普通类的路径
3、创建对象
@Test
public void test_01(){
//1、加载配置文件
ApplicationContext context = new ClassPathXmlApplicationContext("test_01.xml");
//2、创建对象
Test_01 test_01 = context.getBean("test_01", Test_01.class);
test_01.doSome();
}
四、IOC 容器
1、IOC 底层原理
(1)什么是IOC ?
- 控制反转:把对象创建和对象之间的调用过程,交给 Spring 管理
- 使用IOC目的:为了耦合度降低
(2)IOC 底层原理 由:XML 解析,反射,工厂设计模式 组成
(3)图解 IOC 底层原理:
我们用普通方式创建对象,就是 new 对象。这样耦合度太高了,不仅要修改 Dao ,还要修改 Service 。而且,Spring 不会管理。所以 IOC 可以大程度的降低耦合度。就是通过 XML解析,反射,工厂模式三个技术来实现的。
2、IOC 接口
(1)IOC 思想就是基于IOC容器完成的,IOC容器底层就是个工厂对象
(2)Spring 提供 IOC 容器实现的俩种方式:【俩个接口】
- BeanFactory :是Spring内部的使用接口,不提供开发人员使用,加载配置文件的时候不会创建对象。在获取对象的时候才会创建。
context.getBean("test_01", Test_01.class);
- ApplicationContext:BeanFactory的子接口,提供更多更强大的功能,一般由开发人员使用。加载配置文件的时候就会创建对象。
(3)ApplicationContext 下的接口实现类:
FileSystemXMLApplicationContext :配置文件在电脑上的路径。
什么是Bean 管理 ?
- Bean管理指的是俩个操作:
- Spring 创建对象
- Spring 注入属性
- 基于 Bean 管理有俩个实现方式:
- 基于xml 配置文件实现
- 基于 注解方法实现
3、IOC 操作 Bean 管理(基于xml)
(1)基于 xml 方式创建对象
在创建对象时,实体类中一定要有 无参构造方法,除非是通过有参构造方法注入属性。
<!--id:自定义的别名--> <bean id="test_01" class="yangzhaoguang.spring5.Test_01"></bean>
在 spring 配置文件中,使用 bean 标签,标签里面添加对应属性,就可以实现对象创建。在 bean 标签有很多属性,介绍常用的属性:* id 属性:唯一标识* class 属性:类全路径(包类路径)对象中仅有 有参构造 方法,是会出错的。
(2)基于 xml 方式 注入属性
DI:依赖注入属性第一种注入方式:set 注入*******属性需要有自己的 set 方法。
public class User { private String name ; private Integer age ; public User() { } public void setName(String name) { this.name = name; } public void setAge(Integer age) { this.age = age; } }
注入属性时必须在 bean 标签内部完成。<!--1、创建对象--> <bean id="user" class="yangzhaoguang.spring5.User"> <!--2、set 方法注入属性--> <!--name:属性名称 value:注入的属性值--> <property name="name" value="张三"></property> <property name="age" value="20"></property> </bean>
第二种注入方式:通过有参构造注入
<!--第二种方式:通过有参构造方法注入--> <bean id="student" class="yangzhaoguang.spring5.Student"> <constructor-arg name="name" value="李四"></constructor-arg> <constructor-arg name="id" value="2"></constructor-arg> </bean>
第三种方式:p 名称空间注入【不常用】
<!--增加p名称在属性配置中。在beans标签里面配置--> xmlns:p="http://www.springframework.org/schema/p" <bean id="user" class="yangzhaoguang.spring5.User" p:name="王五" p:age="18"> </bean>
(3)基于xml 方式 注入其他属性的值
设置 null 值
name:设置null值的属性名
<property name="age"> <null></null> </property>
设置特殊符号
使用 : <![CDATA[ ]]>
<!--设置特殊符号--> <property name="name"> <value><![CDATA[大佐ぁ次]]></value> </property>
(4) 基于xml 方式 外部 Bean 注入
【属性值是一个Bean对象,并且这个Bean对象在外部】
原始方式:
在 Service 层调用 Dao 层中的方法:原始方式 new 对象在调用。
使用 xml 中的 set 注入:
在 Service 层 创建 UserDao 属性,并生成set方法。
//使用 xml 文件中的 set 注入,为 userDao 属性赋值 private UserDao userDao ; public void setUserDao(UserDao userDao) { this.userDao = userDao; }
xml 配置文件:
<!--创建 UserServiceImpl 对象--> <bean id="userService" class="yangzhaoguang.service.UserServiceImpl"> <!--为userDao属性赋值 由于 userDao 属性是一个对象,所以需要使用 ref ref:对象的id值 --> <property name="userDao" ref="userDao"></property> </bean> <!--创建 UserDaoImpl 对象--> <bean id="userDao" class="yangzhaoguang.dao.UserDaoImpl"></bean> </beans>
测试:
/*测试外部 Bean*/ @Test public void add() { //加载配置文件 ApplicationContext context = new ClassPathXmlApplicationContext("test_02.xml"); //创建对象 UserServiceImpl userService = context.getBean("userService", UserServiceImpl.class); userService.add(); }
(5)基于xml 方式 内部Bean和级联使用
【Bean对象在内部】
实体类:
【内部Bean注入】
赋值的对象在被赋值对象的内部
<bean id="emp" class="yangzhaoguang.pojo.Emp"> <!--设置普通属性--> <property name="id" value="1"></property> <property name="ename" value="jackson"></property> <!--设置对象属性--> <property name="dept"> <!--内部Bean注入--> <bean id="dept" class="yangzhaoguang.pojo.Dept"> <property name="dname" value="销售部"></property> </bean> </property> </bean>
测试:
//内部注入 @Test public void test_03(){ //加载配置文件 ApplicationContext context = new ClassPathXmlApplicationContext("test_03.xml"); //创建对象 Emp emp = context.getBean("emp", Emp.class); emp.print(); //id : 1,姓名 :jackson, 部门 :销售部 }
【级联使用】
在 外部 注入的基础上多了一个 属性赋值。
第一种写法:
<bean id="emp" class="yangzhaoguang.pojo.Emp"> <!--设置普通属性--> <property name="id" value="1"></property> <property name="ename" value="大壮"></property> <!--设置对象属性--> <property name="dept" ref="dept"> </property> </bean> <!--级联赋值 将 dept 对象中的属性也会赋值过去 --> <bean id="dept" class="yangzhaoguang.pojo.Dept"> <property name="dname" value="保安部"></property> </bean>
第二种写法:
需要为 Dept 对象 提供一个 get 方法:
<bean id="emp" class="yangzhaoguang.pojo.Emp"> <!--设置普通属性--> <property name="id" value="1"></property> <property name="ename" value="大壮"></property> <!--设置对象属性--> <property name="dept" ref="dept"> </property> <property name="dept.dname" value="传销部"> </property> </bean> <!--级联赋值 将 dept 对象中的属性也会赋值过去 --> <bean id="dept" class="yangzhaoguang.pojo.Dept"></bean>
测试:
//级联赋值 @Test public void test_04(){ //加载配置文件 ApplicationContext context = new ClassPathXmlApplicationContext("test_04.xml"); //创建对象 Emp emp = context.getBean("emp", Emp.class); emp.print(); }
(6)基于xml 方式 注入集合属性
** 注入数组类型属性
** 注入List类型属性
** 注入Map类型属性
实体类:
public class Stu { //数组属性 private String[] arr ; //List集合属性 private List<String> list ; // Map集合 private Map<String,String> map ; public Stu(String[] arr, List<String> list, Map<String, String> map) { this.arr = arr; this.list = list; this.map = map; } public Stu() { } public void setArr(String[] arr) { this.arr = arr; } public void setList(List<String> list) { this.list = list; } public void setMap(Map<String, String> map) { this.map = map; } /*遍历*/ public void print(){ System.out.println(Arrays.toString(arr)); System.out.println(list); System.out.println(map); } }
xml 配置文件:
<!--注入集合类型--> <bean id="stu" class="pojo.Stu"> <!--注入数组类型--> <property name="arr"> <array> <value>数据结构</value> <value>算法</value> </array> </property> <!--注入List类型--> <property name="list"> <list> <value>Java</value> <value>Python</value> </list> </property> <!--注入Map类型--> <property name="map"> <map> <entry key="1" value="java从入门到入土"></entry> <entry key="2" value="javaWeb"></entry> </map> </property> </bean>
当集合中存储不是基本数据类型,而是对象:
准备一个实体类:
public class Course { private String cname ; public void setCname(String cname) { this.cname = cname; } @Override public String toString() { return "Course{" + "cname='" + cname + '\'' + '}'; } }
在Stu 实体类中准备一个List,能存储 Course 类型数据:
//List集合 存的是 Course 类型 List<Course> courseList ; public void setCourseList(List<Course> courseList) { this.courseList = courseList; }
xml 配置文件:
<!--注入List类型,存储类型是:Course--> <property name="courseList"> <list> <!--存入多个对象--> <ref bean="course1"></ref> <ref bean="course2"></ref> </list> </property> </bean> <!--创建多个对象--> <bean id="course1" class="pojo.Course"> <!--为对象的属性注入值--> <property name="cname" value="Java"></property> </bean> <bean id="course2" class="pojo.Course"> <property name="cname" value="Python"></property> </bean>
目前的集合只能由一个对象使用,想要将集合变成公共的怎么办?
把集合的注入部分提取出来:
- 在配置文件中引入 util 名称空间
- 使用 util 标签提取使用
<!--提取list类型属性注入--> <util:list id="bookList"> <value>Java</value> <value>Python</value> <value>C++</value> </util:list> <!--创建对象,注入使用--> <bean id="book" class="pojo.Book"> <!--ref 与 上面的id是一样的。--> <property name="list" ref="bookList"></property> </bean>
public class Book { private List<String> list ; public void setList(List<String> list) { this.list = list; } @Override public String toString() { return "Book{" + "list=" + list + '}'; } }
(7)FactoryBean 管理
Spring 中 有俩种类型的Bean,一种是 普通的 bean,一种是 工厂bean【FactoryBean】
普通 bean:在 配置文件 中定义的bean类型就是返回的类型
比如:以下定义的就是 Book 类,返回的就是 Book 类型
工厂bean:在 配置文件 中定义的bean类型可以和返回的类型不一样。
实现工厂bean 的步骤:
1、创建类实现 FactoryBean 接口
2、实现方法,在方法中设置返回值类型。
这样他的返回值类型时 Course 类
(8)bean 的作用域
在 spring 里面,默认情况下 bean 是单实例对象,但是可以设置为多实例对象
《1》如何设置单实例还是多实例( 1 )在 spring 配置文件 bean 标签里面有属性( scope )用于设置单实例还是多实例(2)scope 属性值第一个值 singleton ,表示是单实例对象 默认就是 singleton第二个值 prototype ,表示是多实例对象![]()
《2》singleton 和 prototype 的区别:
singleton,表示是单实例对象,prototype,表示是多实例对象
设置 scope 属性值是 singleton 时,会在加载配置文件的时候就创建对象
设置 scope 属性值是 prototype 时,执行 getBeans 才会创建对象。
《3》scope 中海油俩个值,session,request。他会把创建的对象放到 对应的域中
(9)bean 的生命周期
( 1 )通过构造器创建 bean 实例(无参数构造)(2)为 bean 的属性设置值和对其他 bean 引用(调用 set 方法)(3)调用 bean 的初始化的方法(需要进行配置初始化的方法)(4) bean 可以使用了(对象获取到了)(5)当容器关闭时候,手动调用 bean 的销毁的方法(需要进行配置销毁的方法)演示:public class Book { private List<String> list ; public Book() { System.out.println("第一步: 无参构造方法执行,表示创建了对象"); } public void setList(List<String> list) { this.list = list; System.out.println("第二步:set 方法执行,表示为属性注入了值"); } public void init(){ System.out.println("第三步:初始化方法执行"); } public void destroy(){ System.out.println("第五步:销毁方法执行"); } @Override public String toString() { return "Book{" + "list=" + list + '}'; } }
xml:
初始化和销毁方法需要在 xml 中配置,并且销毁方法需要手动调用,不然不会执行。
<!--设置初始化和 销毁方法。 init-method 初始化 destroy-method 销毁 --> <bean id="book" class="pojo.Book" init-method="init" destroy-method="destroy"> <property name="list" ref="bookList"></property> </bean>
(10)xml 方式 自动装配
1、什么是自动装配( 1 )根据指定装配规则(属性名称或者属性类型), Spring 自动将匹配的属性值进行注入2、自动装配方法bean 标签属性 autowire,配置自动装配autowire 属性常用两个值:( 1 )根据属性名称自动注入byName 根据属性名称注入 ,注入值 bean 的 id 值和类属性名称一样(2)根据属性类型自动注入byType 根据属性类型注入(11)xml 方式 引入外部属性文件
有一些固定属性,可能会通过 Properties 文件进行引入。比如:连接数据库的url,username等等一些属性。
《1》先通过德鲁伊连接池 创建 Properties 配置文件
p.driverClassName=com.mysql.jdbc.Driver p.url=jdbc:mysql://localhost:3306/test p.username=root p.password=root
《2》将 Properties 配置文件引入 xml 配置文件中
<!--使用 xml 文件引入外部文件--> <context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder> <!--配置数据库连接池--> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="driverClassName" value="${p.driverClassName}"></property> <property name="url" value="${p.url}"></property> <property name="username" value="${p.username}"></property> <property name="password" value="${p.password}"></property> </bean>
在写Properties配置文件时,使用前缀将名字区别开。不然会报错。我也不知道为啥。。。。。
4、IOC 操作 Bean 管理(基于注解)
(1)使用注解的目的:简化 xml 配置
(2)Spring 针对 Bean 管理中创建对象提供注解
其实就是向IOC里面增加组件,组件可以理解为对象。被标注的类保存在IOC容器中。通过getBean方法取出来。
《1》@Component
《2》@Service
《3》@Controller
《4》@Repository
(3)基于注解方式 创建对象
第一步:引入jar包:
第二步:在xml配置文件中开启扫描
<!-- 开启组件扫描: 如果扫描多个包,用逗号隔开 或者直接写上层的包 --> <context:component-scan base-package="spring5.dao,lib,spring5.service,spring5.web"></context:component-scan> <!-- <context:component-scan base-package="spring5"></context:component-scan>-->
第三步:创建 类 使用注解创建对象
/**使用以下四个注解都可以 * @Repository * @Controller * @Service * value = "userServiceImpl" 相当于我们使用 xml 配置:<bean id="userServiceImpl"></> * 这个value值就是bean标签中的id * * value值也可以不写,他的默认值就是你的类名,首字母小写:userServiceImpl */ // @Component(value = "userServiceImpl") @Component public class UserServiceImpl { public void add(){ System.out.println("UserServiceImpl 中的 add 方法....."); } }
第四步:测试
@Test public void add() { //加载配置文件 ApplicationContext context = new ClassPathXmlApplicationContext("test_01.xml"); //创建对象 UserServiceImpl userServiceImpl = context.getBean("userServiceImpl", UserServiceImpl.class); System.out.println(userServiceImpl); userServiceImpl.add(); /* 输出结果 spring5.service.UserServiceImpl@78047b92 UserServiceImpl 中的 add 方法..... */ }
《1》关于组件扫描配置
<!--组件扫描配置--> <!-- 实例一: use-default-filters="false":表示不使用默认 filter ,自己配置filter --> <context:component-scan base-package="spring5" use-default-filters="false"> <!--include-filter:表示只扫描 Controller 注解 --> <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/> </context:component-scan> <!--实例二--> <context:component-scan base-package="spring5"> <!--exclude-filter:表示不扫描 Repository 注解--> <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Repository"/> </context:component-scan>
(4)用 注解 方式 注入属性
《1》@AutoWired 根据属性类型自动装配
《2》@Qualifier 根据属性名称进行注入
《3》@Resource 可以根据类型注入,也可以根据名称注入
《4》@Value 注入普通属性【以上三个都是注入对象属性的】
@AutoWired 演示:在 Service 层注入 Dao 对象:
我们之前使用 xml 方式:
1、先创建 UserDaoImpl 和 UserServiceImpl 的对象
2、在 Service 层提供一个 UserDao属性,并提供一个 set 方法。
3、在 xml 文件中进行配置
使用注解的方式:
1、先创建 UserDaoImpl 和 UserServiceImpl 的对象
2、在 Service 层提供一个 UserDao属性,但是并不需要提供set 方法
@Qualifier 演示:他需要和 @AutoWired 一起使用:如果不一起使用就会报空指针
什么时候用 @Qualifier ? 当一个接口有多个实现类的时候,根据类型注入已经找不到哪个实现类了。就使用 @Qualifier
@Resource 注入属性演示:这个 @Resource 并不是 Spring 中的,而是 JDK 中的
@Value 注解演示:
//普通属性注入 @Value("张三") private String name ;
(5)完全注解开发
使用配置类代替 xml 文件,完成注解开发
@Component : [kəmˈpəʊnənt]
//注解类,代替xml @Configuration //表示 当前这个类为注解类 @ComponentScan(basePackages = {"spring5"}) // 扫描的范围---spring5包下 public class AnnotationConfig { }
除了测试的方法和以前不一样,剩下都一样。
五、AOP
1、AOP 概念
(1)什么是 AOP?
(1)面向切面编程(方面),利用 AOP 可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。(2)通俗描述:不通过修改源代码方式,在主干功能里面添加新功能(2)AOP 底层原理
AOP 底层使用的是 动态代理【 代理模式 为其他对象提供一个 代理以控制对某个对象的访问】第一种情况:有接口的情况,使用 JDK 中的动态代理创建接口实现类的代理对象,增强类的方法
第二种情况:没有接口的情况,使用 CGLIB 动态代理创建子类的代理对象,增强类中的方法![]()
(3)JDK 动态代理实现
底层代码实现,Spring 里已经进行封装了!
JDK 中实现动态代理,在java.lang包下的 Proxy 类中:
使用这个方法为接口的实现类创建代理对象:
方法有三个参数:第一参数,类加载器第二参数,增强方法所在的类,这个类实现的接口,支持多个接口第三参数,实现这个接口 InvocationHandler,创建代理对象,写增强的部分public class UserDaoProxy { public static void main(String[] args) { Class[] userDaoClass = {UserDao.class}; //获取动态代理对象 //参数分别是:类加载器,增强方法所在的类的接口数组,InvocationHandler 增强实现类,写增强的代码 //匿名内部类的形式。 // Proxy.newProxyInstance(UserDaoProxy.class.getClassLoader(), userDao, new InvocationHandler() { // @Override // public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // return null; // } // }); UserDao userDao = new UserDaoImpl(); UserDao userDao1 = (UserDao) Proxy.newProxyInstance(UserDao.class.getClassLoader(), userDaoClass, new UserDaoImplProxy(userDao)); int res = userDao1.add(1, 2); System.out.println("增强完的结果:" + res); } } //除了使用匿名内部类的方式,还可以创建一个类实现 InvocationHandler 接口 class UserDaoImplProxy implements InvocationHandler { //一般使用有参构造方法来传递调用方法时所需要的对象 private Object obj; public UserDaoImplProxy(Object obj) { this.obj = obj; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //在这里实现增强的部分 System.out.println("执行方法之前执行的代码。。。。。"); //invoke参数:方法所在类的对象,参数 //返回的是方法结果 Object res = method.invoke(obj, args); //还可以通过方法名对具体的方法做修改 if ("add".equals(method.getName())) { //对 add 方法进行修改 } else if ("update".equals(method.getName())) { // 对update 方法进行修改 } System.out.println("执行方法之后执行的代码。。。。。。"); return res; } }
(4)AOP 的基本术语
《1》连接点
类的哪些方法可以被增强,这些方法被称为连接点
《2》切入点
实际被真正增强的方法,称为切入点
《3》通知(增强)
实际上增强的部分,叫做通知。
通知又分为:
- @Before 前置通知:在增强方法的前面执行
- @AfterReturning 后置通知,又叫返回通知:在增强方法的后面执行
- @Around 环绕通知:在增强方法的前面和后面都会执行
- @AfterThrowing 异常通知:出现异常执行
- @After 最终通知:
《4》切面
是一个动作,将通知应用到切入点的过程,就是切面
比如:我们在刚才的例子中 if。。else 的动作就是切面。
(5)AOP操作
Spring 框架一般都是基于 AspectJ 实现 AOP 操作《1》什么是 AspectJ ?AspectJ 不是 Spring 组成部分,独立 AOP 框架,一般把 AspectJ 和 Spirng 框架一起使 用,进行 AOP 操作《2》基于 AspectJ 进行 AOP 操作基于 xml 方式基于注解方式(经常使用)《3》引入相关的 jar 包
《4》切入点表达式切入点表达式的作用: 知道哪个类里面的哪个方法进行增强语法结构:execution ([ 权限修饰符 ] [ 返回类型 ] [ 类全路径 ] [ 方法名称 ] [ 参数列表 ] )![]()
(6)基于注解方式完成AOP操作
《1》创建普通类:被增强的类
//被增强的类 public class UserServiceImpl { public void add(){ System.out.println("UserServiceImpl中的add 方法、、、、、"); } }
《2》创建增强类
//增强类 public class UserServiceImplProxy { //将这个方法作为 前置通知 增加到UserServiceImpl中的add方法前执行。 public void before(){ System.out.println("前置通知....."); } }
《3》进行通知配置
- 开启注解扫描【注解类或者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/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"> <!--开启注解扫描--> <context:component-scan base-package="aopByAnnotation"></context:component-scan> </beans>
使用@Component(四个注解都可以)创建 增强类 与 被增强类的对象
- 在配置文件中开启 AspectJ 生成代理对象
<!--开启 aspectj 生成代码对象--> <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
用 @Aspect 在增强类中生成代理对象使
《4》配置不同类型的通知
在增强类里面,通知上面增加通知类型注解,使用切入点表达式配置
@Component //增强类 @Aspect public class UserServiceImplProxy { //前置通知类型注解。 // * 表示所有的修饰符类型 @Before(value = "execution(* aopByAnnotation.UserServiceImpl.add(..))") //将这个方法作为 前置通知 增加到UserServiceImpl中的.add方法前执行。 public void before(){ System.out.println("前置通知....."); } //后置通知(返回通知)没有异常的时候执行 @AfterReturning(value = "execution(* aopByAnnotation.UserServiceImpl.add(..))") public void afterReturning(){ System.out.println("后置通知....."); } //环绕通知 //会在增强方法前面和后面都要执行。 @Around(value = "execution(* aopByAnnotation.UserServiceImpl.add(..))") public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { System.out.println("环绕之前....."); //被增强的方法执行 proceedingJoinPoint.proceed(); System.out.println("环绕之后....."); } //异常通知。只有异常的时候会执行 @AfterThrowing(value = "execution(* aopByAnnotation.UserServiceImpl.add(..))") public void afterThrowing(){ System.out.println("异常通知....."); } //最终通知,不管有没有异常都会执行 @After(value = "execution(* aopByAnnotation.UserServiceImpl.add(..))") public void after(){ System.out.println("最终通知....."); } }
《5》抽取切入点表达式
上面五种类型的通知,它们的切入点表达式都是一样的,所以我们可以抽取出来。
//抽取切入点表达式 @Pointcut(value = "execution(* aopByAnnotation.UserServiceImpl.add(..))") public void pointDemo(){ }
在切入点表达式的位置上调用这个方法:
《6》有多个增强类对同一个方法增强,设置增强类优先级
再增强类上,使用 @Order(数值类型) 注解,数值越小优先级越高。
《7》完全注解开发
@Configuration //注解类 @ComponentScan(basePackages = {"aopByAnnotation","aopByXML"}) //设置扫描范围 //开启Aspect 生成代理对象 相当于 <aop:aspectj-autoproxy></aop:aspectj-autoproxy> @EnableAspectJAutoProxy(proxyTargetClass = true) public class ConfigAOP { }
(7)基于XML方式完成AOP操作
1 、创建两个类,增强类和被增强类,创建方法2 、在 spring 配置文件中创建两个类对象<!--创建增强类与被增强类的对象--> <bean id="book" class="aopByXML.Book"></bean> <bean id="bookProxy" class="aopByXML.BookProxy"></bean>
3 、在 spring 配置文件中配置切入点<!--配置增强方法--> <aop:config> <!--设置切入点--> <aop:pointcut id="point" expression="execution(* aopByXML.Book.add(..))"/> <!--设置切面--> <aop:aspect ref="bookProxy"> <!--增强作用在具体的方法上--> <aop:before method="before" pointcut-ref="point"></aop:before> </aop:aspect> </aop:config>
六、JDBCTemplate
Spring 对 JDBC 进行了封装,使用 JDBCTemplate 可以很方便的操作数据库。
1、使用 JDBCTemplate 前的准备工作
(1)引入相关 jar 包:
(2)在xml 文件中配置好数据库连接池并创建好数据库
(3)创建 JDBCTemplate对象,注入DataSource对象属性。
<!--创建 JDBCTemplate 对象-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!--注入DataSource属性-->
<property name="dataSource" ref="dataSource"></property>
</bean>
(4)创建 service 层和 dao层,service 注入 dao 对象,在dao层注入 JDBCTemplate对象。
2、使用 JDBCTemplate 对数据库进行操作
《1》实现数据库增删改查
BookDao :
public interface BookDao {
int add(Book book);
int update(Book book);
int del(Integer id);
//查询数据库记录
int findCount();
//根据id,查询返回对象
Book findForOne(Integer id);
//查询多条记录
List<Book> list();
}
BookDaoImpl:
@Repository //注解创建对象
public class BookDaoImpl implements BookDao {
//注入JDBCTemplate对象。不需要我们手动写连接数据库,使用Spring里面的 JDBCTemplate对象
@Autowired //根据数据类型注入属性
private JdbcTemplate jdbcTemplate;
//实现增加功能
@Override
public int add(Book book) {
String sql = "insert into book(name,price) values(?,?)";
return jdbcTemplate.update(sql, book.getName(), book.getPrice());
}
//修改
@Override
public int update(Book book) {
String sql = "update book set name=? ,price =? where id=?";
return jdbcTemplate.update(sql, book.getName(), book.getPrice(), book.getId());
}
//删除
@Override
public int del(Integer id) {
String sql = "delete from book where id= ?";
return jdbcTemplate.update(sql, id);
}
@Override
public int findCount() {
String sql = "select count(*) from book";
/*返回类型的class*/
return jdbcTemplate.queryForObject(sql, int.class);
}
@Override
public Book findForOne(Integer id) {
String sql = "select * from book where id=?";
return jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<Book>(Book.class), id);
}
@Override
public List<Book> list() {
String sql = "select * from book";
return jdbcTemplate.query(sql, new BeanPropertyRowMapper<Book>(Book.class));
}
}
BookService与BookDao代码一样。
BookServiceImpl:
@Service //注解创建对象
public class BookServiceImpl implements BookService {
//使用注解注入属性
@Autowired
private BookDao bookDao;
@Override
public int add(Book book) {
return bookDao.add(book);
}
@Override
public int update(Book book) {
return bookDao.update(book);
}
@Override
public int del(Integer id) {
return bookDao.del(id);
}
@Override
public int findCount() {
return bookDao.findCount();
}
@Override
public Book findForOne(Integer id) {
return bookDao.findForOne(id);
}
@Override
public List<Book> list() {
return bookDao.list();
}
}
测试:
@Test
public void add() {
ApplicationContext context = new ClassPathXmlApplicationContext("test_01.xml");
BookServiceImpl bookServiceImpl = context.getBean("bookServiceImpl", BookServiceImpl.class);
Book book = new Book(null,"钢铁是怎样炼成的",new BigDecimal(233));
//增加
// bookServiceImpl.add(book);
//修改
// bookServiceImpl.update(new Book(2,"废物一生",new BigDecimal(999)));
//删除
// bookServiceImpl.del(1);
//查询数据库记录条数
int count = bookServiceImpl.findCount();
System.out.println(count);
//查询对象
Book book1 = bookServiceImpl.findForOne(2);
System.out.println(book1);
//查询返回集合
List<Book> list = bookServiceImpl.list();
list.forEach(System.out::println);
}
《2》批量增加,修改,删除操作:
Dao层:
Service 层:
测试:
七、事务
1、事务的概念
事务是数据库操作最基本单元,逻辑上的一组操作,要么都成功,如果有一个失败,所有操作都失败。
最经典的案例:就是银行转账。
2、事务的四大特性(ACID)
- 原子性
- 一致性
- 隔离性
- 持久性
3、Spring 中事务的管理
一般事务的处理都放在 service 层。
(1)在Spring事务管理操作:
有俩种方式:编程式事务管理和声明式管理
(2)声明式管理包括:
基于注解管理
基于xml管理
(3)在 Spring 进行声明式事务管理,底层使用 AOP 原理(4)Spring 事务管理 API《1》提供一个接口,代表事务管理器,这个接口针对不同的框架提供不同的实现类![]()
(5)注解声明式管理事务
《1》在 Spring 开启事务管理器
<!--配置事务管理器,并注入DataSource--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean>
《2》开启事务注解
<!--开启事务注解--> <tx:annotation-driven transaction-manager ="transactionManager"></tx:annotation-driven> 需要引入命名空间: http://www.springframework.org/schema/tx https://www.springframework.org/schema/tx/spring-tx.xsd xmlns:tx="http://www.springframework.org/schema/tx"
《3》在 service 层加上事务注解@Transactional
@Transactional可以增加到类上,也可以增加到方法上。增加到类上表示所有的方法都能使用事务,增加到方法上只有该标注的方法能使用
(6)声明式事务管理参数配置
@Transactional 这个注解可以配置的参数:
《1》propagation 事务传播行为:多事务方法直接进行调用,这个过程中事务 是如何进行管理的
事务方法:对数据库的增删改,查不算因为查没有涉及到数据变化。
多事务方法:在事务方法里调用其他方法。
设置传播行为:
《2》isolation 隔离级别:多事务操作之间不产生影响。不考虑隔离性会产生三个问题:
脏读,不可重复读,幻读(虚读)
脏读:一个未提交的事务读到另一个未提交的事务(读未提交)
不可重复度:一个未提交的事务读到另一个提交事务修改的数据(读已提交)
幻读:一个未提交的事务读到另一个提交事务增加的数据
设置隔离级别可解决读问题:
设置隔离级别:
《3》timeout 超时时间 : 事务需要在一定时间内进行提交,如果超过 超时时间 自动回滚。默认时间 -1 ,表示不超时。设置时间以秒为单位。
《4》readOnly 是否只读:读:表示查询操作 写:添加修改删除操作
默认就是 false,可以进行增删改查
《5》rollbackFor 回滚 :设置出现哪些异常进行事务回滚
《6》noRollbackFor 不回滚:设置出现哪些异常不进行事务回滚
(7)完全注解方式
//使用完全注解方式使用事务 @Configuration //注解类 @ComponentScan(basePackages = {"dao","service"}) //设置注解扫描范围 @EnableTransactionManagement //开启事务注解 public class ConfigTx { @Bean //1、创建数据库连接池 public DataSource getDataSource(){ DruidDataSource dataSource = new DruidDataSource(); dataSource.setDriverClassName("com.mysql.jdbc.Driver"); dataSource.setUrl("jdbc:mysql://localhost:3306/test"); dataSource.setUsername("root"); dataSource.setPassword("root"); return dataSource; } @Bean //2、创建JDBCTemplate对象,并注入 DataSource //DataSource 经过上面已经创建好放到 IOC 容器当中。 //会自动根据类型注入属性 public JdbcTemplate getJdbcTemplate(DataSource dataSource){ JdbcTemplate jdbcTemplate = new JdbcTemplate(); //注入属性 jdbcTemplate.setDataSource(dataSource); return jdbcTemplate; } @Bean //表示创建对象 //3、创建 事务管理器对象,并注入 DataSource public DataSourceTransactionManager getTransactionManager(DataSource dataSource){ DataSourceTransactionManager transactionManager = new DataSourceTransactionManager(); //注入 DataSource 对象。 transactionManager.setDataSource(dataSource); return transactionManager ; } }
八、Spring 5 中的新功能
1、整个 SPring5 框架的diamante基于Java8 ,运行时兼容 Java9,许多不建议使用的类和方法在代码库中删除。
2、Spring 5 框架自带了通用的日志封装
(1)Spring5 已经移除 Log4jConfigListener,官方建议使用 Log4j2
(2)Spring5 框架整合 Log4j2
整合日志框架
1、导入相关jar包:
2、创建 log4j2.xml 文件 ,名字不能错;
Nullable注解
@Nullable 注解可以出现在方法、属性、参数上,表示:方法返回值可以为空,属性可以为空,参数可以为空。
Spring5 核心容器支持函数式风格 GenericApplicationContext
//函数式风格创建对象,交给 spring 进行管理 @Test public void testGenericApplicationContext() { //1 创建 GenericApplicationContext 对象 GenericApplicationContext context = new GenericApplicationContext(); //2 调用 context 的方法对象注册 context.refresh(); // "name":可写可不写,不写就通过类的全路径创建对象。写就可以通过name创建对象 context.registerBean("user1",User.class,() -> new User()); //3 获取在 spring 注册的对象 // User user = (User)context.getBean("com.atguigu.spring5.test.User"); User user = (User)context.getBean("user1"); System.out.println(user); }
前面说过,我们自己new的对象,Spring是不会进行管理的。但是我们可以通过 GenericApplicationContext 来进行注册。让Spring进行管理我们自己new'出来的对象。
Spring框架整合JUnit 5
第一步:引入 JUnit 5 相关 jar 包:
第二步:测试
// @ExtendWith(SpringExtension.class) // @ContextConfiguration("classpath:test_01.xml") //加载配置文件,自动创建好了对象 // 也可用以下一行代码代替上面俩行: @SpringJUnitConfig(locations = "classpath:test_01.xml") public class Test_Junit5 { @Autowired private UserService userService ; @Test public void test(){ userService.account(200.0); } }