SpringFramework5.x
- Spring IOC工厂
- Spring AOP编程
- 持久层集成
- 事务处理
- MVC框架继承
- 注解编程
2002年Rob Jhonson提出EJB存在的缺陷
EJB(Enterprise Java Bean)缺陷
- 运行环境苛刻(需要运行在EJB容器中)
- 代码移植性差
总结:EJB是重量级框架
什么是Spring
Spring是一个轻量级JavaEE解决方案,整合了众多优秀的设计模式
-
轻量级
-
对于运行环境没有额外要求
开源 tomcat resion jetty
收费 weblogic websphere
-
代码移植性高
不需要实现额外接口
-
-
JavaEE的解决方案
面对每一层都有解决方案
-
整合设计模式
工厂、代理、模板、策略
设计模式
1.广义
面向对象设计中,解决特定问题的经典代码
2.狭义
GOF4人定义的23种设计模式
工厂设计模式
-
概念:通过工厂类创建对象
-
好处:解耦合
耦合:代码间的强关联关系,一方的改变会影响另一方
问题:不利于代码维护
//简单案例:把接口的实现类硬编码在程序中 UserServiceImpl userService = new UserServiceImpl();
设计简单工厂模式
工厂类
public class BeanFactory {
public static UserService getUserService(){
return new UserServiceImpl();
}
}
测试类
import com.liu.pojo.BeanFactory;
import com.liu.pojo.User;
import com.liu.pojo.UserService;
import com.liu.pojo.UserServiceImpl;
import org.junit.Test;
public class TestSpring {
@Test
public void test01(){
//UserServiceImpl userService = new UserServiceImpl();
//通过工厂类创建对象,解耦合
UserService userService = BeanFactory.getUserService();
userService.login("liu","123456");
User user = new User("liushihao","123456");
userService.register(user);
}
}
但是这种工厂类的设计过于简单,仍然引入了具体的UserServiceImpl,仍然存在耦合。
改进(反射+配置文件)
对象的创建方式:
-
直接调用构造方法创建对象
UserServiceImpl userService = new UserServiceImpl();
-
通过反射的形式创建对象 解耦合
Class clazz = Class.forName(“com.liu.pojo.UserServiceImpl”);
UserService userService = (UserService)clazz.newInstance();
package com.liu.pojo;
public class BeanFactory {
public static UserService getUserService(){
UserService userService = null ;
try {
//通过反射一种方式还是无法彻底解决耦合,还是引入了接口实现类的全限定类名
Class clazz = Class.forName("com.liu.pojo.UserServiceImpl");
userService = (UserService)clazz.newInstance();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return userService;
}
}
通过添加小的配置文件解决Class.forName(“com.liu.pojo.UserServiceImpl”);中的耦合问题
配置文件
#Properties类型的集合来存储Properties文件的内容
#特殊的Map key=String value=String
#Properties [userService = com.liu.pojo.UserServiceImpl]
#Properties.getProperty("userService")就可以获取com.liu.pojo.UserServiceImpl
userService = com.liu.pojo.UserServiceImpl
工厂类
package com.liu.pojo;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
public class BeanFactory {
private static Properties env = new Properties();
//IO是系统级资源,我们应该避免重复性地打开IO,最好在程序启动时一次性读取想要的内容
static{
//1.获得输入流
InputStream inputStream = BeanFactory.class.getResourceAsStream("/applicationContext.properties");
//2.文件的内容封装到Properties集合中 key="userService" value="com.liu.pojo.UserServiceImpl"
try {
env.load(inputStream);
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
public static UserService getUserService(){
UserService userService = null ;
try {
Class clazz = Class.forName(env.getProperty("userService"));
userService = (UserService)clazz.newInstance();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return userService;
}
}
日后业务需要改动,只需将新的实现类写入配置文件applicationContext.properties中
但是这样的设计还是存在问题,只要有新的实现类出现,就需要在工厂类中添加
public static XXXX getXXXX(){
XXXX xxxx = null;
try {
Class clazz = Class.forName(env.getProperty("xxxx"));
xxxx = (XXXX)clazz.newInstance();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return xxxx;
}
这样的话会存在大量冗余代码
通用工厂设计
package com.liu.pojo;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
public class BeanFactory {
private static Properties env = new Properties();
static{
InputStream inputStream = BeanFactory.class.getResourceAsStream("/applicationContext.properties");
try {
env.load(inputStream);
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
//key代表小配置文件中的key [userDAO, userService]
public static Object getBean(String key){
Object ret = null;
try {
Class clazz = Class.forName(env.getProperty(key));
ret = clazz.newInstance();
}catch (Exception e){
}
return ret;
}
}
至此,需要创建对象时只需要调用工厂类的getBean()方法,传入配置文件中对应的字符串即可。
//getBean是静态方法,可以直接类名.方法名进行调用UserService userService = (UserService) BeanFactory.getBean("userService");
通用工厂的使用
-
定义类型(类)
-
通过配置文件的配置告知给工厂(applicationContext.properties)
key = value
-
通过工厂获得类的对象
Object ret = BeanFactory.getBean(“key”);
总结
Spring本质:工厂 ApplicationContext(applicationContext.xml)
思路与上述工厂的设计一致,只不过Spring的工厂更为强大。
SpringHelloWorld
环境搭建
Spring的jar包(在pom.xml中设置依赖)
<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.4.RELEASE</version>
</dependency>
Spring的配置文件
-
配置文件的路径:任意位置 没有硬性要求
-
配置文件的命名:没有硬性要求 建议applicationContext.xml
一般配置方式
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CLfY6wNf-1620665224815)(D:\截图\image-20210321211433462.png)]
Spring的核心API
-
ApplicationContext
- 作用:Spring提供的ApplicationContext这个工厂用于对象的创建
- 好处:解耦合
- ApplicationContext是接口类型
接口:屏蔽实现的差异两种实现: 非web环境:ClassPathXmlApplicationContext(main junit) web环境:XmlWebApplicationContext
- 重量级资源
ApplicationContext工厂的对象(ClassPathXmlApplicationContext和XmlWebApplicationContext)占用大量内存重量级资源特点:故一个应用只会创建一个工厂对象ApplicationContext工厂一定是线程安全的(多线程并发访问)
程序开发
-
创建类型
-
配置applicationContext.xml
<bean id="person" class="com.liu.pojo.Person"></bean>
-
通过工厂类获得对象 ClassPathXmlApplicationContext
//1.获取Spring的工厂 ApplicationContext context = new ClassPathXmlApplicationContext("/applicationContext.xml");//2.通过工厂类获得对象 Person person = (Person) context.getBean("person");
细节分析
名词解释
Spring工厂创建的对象,叫做bean或者组件(component)
Spring工厂相关的方法
//通过这种方式获得对象就不需要进行强制类型转换
Person person = context.getBean("person", Person.class);
//当前配置文件中只能有一个<bean class是Person类型
Person person = context.getBean(Person.class);
//获取当前配置文件中所有bean标签的id值
String[] beanDefinitionNames = context.getBeanDefinitionNames();
//获取配置文件中对应类型的id值
String[] beanNamesForType = context.getBeanNamesForType(Person.class);
//判断是否存在对应id的bean 返回值是boolean类型
//containsBeanDefinition 只能根据id判断,无法判断name
context.containsBeanDefinition("person");
//containsBean id、name都可以判断
context.containsBean("person");
配置文件中需要注意的细节
- 只配置class属性
<bean class="com.liu.pojo.Person"></bean>
a)这种没有id值的配置 Spring会自动生成一个id com.liu.pojo.Person#0
b)应用场景:如果这个bean只需要使用一次,那么就可以省略id值
如果这个bean需要使用多次或者被其他bean引用,则需要设置id值
-
name属性
作用:用于在Spring配置文件中,为bean对象定义别名(小名)
与id相同的地方
1)context.getBean(“id/name”) --> object
2)<bean id="" class="" 等效于 <bean name="" class=""
与id不同的地方
1)别名可以定义多个,id是唯一的
2)以前XML的id属性的值有命名要求:以字母开头,后面跟字母、数字、下划线、连字符
而name属性的值命名没有要求 如/person
name属性会应用在特殊命名场景下:/person(spring + struts1)
但是XML发展到今天已经不存在id命名的限制,现在优先使用id
3)工厂的方法中id和name
//containsBeanDefinition 只能根据id判断,无法判断name context.containsBeanDefinition("person"); //containsBean id、name都可以判断 context.containsBean("person");
Spring工厂的底层实现原理(简易)
Spring工厂是可以调用private修饰的构造方法创建对象的
思考
问题:Spring的工厂很强大,那么未来开发中是不是所有对象都交给Spring工厂来创建?
回答:理论上是的。但是有特例—实体对象(entity,会封装数据库表中的数据,这些数据会操作数据库),这类对象交给持久层框架来创建
Spring5.x与日志框架整合
作用
Spring与日志框架整合,日志框架就可以在控制台输出Spring运行过程中的一些重要信息(对象的创建、销毁、进行了什么操作等)。便于了解Spring的运行过程,利于程序的调试
如何整合
早期Spring1、2、3都是与commons-logging.jar进行整合
Spring5.x默认整合的日志框架是logback、log4j2
Spring整合log4j
1.引入log4j相关jar包
pom.xml
<!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-log4j12 -->
<dependency>
<!-- 日志门面,引入后可以将默认的logback、log4j2摒弃,使之可以支持log4j-->
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.25</version>
</dependency>
<!-- https://mvnrepository.com/artifact/log4j/log4j -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
2.引入log4j.properties配置文件
#resources文件夹目录下
### 配置根
log4j.rootLogger = debug,console
###日志输出到控制台显示
log4j.appender.console = org.apache.log4j.ConsoleAppender
log4j.appender.console.Target = System.out
log4j.appender.console.layout = org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern =%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n
注入(Injection)
通过Spring工厂及配置文件,为所创建对象的成员变量赋值
为什么需要注入
通过编码的方式为成员变量赋值,存在耦合
@Test
public void test05(){
ApplicationContext context = new ClassPathXmlApplicationContext("/applicationContext.xml");
Person person = (Person) context.getBean("person");
//原来赋值的方法,通过代码为成员变量赋值,存在耦合
person.setId(1);
person.setName("liushihao");
System.out.println(person);
}
如何进行注入
1.在类中为成员变量提供set、get方法
2.配置Spring配置文件
<bean id="person" name="p" class="com.liu.pojo.Person">
<property name="id" value="10"/>
<property name="name" value="david"/>
</bean>
注入的好处
解耦合
注入的原理
Spring通过底层调用对象属性对应的set方法,完成成员变量的赋值,这种方式也称为set注入
<bean id="person" name="p" class="com.liu.pojo.Person">
等效于
Account account = new Account();
<property name="id" value="10"/>
等效于
account.setId(10);
<property name="name" value="david"/>
等效于
account.setName("david");
Set注入详解
针对于不同类型的成员变量,在标签中需要嵌套不同标签
Set注入类型
JDK内置类型
-
8种基本类型(byte short int long float double char boolean)+String
在property标签中嵌套value
<property name="id"> <value>10</value> </property> <property name="name"> <value>liush</value> </property>
-
数组类型
<property name="emails"> <list> <value>123456@163.com</value> <value>abcdef@163.com</value> <value>qwerty@163.com</value> </list> </property>
-
集合类型
<!-- set集合是无序的 不一定按照赋值顺序存入 这里嵌套value标签是因为Person类中定义的是Set<String> 假如没有定义泛型,则set标签中也可以嵌套其他标签 --> <property name="tels"> <set> <value>138123123</value> <value>432123432</value> <value>132543543</value> </set> </property> <!-- list集合底层是以数组完成 和数组一样 property中嵌套list标签 list集合是有序的、可重复的 --> <property name="addresses"> <list> <value>centrypark</value> <value>swimmingpool</value> <value>blocks</value> <value>blocks</value> <value>blocks</value> </list> </property> <!-- 对于map集合 存储的是键值对 实际上一个键值对就是map中的一个内部类Map.Entry 所以赋值时应嵌套Entry标签 Person类中定义的是Map<String,String> 所以key标签中仍嵌套value标签 --> <property name="qqs"> <map> <entry> <key><value>liush</value></key> <value>1234324</value> </entry> </map> </property> <!-- Properties类型 特殊的Map key和value都只能是String类型 一个prop就是一个键值对 value直接写值即可 不用再嵌套value标签 --> <property name="p"> <props> <prop key="key1">value1</prop> <prop key="key2">value2</prop> </props> </property>
-
复杂的JDK类型(Date)
需要程序员自定义类型转换器处理。
用户自定义类型
第一种方式
-
为成员变量提供set get方法
-
配置文件中进行注入(赋值)
<!-- userService包含一个private UserDAO userDAO; --> <bean id="userService" class="com.liu.pojo.UserServiceImpl"> <property name="userDAO"> <bean class="com.liu.pojo.UserDAOImpl"/> </property> </bean>
第二种方式
-
第一种方式存在的问题
假如有多个类都有UserDAO作为成员变量,那么配置文件中 <property name="userDAO"> <bean class="com.liu.pojo.UserDAOImpl"/> </property> 这段代码会重复出现多次 冗余 且会创建很多次userDAO对象 浪费内存(JVM)资源
-
为成员变量提供set get方法
-
在配置文件中先创建userDAO,然后在有UserDAO作为成员变量的类创建标签中,用ref标签引用这个userDAO对象
<bean id="userDAO" class="com.liu.pojo.UserDAOImpl"/> <bean id="userService" class="com.liu.pojo.UserServiceImpl"> <property name="userDAO"> <ref bean="userDAO"/> </property> </bean> <bean id="orderService" class="com.liu.pojo.orderServiceImpl"> <property name="userDAO"> <ref bean="userDAO"/> </property> </bean> #Spring4.x 废除了 <ref local=""/> 这个标签和<ref bean=""/>基本等效
Set注入的简化写法
基于属性简化
JDK类型注入
<property name="name">
<value>liush</value>
</property>
简化写法
<property name="name" value="liush"/>
注意:value属性只能简化8中基本类型+String的注入标签
用户自定义类型
<property name="userDAO">
<ref bean="userDAO"/>
</property>
简化写法
<property name="userDAO" ref="userDAO" />
基于p命名空间简化
p即property的缩写
JDK类型注入
<bean id="person" class="com.liu.pojo.Person">
<property name="name">
<value>liush</value>
</property>
</bean>
简化写法
<bean id="person" class="com.liu.pojo.Person" p:name="xiaodong" p:id="11" />
用户自定义类型
<bean id="person" class="com.liu.pojo.Person">
<property name="userDAO">
<ref bean="userDAO"/>
</property>
</bean>
简化写法
<bean id="userService" class="com.liu.pojo.UserServiceImpl" p:userDAO-ref="userDAO" />
构造注入
注入:通过Spring的配置文件,为成员变量赋值
Set注入:Spring通过调用set方法 通过配置文件 为成员变量赋值
构造注入:Spring调用构造方法 通过配置文件 为成员变量赋值
步骤
- 提供有参构造方法
package com.liu.constructor;
import java.io.Serializable;
public class Customer implements Serializable {
private String name;
private int age;
public Customer(String name, int age){
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Customer{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
- 配置文件
配置文件中参数顺序也要与类种定义的构造器参数顺序一致
<bean id="customer" class="com.liu.constructor.Customer">
<constructor-arg name="name" value="liushihao"/>
<constructor-arg name="age" value="21"/>
</bean>
构造方法存在重载时
- 参数个数不同时
通过控制<constructor-arg />标签的数量来调用不同的构造方法
- 参数个数相同时
当存在相同个数参数的构造方法时,需要在标签中添加type=""来告诉Spring 进行区分
<constructor-arg type="" />
注入的总结
问题:未来实际应用时使用set注入还是构造注入?
答:set注入用得更多。
原因:构造注入麻烦(存在重载)、Spring框架底层用了大量的set注入。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RRKU4wLW-1620665224818)(D:\截图\image-20210324221624478.png)]
控制反转与依赖注入
控制反转(IOC:Inverse of Control)
纯概念
控制:对于成员变量赋值的控制权
没有Spring时,直接在代码中完成对成员变量的赋值,控制权在代码中,存在耦合
控制反转:对于成员变量赋值的控制权,从代码中转移到了Spirng配置文件和工厂,解耦合
底层实现:工厂设计模式
依赖注入(DI:Dependency Injection)
一种Spring的编程方式,应理解体会思想并应用于后续开发
注入:通过Spring的工厂及配置文件,为对象(bean、组件)的成员变量赋值
依赖注入:当一个类需要另一个类时,就意味着依赖,一旦出现依赖,就可以将另一个类作为本类的成员变量,最终通过Spring配置文件进行注入(赋值),解耦合。
例如UserService依赖UserDAO访问数据库来实现业务,那么UserService就可以将UserDAO设为成员 变量,然后通过Spring的配置文件进行赋值注入。
Spring工厂创建复杂对象
此前创建的都是简单对象,简单对象指的就是可以直接通过new 构造方法来创建的对象
复杂对象
复杂对象:指的是不能直接通过new 构造方法创建的对象
例子
//jdbc中的Connection
Class.forName("com.mysql.jdbc.Driver")
DriverManager.getConnection();
//Mybatis中的SqlSessionFactory
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
new SqlSessionFactoryBuilder().build(inputStream);
Spring工厂创建复杂对象的3种方式
FactoryBean接口
Spring原生提供 重要
-
开发步骤
实现FactoryBean接口和其中的一些方法
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lco0fJFb-1620665224819)(D:\截图\image-20210325212715623.png)]
在pom.xml中添加依赖
<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.17</version> </dependency>
创建FactoryBean
package com.liu.factorybean;import org.springframework.beans.factory.FactoryBean; import java.sql.Connection;import java.sql.DriverManager; public class ConnectionFactoryBean implements FactoryBean<Connection> { //用于书写创建复杂对象的代码 类实现接口时引用了泛型,所以这里返回Connection public Connection getObject() throws Exception { Class.forName("com.mysql.jdbc.Driver"); Connection conn = DriverManager.getConnection("jdbc:mysql://localhost/liush", "root", "123456"); return conn; } public Class<?> getObjectType() { return Connection.class; } public boolean isSingleton() { return false; } }
Spring配置文件的配置
<bean id="conn" class="com.liu.factorybean.ConnectionFactoryBean"/>
此时context.getBean(“conn”)获得的是ConnectionFactoryBean所创建的复杂对象Connection,对于复杂对象,此时ConnectionFactoryBean变得相对次要了
细节
-
如果就想获取FactoryBean类型的对象 就写context.getBean("&conn"),获取ConnectionFactoryBean对象
-
isSingleton方法
返回ture 只会创建一个复杂对象
返回false 每一次都会创建新的对象
根据对象的特点来决定是否单例,如果对象能被大家共用就返回true,否则false
连接对象重要作用是控制事务,不能被大家共用,返回false比较合理
SqlSessionFactory是重量级资源,返回true比较合理
-
mysql高版本连接创建时,需要制定ssl证书,控制台会警告报错
解决:
在连接的url字符串后添加useSSL=false即可 "jdbc:mysql://localhost:3306/liush?useSSL=false"
-
对于依赖注入的体会
对FactoryBean进行改进,将jdbc连接中重要的参数抽取为成员变量,提供set get方法
package com.liu.factorybean;import org.springframework.beans.factory.FactoryBean;import java.sql.Connection;import java.sql.DriverManager;public class ConnectionFactoryBean implements FactoryBean<Connection> { private String driverClassName; private String url; private String user; private String password; public String getDriverClassName() { return driverClassName; } public void setDriverClassName(String driverClassName) { this.driverClassName = driverClassName; } public String getUrl() { return url; } public void setUrl(String url) { this.url = url; } public String getUser() { return user; } public void setUser(String user) { this.user = user; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public Connection getObject() throws Exception { Class.forName(driverClassName); Connection conn = DriverManager.getConnection(url, user, password); return conn; } public Class<?> getObjectType() { return Connection.class; } public boolean isSingleton() { return false; }}
在配置文件中赋值注入
<bean id="conn" class="com.liu.factorybean.ConnectionFactoryBean"> <property name="driverClassName" value="com.liu.factorybean.ConnectionFactoryBean"/> <property name="url" value="jdbc:mysql://localhost:3306/liush?useSSL=false"/> <property name="user" value="root"/> <property name="password" value="ls2000818"/></bean>
-
FactoryBean的实现原理(简易版)
问题
- 为什么Spring规定FactoryBean接口让我们实现 并且要把创建复杂对象的代码写在getObject方法当中?
- 为什么context.getBean(“conn”)获得的是ConnectionFactoryBean所创建的复杂对象Connection,而不是获取的Factory Bean?
Java底层基本上都是反射+接口回调 “接口加反射,什么都能做”
Spring运行流程
- 通过conn获得ConnectionFactoryBean类的对象,进而通过instanceof判断出是FactoryBean接口的实现类
- Spring按照规定 调用getObject方法 获得Connection对象
- 返回Conncetion
-
FactoryBean总结
Spring中用于创建复杂对象的一种方式,也是Spring原生提供的,后续整合其他框架是,会大量应用FactoryBean
实例工厂
应用实例工厂创建复杂对象的原因:
-
避免Spring框架的侵入
选择FactoryBean方式时要implements FactoryBean接口,日后如果不用Spring就失效了
-
整合遗留系统
原有系统遗留了FactoryBean但是只能用.class文件,无法在源码中实现FactoryBean接口,只能new ConnectionFactory().getConnection(); 就是对工厂进行了实例化
//遗留的工厂类package com.liu.factorybean;import java.sql.Connection;import java.sql.DriverManager;import java.sql.SQLException;public class ConnectionFactory { public Connection getConnection(){ Connection conn = null; try { Class.forName("com.mysql.jdbc.Driver"); conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/liush?useSSL=false","root","ls2000818"); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (SQLException throwables) { throwables.printStackTrace(); } return conn; }}
- 开发
配置文件配置后测试即可
<bean id="connFactory" class="com.liu.factorybean.ConnectionFactory"></bean><bean id="conn" factory-bean="connFactory" factory-method="getConnection"/>
静态工厂
静态即工厂即工厂的方法是static的
public class StaticConnectionFactory { public static Connection getConnection(){ ... }}
配置文件
<bean id="conn" class="com.liu.factorybean.StaticConnectionFactory" factory-method="getConnection"/>
与实例工厂的区别就是创建StaticConnectionFactory就可以直接调用getConnection方法,不用再另外配置一个bean标签
Spring工厂创建对象的总结
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8NgQg97c-1620665224821)(D:\截图\image-20210326171343999.png)]
如果不考虑遗留系统整合,建议使用实现FactoryBean接口方式来创建负责对象
控制Spring工厂创建对象的次数
创建负责对象时如果使用实现FactoryBean接口,其中有isSingleton方法,解决了复杂对象创建次数的控制。
简单对象
只需要在bean标签中添加scope属性即可
singleton(默认):只创建一次
prototype:每次都创建新的对象
<bean id="account" scope="prototype" class="com.liu.scope.Account"/>
复杂对象
implements FactoryBean{ isSingleton(){ return true 只创建一次 return false 每次都创建新的对象 }}实例工厂、静态工厂 没有isSingleton方法,还是通过scope属性控制对象的创建次数
为什么要控制对象的创建次数?
好处:节省不必要的内存浪费
-
什么样的对象只创建一次 能被共用的、线程安全的
SqlSessionFactory、DAO、Service
-
什么样的对象每次都要创建新的 不能被共用的、线程不安全的
Connection、SqlSession|Session、Action(Struts2中)
对象的生命周期
概念
指的是一个对象创建、存活、消亡的完整过程
为什么要学习对象的生命周期
由Spring负责对象的创建、存活、销毁,了解生命周期,有利于更好地使用Spring为我们创建对象
生命周期的3个阶段
创建阶段
Spring工厂何时创建对象
scope=“singleton”
Spring工厂创建的同时,完成对象的创建
scope=“prototype”
Spring会在获取对象即context.getBean("")的同时,创建对象
如果singleton时也需要在获取时再创建对象,在bean中添加lazy-init属性
<bean id="product" scope="singleton" class="com.liu.life.Product" lazy-init="true"/>
初始化阶段
Spring工厂在**创建完对象后**,调用对象的初始化方法,完成对应的初始化操作
初始化方法提供:由程序员根据需求提供,最终完成初始化
初始化方法调用:Spring工厂调用
初始化方式
- 实现Spring提供的initializingBean接口
public class Product implements InitializingBean { //必须继承的初始化方法 根据实际需求在里面做一些初始化操作 Spring就会进行调用 public void afterPropertiesSet() throws Exception { ... }}
使用这种方法Spring会自行调用,不需要额外配置
- 对象中提供一个简单方法
public void myInit(){ }然后在配置文件bean标签中添加init-method属性 告诉Spring来调用这个方法来初始化<bean id="product" class="com.liu.life.Product" init-method="myInit"/>
使用这种方式可以不与Spring耦合,但需要额外配置
细节分析
- 如果一个对象即实现InitializingBean,同时又提供了普通的初始化方法
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ASpUXM5o-1620665224822)(D:\截图\image-20210326202506983.png)]
执行顺序:IntializingBean ===> 普通初始化方法
-
如果同时也给对象注入赋值
执行顺序:则先注入,再初始化
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JNo36bI6-1620665224823)(D:\截图\image-20210326203051216.png)]
-
什么是初始化操作
对于资源的初始化:数据库、IO、网络 … 以后使用较少
销毁阶段
Spring销毁对象前会调用对象的销毁方法,完成销毁操作,即资源释放的操作
-
Spring什么时候销毁所创建的对象
工厂关闭时 context.close();
-
销毁方法提供:程序员根据实际需求,定义销毁方法,完成销毁操作
销毁方法调用:Spring工厂
- DisposableBean
//Product类实现接口 并继承方法public class Product implements InitializingBean, DisposableBean { public void destroy() throws Exception { System.out.println("Product.destroy"); }}//测试@Testpublic void test13(){ //close方法是定义在ClassPathXmlApplicationContext中的,所以要把原来的父类声明改为子类声明 ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("/applicationContext.xml"); Product product = (Product) context.getBean("product"); context.close();}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6cri18L7-1620665224823)(D:\截图\image-20210327110448441.png)]
- 定义一个普通销毁方法
和初始化类似,在类中定义普通销毁方法后,在配置文件bean标签中添加destroy-method属性指定定义的普通销毁方法即可
<bean id="product" class="com.liu.life.Product" init-method="myInit" destroy-method="myDestroy" />
细节分析
-
销毁方法的操作只适用于 scope = “singleton” 的对象
-
什么叫做销毁操作
主要指一些资源的释放 io.close() connection.close() 以后使用较少
总结
配置文件参数化
-
Spring的配置文件中存在的经常需要修改的字符串
与数据库连接相关的参数(driver、url、user、password)为代表
-
经常变化的字符串,在Spring的配置文件中直接修改
不利于项目维护(修改)
把Spring配置文件中需要经常修改的字符串信息,转移到一个更小的配置文件中,利于Spring配置文件的维护
一般是 xml ====> properties
开发步骤
-
提供一个小的配置文件(.properties)
文件的名字和放置位置没有要求
jdbc.driverClassName = com.mysql.jdbc.Driverjdbc.url = jdbc:mysql://localhost:3306/liush?useSSL=falsejdbc.userName = rootjdbc.password = ls2000818
- Spring配置文件和小配置文件进行整合
<!-- Spring配置文件与小配置文件进行整合 开发时main和resources是同级分开的两个目录 但是编译时其实两者是合并的 classpath即类路径 指的就是target/classes--><context:property-placeholder location="classpath:dataBase.properties"/>
- 在Spring配置文件中通过${key}的方式获取小配置文件中对应的值
<bean id="conn" class="com.liu.factorybean.ConnectionFactoryBean"> <property name="driverClassName" value="${jdbc.driverClassName}"/> <property name="url" value="${jdbc.url}"/> <property name="user" value="${jdbc.userName}"/> <property name="password" value="${jdbc.password}"/></bean>
自定义类型转换器
类型转换器
作用:Spring通过类型转换器通过配置文件中 vlaue="" 的字符串数据,转换成了对象中成员变量对应类型的数据,进而完成注入
自定义类型转换器
为什么需要自定义类型转换器:当Spring内部没有提供特定的类型转换器时,实际应用中又需要使用,那就需要自己定义。
例如:不同国家地区使用的日期格式不一样,Spring没有提供字符串String(“2021-03-27”)转换为日期Date的类型转换器
开发步骤
- 实现converter接口
package com.liu.converter;
import org.springframework.core.convert.converter.Converter;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
//Converter<s, t> 由s转换为t
public class MyDateConverter implements Converter<String, Date> {
/*
* convert方法的作用:String ==> Date
* SimpleDateFormat sdf = new SimpleDateFormat();
* sdf.parset(String) ===> Date
*
* param:source 代表的就是配置文件中的日期字符串 <value>2021-03-27</value>
* return:把转换好的Date作为convert方法的返回值后,Spring会自动为Person中的birthday属性进行注入(赋值)
* */
public Date convert(String source) {
Date date = null;
try {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
date = sdf.parse(source);
} catch (ParseException e) {
e.printStackTrace();
}
return date;
}
}
- Spring配置文件中进行注册
- 创建MyDateConverter对象
<bean id="myDateConverter" class="com.liu.converter.MyDateConverter"/>
-
类型转换器的注册
目的:告知Spring框架,自定义的MyDateConverter是一个类型转换器
<!-- 用于注册类型转换器 ConversionServiceFactoryBean中的参数是set private Set<?> converters;所以<properties>中嵌套<set>--><bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean"> <property name="converters"> <set> <ref bean="myDateConverter"/> </set> </property></bean>
细节分析
- 将MyDateConverter中的日期格式提取为成员变量,通过依赖注入的方式,由配置文件完成赋值
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");public class MyDateConverter implements Converter<String, Date> { private String dateFormat; getter and setter public Date convert(String source) { Date date = null; try { SimpleDateFormat sdf = new SimpleDateFormat(dateFormat); date = sdf.parse(source); } catch (ParseException e) { e.printStackTrace(); } return date; }}
配置文件
<bean id="myDateConverter" class="com.liu.converter.MyDateConverter"> <property name="dateFormat" value="yyyy-MM-dd"/></bean>
-
对于普通的bean,id可以随便写,只要唯一就行
但是对于ConversionServiceFactoryBean这个类而言,id必须为 conversionService
-
Spring框架内置的日期类型转换器
日期格式:2021/03/27 (我们更习惯写的是2021-03-27)
后置处理Bean
BeanPostProcessor
-
是Spring工厂中非常有价值的高级特性,底层进行高级封装时都有这个技术的影子
-
AOP底层实现中BeanPostProcessor是很重要的技术环节
作用:对Spring工厂所创建的对象,进行再加工。
BeanPostProcessor本身是一个接口,实现接口后,将具体加工操作写在接口规定的方法中即可
后置处理Bean原理运行分析
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-McFEMz9K-1620665224825)(D:\截图\image-20210327194809913.png)]
BeanPostProcessor接口规定要实现的方法:Object postProcessBeforeInitialization(Object bean, String beanName)作用:Spring创建完对象,进行注入后,可以运行该Before方法进行加工获得Spring创建好的对象:通过方法的参数最后把加工后的对象作为返回值,交给Spring框架Object postProcessAfterInitialization(Object bean, String beanName)作用:Spring执行完对象的初始化操作后,可以运行该After方法进行加工获得Spring创建好的对象:通过方法的参数最后把加工后的对象作为返回值,交给Spring框架
实际应用当中,很少处理Spring的初始化操作,所以后置处理没必要分Before和After,只需要实现After方法即可
但是既然实现了BeanPostProcessor接口,即使postProcessBeforeInitialization方法不对bean进行加工,也必须return bean对象交回给Spring
开发步骤
- 类 实现BeanPostProcessor接口
package com.liu.beanpost;import org.springframework.beans.BeansException;import org.springframework.beans.factory.config.BeanPostProcessor;public class MyBeanPostProcessor implements BeanPostProcessor { public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { return bean; } public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { Categroy categroy = (Categroy) bean; categroy.setName("xiaoming"); return categroy; }}
- Spring的配置文件中进行配置
<bean id="c" class="com.liu.beanpost.Categroy"> <property name="id" value="10"/> <property name="name" value="liush"/></bean><bean id="myBeanPostProcessor" class="com.liu.beanpost.MyBeanPostProcessor"/>
-
细节
BeanPostProcessor会对Spring工厂中创建的所有对象进行加工
所以所有对象都会经过加工方法,应加入类型判断
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { if (bean instanceof Categroy) { Categroy categroy = (Categroy) bean; categroy.setName("xiaoming"); } return bean;}
静态代理设计模式
为什么需要代理设计模式
问题
- 在JavaEE分层开发中,哪个层次对于我们来说最重要?
DAO ===> Service ===> ControllerService是最重要的,开发中就是要满足用户业务需求
- Service层中包含哪些代码?
Service层 = 核心业务功能(几十行 上百行) + 额外(附加)功能1. 核心功能 业务运算、DAO调用2. 额外功能 不属于业务、可有可无、代码量很小 如 事务、日志、性能
- 额外功能书写在Service层中好不好?
Service层调用者的角度(Controller):需要在Service层中书写额外功能
如需要事务来保证操作的完整性
软件设计者的角度:额外功能不应该写在Service层中
毕竟可有可无,有需求得往里加,没有需求得删,不利于代码维护
- 现实生活中的解决方案
比如房东(Service)想要出租房屋,需要打广告、带人看房(额外功能)和签合同、收房租(核心业务功能),但是打广告和带人看房自己做太累了,对于房客(调用者)来说又必须有这两项才能决定租房东的房子,此时,就出现了中介(Proxy),即代理。
通过引入代理(中介)类,由代理类来完成额外功能,并调用目标类(房东)的核心功能(签合同、收房租),就同时解决了房客、房东的问题。后续如果对代理类附加功能的完成不满意,直接更换代理。
代理设计模式
通过代理类,为原始类(目标类)增加额外的功能
好处:利于原始(目标)类的维护,额外功能的变化不影响原始类的代码
名词解释
-
目标类 原始类
指的是业务类(核心功能)
-
目标方法 原始方法
目标(原始)类中的方法
-
额外(附加)功能
以日志、性能、事务为代表
代理开发的核心要素
代理类 = 和原始类实现相同的接口 + 目标(原始类)类的核心功能方法 + 额外功能房东 ===> public interface UserService{ m1 m2 } UserServiceImpl implements UserService{ m1 ===> DAO调用 m2 ===> 业务运算 } UserServiceProxy implements UserService{ m1 m2 }
开发步骤
静态代理:每有一个新的原始类,就要手写一个新的代理类
目标(原始)类
package com.liu.proxy;public class UserServiceImpl implements UserService{ public void register(User user) { System.out.println("UserServiceImpl.register 业务运算 + DAO调用"); } public boolean login(String name, String password) { System.out.println("UserServiceImpl.login 业务运算 + DAO调用"); return true; }}
代理类
package com.liu.proxy;public class UserServiceProxy implements UserService{ private UserServiceImpl userService = new UserServiceImpl(); public void register(User user) { System.out.println("-----log-----"); userService.register(user); } public boolean login(String name, String password) { System.out.println("-----log-----"); return userService.login(name, password); }}
存在的问题
1. 静态代理文件过多,不利于项目管理 随着原始类增加,类文件数量成倍增长 UserServiceImpl UserServiceProxy OrderServiceImpl OrderServiceProxy 2. 额外功能维护性非常差 代理类中 额外功能修改起来很麻烦
动态代理开发
概念上和静态代理一样,不同点在于开发方式和底层实现。
开发步骤
搭建环境
pom.xml文件中引入依赖<!-- https://mvnrepository.com/artifact/org.springframework/spring-aop --><dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>5.1.14.RELEASE</version></dependency><!-- https://mvnrepository.com/artifact/org.aspectj/aspectjrt --><dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjrt</artifactId> <version>1.8.8</version></dependency><!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver --><dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.8.3</version></dependency>
创建原始类对象
原始类UserServiceImpl
package com.liu.proxy;public class UserServiceImpl implements UserService{ public void register(User user) { System.out.println("UserServiceImpl.register 业务运算 + DAO调用"); } public boolean login(String name, String password) { System.out.println("UserServiceImpl.login 业务运算 + DAO调用"); return true; }}
在工厂配置文件中配置创建对象
<bean id="userService" class="com.liu.proxy.UserServiceImpl"></bean>
额外功能
实现Spring提供的MethodBeforeAdvice接口
package com.liu.dynamic;import org.springframework.aop.MethodBeforeAdvice;import java.lang.reflect.Method;public class Before implements MethodBeforeAdvice { /* * before方法作用:在原始类的核心业务方法执行前,运行该方法中的额外功能 * */ public void before(Method method, Object[] objects, Object o) throws Throwable { System.out.println("--method before advice log--"); }}
配置文件中创建该类
<bean id="before" class="com.liu.dynamic.Before"/>
定义切入点
切入点:额外功能加入的位置(哪个方法)
目的:由程序员根据实际需求,决定将额外功能加入给哪个原始方法
简单测试:所有方法都作为切入点,都加入额外的功能配置文件中加入<aop:config> <!-- 所有的方法都作为切入点,加入额外功能 --> <aop:pointcut id="pc" expression="execution(* *(..))"/></aop:config>
组装(整合前两步)
在刚才的<aop:config></aop:config>标签中添加:<!-- 组装:把切入点与额外功能进行整合 --><aop:advisor advice-ref="before" pointcut-ref="pc"/>表达的含义:对于切入点pc(所有方法)加入before这个额外功能
调用
目的:获得Spring工厂创建的动态代理对象,并对其进行调用
细节
UserService userService = (UserService) context.getBean("userService");
- 此时 通过原始类的id 从Spring的工厂中获得的是**代理对象**
- 获得代理对象后,可以通过声明接口类型,进行对象的存储(因为代理类实现了和原始类一样的接口)
@Testpublic void test02(){ ApplicationContext context = new ClassPathXmlApplicationContext("/applicationContext.xml"); UserService userService = (UserService) context.getBean("userService"); userService.register(new User()); userService.login("liush","123456");}
输出结果
细节分析
- Spring所创建的动态代理类在哪里?
Spring框架在运行时,通过动态字节码技术在JVM创建的,运行在JVM内部,等程序结束后回合JVM一起消失。动态代理类 ===> 动态字节码普通过程: .java ==> .class(字节码) ==> 加载到虚拟机中运行* 动态字节码技术:通过第三方动态字节码框架,在JVM中创建对应类的字节码,进而创建对象,当虚拟机结束,动态字节码也跟着消失。* 不需要去定义类文件,JVM运行过程中动态创建,所以不会造成静态代理中类文件成倍增长的问题。
- 动态代理编程简化代理的开发
在额外功能不改变的情况下,创建其他目标(原始)类的代理对象时,只需要在配置组装过的配置及文件中添加该bean即可。
- 动态代理中额外功能的维护性大大改善
需要修改额外功能时直接写新的,在配置文件中修改即可
Spring动态代理详解
额外功能详解
MethodBeforeAdvice分析
作用:额外功能运行在原始方法执行之前public class Before implements MethodBeforeAdvice { /* * 参数 * Method:增加额外功能所的那个原始方法 * Object[]:增加额外功能的那个原始方法对应的参数集合 如login方法的就是String name, String password * Object:增加额外功能的原始对象 * */ public void before(Method method, Object[] objects, Object o) throws Throwable { System.out.println("--method before advice log--"); }}
debug观察
before方法的三个参数在实际应用中如何使用? 不一定使用,会根据实际需要来进行使用
MethodInterceptor(方法拦截器)分析
-
MethodBeforeAdvice ==> 只能运行在方法执行之前
-
MethodInterceptor更为灵活,可以根据需要运行在之前,也可以在之后,还可以在前后都同时运行。实际应用中建议实现这个接口。
package com.liu.dynamic;import org.aopalliance.intercept.MethodInterceptor;import org.aopalliance.intercept.MethodInvocation;public class Around implements MethodInterceptor { /* * 将额外功能写在invoke方法当中,额外功能就可以运行在原始方法之前、之后、前后同时运行 * * 参数MethodInvocation(底层是对Method的更高级的封装):增加额外功能的原始方法 * methodInvocation.proceed() ==> 对应的原始方法运行 * 在这句代码的前后就可以自定义额外功能 * * 返回值Object:原始方法的返回值 * */ public Object invoke(MethodInvocation methodInvocation) throws Throwable { //额外功能 System.out.println("-----额外功能运行在原始方法之前-----"); Object ret = methodInvocation.proceed(); //额外功能 System.out.println("-----额外功能运行在原始方法之后-----"); return ret; }}//什么样的功能需要在原始方法前后都运行? 事务。
额外功能运行在原始方法抛出异常时
public Object invoke(MethodInvocation methodInvocation) throws Throwable { Object ret = null; try { ret = methodInvocation.proceed(); } catch (Throwable throwable) { System.out.println("---原始方法抛出异常 执行额外的功能---"); throwable.printStackTrace(); } return ret;}
- MethodInterceptor影响原始方法的返回值
原始方法的返回值直接作为invoke方法的返回值返回,MethodInterceptor不会影响原始方法的返回值 Object ret = methodInvocation.proceed(); return ret;MethodInterceptor影响原始方法的返回值:修改return的值,不直接返回原始方法的运行结果即可
切入点详解
切入点决定额外功能的加入位置(方法)<aop:pointcut id="pc" expression="execution(* *(..))"/>execution(* *(..)) ===> 匹配了所有方法1. excution() 切入点函数2. * *(..) 切入点表达式
切入点表达式
public void add(String a, String b) * *(..) *即通配符 第一个* ==> 修饰符没有要求 两个*之间的空格 ==> 返回值 () ==> 参数表 括号中的.. ==> 对有没有参数、参数类型和参数数量都没有要求
- 定义login方法作为切入点
* login(..)其他方法修改方法名即可
- 定义login方法且定义login方法有两个字符串类型的参数作为切入点
* login(String,String)此时,String是java.lang包下的,只写String即可如果不是java.lang包下的,要写全限定类名* login(String,..) 只限定第一个参数为String,后面的参数数量及类型都没有要求
- 以上的切入点表达式都不够精准
精确定位方法: * 包.类.方法(参数)
类切入点
对指定的类中所有方法都加入额外功能语法1: * 包.类.*(..)语法2(将不同包中的同名类作为切入点): * *.类.*(..) 类只存在一级包 * *..类.*(..) 类存在多级包
包切入点
指定包中外额外功能加入的位置语法:* 包.*.*(..) * com.liu.proxy.*.*(..) 必须在proxy包中,不能在proxy的子包中当前包及其子包:* com.liu.proxy..*.*(..)# 包切入点更具实际应用价值
切入点函数
作用:用于执行切入点表达式
execution
最为重要的切入点函数,功能最全可以执行 方法、类、包切入点表达式* 弊端:书写比较冗长麻烦注意:其他切入点函数,功能上和execution完全一致,只是简化其书写复杂程度
args
作用:主要用于函数(方法)参数的匹配如:切入点要求方法参数是两个字符串类型的参数exeition写法: execution(* *(String,String))args写法: args(String,String)
within
作用:用于进行类、包切入点表达式的匹配如:切入点要求是UserServiceImpl这个类execution写法: execution(* *..UserServiceImpl.*(..))within写法: within(*..UserServiceImpl)
@annotation
作用:为具有特殊注解的方法加入额外的功能 1. 自定义注解package com.liu;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)public @interface Log {}2. 在方法上加上注解package com.liu.proxy;import com.liu.Log;public class UserServiceImpl implements UserService{ @Log public void register(User user) { System.out.println("UserServiceImpl.register 业务运算 + DAO调用"); } public boolean login(String name, String password) { System.out.println("UserServiceImpl.login 业务运算 + DAO调用"); return true; }}3. 配置文件<aop:pointcut id="pc" expression="@annotation(com.liu.Log)"/>
切入点函数的逻辑运算
指的是整合多个切入点函数,一起配合工作,完成更加复杂的需求
- and 与操作
案例:想选择方法名为login,参数为两个字符串的方法作为切入点1. execution(* login(String,String))2. execution(* login(..)) and args(String,String)# 注意:与操作不能用于同种类型的切入点函数案例:需要增强一个类中的register和login方法execution(* login(..)) and execution(* register(..))是无法完成需求的表达的是一个方法同时函数名为login和register应为execution(* login(..)) or execution(* register(..))
- or 或操作
execution(* login(..)) or execution(* register(..))
AOP编程
Spring工厂封装了工厂设计模式,AOP编程封装的是代理模式
概念
AOP:Aspect Oriented Programing,面向切面编程
本质就是Spring的动态代理开发,通过代理类为原始类增加额外功能
好处:利于原始类的维护
以切面为基本单位的程序开发,通过切面间的彼此协同,相互调用,完成程序的构建
切面 = 切入点 + 额外功能
OOP:Object Oriented Programing,面向对象编程 Java 以对象为基本单位的程序开发,通过对象间的彼此协同,相互调用,完成程序的构建POP:Producer Oriented Programing,面向过程编程 C 以过程为基本单位的程序开发,通过过程间的彼此协同,相互调用,完成程序的构建# AOP编程是OOP编程的有意补充
开发步骤
就是Spring动态代理开发的步骤1. 原始对象2. 额外功能(MethodInterceptor)3. 切入点4. 组装切面(额外功能 + 切入点)
切面的名词理解
切面 = 切入点 + 额外功能对于“切面”理解1. 几何学 面 = 一些相同性质的点切面 = 具有相同额外功能的切入点2. 有多个ServiceImpl,每个ServiceImpl都有一个切入点,多个点就组成了一个面,切入到Service中
AOP的底层实现原理
也是Spring动态代理的底层实现原理
核心问题
1. AOP如何创建动态代理类(动态字节码技术)2. Spring的工厂如何加工创建代理对象 通过原始对象的id,获得的是代理对象
动态代理类的底层创建
代理创建3要素1. 原始对象2. 额外功能3. 代理对象和原始对象实现相同的接口
JDK的动态代理
创建原始对象
UserService userService = new UserServiceImpl();
JDK创建动态代理
Proxy.newProxyInstance(classLoader, interfaces, invocationhandler);
- 参数classLoader
借用一个类加载器,创建代理类的Class对象,进而创建代理对象
类加载器作用
1. 把对应类的字节码文件加载到JVM中
2. 创建类的Class对象,进而创建这个类的对象
例如创建一个User,先要写User.java,编译后生成User.class,再经过以上两步,才能创建出一个对象
如何获得类加载器:JVM会为每个.class文件自动分配与之对应的ClassLoader
而动态代理是通过动态字节码技术,将动态代理类字节码直接写到JVM中,没有具体的.java和.class文件,所以JVM不会为之分配CL,但是创建动态代理对象又需要CL来创建其Class对象,此时,就需要借用一个ClassLoader。这就是newProxyInstance方法第一个参数的含义。
- 参数interfaces
原始对象实现的接口,userService.getClass().getInterfaces();
- 参数invocationhandler
invoke方法:用于书写额外功能,额外功能可以运行在原始方法执行前、后、前后、抛出异常时
和MethodInterceptor的Object invoke(MethodInvocation invocation)类似
Object:原始方法的返回值
参数:Proxy可以忽略,代表的是代理对象 通过Proxy.newProxyInstance(...)创建
Method额外功能所增加给的那个原始方法
Object[]原始方法的参数
Object invoke(Object proxy, Method method, Object[] args){
System.out.println("---log---");
//运行原始对象的原始方法
Object ret = method.invoke(userService, args);
}
编码
package com.liu.jdk;import com.liu.proxy.User;import com.liu.proxy.UserService;import com.liu.proxy.UserServiceImpl;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;public class TestJDKProxy { public static void main(String[] args) { /* * 1.创建原始对象 * * jdk8.x前 需要声明为final * final UserService userService = new UserServiceImpl(); * */ final UserService userService = new UserServiceImpl(); //2.JDK创建动态代理 //内部类创建InvocationHandler InvocationHandler handler = new InvocationHandler() { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("---proxy log---"); //原始方法运行 Object ret = method.invoke(userService, args); return ret; } }; UserService userServiceProxy = (UserService) Proxy.newProxyInstance(userService.getClass().getClassLoader(), userService.getClass().getInterfaces(), handler); //运行 userServiceProxy.login("liush", "123456"); userServiceProxy.register(new User()); }}
Cglib的动态代理
实际应用场景:需要给一个没有实现任何接口的原始类创建代理类Cglib创建动态代理的原理 父子继承关系创建代理对象,原始类作为父类,代理类作为子类。 这样既可以保证二者方法一致,又可以在代理类中提供原始方法新的实现(额外功能+原始方法调用)
package com.liu.cglib;
import com.liu.proxy.User;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class TestCglib {
public static void main(String[] args) {
//1.创建原始对象
final UserService userService = new UserService();
/*
* 2.Cglib创建动态代理对象
*
* JDK创建动态代理
* Proxy.newProxyInstance(classLoader, interfaces, invocationhandler)
*
* Cglib创建动态代理
* 设置类加载器:Enhancer.setClassLoader()
* 设置父类:Enhancer.setSuperClass()
* 设置额外功能:Enhancer.setCallback() ===> 需要MethodInterceptor(cglib) 与invocationHandler相似
* Enhancer.create() ===> 创建代理
* */
Enhancer enhancer = new Enhancer();
enhancer.setClassLoader(TestCglib.class.getClassLoader());
enhancer.setSuperclass(userService.getClass());
MethodInterceptor interceptor = new MethodInterceptor() {
//等同于InvocationHandler中的invoke方法
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("---cglib log---");
Object ret = method.invoke(userService, args);
return ret;
}
};
enhancer.setCallback(interceptor);
UserService userServiceProxy = (UserService) enhancer.create();
userServiceProxy.login("liush", "123456");
userServiceProxy.register(new User());
}
}
总结
1. JDK动态代理 Proxy.newProxyInstance() 通过接口创建代理的实现类2. Cglib动态代理 Enhance 通过继承父类创建的代理类
Spring工厂如何加工原始对象创建代理类
BeanPostProcessor + JDK/Cglib动态代理
编码(模拟Spring加工原始对象)
模拟Spring的AOP原理,没有引入切入点,比较粗糙,只关注其核心——代理类的创建
ProxyBeanPostProcessor类
package com.liu.factory;import org.springframework.beans.BeansException;import org.springframework.beans.factory.config.BeanPostProcessor;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;public class ProxyBeanPostProcessor implements BeanPostProcessor { public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { return bean; } /* * 在方法中通过Proxy.newProxyInstance()创建代理类 * */ public Object postProcessAfterInitialization(final Object bean, String beanName) throws BeansException { InvocationHandler handler = new InvocationHandler() { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("---new log---"); Object ret = method.invoke(bean, args); return null; } }; return Proxy.newProxyInstance(ProxyBeanPostProcessor.class.getClassLoader(), bean.getClass().getInterfaces(), handler); }}
配置文件
<bean id="userService" class="com.liu.factory.UserServiceImpl"></bean> <!-- 1.实现BeanPostProcessor接口 在其中进行加工 2.配置文件中对BeanPostProcessor进行配置 --> <bean id="proxyBeanPostProcessor" class="com.liu.factory.ProxyBeanPostProcessor"/></beans>
Test类
package com.liu.factory;import com.liu.proxy.User;import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;public class Test { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("/applicationContext1.xml"); UserService userService = (UserService) context.getBean("userService"); userService.login("liushi", "123545"); userService.register(new User()); }}
基于注解的AOP编程
基于注解的AOP编程开发步骤
-
原始对象
-
额外功能
-
切入点
-
组装切面
- 之前的MethodInterceptor开发
1. 额外功能 public class Around implements MethodInterceptor{ public Object invoke(MethodInvocation invocation){ Object ret = invocation.proceed(); return ret; } } 配置文件中创建: <bean id="around" class="com.liu.dynamic.Around"/>2. 切入点 <aop:config> <aop:pointcut id="pc" expression="..."/> </aop:config>3. 组装 <aop:advisor advice-ref="around" pointcut-ref="pc"/>
-
基于注解进行AOP编程
原始对象的创建步骤不变
通过切面类完成 额外功能、切入点、组装切面
@Aspect 切面类
定义额外功能 @Around
定义切入点 @Around(“execution(* login(…))”)
package com.liu.aspect;import org.aspectj.lang.ProceedingJoinPoint;import org.aspectj.lang.annotation.Around;import org.aspectj.lang.annotation.Aspect;@Aspectpublic class MyAspect { @Around("execution(* login(..))") //不需要再implements MethodInterceptor //around方法相当于invoke 参数变为ProceedingJoinPoint public Object around(ProceedingJoinPoint joinPoint) throws Throwable { System.out.println("---Aspect Log---"); Object ret = joinPoint.proceed(); return ret; }}
在配置文件中配置,创建切面类并告知Spring使用注解进行AOP开发
<bean id="around" class="com.liu.aspect.MyAspect"/><!--告知Spring现在基于注解进行AOP开发--> <aop:aspectj-autoproxy />
细节
切入点复用
在切面类中定义一个函数,加上@PointCut注解,通过这种方式抽取出切入点表达式,利于后续维护。
@Aspect
public class MyAspect {
//提取冗余的切入点表达式
//对于具有相同切入点的around、around1方法,后续只需修改该函数的注解即可
@Pointcut("execution(* login(..))")
public void myPointCut(){
}
@Around(value = "myPointCut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("---Aspect Log---");
Object ret = joinPoint.proceed();
return ret;
}
@Around(value = "myPointCut()")
public Object around1(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("---Aspect Tx---");
Object ret = joinPoint.proceed();
return ret;
}
}
动态代理的创建方式
AOP底层实现
1. JDK 通过实现接口 创建代理对象
2. Cglib 通过继承父类 创建代理对象
- 默认情况下,AOP编程底层应用的是JDK方式创建动态代理
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-unmYDV8Z-1620665224828)(D:\截图\image-20210408132006376.png)]
- 基于注解AOP开发,让Spring使用Cglib方式创建动态代理
<aop:aspectj-autoproxy proxy-target-class="true"/>
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yPfwZOdq-1620665224828)(D:\截图\image-20210408132249317.png)]
- 传统(非注解)AOP开发,改为Cglib方式创建动态代理
<aop:config proxy-target-class="true"> ...</aop:config>
AOP开发中的坑
当原始类中存在不同方法间的互相调用时,this.XXX方法(普通调用)不会被加上额外功能
package com.liu.aspect;import com.liu.Log;import com.liu.proxy.User;public class UserServiceImpl implements UserService { public void register(User user) { System.out.println("UserServiceImpl.register 业务运算 + DAO调用"); //同一个类中,不同业务方法之间相互调用 this.login("liush", "123456"); } public boolean login(String name, String password) { System.out.println("UserServiceImpl.login 业务运算 + DAO调用"); return true; }}
测试类中调用register方法,结果为---Aspect Log------Aspect Tx---UserServiceImpl.register 业务运算 + DAO调用UserServiceImpl.login 业务运算 + DAO调用* this.login没有被加上额外功能因为this指向的是原始对象UserServiceImpl,此时调用的是原始对象的login方法,即没有加上额外功能的核心方法* 那么如何解决?通过工厂的getBean()才能拿到代理对象 工厂又是重量级资源,应该只创建一次,那么就要从测试类中拿到工厂# 此时原始类需要实现ApplicationContextAware接口
package com.liu.aspect;import com.liu.Log;import com.liu.proxy.User;import org.springframework.beans.BeansException;import org.springframework.context.ApplicationContext;import org.springframework.context.ApplicationContextAware;public class UserServiceImpl implements UserService, ApplicationContextAware { //将工厂设置为一个成员属性 private ApplicationContext context; public void register(User user) { System.out.println("UserServiceImpl.register 业务运算 + DAO调用"); //还是通过工厂来调用代理对象,就可以解决 UserService userService = (UserService) context.getBean("userService"); userService.login("liush", "234234"); //同一个类中,不同业务方法之间相互调用// this.login("liush", "123456"); } public boolean login(String name, String password) { System.out.println("UserServiceImpl.login 业务运算 + DAO调用"); return true; } public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.context = applicationContext; }}
AOP总结
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NJZBGJCh-1620665224828)(D:\截图\image-20210408224256347.png)]
AOP编程概念
实际上就是Spring动态代理开发,通过代理类为原始类增加额外功能。
好处:利于原始类的维护,日后只需修改代理类
AOP编程开发步骤
原始对象 ==> 额外功能 ==> 切入点 ==> 组装切面
底层实现
1. JDK动态代理(默认) 通过接口创建代理的实现类Proxy.newProxyInstance(classLoader, interfaces, invocationhandler);2. Cglib动态代理 通过继承父类创建的代理类Enhancer enhancer = new Enhancer();enhancer.setClassLoader();enhancer.setSuperclass();MethodInterceptor interceptor = new MethodInterceptor() { ...};enhancer.setCallback(interceptor);Object proxy = enhancer.create();如果想切换为Cglib,在配置文件中的<aop:config>、<aop:aspect-autoproxy>标签添加proxy-target-class="true"即可# 加工原始类从而创建代理对象 底层还依附于BeanPostProcessor
编程方式
创建完原始对象后1. 传统方式实现MethodInterceptor接口,并实现其中的invoke(MethodInvocation)方法配置文件中 <aop:config> <aop:pointcut id="" expression=""/> <aop:advisor pointcut-ref="" advice-ref="MethodInterceptor的实现类"/> </aop:config> 切入点表达式 方法 * login(..) 类 *..UserServiceImpl.*(..) 包 *.com.liu..*.*(..)切入点函数 execution within args @annotation2. 基于注解方式切面类完成额外功能、切入点和组装切面 @Aspect public class MyAspect{ @PoinCut() public void myPoinCut(){} @Around public Object around(ProceedingJoinPoint joinpoint){ -----加入额外功能----- Object ret = jounPoint.proceed(); return ret; } }配置文件中 <bean id="MyAspect" class="" /> <aop:aspect-autoproxy />
持久层整合
Spring框架为什么要与持久层整合?
- JavaEE开发需要持久层进行对数据库的访问
- JDBC、Hibernate、MyBatis进行持久层开发过程中都存在大量的冗余代码
- Spring基于模板设计模式对这些持久层技术进行了封装
Spring能与哪些持久层技术进行整合?
- JDBC ——JDBCTemplate
- Hibernate(JPA) ——HibernateTemplate
- MyBatis ——SqlSessionFactoryBean、MapperScannerConfigure
Spring整合MyBatis
对MyBatis中的不足进行改进
MyBatis开发步骤回顾
1. 搭建环境(pom.xml中引入依赖、建立全局配置文件、映射文件)
2. 实体
3. 实体别名
4. 建表
5. 创建DAO接口
6. 编写Mapper文件
7. 注册Mapper文件
8. MyBatis API调用
搭建环境
1. pom.xml文件中引入依赖
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.6</version>
</dependency>
2. 建立全局配置文件配置数据库连接信息
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<typeAliases></typeAliases>
<environments default="mysql">
<environment id="mysql">
<transactionManager type="JDBC" />
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver" />
<property name="url"
value="jdbc:mysql://localhost:3306/mybatis?useSSL=false" />
<property name="username" value="root" />
<property name="password" value="ls2000818" />
</dataSource>
</environment>
</environments>
<mappers></mappers>
</configuration>
3. 建立Mapper映射文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="">
</mapper>
实体类
package com.liu.mybatis;
import java.io.Serializable;
//序列化:便于存储(完整性)、便于传输(可传递性)
public class User implements Serializable {
private Integer id;
private String name;
private String password;
public User() {
}
public User(Integer id, String name, String password) {
this.id = id;
this.name = name;
this.password = password;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
实体类别名
全局配置文件config.xml中
<typeAliases>
<typeAlias type="com.liu.mybatis.User" alias="user"></typeAlias>
</typeAliases>
数据库建表
use mybatis;
create table t_users(
id int primary key auto_increment,
name varchar(12),
password varchar(12)
);
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-toOQETFH-1620665224829)(D:\截图\image-20210411162347392.png)]
创建DAO接口
package com.liu.mybatis;
public interface UserDAO {
public void save(User user);
}
配置Mapper.xml文件
添加namespace并编写mysql语句
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.liu.mybatis.UserDAO">
<insert id="save" parameterType="user">
insert into t_users(name,password) values (#{name},#{password})
</insert>
</mapper>
注册Mapper.xml文件
在全局配置文件Config.xml中<mappers> <mapper resource="UserDAOMapper.xml" /></mappers>
API调用
package com.liu.mybatis;import org.apache.ibatis.io.Resources;import org.apache.ibatis.session.SqlSession;import org.apache.ibatis.session.SqlSessionFactory;import org.apache.ibatis.session.SqlSessionFactoryBuilder;import java.io.IOException;import java.io.InputStream;public class TestMyBatis { public static void main(String[] args) throws IOException { InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml"); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); SqlSession session = sqlSessionFactory.openSession(); UserDAO userDAO = session.getMapper(UserDAO.class); User user = new User(); user.setName("liush"); user.setPassword("123456"); userDAO.save(user); session.commit(); }}
MyBatis在开发过程中存在的问题
配置繁琐 代码冗余
实体别名注册Mapper文件API调用# 以上三个步骤,在实体类、Mapper文件多了之后需要写大量重复代码
Spring与MyBatis整合思路分析
两个核心
- 创建SqlSessionFactory 通过SqlSessionFactoryBean完成
- 创建DAO对象 通过MapperScannerConfigure完成
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);# 封装为SqlSessionFactoryBean,用于创建SqlSessionFactory<bean id="dataSource" class="" /><bean id="ssfb" class="...SqlSessionFactory"> <!-- 通过属性的注入,替代mybatis-config.xml --> <property name="dataSource" ref="dataSource" ... /> <property name="typeAliasesPackage" ... /> ===> 实体别名 指定实体所在的包名,Spring会自动创建别名 User=>User <property name="mapperLocations" ... /> ===> 注册Mapper文件 通配设置 *Mapper.xml</bean>SqlSession session = sqlSessionFactory.openSession();UserDAO mapper = session.getMapper(UserDAO.class);# 封装为MapperScannerConfigurer<bean id="scanner" class="...MapperScannerConfigure"> <property name="sqlSessionFactoryBeanName" value="ssfb"/> <property name="basePackage" /> ===> 设置DAO接口所在的包</bean># 最终创建DAO对象MapperScannerConfigure所创建的DAO对象,其id值(context.getBean("id"))是接口的首单词首字母小写 UserDAO ===> userDAO ProductDAO ===> productDAO
开发步骤
搭建环境
<!--在原有基础上添加三个依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.1.14.RELEASE</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.2</version>
</dependency>
<!-- 连接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.18</version>
</dependency>
applicationContext.xml进行相关配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 连接池 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=false&allowPublicKeyRetrieval=true"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</bean>
<!-- 创建SqlSessionFactory SqlSessionFactoryBean -->
<bean id="sqlSessionFactoryBean" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="typeAliasesPackage" value="com.liu.entity"/>
<property name="mapperLocations">
<list>
<value>classpath:com.liu.mapper/*Mapper.xml</value>
</list>
</property>
</bean>
<!-- 创建DAO对象 MapperScannerConfigure -->
<bean id="scanner" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactoryBean"/>
<property name="basePackage" value="com.liu.dao"/>
</bean>
</beans>
编码
# 整合之后,实际开发中经常根据需求写的代码
1. 实体
2. 表
3. 创建DAO接口
4. 实现Mapper文件
- 实体
package com.liu.entity;
import java.io.Serializable;
public class User implements Serializable {
private Integer id;
private String name;
private String password;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
- 表
使用之前建的表
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-doXMBWfC-1620665224830)(file://D:/%E6%88%AA%E5%9B%BE/image-20210411162347392.png?lastModify=1618279891)]
- 创建DAO接口
package com.liu.dao;import com.liu.entity.User;public interface UserDAO { public void save(User user);}
- 实现Mapper.xml文件
<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.liu.dao.UserDAO"> <insert id="save" parameterType="User"> insert into t_users(name,password) values (#{name},#{password}) </insert></mapper>
- API调用
package com.liu;import com.liu.dao.UserDAO;import com.liu.entity.User;import org.junit.Test;import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;import java.lang.annotation.Target;public class TestMyBatisSpring { /* * 用于测试Spring与MyBatis的整合 * */ @Test public void test01(){ ApplicationContext context = new ClassPathXmlApplicationContext("/applicationContext.xml"); UserDAO userDAO = (UserDAO) context.getBean("userDAO"); User user = new User(); user.setName("liushihao"); user.setPassword("987654321"); userDAO.save(user); }}
运行结果
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Duwvm3yU-1620665224830)(D:\截图\image-20210413121520516.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4Ud92RZV-1620665224831)(D:\截图\image-20210413122106939.png)]
细节分析
- 整合Spring和Mybatis之后,DAO没有提交事务,但是数据能够插入数据库中
# 核心 Connection ===> tx本质上控制连接对象的是连接池dataSource1. 之前单独使用Mybatis提供的连接池对象 Connection.setAutoCommit(false); 会将true改为false,需要手工提交2. 整合之后使用Druid(C3P0 DBCP)作为连接池 Connection.setAutoCommit(true); true为默认值,保持自动提交事务 一条sql 自动提交 注意:在实际应用中,为了保证自主操作的完整性(多条sql一起成功,一起失败),我们还是使用手工提交,后续通过Spring的事务控制来解决。
Spring的事务处理
1. 概念
保证业务操作完整性的一种数据库机制
2. 事务的4个特点:ACID
Atomicity:原子性
Consistency:一致性
Isolation:隔离性
Durability:持久性
如何控制事务
1. JDBC 核心 Connection
开启手动提交:Connection.setAutoCommit(false);
Connection.commit();
Connection.rollback();
2. MyBatis
MyBatis会自动开启事务
sqlSession.commit();
sqlSession.rollback();
Session底层封装的是Connection对象
# 结论:控制事务的底层都是Connection对象完成的
Spring控制事务开发的分析
事务——额外功能
Spring是通过AOP的方式进行事务开发
- 原始对象
public class XXXUserServiceImpl{}
1. 原始对象 ===> 原始方法 ===> 核心功能(业务处理 + DAO调用)
2. DAO作为Service的成员变量,通过依赖注入的方式进行赋值
- 额外功能
1. 实现MethodInterceptor
public Object invoke(MethodInvocation invocation){
try{
Connection.setAutoCommit(false);
Object ret = invocation.proceed();
Connection.commit();
}catch(Exception e){
Connection.rollback();
}
return ret;
}
2. 切面类@Aspect
@Around
# 在Spring中,事务封装为org.springframework.jdbc.datasource.DataSourceTransactionManager
需要为其注入DataSource(注入Connection)
- 切入点
注解@Transactional
1. 加在类上:类种所有方法都会加上事务功能
2. 加在方法上,该方法加上事务功能
- 组装切面
组装切入点和切面
配置文件中的标签
<tx:annotation-driven transaction-manager=""/> 自动扫描@Transactional
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BcEqS4J7-1620665224831)(D:\截图\image-20210414212935183.png)]
编码
- 搭建环境
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.1.14.RELEASE</version>
</dependency>
- 编码
1. 原始对象
<bean id="userService" class="com.liu.service.UserServiceImpl">
<!-- 使用Spring与MyBatis整合时的scanner为我们创建的DAO对象 -->
<property name="userDAO" ref="userDAO"/>
</bean>
2. 额外功能(事务)
<!-- DataSourceTransactionManager -->
<bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 使用Spring与MyBatis整合时的dataSource进行注入 -->
<property name="dataSource" ref="dataSource"/>
</bean>
3. 切入点
@Transactional
public class UserServiceImpl implements UserService {
private UserDAO userDAO;
4. 组装切面
<tx:annotation-driven transaction-manager="dataSourceTransactionManager"/>
- 细节
<tx:annotation-driven transaction-manager="dataSourceTransactionManager" proxy-target-class="true"/>
进行动态代理底层实现的切换
默认false JDK
true Cglib
事务属性(Transaction Attribute)
什么是事务属性
属性:描述物体特征的一系列值
事务属性:描述事务特征的一系列值
- 隔离属性
- 传播属性
- 只读属性
- 超时属性
- 异常属性
如何添加事务属性
在事务注解中添加属性
@Transactional(isolation=, propagation=, readOnly=, timeOut=, rollbackFor=, noRollbackFor=,)
事务属性详解
隔离属性Isolation
描述了事务解决并发问题的特征
1. 什么是并发
多个事务(用户)在同一时间访问操作了相同的数据
同一时间:不是精确的同一时刻,可能有0.000几秒的微小差异
2. 并发会产生哪些问题
脏读
不可重复读
幻影
3. 并发问题如何解决
通过在隔离属性中设置不同的值来解决
事务并发产生的问题
- 脏读
# 一个事务读取了另一个事务中还没有提交的数据,假如另一个事务rollback,会产生数据不一致的问题
解决方案
@Transactional(isolation = Isolation.READ_COMMITTED)
- 不可重复读
# 一个事务中,多次读相同的数据,但是读取的结果不一样(被其他事务修改)
注意: 1.不是脏读 2.一个事务中
解决方案
@Transactional(isolation = Isolation.REPEATABLE_READ)
本质:加上一个行锁(有事务在读的时候其他事务只能读,要改只能等)
- 幻影读
# 一个事务中,多次对整表进行查询统计,但是结果不一样,产生数据不一致问题
解决方案
@Transactional(isolation = Isolation.SERIALIZABLE)
本质:加上一个表锁
- 总结
并发安全程度:SERIALIZABLE > REPEATABLE_READ > READ_COMMITTED
系统运行效率:READ_COMMITTED > REPEATABLE_READ > SERIALIZABLE
数据库对于隔离属性的支持
隔离属性的值 | MySQL | Oracle |
---|---|---|
ISOLATION_READ_COMMITTED | ✔ | ✔ |
ISOLATION_REPEATABLE_READ | ✔ | |
ISOLATION_SERIALIZABLE | ✔ | ✔ |
Oracle不支持REPEATABLE_READ 如何解决不可重复读?# 采用多版本比对的方式来解决
默认的隔离属性
ISOLATION_DEFAULT:会调用不同数据库所设置的默认隔离属性1. MySQL:REPEATABLE_READ2. Oracle:READ_COMMITTED
-
查看数据库的默认隔离属性
MySQL:select @@transaction_isolation
Oracle:麻烦 多表连接 + 动态视图
隔离属性在实际应用中的建议
# 优先保证效率,推荐使用Spring指定的ISOLATION_DEFAULT,方便实际应用中,并发访问的可能性比较低 必须有海量用户作为前提,即使同时点击,到数据库也不一定是同时如果真遇到并发问题,使用乐观锁,隔离属性是物理锁 Hibernate(JPA):Version MyBatis:通过拦截器自定义开发
传播属性Propagation
描述了解决事务嵌套问题的特征
存在的问题:一个类的方法中调用了其他方法,两者都有事务,大事务中嵌套了很多小事务,它们彼此影响,最终导致外部的大事务丧失原子性解决:传播属性
传播属性的值及其用法
核心:保证同一时间只有一个事务存在
传播属性的值 | 外部不存在事务 | 外部存在事务 | 用法 | 备注 |
---|---|---|---|---|
REQUIRED | 开启新事务 | 融合到外部事务中 | @Transactional(propagation = Propagation.REQUIRED) | 应用在增删改方法 |
SUPPORTS | 不开启新事务 | 融合到外部事务中 | @Transactional(propagation = Propagation.SUPPORTS) | 应用在查询方法 |
REQUIRES_NEW | 开启新事务 | 挂起外部事务,创建新事务 | @Transactional(propagation = Propagation.REQUIRES_NEW) | 应用在日志中 |
NOT_SUPPORTED | 不开启新事务 | 挂起外部事务 | @Transactional(propagation = Propagation.NOT_SUPPORTED) | 极其不常用 |
NEVER | 不开启新事务 | 抛出异常 | @Transactional(propagation = Propagation.NEVER) | 极其不常用 |
MANDATORY | 抛出异常 | 融合到外部事务中 | @Transactional(propagation = Propagation.MANDATORY) | 极其不常用 |
默认的传播属性
REQUIRED是传播属性的默认值# 实际应用建议增删改方法:直接使用默认值REQUIRED查询操作:显示地指定SUPPORTS
其他属性
只读属性readOnly
针对于只进行查询操作的业务方法,可以加入只读属性,提高其运行效率默认值为false使用:@Transactional(readOnly = true)
超时属性timeOut
# 指定了事务等待的最长时间1. 为什么事务要进行等待? 当前事务访问数据时,该数据被别的事务进行了加锁处理,那么该事务就必须等待2. 等待时间 单位:秒3. 使用 @Transactional(timeout = 2)4. 超时属性的默认值:-1 含义:最终由对应的数据库来指定 实际应用中很少指定超时属性
异常属性Exception
Spring事务处理过程中1. 默认对于RuntimeException及其子类,采用的是回滚的策略 默认对于Exception及其子类,采用的是提交的策略2. 使用 @Transactional(rollbackFor = {java.lang.Exception.class}, noRollbckFor = {...}) 实际应用中一般使用默认值,且异常使用RuntimeException及其子类
事务属性常见配置总结
1. 隔离属性 默认值2. 传播属性 增删改:Required(默认值) 查询:Supports3. 只读属性 增删改:readOnly(false) 查询readOnly(true)4. 超时属性 默认值 -15. 异常属性 默认值实际应用:增删改操作 @Transactional查询操作 @Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
基于标签的事务配置方式(事务开发的第二种形式,了解)
# 基于注解 @Transactional的事务配置1. 原始对象<bean id="userService" class="com.liu.service.UserServiceImpl"> <!-- 使用Spring与MyBatis整合时的scanner为我们创建的DAO对象 --> <property name="userDAO" ref="userDAO"/></bean>2. 额外功能(事务)<!-- Spring中的DataSourceTransactionManager封装了事务处理的额外功能 --><bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <!-- 使用Spring与MyBatis整合时的dataSource进行注入 --> <property name="dataSource" ref="dataSource"/></bean>3. 切入点@Transactionalpublic class UserServiceImpl implements UserService { private UserDAO userDAO;4. 组装切面<tx:annotation-driven transaction-manager="dataSourceTransactionManager"/># 基于标签的事务配置方式与基于注解方式的差异在于第3、4步1. 原始对象2. 额外功能3. 事务属性 <tx:advice id="txAdvice" transaction-manager="dataSourceTransactionManager" > <tx:attributes> <tx:method name="register" isolation="", propagation=""></tx:method> </tx:attributes> </tx:advice>4. 组装切面<aop:config> <aop:pointcut id="pc" expression="execution(* com.liu.service.UserServiceImpl.register(..))"></aop:pointcut> <aop:advisor advice-ref="txAdvice" pointcut-ref="pc"></aop:advisor></aop:config>
- 实际应用
普通的标签事务配置存在冗余,给新的方法添加事务就得新增一个tx:method标签1. 实际应用中将进行增删改的方法都以modify开头,然后在标签中使用通配符 查询方法命名无所谓2. 实际应用中组装时expression指定为包名1. 原始对象2. 额外功能3. 事务属性 <tx:advice id="txAdvice" transaction-manager="dataSourceTransactionManager" > <tx:attributes> <tx:method name="modify*"></tx:method> <tx:method name="*" propagation="SUPPORTS",read-only="true"></tx:method> </tx:attributes> </tx:advice>4. 组装切面<aop:config> <aop:pointcut id="pc" expression="execution(* com.liu.service..*.*(..))"></aop:pointcut> <aop:advisor advice-ref="txAdvice" pointcut-ref="pc"></aop:advisor></aop:config>
Spring整合MVC
环境搭建
- 在项目中添加新的模块,以webapp构建
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GNf536zf-1620665224832)(D:\截图\image-20210422110929801.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OkI3JRGp-1620665224833)(D:\截图\image-20210422111021217.png)]
BUID SUCCESS构建成功
- 在src/main下创建java、resource包,并设置为source root
- 配置pom.xml文件
<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.liu</groupId> <artifactId>Spring5-04-Web</artifactId> <version>1.0-SNAPSHOT</version> <packaging>war</packaging> <name>Spring5-04-Web Maven Webapp</name> <!-- FIXME change it to the project's website --> <url>http://www.example.com</url> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> </properties> <dependencies> <!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api --> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0</version> <scope>provided</scope> </dependency> <!-- https://mvnrepository.com/artifact/javax.servlet.jsp/javax.servlet.jsp-api --> <dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>javax.servlet.jsp-api</artifactId> <version>2.3.1</version> <scope>provided</scope> </dependency> <!-- https://mvnrepository.com/artifact/javax.servlet.jsp.jstl/jstl --> <dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework/spring-web --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>5.1.14.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>5.1.14.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-beans</artifactId> <version>5.1.14.RELEASE</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>RELEASE</version> <scope>test</scope> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework/spring-context --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.1.4.RELEASE</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.4.6</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.1.14.RELEASE</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>2.0.2</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.18</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>5.1.14.RELEASE</version> </dependency> <!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-log4j12 --> <dependency> <!-- 日志门面,引入后可以将默认的logback、log4j2摒弃,使之可以支持log4j--> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.7.25</version> </dependency> <!-- https://mvnrepository.com/artifact/log4j/log4j --> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.48</version> </dependency> </dependencies> <build> </build></project>
为什么要整合MVC框架
# MVC框架1. 提供了控制器(Controller) 用于调用Service2. 提供请求响应的处理3. 接收请求参数4. 控制程序的运行流程5. 视图解析(JSP JSON Freemarker Thymeleaf)
Spring可以整合哪些MVC框架
1. struts12. webwork3. jsf4. struts25. springMVC 应用广泛
Spring整合MVC框架的核心思路
工厂
工厂-Spring核心1. Web开发过程中如何创建工厂 ApplicationContext context = new ClassPathXmlApplicationContext("/applicationContext.xml"); 使用WebXmlApplicationContext()2. 如何保证工厂(重量级资源)唯一、可以被共用 被共用:Web开发中用于存储对象的作用域 request/session/ServletContext(application) 把工厂存储在ServletContext这个作用域中 ServletContext.setAttribute("xxx", context); 唯一:ServletContext对象(只会被创建一次)创建的同时 运行ApplicationContext context = new ClassPathXmlApplicationContext("/applicationContext.xml"); 如何知道ServletContext对象什么时候创建? 使用ServletContextListener监听,在ServletContext创建时就会被调用(只会被调用一次),把创建工厂的代码写在ServletContextListener中,则只会被创建一次 # 总结既要创建工厂,又要保证其唯一、能被共用。1. ServletContextListener(只会被创建一次)中创建工厂保证了唯一2. ServletContext.setAttribute("xxx", context)保证了工厂能被共用Spring将上述两步封装为ContextLoaderListener 1. 创建工厂 2. 把工厂存储在ServletContext中
ContextLoaderListener使用方式在web.xml中<listener> <listener-class>org.springfamework.web.context.ContextLoaderListener</listener-class></listener><!-- 告诉配置文件的路径 --><context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:applicationContext.xml</param-value></context-param>
控制器
依赖注入:把Service对象注入控制器对象
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hwZ6Xt9l-1620665224833)(D:\截图\image-20210425110933038.png)]
注解基础
概念及原因
1. 概念 指的是在类或方法上加入特定的注解(@XXX),完成特定功能的开发2. 为什么使用注解编程 代码简洁,开发速度大大提高 Spring2.x引入注解 Spring3.x完善注解 SpringBoot普及推广注解编程
作用
- 替换XML这种配置方式,简化配置
@Componentpublic class User{ UserDAO ==> userDAO }即可实现原本在XML配置文件中需要配置标签来完成的工作<bean id="user" class="com.liu.annotation.User"/>
- 替换接口,实现调用双方的契约性
调用者如果不知道被调用者的具体类名和方法名,则需要使用一个接口来建立契约,规定被调用者中必须实现并提供给调用者的方法。 例如之前额外功能中需要实现MethodInterceptor接口,额外功能需要按规定写在invoke方法中,而使用注解可以替换接口,额外功能的方法没有要求,只需要加上@Around注解即可。 更为灵活和方便。
Spring注解的发展历程
1. Spring2.x开始支持注解编程 @Component、@Service、@Scope... 目的:为了在某些情况下简化XML的配置,作为XML开发的有益补充2. Spring3.x @Configuration、@Bean... 目的:彻底替换XML开发,基于纯注解编程3. Spring4.x SpringBoot 提倡使用注解编程进行常见的开发
Spring注解开发问题思考
Spring基于注解进行配置后,代码还能否方便维护呢? 基于注解开发,修改时需要直接修改代码。# 在Spring应用注解是,如果对注解配置的内容不满意,可以通过Spring配置文件进行覆盖
Spring基础注解(Spring2.x)
# 这个阶段的注解,仅仅是简化XML的配置,并不能完全替代XML
对象创建相关注解
搭建开发环境
<context:component-scan base-package="com.liu"/>作用:让Spring框架在指定的包及其子包中扫描对应的注解,使其生效。
@Component
作用:替换原有Spring配置文件中的<bean id="..." class="...">标签1. 通过反射获取class——类的全限定类名2. 默认id值为首单词首字母小写 UserDAO ==> userDAO
细节
- 如何显式地指定工厂创建对象的id值
@Component("u")
- Spring配置文件如何覆盖注解配置内容
applicationContext.xml<bean id="u" class="com.liu.bean.User">...</bean>id、class的值要和注解中设置的保持一致,才能进行覆盖
@Component的衍生注解
@Repository@Service@Controller这些衍生注解本质上就是@Component,作用、细节都是完全一样的# 提供衍生注解的目的:更加准确地表达某个类型的作用@Repository用在DAO @Service用在Service @Controller用在Controller* 注意:Spring整合MyBatis时不使用@Repository、@Component,因为DAO是UserDAOMapper.xml动态创建的
@Scope
作用:控制简单对象的创建次数@Scope("singleton(默认)/prototype")替换<bean id="" class="" scope="singleton/prototype"/>
@Lazy
作用:延迟创建单例对象@Lazy替换<bean id="" class="" lazy="true"/>
生命周期方法相关注解
1. 初始化相关方法 @PostConstruct 实现InitializingBean接口 或 <bean init-method="" />2. 销毁方法 @PreDestroy 实现DisposableBean 或 <bean destroy-method="" /> 注意1. 以上两个注解并不是Spring提供的,是JSR(JavaEE规范)520提供的2. 再一次验证,注解替换了接口,实现了契约性
注入相关注解
用户自定义类型注入@Autowired
xml配置方式是创建userDAO然后再在userService的bean中通过<property>注入,实际上是调用set方法,所以@Autowired加在set方法上
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nL650PbM-1620665224834)(D:\截图\image-20210427111756065.png)]
# 细节1. Autowired注解基于类型进行注入【推荐】 基于类型注入指的是注入对象的类型,必须与目标成员变量类型相同或是其子类(实现类)。保证了不会注入其他的加了@Repository注解的类型2. 如果是基于id值进行注入(了解即可) 需要@Autowired和@Qualifier配合使用 @Autowired @Qualifier("userDAOImpl") public void setUserDAO(UserDAO userDAO) { this.userDAO = userDAO; }3. @Autowired注解放置位置 a)放置在对应成员变量的set方法上 # b)直接把注解放在成员变量上,Spring此时会通过反射直接对成员变量进行注入【推荐】4. JavaEE规范中类似功能的注解 JSR520 @Resource(name="userDAOImpl") 基于id进行注入 与@Autowired @Qualifier("userDAOImpl")配合使用效果一样 注意: 如果应用@Resource注解时没有指定name或者name没有配对成功,那么会按照类型进行注入 JSR330 @Inject 作用和@Autowired完全一致 基于类型进行注入 应用在EJB3.0中 需要引入依赖才能使用 <dependency> <groupId>javax.inject</groupId> <artifactId>javax.inject</artifactId> <version>1</version> </dependency>
JDK类型注入@Value
- 开发步骤
类似之前的配置文件参数化1. 创建xxx.properties文件 在其中设置id=... name=...2. 配置Spring的工厂来读取这个参数配置文件 在applicationContext.xml中添加 <context:property-placeholder location="classpath:init.properties"/>3. 在类型的属性上加上@Value注解 @Value("${key}")
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Nqy0Q65T-1620665224834)(D:\截图\image-20210427155408774.png)]
- @PropertySource
作用:用于替换Spring配置文件中的<context:property-placeholder location="..."/># @PropertySource("...")加在类型上,替换开发步骤中的第二步
- @Value注解使用细节
1. @Value注解不能使用在静态成员变量上2. @Value + @Properties这种方式不能注入集合类型 Spring提供了新的配置形式 YAML YML(SpringBoot)
注解扫描详解
<context:component-scan base-package="com.liu"/>默认扫描策略:扫描当前指定的包及其子包 # 不够灵活
- 排除方式
排除不想进行操作的类型<context:component-scan base-package="com.liu"> <context:exclude-filter type="" expression="" /> type: assignable 排除特定类型 annotation 排除特定的注解 aspectj regex custom</context:component-scan>
- 包含方式
<context:component-scan base-package="com.liu" use-default-filters="false"> <context:include-filter type="" expression="" /></context:component-scan>和排除方式的区别1. use-defalt-filters="false" 作用:让Spring默认的注解扫描方式失效2. <context:include-filter type="" expression="" /> type: assignable 包含特定类型 annotation 包含特定的注解 aspectj regex custom 自定义策略框架底层开发
注解开发的思考
- 配置互通
Spring基于注解、配置文件的配置是可以互通的@Repositorypublic class UserDAOImpl{}public class UserServiceImpl{ public UserDAO userDAO; set get}<bean id="userService" class="com.liu.UserServiceImpl"> <property name="userDAO" ref="userDAOImpl"/></bean>
- 什么情况下使用注解 什么情况下使用配置文件
@Component 替换 <bean>标签1. 基础注解(@Component @Autowired @Value)只能用于程序员开发的类型 User、UserService、UserDAO2. 使用其他非程序员开发的类型时,使用配置文件<bean>进行配置 SqlSessionFactoryBean、MapperScannerConfigure
Spring高级注解(Spring3.x及以上)
# Spring在3.x提供新的注解,用于替换XML配置文件。
配置Bean-@Configuration
@Configurationpublic class AppConfig{ }加上@Configuration注解之后,AppConfig这个类就成了配置Bean
问题
- 配置Bean替换了XML文件的什么内容?
原来由applicationContext.xml内完成的创建bean、属性(依赖)注入、注解扫描配置等
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iiCwxqCn-1620665224835)(D:\截图\image-20210504133317554.png)]
- 原来的ClassPathXmlApplication替换为AnnotationConfigApplicationContext
1. 创建工厂代码 ApplicationContext context = new AnnotationConfigApplicationContext();2. 指定配置文件 两种方式 1)指定配置Bean的Class ApplicationContext context = new AnnotationConfigApplicationContext(Appconfig.class); 2)指定配置Bean的所在路径 ApplicationContext context = new AnnotationConfigApplicationContext("com.liu");
相关细节
-
基于注解开发使用日志
不能集成Log4j ?后续都使用logback
pom.xml文件中引入相关jar包 <!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-api --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.25</version> </dependency> <!-- https://mvnrepository.com/artifact/org.slf4j/jcl-over-slf4j --> <dependency> <groupId>org.slf4j</groupId> <artifactId>jcl-over-slf4j</artifactId> <version>1.7.25</version> </dependency> <!-- https://mvnrepository.com/artifact/ch.qos.logback/logback-classic --> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.2.3</version> <scope>test</scope> </dependency> <!-- https://mvnrepository.com/artifact/ch.qos.logback/logback-core --> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-core</artifactId> <version>1.2.3</version> </dependency> <!-- https://mvnrepository.com/artifact/org.logback-extensions/logback-ext-spring --> <dependency> <groupId>org.logback-extensions</groupId> <artifactId>logback-ext-spring</artifactId> <version>0.1.4</version> </dependency>
- @Configuration注解的本质
本质:是@Component注解的衍生注解
@Bean注解
@Bean注解在配置Bean内进行使用,等同于XML配置文件中的<bean>标签
基本使用
对象的创建
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aUeC2T2j-1620665224835)(D:\截图\image-20210504204810226.png)]
1. 简单对象
能够直接通过new的方式进行创建的对象
User、UserService、UserDAO
2. 复杂对象
不能直接通过new的方式进行创建的对象
Connection、SqlSessionFactory
- 注意事项
如果使用FactoryBean的方式封装了创建复杂对象的过程,可以在AppConfig的@Bean中对其进行整合
实际开发中在@Bean中直接创建即可,整合主要运用在遗留系统当中
@Bean
public Connection conn1(){
Connection conn = null;
try {
ConnectionFactoryBean connectionFactoryBean = new ConnectionFactoryBean();
conn = connectionFactoryBean.getObject();
} catch (Exception e) {
e.printStackTrace();
}
return conn;
}
自定义id值
@Bean("u")
控制对象的创建次数
@Bean
@Scope("prototype/singleton") 默认为singleton
注入
用户自定义类型
@Configuration
public class AppConfig1 {
@Bean
public UserDAO userDAO(){
return new UserDAOImpl();
}
@Bean
public UserService userService(UserDAO userDAO){
UserServiceImpl userService = new UserServiceImpl();
userService.setUserDAO(userDAO);
return userService;
}
}
//简化写法
也可以在userService.setUserDAO()中直接调用userDAO方法
@Bean
public UserService userService(){
UserServiceImpl userService = new UserServiceImpl();
userService.setUserDAO(userDAO());
return userService;
}
JDK类型
手动setXXX
@Bean
public Customer customer(){
Customer customer = new Customer();
customer.setId(1);
customer.setName("liu");
return customer;
}
- 细节分析
# 如果直接在代码中手动setXXX,存在耦合问题,此时可以使用之前的@PropertySource和@Value注解,配合参数配置文件
@Configuration
@PropertySource("classpath:/init.properties")
public class AppConfig1 {
@Value("${id}")
private Integer id;
@Value("${name}")
private String name;
@ComponentScan注解
1. 在配置Bean中使用,等同于原来xml文件中的<context:component-scan>标签
2. 目的:进行相关注解的扫描(@Component、@Value...)
@Configuration
@ComponentScan(basePackages = "com.liu.scan")
public class AppConfig_Scan {
}
排除和包含的使用
- 排除
1. xml配置文件方式
<context:component-scan base-package="com.liu">
<context:exclude-filter type="" expression="" />
</context:component-scan>
2. 注解方式
@Configuration
@ComponentScan(basePackages = "com.liu.scan",
excludeFilters = {
@ComponentScan.Filter(type = FilterType.ANNOTATION, value = {Service.class}),
@ComponentScan.Filter(type = FilterType.ASPECTJ, pattern = "*..User1")})
public class AppConfig_Scan {
}
type = FilterType.ANNOTATION value
.ASSIGNABLE_TYPE value
.ASPECTJ pattern
.REGEX pattern
.CUSTOM value
- 包含
<context:component-scan base-package="com.liu" use-default-filters="false"> <context:include-filter type="" expression="" /></context:component-scan> @ComponentScan(basePackages = "com.liu.scan", useDefaultFilters = false, includeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION, value = {Service.class})})public class AppConfig_Scan {}
Spring工厂创建对象的多种配置方式
多种配置的应用场景
主要使用的是@Component和@Bean注解
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-x3EdNjkd-1620665224835)(D:\截图\image-20210507212002944.png)]
配置优先级
@Component及其衍生注解 < @Bean < 配置文件Bean标签
# 优先级高的可以覆盖优先级低的,id值必须保持一致
将配置文件与注解整合,在配置bean上添加
@ImportResource("applicationContext.xml")
- 解决基于注解进行配置的耦合问题
如对原来@Configuration中的@Bean不满意
@Bean
public UserDAO userDAO(){
return new UserDAOImpl();
}
# 直接创建新的UserDAOImpl类型
然后整合配置Bean和配置文件,在applicationContext.xml对其进行覆盖
那么此时需要修改配置Bean的源码(加上@ImportResource),也存在耦合
# 可以直接创建新的配置Bean,然后在创建工厂时引用多个
ApplicationContext context = new AnnotationConfigAoolicationContext(AppConfig1.class, AppConfig2.class);
整合多个配置信息
- 为什么会有多个配置信息
拆分多个配置Bean的开发,是一种模块化开发的形式,也体现面向对象、各司其职的设计思想
- 多配置信息整合的方式
1. 多个配置Bean的整合2. 配置Bean与@Component相关注解的整合3. 配置Bean与SpringXML配置文件的整合
- 整合多种配置需要关注的要点
1. 如何使多配置信息汇总成为一个整体2. 如何实现跨配置的注入
多个配置Bean的整合
多配置的信息汇总
1. base-package进行多个配置Bean的整合 在创建工厂时直接扫描整个包,进行整合 ApplicationContext context = new AnnotationConfigApplicationContext("com.liu.config");2. 将一个配置Bean作为主配置,引入其他的配置Bean @Configuration @Import(AppConfig2.class) public class AppConfig1 { 3. 创建工厂时指定多个配置Bean的Class对象(少用) AnnotationConfigApplicationContext(AppConfig1.class, AppConfig2.class);
跨配置进行注入
在应用配置Bean的过程中,不管使用哪种方式进行配置信息的汇总,注入都是通过成员变量上添加@Autowired注解完成。
@Configuration@Import(AppConfig2.class)public class AppConfig1 { @Autowired private UserDAO userDAO; @Bean public UserService userService(){ UserServiceImpl userService = new UserServiceImpl(); userService.setUserDAO(userDAO); return userService; }}@Configurationpublic class AppConfig2 { @Bean public UserDAO userDAO(){ return new UserDAOImpl(); }}
配置Bean与@Component相关注解的整合
在配置Bean中添加**@ComponentScan(basePackages = “com.liu.xxx”)**即可
@Component(@Repository)
public class UserDAOImpl implements UserDAO{
}
@Configuration
@ComponentScan(basePackages = "com.liu.xxx")
public class AppConfig{
@Autowired
private UserDAO userDAO;
@Bean
public UserService userService(){
UserServiceImpl userService = new UserServiceImpl();
userService.setUserDAO(userDAO);
return userService;
}
}
配置Bean与配置文件整合
应用场景
1. 遗留系统的整合
2. 配置覆盖
配置Bean引入@ImportResource("applicationContext.xml")即可
配置Bean的底层实现原理
Spring在配置Bean中加入@Configuration注解后,底层就会通过Cglib的方式,进行对象相关的配置、处理
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nW3X3MNk-1620665224836)(D:\截图\image-20210509172846456.png)]
四维一体的开发思想
什么是四维一体
Spring在开发一个功能有四种方式,虽然开发方式不同,但最终效果是一样的
# 且底层使用的都是PropertySourcesPlaceholderConfigurer这个类
1. 基于Schema
2. 基于特定功能注解
3. 基于原始bean标签
4. 基于@Bean注解
四维一体开发案例
案例:小配置文件进行注入
1. <context:property-placeholder location="classpath:four.properties"/>
2. @PropertySource 【推荐】
3. <bean id="" class="PropertySourcePlaceholderConfigure"/>
4. @Bean 【推荐】
纯注解AOP编程
搭建环境
1. 应用配置Bean
2. 注解扫描
@Configuration
@ComponentScan(basePackages = "com.liu.aop")
public class AppConfig {
}
开发步骤
1. 原始对象
@Service(@Component)
public class UserServiceImpl implements UserService{
}
2. 创建切面类(额外功能、切入点、组装切面)
@Aspect
@Component
public class MyAspect {
@Around("execution(* login(..))")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("---Aspect Log---");
Object ret = joinPoint.proceed();
return ret;
}
}
3. @EnableApectjAutoProxy加在配置Bean上
替换原Spring配置文件中<aop:aspectj-autoproxy/>
细节分析
1. 代理创建方式的切换 JDK Cglib
原来:<aop:aspectj-autoproxy proxy-target-class="true/false" />
@EnableAspectjAutoProxy(proxyTargetClasss = true)
2. SpringBoot AOP的开发方式
程序员只用完成原始对象创建、切面类(额外功能、切入点、组装切面)两步
第三步SpringBoot已经设置好了
Spring AOP代理默认实现是JDK,SpringBoot默认的是Cglib
纯注解Spring整合MyBatis
基础配置(放在配置Bean当中)
1. 连接池
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=false&allowPublicKeyRetrieval=true"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</bean>
@Bean
public DataSource dataSource(){
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName("");
dataSource.setUrl("");
...
return dataSource;
}
2. SqlSessionFactoryBean
<bean id="sqlSessionFactoryBean" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="typeAliasesPackage" value="com.liu.entity"/>
<property name="mapperLocations">
<list>
<value>classpath:com.liu.mapper/*Mapper.xml</value>
</list>
</property>
</bean>
@Bean
public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource){
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSource);
sqlSessionFactoryBean.setTypeAliasesPackage("com.liu.mybatis");
sqlSessionFactoryBean.setMapperLocations(new ClassPathResource("UserDAOMapper.xml"));
return sqlSessionFactoryBean;
}
3. 创建DAO对象 MapperScannerConfigure
<bean id="scanner" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactoryBean"/>
<property name="basePackage" value="com.liu.dao"/>
</bean>
Spring提供了专属的注解@MapperScan(),会自动扫描SqlSessionFactoryBean,只用设置basePackages
配置Bean添加@MapperScan(basePackages="com.liu.dao")
- 编码
1. 实体
2. 表
3. DAO接口
4. Mapper文件
MapperLocations编码时通配的写法
//设置Mapper文件的路径
sqlSessionFactoryBean.setMapperLocations(Resource...可变长参数);
sqlSessionFactoryBean.setMapperLocations(new ClassPathResource("UserDAOMapper.xml"));
实际应用中,有一组Mapper.xml文件
//路径通配解析
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
Resource[] resources = resolver.getResources("com.liu.mapper/*Mapper.xml");
sqlSessionFactoryBean.setMapperLocations(resources)
配置Bean中数据耦合的问题
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/mybatis?useSSL=false");
dataSource.setUsername("root");
dataSource.setPassword("123456");
sqlSessionFactoryBean.setTypeAliasesPackage("com.liu.mybatis");
Resource[] resources = resolver.getResources("com.liu.mapper/*Mapper.xml");
过多具体的字符串硬编码在代码中,存在耦合
将这些字符串信息提取到配置文件中 然后进行注入
1. mybatis.properties文件
mybatis.driverClassName = com.mysql.jdbc.Driver
mybatis.url = jdbc:mysql://localhost:3306/mybatis?useSSL=false
mybatis.username = root
mybatis.password = 123456
mybatis.typeAliasesPackages = com.liu.mybatis
mybatis.mapperLocations = com.liu.mapper/*Mapper.xml
2. 创建一个实体类来封装配置信息
@Component
@PropertySource("classpath:mybatis.properties")
public class MybatisProperties {
@Value("${mybatis.driverClassName}")
private String driverClassName;
@Value("${mybatis.url}")
private String url;
@Value("${mybatis.username}")
private String username;
@Value("${mybatis.password}")
private String password;
@Value("${mybatis.typeAliasesPackages}")
private String typeAliasesPackages;
@Value("${mybatis.mapperLocations}")
private String mapperLocations;
getter、setter
}
3. 将封装了配置信息的实体类注入到配置Bean中,然后替换硬编码的具体字符串
@Autowired
private MybatisProperties mybatisProperties;
dataSource.setDriverClassName(mybatisProperties.getDriverClassName());
dataSource.setUrl(mybatisProperties.getUrl());
dataSource.setUsername(mybatisProperties.getUsername());
dataSource.setPassword(mybatisProperties.getPassword());
sqlSessionFactoryBean.setTypeAliasesPackage(mybatisProperties.getTypeAliasesPackages());
Resource[] resources = resolver.getResources(mybatisProperties.getMapperLocations());
纯注解事务编程
1. 原始对象 XXXService
<bean id="userService" class="com.liu.service.UserServiceImpl">
<property name="userDAO" ref="userDAO"/>
</bean>
@Service
public class UserServiceImpl implements UserService{
@Autowired
private UserDAO userDAO;
}
2. 额外(事务)功能
<!-- DataSourceTransactionManager -->
<bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
@Bean
public DataSourceTransactionManager dataSourceTransactionManager(DataSource dataSource){
DataSourceTransactionManager dstm = new DataSourceTransactionManager();
dstm.setDataSource(dataSource);
reutrn dstm;
}
3. 事务属性(切入点)
@Transactional
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserDAO userDAO;
4. 基于Schema的事务配置
<tx:annotation-driven transaction-manager="dataSourceTransactionManager"/>
配置Bean加上@EnableTransactionManagement
Spring整合MyBatis ---> DAO
事务基于注解 ---> Service
Spring框架中YML的使用
YML(YAML)是一种新形式的配置文件,比XML简单,比Properties强大
- properties进行配置存在的问题
1. 表达过于繁琐且无法表达数据的内在联系
2. 无法表达对象、集合类型
YML语法简介
1. 定义yml文件
xxx.yml xxx.yaml
2. 语法
1)基本语法
key: value
2)对象 通过缩进表达从属关系
account:
id: 1
password: 123456
3)定义集合
service:
- 1111
- 2222
Spring整合YML
思路分析
xxx.properties ==> Properties集合 ==> PropertySourcePlaceholderConfigurer ==> 注入给相应成员变量
# 整合思路
解决怎么让Spring读取yml、将读取的yml转换为Properties被PropertySourcePlaceholderConfigurer读取
使用YamlPropertiesFactoryBean
1. 准备yml配置文件
init.yml
name: liush
password: 123456
2. 读取yml 转换成Properties
YamlPropertiesFactoryBean.setResources(new ClassPathResource(yml文件的路径));
YamlPropertiesFactoryBean.getObject() ==> Properties
3. 将Properties设置给PropertySourcePlaceholderConfigurer 【核心】
PropertySourcePlacholderConfigurer.setProperties();
4. 类中@Value注解进行注入
开发步骤
- 环境搭建
<!-- https://mvnrepository.com/artifact/org.yaml/snakeyaml -->
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
<version>1.25</version>
</dependency>
最低版本 1.18
- 编码
1. 准备yml配置文件
#name: liush
#password: 123456
account:
name: lius
password: 1234567
2. 配置Bean中完成 YAML读取、PropertySourcePlaceholderConfigurer的创建
public PropertySourcesPlaceholderConfigurer configurer(){
YamlPropertiesFactoryBean yamlPropertiesFactoryBean = new YamlPropertiesFactoryBean();
yamlPropertiesFactoryBean.setResources(new ClassPathResource("init.yml"));
Properties properties = yamlPropertiesFactoryBean.getObject();
PropertySourcesPlaceholderConfigurer configurer = new PropertySourcesPlaceholderConfigurer();
configurer.setProperties(properties);
return configurer;
}
3. 类加入@Value注解
@Component
public class Account {
@Value("${account.name}")
private String name;
@Value("${account.password}")
private String password;
Spring与YML集成的问题
1. 集合集成问题
如果是注入list集合
list:
- 111
- 222
会报错,YamlPropertiesFactoryBean无法解析
解决:使用SpringEL表达式,先拿整个字符串再拆分
list: 111,222
@Value("#{'${list}'.split(',')}")
2. 对象类型的YAML进行配置时过于繁琐
@Value("${account.name}")
private String name;
@Value("${account.password}")
private String password;
Spring目前无法解决
# SpringBoot中通过@ConfigurationProperties注解可以解决以上两个问题