Spring学习笔记
Spring框架概述
- Spring是轻量级 开源的 J2EE框架
- Spring可以解决企业应用开发的复杂性
- Spring有两个核心部分:IOC和AOP
IOC:控制反转,把创建对象过程交给Spring进行管理
AOP:面向切面,不修改源代码进行功能增强
Spring特点
-
方便解耦,简化开发
-
AOP编程支持
-
方便程序的测试
-
方便和其他框架进行整合
-
方便进行事务操作
-
降低API开发难度
……
入门案例
用配置的方式创建对象
-
下载地址:https://repo.spring.io/release/org/springframework/spring/
-
创建普通java项目,导入spring的基本jar包。另加一个(commons-logging-1.1.1.jar)
-
创建Spring配置文件,在配置文件中配置需要创建的对象
-
配置文件使用的是.xml文件。我们在src下创建一个随意名字的.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"> <!--用配置的形式创建User对象--> <bean id="user" class="com.company.User"></bean> </beans>
<bean>标签专门用来做对象创建的事情。class属性是需要创建的对象的全类名
-
创建一个Test类来测试这种创建对象的方式:
public class TestSpring { @Test public void testAdd() { // 1、加载spring的配置文件 ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml"); // 2、获取配置中创建的对象 User user = context.getBean("user", User.class); // 测试由Spring配置文件方式创建对象 System.out.println(user); user.add(); } }
其中,getBean方法第一个参数是配置文件bean1.xml中<bean>标签的id值。
-
IOC容器
什么是IOC
- 控制反转,把对象的创建 和 对象之间的调用过程 交给Spring进行管理
- 使用IOC目的:为了降低耦合度
- 入门案例就是IOC的一种实现
IOC底层原理
- xml解析
- 工厂模式
- 反射
工厂模式
当需要在A类中调用B类的方法时,引入一个工厂类,让工厂类完成对象创建的工作
IOC过程
-
xml配置文件,配置需要创建的对象
<bean id="dao" class="com.company.User"></bean>
-
有Service类和User类,创建工厂类
public class UserFactory { public static User getUser() { String classValue = class属性值 // 通过xml解析得到bean标签中的class属性,即全类名 Class aClass = Class.forName(classValue); // 反射 得到类的字节码对象 return (User) aClass.newInstance(); // 通过字节码对象创建这个类的实例对象,已弃用,可用aClass.getDeclaredConstructor().newInstance()替代 } }
IOC(接口)
- IOC思想基于IOC容器完成,IOC容器底层就是对象工厂
- Spring提供两种方式实现IOC容器(两个接口)
- BeanFactory:IOC容器基本实现方式,一般不供开发人员使用
- 加载配置文件时并不会创建对象,在使用时才创建
- ApplicationContext:BeanFactory的子接口,功能更强大。给我们开发人员使用
- 加载配置文件后就已经创建了对象
- BeanFactory:IOC容器基本实现方式,一般不供开发人员使用
ApplicationContext接口的实现类(Ctrl+H):
IOC操作Bean管理
什么是Bean管理?
-
Spring创建对象
-
Spring注入属性
Bean管理操作有两种方式
-
基于xml配置文件方式实现
<bean id="user" class="com.company.User"></bean>
<bean>标签的常用属性:
id
:唯一标识class
:类全路径
创建对象时,默认执行无参构造方法
-
基于注解方式实现
注入属性
-
基于xml方式注入属性
-
DI
:依赖注入,就是注入属性-
使用set方法注入
<!--set方法注入属性--> <bean id="book" class="com.company.Book"> <property name="bname" value="金瓶梅"/> </bean>
将成员变量
bname
的值设置为金瓶梅
-
使用有参构造注入
<!--有参构造注入属性--> <bean id="orders" class="com.company.Order"> <constructor-arg name="oname" value="电脑订单"/> <constructor-arg name="address" value="China"/> </bean>
-
-
xml方式注入属性时的特殊情况
- null值
<!-- bname属性值为null -->
<bean id="book" class="com.company.Book">
<property name="bname">
<null/>
</property>
</bean>
- 属性值包含特殊符号
<!-- 假设属性bname的值为 <<金瓶梅>> -->
<!-- 方式一:采用转义的符号-->
<bean id="book" class="com.company.Book">
<property name="bname" value="<<金瓶梅>>"/>
</bean>
<!-- 方式二:采用CDATA的格式-->
<bean id="books" class="com.company.Book">
<property name="aname">
<value><![CDATA[<<金瓶梅>>]]></value>
</property>
</bean>
注入属性-外部bean
1、创建两个类 service类 和 dao类
2、在service调用dao里面的方法
普通形式:
// UserDaoImpl.java
public class UserDaoImpl implements UserDao{
@Override
public void update() {
System.out.println("dao包中UserDaoImpl类下的update方法被调用");
}
}
// UserService.java
public class UserService {
public void add() {
System.out.println("service包中UserService类下的add方法被调用");
/* 这里,假设要在service中调用dao的方法 */
// 创建UserDao对象
UserDao userDao = new UserDaoImpl();
userDao.update(); // 创建对象后直接调用
}
}
Spring形式
需要将UserDao作为属性注入到service
// UserService.java
public class UserService {
// 创建UserDao类型属性,生成其对应set方法
private UserDao userDao;
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
public void add() {
System.out.println("service包中UserService类下的add方法被调用");
userDao.update(); // 调用
}
}
配置文件:
<!-- 1、service和dao对象的创建-->
<bean id="userService" class="com.company.service.UserService">
<!-- 2、注入userDao对象
name:类中的属性名称
ref:创建userDao对象 相应的 bean标签中的id值
-->
<property name="userDao" ref="userDao"/>
</bean>
<bean id="userDao" class="com.company.dao.UserDaoImpl"></bean>
用一个测试类观察输出情况:
public class TestBean {
@Test
public void test() {
ApplicationContext context = new ClassPathXmlApplicationContext("bean2.xml");
UserService userService = context.getBean("userService", UserService.class);
userService.add();
}
}
注入属性-内部bean
-
一对多关系:例如部门和员工
一个部门可以有多个员工,员工属于某一个部门
-
在实体类之间表示 一对多 关系。代码表示为:
package com.company.bean; /** * 部门类 */ public class Dept { private String dname; public void setDname(String dname) { this.dname = dname; } }
package com.company.bean; /** * 员工类 */ public class Emp { private String ename; private String gender; // 用一个对象表示员工属于某一个部门 private Dept dept; public void setEname(String ename) { this.ename = ename; } public void setGender(String gender) { this.gender = gender; } public void setDept(Dept dept) { this.dept = dept; } }
-
在spring中用配置文件进行配置
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="emp" class="com.company.bean.Emp"> <property name="ename" value="小明"/> <property name="gender" value="男"/> <!-- 设置对象类型的属性--> <property name="dept"> <bean id="dept" class="com.company.bean.Dept"> <property name="dname" value="研发部"/> </bean> </property> </bean> </beans>
这里可以使用ref属性指向一个外部bean,也可以:在一个bean中直接写另一个bean。
-
将
toString()
方法都加入到Emp
和Dept
两个类中,测试一下:@Test public void testInnerBean(){ ApplicationContext context = new ClassPathXmlApplicationContext("bean3.xml"); Emp emp = context.getBean("emp", Emp.class); System.out.println(emp); }
可以看到正常输出:
Emp{ename='小明', gender='男', dept=Dept{dname='研发部'}} Process finished with exit code 0
xml注入集合属性
-
注入数组类型属性
-
注入List集合类型属性
-
注入Map集合类型属性
演示:创建一个Stu类,该类中包含多个集合类型属性
// Stu.java
package com.company.collectiontype;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class Stu {
// 1、数组类型属性
private String[] courses;
// 2、List集合类型属性
private List<String> list;
// 3、Map集合类型属性
private Map<String, String> map;
// 4、Set集合类型属性
private Set<String> set;
public void setSet(Set<String> set) {
this.set = set;
}
public void setList(List<String> list) {
this.list = list;
}
public void setMap(Map<String, String> map) {
this.map = map;
}
public void setCourses(String[] courses) {
this.courses = courses;
}
@Override
public String toString() {
return "Stu{" +
"courses=" + Arrays.toString(courses) +
", list=" + list +
", map=" + map +
", set=" + set +
'}';
}
}
然后在配置文件中注入对应属性。bean1.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来完成对象创建-->
<bean id="stu" class="com.company.collectiontype.Stu">
<!-- 数组类型的属性注入-->
<property name="courses">
<list>
<value>Java课</value>
<value>MySQl课</value>
</list>
</property>
<!-- List集合类型属性注入-->
<property name="list">
<array>
<value>小明</value>
<value>小花</value>
</array>
</property>
<!-- Map类型属性注入-->
<property name="map">
<map>
<entry key="JAVA" value="Java课"/>
<entry key="MYSQL" value="MySQL课"/>
</map>
</property>
<!-- Set类型属性注入-->
<property name="set">
<set>
<value>Mysql数据库</value>
<value>Redis数据库</value>
</set>
</property>
</bean>
</beans>
其中,集合数据类型的property标签下,既可以使用<list>
标签,也可使用<array>
标签。
map类型标签下是<entry>
标签。
用@Test
测试:
// TestSpring.java
public class TestSpring {
@Test
public void testCollection() {
ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
Stu stu = context.getBean("stu", Stu.class);
System.out.println(stu);
}
}
可以看到,正常输出:
Stu{courses=[Java课, MySQl课], list=[小明, 小花], map={JAVA=Java课, MYSQL=MySQL课}, set=[Mysql数据库, Redis数据库]}
Process finished with exit code 0
集合里面设置对象类型的值
假设Stu.java
中有属性private List<Course> courseList
表示学生选的课程,则在bean1.xml中新增如下配置:
# bean1.xml
。。。。。。
<!-- 注入list集合类型,值是对象-->
<property name="courseList">
<list>
<ref bean="course1"/>
<ref bean="course2"/>
</list>
</property>
</bean>
<!-- 创建多个Course对象-->
<bean id="course1" class="com.company.collectiontype.Course">
<property name="cname" value="语文"/>
</bean>
<bean id="course2" class="com.company.collectiontype.Course">
<property name="cname" value="数学"/>
</bean>
</beans>
FactoryBean
-
Spring有两种类型bean,一种普通的我们创建的bean,另一种是工厂bean
-
普通bean:在配置文件中定义的bean类型 就是返回类型
class="com.company.collectiontype.Stu">
这里定义的是Stu类型,代码中得到的也是Stu类型:Stu stu = context.getBean("stu", Stu.class);
工厂bean:在配置文件中定义的类型 可以跟返回类型不一样
工厂bean:
1、创建类,让这个类作为工厂bean,实现接口FactoryBean
2、实现接口里面的方法,在实现的方法中定义返回的类型
Bean的作用域
- 在Spring里面,可以设置bean实例是 单实例 或 多实例
- Spring里面默认情况是单实例
通过spring配置文件bean
标签里面的属性(scope)用于设置单实例或是多实例。
不写这个属性默认就是singleton
singleton 和 prototype 创建对象的时机不一样:
public class TestSpring {
@Test
public void testCollection() {
// ① 单实例 singleton 加载配置文件时就会创建单实例对象
ApplicationContext context = new ClassPathXmlApplicationContext("facbean.xml");
// ② 多实例 prototype 在调用getBean时创建对象
MyBean myBean = context.getBean("myBean", MyBean.class);
System.out.println(myBean);
}
}
Bean生命周期
生命周期:从对象创建到对象销毁的过程
bean生命周期
- 通过构造器创建bean实例(执行无参构造器)
- 为bean的属性设置值和对其他bean引用(调用set方法)
- 调用bean的初始化方法(需要进行相关配置)
- bean可以使用(对象获取到了)
- 当容器关闭时,调用bean的销毁方法(需要进行配置销毁的方法)
演示
// Orders.java
package com.company.bean;
public class Orders {
private String oname;
public Orders() {
System.out.println("first:执行无参构造器创建bean实例");
}
public void setOname(String oname) {
this.oname = oname;
System.out.println("second:调用set方法设置属性值");
}
// 第三步的内容:需要自己 创建执行的初始化方法。(需要自己配置bean中的 init-method)
public void initMethod(){
System.out.println("third:执行初始化方法");
}
// 销毁对象的内容:对象销毁时执行这个方法,也需要在bean中配置 destroy-method
public void destroyMethod(){
System.out.println("fifth:执行销毁方法");
}
}
<?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="orders" class="com.company.bean.Orders" init-method="initMethod" destroy-method="destroyMethod">
<property name="oname" value="汽车订单"/>
</bean>
</beans>
然后用测试类查看:
@Test
public void testLife(){
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean4.xml");
Orders orders = context.getBean("orders", Orders.class);
System.out.println("fourth:得到了创建的对象");
System.out.println(orders);
// 手动让bean实例销毁
context.close();
}
执行@Test后输出结果如下:
first:执行无参构造器创建bean实例
second:调用set方法设置属性值
third:执行初始化方法
fourth:得到了创建的对象
com.company.bean.Orders@34f6515b
fifth:执行销毁方法
Process finished with exit code 0
bean的后置处理器
在以上5个步骤,第3步 调用初始化方法的前后,各有一步骤
……
-
2.5、把bean实例传递bean后置处理器方法
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException
-
③、调用bean的初始化方法(需要进行相关配置)
-
3.5、把bean实例传递bean后置处理器方法
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException
……
后置处理器的演示
⒈创建类,实现接口BeanPostProcessor
,创建后置处理器的类
// MyBeanPost.java
package com.company.bean;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.lang.Nullable;
public class MyBeanPost implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("初始化方法之前执行");
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("初始化方法之后执行");
return bean;
}
}
⒉在配置文件中指定这个类
<?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="orders" class="com.company.bean.Orders" init-method="initMethod" destroy-method="destroyMethod">
<property name="oname" value="汽车订单"/>
</bean>
<!-- 后置处理器是需要在配置文件中手动配置的,这样spring才能知道这个类是后置处理器-->
<bean id="myBeanPost" class="com.company.bean.MyBeanPost"/>
<!-- 这个配置会对每个bean都生效-->
</beans>
⒊执行测试类观察输出结果
first:执行无参构造器创建bean实例
second:调用set方法设置属性值
初始化方法之前执行
third:执行初始化方法
初始化方法之后执行
fourth:得到了创建的对象
com.company.bean.Orders@16aa8654
fifth:执行销毁方法
Process finished with exit code 0
xml自动装配
⒈根据指定装配规则,由spring自动将匹配的属性值进行注入
⒉演示自动装配过程
我们新建两个类代表员工Emp
和部门Dept
,其中员工中有属性private Dept dept;
// Emp.java
package com.company.autowire;
public class Emp {
private Dept dept;
public void setDept(Dept dept) {
this.dept = dept;
}
@Override
public String toString() {
return "Emp{" +
"dept=" + dept +
'}';
}
public void test() {
System.out.println(dept);
}
}
// Dept.java
package com.company.autowire;
public class Dept {
@Override
public String toString() {
return "Dept{}";
}
}
配置文件bean5.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="dept" class="com.company.autowire.Dept">
</bean>
<bean id="emp" class="com.company.autowire.Emp">
<property name="dept" ref="dept"/>
</bean>
</beans>
然后用测试类输出一下看结果:
public class TestSpring {
@Test
public void test5() {
ApplicationContext context = new ClassPathXmlApplicationContext("bean5.xml");
Emp emp = context.getBean("emp", Emp.class);
System.out.println(emp); // 打印结果为 Emp{dept=Dept{}}
}
}
那么如何实现自动装配呢?
bean标签中有个属性autowire
autowire
常用的两个属性:
byName
:根据属性名字注入- 注入值bean的id值和类属性名称一样
byType
:根据属性类型注入
<bean id="dept" class="com.company.autowire.Dept">
</bean>
<bean id="emp" class="com.company.autowire.Emp" autowire="byName">
<!-- <property name="dept" ref="dept"/>-->
</bean>
可以看到,增加了autowire
后即使bean标签中的property
被注释,测试类也能正常输出结果
外部属性文件
⒈方式一:直接配置数据库信息(配置Druid数据库连接池)
-
引入Druid连接池依赖jar包。然后在配置文件中创建对象并设置
property
,将mysql数据库的信息填入:<?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.cj.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/lxyker"/> <property name="username" value="root"/> <property name="password" value="123456"/> </bean> </beans>
⒉方式二:引入外部属性文件配置数据库连接池
-
创建外部属性文件,properties格式文件,写上数据库的信息。jdbc.properties:
driverClassName = com.mysql.cj.jdbc.Driver url = jdbc:mysql://localhost:3306/lxyker username = root password = 123456
-
把外部属性文件引入到spring配置文件中
在此之前需引入
context
名称空间,引入之后才能使用它的相关标签在spring配置文件中使用标签:
<context:property-placeholder>
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/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"> <!-- 引入外部属性文件,classpath后面是.properties文件的路径 --> <context:property-placeholder location="classpath:jdbc.properties"/> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="driverClassName" value="${driverClassName}"/> <property name="url" value="${url}"/> <property name="username" value="${username}"/> <property name="password" value="${password}"/> </bean> </beans>
基于注解方式
注解是代码中的特殊标记,格式为:@注解名称(属性名称1=属性值1, 属性名称2=属性值2……)
注解可以简化xml配置
spring针对Bean管理中创建对象提供的注解
-
@Component
-
@Service
-
@Controller
-
@Repository
这四个注解功能一样,都可以用来创建bean实例
基于注解方式实现对象的创建
⒈引入依赖 spring-aop-5.3.5.jar
⒉开启组件扫描
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/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">
<!-- 开启组件扫描
1、如果要扫描多个包,base-package的值可以用逗号隔开。或者直接写它们的上层目录
-->
<context:component-scan base-package="com.company"/>
</beans>
⒊创建类,在类上面添加创建对象的注解(上面四个注解之一)
package com.company.service;
import org.springframework.stereotype.Component;
@Component(value = "userService") // 相当于配置文件中的<bean id="userService" class=.../>
public class UserService {
public void add() {
System.out.println("Service add ……");
}
}
再用测试类查看一下输出:
public class TestSpring {
@Test
public void testService() {
ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
UserService userService = context.getBean("userService", UserService.class);
userService.add();
}
}
可以发现add()
方法被正常调用。
⒋开启组件扫描的细节问题
扫描部分include filter+排除部分exclude filter
⒌基于注解方式实现属性注入
@Autowired
:根据属性类型进行自动装配
@Qualifier
:根据属性名称进行注入
@Resource
:即可根据类型注入,亦可根据名称注入。jdk11版本没有
@Value
:与上面三个不同,这是注入普通类型属性
演示:(以@Autowired
为例)
① 把service和dao对象创建,在service和dao类添加创建对象的注解
② 在service注入dao对象。在service类中先添加dao类型属性,在属性上使用注解
// UserService.java
package com.company.service;
import com.company.dao.UserDao;
import com.company.dao.UserDaoImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class UserService {
@Autowired
// @Qualifier(value = "userDaoImpl2") 如果有多个对象,用Qualifier区分
private UserDao userDao;
public void add() {
System.out.println("Service add ……");
userDao.add();
// 只有前面有Autowired的注解,这里才能直接调用add()
// 相当于 UserDao userDao = new UserDaoImpl();
}
}
// UserDaoImpl.java
package com.company.dao;
import org.springframework.stereotype.Repository;
@Repository
public class UserDaoImpl implements UserDao{
@Override
public void add() {
System.out.println("UserDaoImpl add.......");
}
}
- Qualifier根据名称进行注入 需要和@Autowired一起使用。
⒍完全注解开发
① 创建配置类,用于替代xml配置文件
// SpringConfig.java
package com.company.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration // 把当前类作为配置类,用以替代xml文件
@ComponentScan(basePackages = {"com.company"}) // 相当于之前配置文件中的包扫描路径
public class SpringConfig {
}
② 编写测试类
@Test
public void testComplete() {
// 加载配置类
ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
UserService userService = context.getBean("userService", UserService.class);
userService.add();
}
AOP
- 面向切面编程
通俗描述:不修改源代码,在主干功能上添加新的功能
AOP底层原理
-
AOP底层使用动态代理
-
有两种情况的动态代理
-
有接口的情况,使用JDK动态代理
创建接口实现类代理对象,来增强类的方法
-
-
- 无接口的情况,使用CGLIB动态代理
AOP(JDK动态代理)
⒈使用JDK动态代理,使用Proxy类 里面的方法 创建代理对象
调用newProxyInstance
方法
这个方法有三个参数:
ClassLoader loader
:类加载器
类<?>[] interfaces
:增强方法所在的类 这个类实现的接口,支持多个接口
InvocationHandler h
:实现这个接口InvocationHandler,创建代理对象,写增强的方法
⒉编写JDK动态代理的代码
-
创建接口,定义方法
package com.company.dao; public interface UserDao { public int add(int a, int b); public String update(String id); }
-
创建接口实现类,实现其方法
package com.company.dao; public class UserDaoImpl implements UserDao{ @Override public int add(int a, int b) { System.out.println("UserDaoImpl中的add方法执行了……"); return a + b; } @Override public String update(String id) { System.out.println("UserDaoImpl中的update方法执行了……"); return id; } }
-
使用Proxy类创建接口代理对象
package com.company; import com.company.dao.UserDao; import com.company.dao.UserDaoImpl; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.Arrays; public class JDKProxy { public static void main(String[] args) { // 创建接口实现类代理对象 Class[] interfaces = {UserDao.class}; UserDao userDao = new UserDaoImpl(); UserDao dao = (UserDao) Proxy.newProxyInstance(JDKProxy.class.getClassLoader(), interfaces, new UserDaoProxy(userDao)); System.out.println("返回值为:" + dao.add(1, 2)); } } // 创建代理对象代码 class UserDaoProxy implements InvocationHandler { // 1、创建的是谁的代理对象,就把谁传递过来 // 用 有参构造传递 private Object obj; public UserDaoProxy(Object obj){ this.obj = obj; } // invoke方法中写 增强的逻辑 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 原方法之前 System.out.println("方法之前执行……" + method.getName() + ";所传参数:" + Arrays.toString(args)); // 被增强的方法 Object res = method.invoke(obj, args); // 原方法之后 System.out.println("方法之后执行……" + obj); return res; } }
AOP(相关术语)
- 连接点:类中的哪些方法可以被增强,这些方法就是连接点
- 切入点:实际上被增强的方法,被称为切入点
- 通知(增强):
- 实际增强的逻辑部分 称为通知
- 通知的五种类型:
- 前置通知
- 后置通知
- 环绕通知
- 异常通知
- 最终通知
- 切面:是一个动作,把通知应用到切入点的过程
AOP操作
Spring框架一般基于AspectJ
实现AOP操作
AspectJ不是Spring的组成部分,它是独立的,一般把两者结合起来 进行AOP操作
基于AspectJ实现AOP操作
- 基于xml配置文件实现
- 基于注解方式实现(常用)
在项目工程中先引入AOP相关的依赖
切入点表达式
-
作用是 知道对哪个类里面的哪个方法进行增强
-
语法结构:
execution([权限修饰符] [返回类型] [类全路径] [方法名称](参数列表))
举例1:对com.lxyker.dao.BookDao类里面的add()方法进行增强
execution(* com.lxyker.dao.BookDao.add(..))
举例2:对com.lxyker.dao.BookDao类里面的所有方法进行增强
execution(* com.lxyker.dao.BookDao.*(..))
举例3:对com.lxyker.dao包中所有类的所有方法都进行增强
execution(* com.lxyker.dao.*.*(..))
AOP操作(AspectJ注解)
⒈创建类,在类里面定义方法add
// User.java
package com.company.aopanno;
public class User {
public void add(){
System.out.println("add()....");
}
}
⒉创建增强类(编写增强逻辑)
-
在增强类里面,创建方法,让不同的方法代表不同通知类型
package com.company.aopanno; // 增强类 public class UserProxy { // 前置通知 public void before(){ System.out.println("before()....."); } }
⒊进行通知的配置
-
在spring配置文件中,开启注解扫描
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"> <!-- 开启注解扫描--> <context:component-scan base-package="com.company.aopanno"/> </beans>
-
使用注解创建User和UserProxy对象
// 增强类 @Component public class UserProxy { …… ------------------------------------------------------------- // 被增强的类 @Component public class User { ……
-
在增强的类上面添加注解
@Aspect
// 增强类 @Aspect // 生成代理对象 @Component public class UserProxy { ……
-
在spring配置文件中开启生成代理对象
<!-- 开启注解扫描--> <context:component-scan base-package="com.company.aopanno"/> <!-- 开启Aspect生成代理对象--> <aop:aspectj-autoproxy/>
⒋配置不同类型的通知
-
在增强类的里面,在作为通知方法上面 添加通知类型注解,使用切入点表达式配置
package com.company.aopanno; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.springframework.stereotype.Component; // 增强类 @Aspect // 生成代理对象 @Component public class UserProxy { // 前置通知 @Before(value = "execution(* com.company.aopanno.User.add(..))") // 前置通知,其中value可省略 public void before(){ System.out.println("before()....."); } }
测试:
public class TestAOP {
@Test
public void testAopAnno() {
ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
User user = context.getBean("user", User.class);
user.add();
}
}
可以看到,前置通知的输出结果:
before().....
add()....
Process finished with exit code 0
⒌相同的切入点抽取
// 增强类
@Aspect // 生成代理对象
@Component
public class UserProxy {
// 相同切入点抽取
@Pointcut(value = "execution(* com.company.aopanno.User.add(..))")
public void pointCutDemo() {
}
// 前置通知
@Before(value = "pointCutDemo()") // 直接写抽取的切入点方法+括号即可
public void before() {
System.out.println("before().....");
}
}
⒍有多个增强类对同一个方法进行增强时,优先级的设置
在增强类上添加注解@Order(数字)
。这个数字越小,优先级越高
// 增强类
@Aspect // 生成代理对象
@Component
@Order(1)
public class UserProxy {
……
AOP操作(AspectJ配置文件)
重点了解注解方式
JdbcTemplate
什么是JdbcTemplate?
Spring框架对JDBC进行封装,使用JdbcTemplate能够很方便地对数据库进行操作
准备工作
-
引入依赖
-
在spring配置文件中 配置数据库连接池
<!-- 数据库连接池配置--> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close"> <property name="url" value="jdbc:mysql://localhost:3306/lxyker"/> <property name="username" value="root"/> <property name="password" value="123456"/> <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/> </bean>
-
配置JdbcTemplate对象,注入DataSource
<!-- 创建JdbcTemplate对象--> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <!-- 注入DataSource信息--> <property name="dataSource" ref="dataSource"/> </bean>
-
创建service类和dao类,在dao注入JdbcTemplate对象
配置文件
<!-- 开启组件扫描--> <context:component-scan base-package="com.company"/>
Service
@Service public class BookService { // 注入dao @Autowired private BookDao bookDao; }
Dao
@Repository public class BookDaoImpl implements BookDao { // 注入JdbcTemplate @Autowired private JdbcTemplate jdbcTemplate; }
JdbcTemplate操作数据库
-
对应数据库表 创建实体类
数据库表如下:
create table if not exists t_book ( book_id bigint(20) not null auto_increment primary key, bookname varchar(100) not null, bstatus varchar(50) not null )
package com.company.entity; public class Book { private String bookId; private String bookname; private String bstatus; public String getBookId() { return bookId; } public void setBookId(String bookId) { this.bookId = bookId; } public String getBookname() { return bookname; } public void setBookname(String bookname) { this.bookname = bookname; } public String getBstatus() { return bstatus; } public void setBstatus(String bstatus) { this.bstatus = bstatus; } }
-
编写service和dao
在dao进行数据库添加操作
调用JdbcTemplate对象里面的update方法实现添加操作
update(String sql, Object... args)
- 第一个参数:sql语句
- 第二个参数:可变参数;设置sql语句的值
@Repository public class BookDaoImpl implements BookDao { // 注入JdbcTemplate @Autowired private JdbcTemplate jdbcTemplate; @Override public void add(Book book) { String sql = "insert into t_book values(?, ?, ?)"; int update = jdbcTemplate.update(sql, book.getBookId(), book.getBookname(), book.getBstatus()); System.out.println(update); } }
测试:
public class TestBook { @Test public void testJdbcTemplate() { ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml"); BookService bookService = context.getBean("bookService", BookService.class); Book book = new Book(); book.setBookId("1"); book.setBookname("Java从入门到精通"); book.setBstatus("Good"); bookService.addBook(book); } }
可以看到正常输出1
4月 23, 2021 3:01:37 下午 com.alibaba.druid.pool.DruidDataSource info 信息: {dataSource-1} inited 1 Process finished with exit code 0
同理,更新和删除的操作部分代码如下:
@Override public void update(Book book) { String sql = "update t_book set bookname = ?, bstatus = ? where book_id = ?"; Object[] args = {book.getBookname(), book.getBstatus(), book.getBookId()}; jdbcTemplate.update(sql, args); } @Override public void delete(String id) { String sql = "delete from t_book where book_id = ?"; jdbcTemplate.update(sql, id); }
JdbcTemplate操作数据库(查询返回某个值)
1、查询表里有多少条记录,返回的是某个值
2、用JdbcTemplate实现查询返回某个值的代码
BookService.java:
// 查询表中有多少条记录
public int findCount(){
return bookDao.selectCount();
}
BookDao.java中定义这个方法,然后BookDaoImpl.java实现类中写出这个方法的具体逻辑:
@Override
public int selectCount() {
String sql = "select count(*) from t_book";
return jdbcTemplate.queryForObject(sql, Integer.class);
}
其中,queryForObject()
方法的第一个参数是查询的语句,第二个参数是返回类型的class。
JdbcTemplate操作数据库(查询返回对象)
调用jdbcTemplate中的queryForObject()
方法,其参数说明如下:
- sql语句
- RowMapper
- 是一个接口,返回不同类型的数据,一般用这个接口的实现类 来完成数据的封装
- 这个实现类
BeanPropertyRowMapper<T>(T.class)
- sql语句中待替换的值
具体代码BookDaoImpl.java如下:
@Override
public Book findBookInfo(String id) {
String sql = "select * from t_book where book_id = ?";
Book book = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<Book>(Book.class), id);
return book;
}
JdbcTemplate操作数据库(查询返回集合)
同理,返回集合 jdbcTemplate应该调用query()
方法
BookDaoImpl.java部分代码如下:
@Override
public List<Book> findAll() {
String sql = "select * from t_book";
List<Book> bookList = jdbcTemplate.query(sql, new BeanPropertyRowMapper<Book>(Book.class));
return bookList;
}
JdbcTemplate批量操作数据库
批量添加
使用jdbcTemplate的batchUpdate()
方法
- sql:执行的sql语句
- batchArgs:List集合,添加多条数据
BookDaoImpl.java:
@Override
public void batchAddBook(List<Object[]> batchArgs) {
String sql = "insert into t_book values (?, ?, ?)";
jdbcTemplate.batchUpdate(sql, batchArgs);
}
事务概念
事务是数据库操作的最基本单元,指的是逻辑上的一组操作,要么都成功,要么都失败。
典型例子:银行转账
事务的四大特性:原子一致隔离持久(ACID)
- 原子性:不可分割
- 一致性:操作之前和操作之后总量不变
- 隔离性:多个事务操作时,不会相互影响
- 持久性:事务提交后表中的数据是真正变化了的
spring中的事务操作
搭建一个银行转账的事务操作环境:
用于测试的表:
创建service,搭建dao,完成对象创建和注入关系
-
service注入dao,在dao注入JdbcTemplate,在JdbcTemplate注入DataSource
bean.xml不变
<!-- 开启组件扫描--> <context:component-scan base-package="com.company"/> <!-- 数据库连接池配置--> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close"> <property name="url" value="jdbc:mysql://localhost:3306/lxyker"/> <property name="username" value="root"/> <property name="password" value="123456"/> <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/> </bean> <!-- 创建JdbcTemplate对象--> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <!-- 注入DataSource信息--> <property name="dataSource" ref="dataSource"/> </bean>
UserDaoImpl.java:
@Repository public class UserDaoImpl implements UserDao{ @Autowired private JdbcTemplate jdbcTemplate; }
UserService.java
@Service public class UserService { // 注入dao @Autowired private UserDao userDao; }
-
在dao创建多钱和少钱两个方法,在service层创建转账方法
UserDaoImpl.java:
@Override public void addMoney() { String sql = "update t_account set money = money + ? where username = ?"; jdbcTemplate.update(sql, 100, "Bob"); } @Override public void reduceMoney() { String sql = "update t_account set money = money - ? where username = ?"; jdbcTemplate.update(sql, 100, "Tom"); }
service层 UserService.java
public void transfer(){ // 少钱 userDao.reduceMoney(); // 多钱 userDao.addMoney(); }
到目前为止,基本的转账环境已经准备就绪。
事务操作的伪代码:
public void transfer() {
try {
// first:开启事务
// second:进行业务操作
// 少钱
userDao.reduceMoney();
// 模拟异常
int a = 1/0;
// 多钱
userDao.addMoney();
// third:未发生异常,则提交事务
} catch (Exception e) {
// fourth:发现异常,则事务回滚
}
}
spring框架已经提供了更方便的事务操作方式
Spring事务管理介绍
⒈事务一般添加到 Java EE三层架构的Service层上(业务逻辑层)
⒉在spring中进行事务管理操作:
- 编程式事务管理
- 声明式事务管理(更常用)
⒊声明式事务管理
- 基于注解方式
- 基于xml配置文件方式
⒋在spring中进行声明式事务管理,底层使用的是AOP
⒌spring事务管理相关API
- 提供了一个接口,代表事务管理器,这个接口针对不同框架提供了不同的实现类
注解方式实现声明式事务管理:
⒈在spring配置文件中配置事务管理器
<!-- 创建一个事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 注入数据源 给哪个数据库进行事务操作-->
<property name="dataSource" ref="dataSource"/>
</bean>
⒉配置文件中,开启事务的注解
<!-- 开启事务注解的组件-->
<tx:annotation-driven transaction-manager="transactionManager"/>
⒊在service类上边添加事务的注解
@Transactional
可以添加到类上边,也可以添加到方法上边- 如果添加到类上边,表示该类中的所有方法都添加事务操作
声明式事务管理的参数配置
@Transactional(参数)
-
propagation:事务传播行为
七种行为,前两种最常用:
REQUIRED:
- A方法调用B方法,如果A方法本身有事务,调用B后,直接使用A的事务;
- A没有事务,调用B后,创建新事务
REQUIRED_NEW:
- 总是创建新的事务
@Transactional(propagation = Propagation.REQUIRED)
-
isolation:事务隔离级别
多个事务之间不会相互影响,这是事务的隔离性
如果没有事务的隔离性,会产生以下问题:
- 脏读:一个未提交的事务 读取到了另一个未提交事务的数据
- 不可重复读:一个未提交的事务 读取到了另一提交事务 的修改数据
- 幻读:一个未提及的事务 读取到了 另一提交事务 添加的数据
脏读 不可重复读 幻读 READ UNCOMMITTED
(读未提交)✔ ✔ ✔ READ COMMITTED
(读已提交)❌ ✔ ✔ REPEATABLE READ
(可重复读)❌ ❌ ✔ SERIALIZABLE
(串行化)❌ ❌ ❌ @Transactional(isolation = Isolation.REPEATABLE_READ)
-
timeout:超时时间
-
事务需要在一定时间内进行提交,否则会回滚
-
默认-1,可以设置,以秒为单位
@Transactional(timeout = 5)
-
-
readOnly:是否只读
- 默认为false
- 设置为true后,只能做查询操作
-
rollbackFor:回滚
- 出现哪些异常进行回滚
-
noRollbackFor:不回滚
- 出现哪些异常不回滚
完全注解形式进行声明式事务管理
创建配置类,用于替代xml文件
package com.company.config;
import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.sql.DataSource;
@Configuration // 表示这是一个配置类
@ComponentScan(basePackages = "com.company") // 包扫描
@EnableTransactionManagement // 开启事务
public class TxConfig {
// 创建数据库的连接池
@Bean
public DruidDataSource getDruidDataSource() {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName("jdbc:mysql://localhost:3306/lxyker");
dataSource.setUrl("root");
dataSource.setUsername("123456");
dataSource.setPassword("com.mysql.cj.jdbc.Driver");
return dataSource;
}
// 创建jdbcTemplate对象
@Bean
public JdbcTemplate getJdbcTemplate(DataSource dataSource) {
JdbcTemplate jdbcTemplate = new JdbcTemplate();
// 注入dataSource
jdbcTemplate.setDataSource(dataSource);
return jdbcTemplate;
}
// 创建事务管理器对象
@Bean
public DataSourceTransactionManager getDataSourceTransactionManager(DataSource dataSource) {
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
transactionManager.setDataSource(dataSource);
return transactionManager;
}
}
Spring5新功能——WebFlux
WebFlux是Spring5新添加的模块,用于Web开发,功能类似SpringMVC。WebFlux使用响应式编程而出现的框架。
同步和异步 阻塞和非阻塞
A调用B,A是调用者,B是被调用者。同步异步针对调用者,阻塞与否针对被调用者。
同步:A发送请求,等待B返回后再做其他操作
异步:A发送请求,不用等待B的返回结果,直接可以做其他事情
阻塞:B收到请求,做完请求的任务之后再给出反馈
非阻塞:B收到请求,立刻给出反馈,再做请求的任务
WebFlux特点:
- 异步非阻塞式:在有限的资源下提高系统吞吐量和伸缩性,以Reactor为基础实现响应式编程
- 函数式编程:Spring5框架基于Java8,WebFlux可使用Java8函数式编程方式实现路由请求
WebFlux和SpringMVC的对比
- 都可以使用注解方式,都可以运行在Tomcat等容器中
- SpringMVC采用命令式编程,WebFlux采用异步响应式编程
响应式编程
Java8及其之前版本
提供观察者模式相关的两个类Observer
和Observable
public class ObserverDemo extends Observable {
public static void main(String[] args) {
ObserverDemo observerDemo = new ObserverDemo();
// 添加观察者
observerDemo.addObserver((o, arg) -> {
System.out.println("-------");
});
observerDemo.addObserver((o, arg) -> {
System.out.println("+++++++");
});
observerDemo.setChanged(); // 数据变化
observerDemo.notifyObservers(); // 通知
}
}
Reactor实现响应式编程
-
Reactor是满足Reactive规范的框架。
-
Reactor有两个核心类,Mono和Flux,这两个类都实现了接口Publisher
-
Flux和Mono都是数据流的发布者,使用Flux和Mono都可以发出三种数据信号(元素值、错误信号、完成信号)。错误和完成 信号 都是终止信号,终止信号用于告诉订阅者数据流结束了
……