学习--实践--总结--实践--总结........
Spring简介:
(1)核心:Spring是一个轻量级的控制反转(IoC)和面向切面(AOP)的框架。
(2)特点:
<1>轻量级的开源的 JavaEE 框架
<2>方便解耦,简化开发
<3>方便进行事务操作
<4>方便和其他框架进行整合......
(3)组成:
Spring框架其实是一个分层架构,它由多个模块组成,每个模块既可以单独存在,也可以与其他模块联合实现。
-
Spring-Core(核心容器):提供 Spring 框架的基本功能。核心容器的主要组件是 BeanFactory,它是工厂模式的实现。BeanFactory 使用控制反转将应用程序的配置和依赖性规范与实际的应用程序代码分开。
-
Spring-Context:这是一个配置文件,向 Spring 框架提供上下文信息。
-
Spring-Aop:面向切面编程,利用 AOP 可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
-
Spring-DAO: 它提供了异常层次结构,利用该结构来管理异常处理和不同数据库供应商抛出的错误消息。这样,我们在编写代码时就无需考虑捕获每种技术不同的异常,降低了需要编写的异常代码数量。
-
Spring-Web:Web上下文模块建立于应用上下文模块之上,提供了一个适合于Web应用的上下文,对Web开发提供功能上的支持,如请求,表单,异常等。
-
Spring Web MVC:实现了Spring MVC的Web应用,Spring的MVC框架使用IoC对控制逻辑和业务对象提供了完全的分离。
-
Spring-ORM:关系映射模块,对流行的对象关系映射 API,包括 JPA、JDO、Hibernate 和 iBatis 提供了的集成层。通过ORM包,可以混合使用所有Spring提供的特性进行“对象/关系”映射,方便开发时小组内整合代码。
spring5入门案例:
1、下载spring5
下载地址 https://repo.spring.io/release/org/springframework/spring/
2、创建一个普通的Javaweb项目
3、在web/WEB-INF/lib下引入jar包(从上述下载的spring中去找)
4、创建User实体类、测试类和xml配置文件:
User实体类:
public class User {
public void add(){
System.out.println("add......");
}
}
testAdd实体类:
public class testAdd {
@Test
public void test(){
//加载spring配置文件
ApplicationContext context = new ClassPathXmlApplicationContext("/bean1.xml");
//获取配置创建的对象
User user = context.getBean("user",User.class);
System.out.println(user);
user.add();
}
}
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">
<!--配置user对象创建-->
<bean id="user" class="com.company.entity.User"></bean>
</beans>
执行结果:
IOC详解:【重点】
1、什么是IOC:
IOC不是什么技术,而是一种设计思想,一个重要的面向对象编程的法则,它能指导我们如何设计出松耦合、更优良的程序。在Java开发中,IOC的设计思想就是将你设计好的对象交给spring容器控制,而不是传统的在类的内部主动创建依赖对象。既然IOC表示控制反转,那么问题来了:
<1>什么是控制?控制了什么?
<2>什么是反转?反转之前是谁控制的?反转之后又是谁控制的?如何控制的?
<3>为什么要反转?反转之前有什么不足?反转之后又能带来什么好处呢?
第一个问题:什么是控制?控制了什么?
我们在用Spring的时候,通常会创建一些类UserService、CustomerService.....同时我们也知道程序在真正运行的时候用到的是具体的UserService对象、CustomerService对象,那么这些对象
是什么时候创建的?谁创建的?包括对象里的属性是什么时候赋的值?谁赋的?我们在开发过程中只是写了类而已,所有这些都是spring帮我们完成的。这也就是问题的答案控制:
- 控制对象的创建
- 控制对象内属性的赋值
当然,如果我们不用Spring,那我们就得自己来做这两件事;反之,我们要做的仅仅是定义类,以及定义哪些属性需要Spring来赋值,由容器来帮忙创建及注入依赖对象(比如某个属性上加@Autowired),这就是反转,表示一种对象控制权的转移。
那么为什么要反转呢?如果我们自己创建对象,自己给对象中的属性赋值,会出现什么情况?
- A类,A类里有一个属性C c;
- B类,B类里也有一个属性C c;
- C类
按照传统的方式,现在程序要运行,那么这三个类的对象都需要创建出来,并且相应的属性都需要有值,那么除了定义这三个类之外,我们还得额外编写创建对象并赋值,如下所示:
- A a = new A();
- B b = new B();
- C c = new C();
- a.c = c;
- b.c = c;
这五行代码是不用Spring的情况下多出来的代码,如果类在多一些,类中的属性在多一些,那相应的代码会更多、更复杂。反之,将对象交给Spring来控制,极大地减轻了程序员的负担。
总结:IOC表示控制反转,如果用spring,那么spring会负责来创建对象以及给对象内的属性赋值;也就是说,对象的控制权将会转交给spring。
2、IOC底层原理
<1>创建XML配置文件,配置创建的对象
<!--配置user对象创建-->
<bean id="user" class="com.company.entity.User"></bean>
<2>创建一个工厂类:使用dom4j解析配置文件+反射
public class UserFactory{
public static UserDao getDao(){
//使用dom4j解析配置文件
String classValue="class属性值";//xml解析
Class class=Class.forName(classValue);//通过反射来创建class对象
return (UserDao)class.newInstatnce();
}
}
3、Spring 提供 IOC 容器实现两种方式:(两个接口)
(1)BeanFactory:IOC 容器基本实现,是 Spring 内部的使用接口,不提供开发人员进行使用 * 加载配置文件时候不会创建对象,在获取对象(使用)才去创建对象
(2)ApplicationContext:BeanFactory 接口的子接口,提供更多更强大的功能,一般由开发人 员进行使用 --------加载配置文件时候就会把在配置文件内的对象进行创建。
4、IOC操作Bean管理(基于 xml 方式)
(1)基于 xml 方式创建对象
<!--配置user对象创建-->
<bean id="user" class="com.company.entity.User"></bean>
<1>使用 bean 标签,标签里面添加对应属性,就可以实现对象创建
<2>常用的属性
* id 属性:唯一标识;
* class 属性:类全路径(包类路径);
<3>在创建对象时候,默认也是执行无参数构造方法完成对象创建
(2)基于 xml 方式注入属性 --------DI(依赖注入)就是注入属性
(3)使用 set 方法进行注入
<1>创建类,定义属性和对应的 set 方法
//使用 set 方法进行注入属性
public class Book {
//创建属性
private String bname;
private String bauthor;
//创建属性对应的 set 方法
public void setBname(String bname) {
this.bname = bname;
}
public void setBauthor(String bauthor) {
this.bauthor = bauthor;
}
}
<2>在 spring 配置文件配置对象创建,配置属性注入
<!--set方法注入属性-->
<bean id="book" class="com.atguigu.spring5.Book">
<!--使用 property 完成属性注入
name:类里面属性名称
value:向属性注入的值
-->
<property name="bname" value="易筋经"></property>
<property name="bauthor" value="达摩老祖"></property>
</bean>
(4)使用有参数构造进行注入
<1>创建类,定义属性,创建属性对应有参数构造方法
<!--使用有参数构造注入-->
public class Orders {
//属性
private String oname;
private String address;
//有参数构造
public Orders(String oname,String address) {
this.oname = oname;
this.address = address;
}
}
<2>在 spring 配置文件中进行配置
<!--有参构造注入属性-->
<bean id="orders" class="com.atguigu.spring5.Orders">
<constructor-arg name="oname" value="电脑"></constructor-arg>
<constructor-arg name="address" value="China"></constructor-arg>
</bean>
(5)p 名称空间注入(了解)
<1>添加 p 名称空间到配置文件中
<2>进行属性注入,在 bean 标签里面进行操作
<!-set方法注入属性-->
<bean id="user" class="com.atguigu.spring5.User"
p:name="张三"
p:age=16>
</bean>
5、IOC 操作 Bean 管理(xml 注入其他类型属性)
(1)字面量
<1>对象的属性值为null
<!-- null值 -->
<bean id="对象名" class="全限定类名">
<property name="属性名">
<null/>
</property>
</bean>
<2>属性值包含特殊符号
<!-- 属性值包含特殊符号 1、把<>进行转义(<&rt;) 2、把带有特殊符号的内容写到CDATA中 -->
<bean id="对象名" class="全限定类名">
<property name="属性名">
<value>
<![CDATA[<<加油鸭>>]]>
</value>
</property>
</bean>
(2)注入属性(外部 bean)
<1>创建两个类: UserService类和 UserDao类
<2>在 service 调用 dao 里面的方法
public class UserService {
//创建 UserDao 类型属性,生成 set 方法
private UserDao userDao;
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
public void add() {
System.out.println("service add......");
userDao.update();
}
}
<3>在 spring 配置文件中进行配置
<!--service 和 dao 对象创建-->
<bean id="userService" class="com.company.service.UserService">
<!--注入 userDao 对象
name 属性:类里面属性名称
ref 属性:创建 userDao 对象 bean 标签 id 值
-->
<property name="userDao" ref="userDaoImpl"></property>
</bean>
<bean id="userDaoImpl" class="com.company.dao.UserDao"></bean>
(3)注入属性(内部Bean)
在实体类之间表示一对多的关系(员工和部门)
<1>部门类:
public class Dept {
private String dname;
public void setDname(String dname) {
this.dname = dname;
}
}
<2>员工类:
//员工类
public class Emp {
private String ename;
private String gender;
//员工属于某一个部门,使用对象形式表示
private Dept dept;
public void setDept(Dept dept) {
this.dept = dept;
}
public void setEname(String ename) {
this.ename = ename;
}
public void setGender(String gender) {
this.gender = gender;
}
}
<3>在spring配置文件中进行配置
<!--内部 bean-->
<bean id="emp" class="com.company.dao.Emp">
<!--设置两个普通属性-->
<property name="ename" value="lucy"></property>
<property name="gender" value="女"></property>
<!--设置对象类型属性-->
<property name="dept">
<bean id="dept" class="com.company.dao.Dept">
<property name="dname" value="安保部"></property>
</bean>
</property>
</bean>
(4)注入属性(级联赋值)
<1>第一种方式:
<!--级联赋值-->
<bean id="emp1" class="com.company.dao.Emp">
<!--设置两个普通属性-->
<property name="ename" value="lucy"></property>
<property name="gender" value="女"></property>
<!--级联赋值-->
<property name="dept" ref="dept1"></property>
</bean>
<bean id="dept1" class="com.company.dao.Dept">
<property name="dname" value="财务部"></property>
</bean>
<2>第二种方式:
------员工属于部门,适用对象形式表示(生成对应的get、set方法)。
<!--级联赋值-->
<bean id="emp2" class="com.company.dao.Emp">
<!--设置两个普通属性-->
<property name="ename" value="lucy"></property>
<property name="gender" value="女"></property>
<!--级联赋值-->
<property name="dept" ref="dept2"></property>
<property name="dept.dname" value="技术部"></property>
</bean>
<bean id="dept2" class="com.company.dao.Dept">
<property name="dname" value="财务部"></property>
</bean>
6、IOC 操作 Bean 管理(xml 注入集合属性)
准备工作:创建类,定义数组、list、map、set 类型属性,生成对应 set 方法,并在 spring 配置文件进行配置。
public class Stu {
//1 数组类型属性
private String[] courses;
//2 list 集合类型属性
private List<String> list;
//3 map 集合类型属性
private Map<String,String> maps;
//4 set 集合类型属性
private Set<String> sets;
public void setSets(Set<String> sets) {
this.sets = sets;
}
public void setCourses(String[] courses) {
this.courses = courses;
}
public void setList(List<String> list) {
this.list = list;
}
public void setMaps(Map<String, String> maps) {
this.maps = maps;
}
}
注意:map和其他集合注入是有差异的,区分记忆。
<!--1 集合类型属性注入-->
<bean id="stu" class="com.company.dao.Stu">
<!--数组类型属性注入-->
<property name="courses">
<array>
<value>java 课程</value>
<value>数据库课程</value>
</array>
</property>
<!--list 类型属性注入-->
<property name="list">
<list>
<value>张三</value>
<value>李四</value>
</list>
</property>
<!--map 类型属性注入-->
<property name="maps">
<map>
<entry key="JAVA" value="java"></entry>
<entry key="PHP" value="php"></entry>
</map>
</property>
<!--set 类型属性注入-->
<property name="sets">
<set>
<value>MySQL</value>
<value>Redis</value>
</set>
</property>
</bean>
(1)在集合里面设置对象类型值:
public class Course {
private String cname; //课程名称
public void setCname(String cname) {
this.cname = cname;
}
@Override
public String toString() {
return "Course{" +
"cname='" + cname + '\'' +
'}';
}
}
<!--集合类型的属性注入-->
<bean id="stu" class="com.company.spring5.dao.Stu">
<!--注入List集合类型。值是对象-->
<property name="coursesList">
<list>
<ref bean="course1"></ref>
<ref bean="course2"></ref>
</list>
</property>
</bean>
<!--创建多个course对象-->
<bean id="course1" class="com.company.spring5.dao.Course">
<property name="cname" value="Spring5框架"></property>
</bean>
<bean id="course2" class="com.company.spring5.dao.Course">
<property name="cname" value="VUE框架"></property>
</bean>
(2)把集合注入部分提取出来:
<1>在spring命名空间中引入util
<2>使用util 标签完成list集合注入提取
<!--提取list集合类型属性注入-->
<util:list id="bookList">
<value>易筋经</value>
<value>九阴真经</value>
<value>九阳神功</value>
</util:list>
<3>提取 list 集合类型属性注入使用
<bean id="book" class="com.company.dao.Book">
<property name="list" ref="bookList"></property>
</bean>
7、IOC 操作 Bean 管理(FactoryBean)
(1)Spring 有两种类型 bean,一种普通 bean,另外一种工厂 bean(FactoryBean)
(2)普通 bean:在配置文件中定义 bean 类型就是返回类型;
(3)工厂 bean:在配置文件定义 bean 类型可以和返回类型不一样
<1>第一步创建类,让这个类作为工厂 bean,实现接口 FactoryBean;
<2>第二步实现接口里面的方法,在实现的方法中定义返回的 bean 类型;
public class Course {
private String cname;
public void setCname(String cname) {
this.cname = cname;
}
@Override
public String toString() {
return "Course{" +
"cname='" + cname + '\'' +
'}';
}
}
public class MyBean implements FactoryBean<Course> {
//定义返回 bean
@Override
public Course getObject() throws Exception {
Course course = new Course();
course.setCname("abc");
return course;
}
@Override
public Class<?> getObjectType() {
return null;
}
@Override
public boolean isSingleton() {
return false;
}
}
<bean id="myBean" class="com.company.dao.MyBean"></bean>
@Test
public void test3() {
ApplicationContext context =
new ClassPathXmlApplicationContext("bean.xml");
Course course = context.getBean("myBean", Course.class);
System.out.println(course);
}
8、Bea的作用域:
在Spring里面,bean 默认是单实例对象。
如何设置单实例还是多实例?
(1)在 spring 配置文件 bean 标签里面有属性(scope)用于设置单实例还是多实例
(2)scope属性值:
第一个值 默认值,singleton,表示是单实例对象;
第二个值 prototype,表示是多实例对象;
(3)singleton 和 prototype 区别:
一、 singleton单实例,prototype 多实例
二 、设置 scope值是singleton时,加载 spring 配置文件时就会创建单实例对象,设置 scope值是prototype时,不是在加载 spring 配置文件时候创建对象,在调用 getBean 方法时候创建多实例对象;
9、Bean生命周期:
(1)通过无参构造创建 Bean 实例
(2)调用set方法为 Bean 的属性设置值和对其他 bean 引用
(3)调用 Bean 的初始化的方法(配置初始化的方法)
(4)使用Bean(对象获取到了)
(5)当容器关闭时候,调用 Bean的销毁的方法(配置销毁的方法)
public class Orders {
//无参数构造
public Orders() {
System.out.println("第一步 执行无参数构造创建 bean 实例");
}
private String oname;
public void setOname(String oname) {
this.oname = oname;
System.out.println("第二步 调用 set 方法设置属性值");
}
//创建执行的初始化的方法
public void initMethod() {
System.out.println("第三步 执行初始化的方法");
}
//创建执行的销毁的方法
public void destroyMethod() {
System.out.println("第五步 执行销毁的方法");
}
}
<bean id="orders" class="com.company.dao.Orders" init-method="initMethod" destroy-method="destroyMethod">
<property name="oname" value="手机"></property>
</bean>
@Test
public void testBean3() {
ClassPathXmlApplicationContext context =
new ClassPathXmlApplicationContext("bean.xml");
Orders orders = context.getBean("orders", Orders.class);
System.out.println("第四步 获取创建 bean 实例对象");
System.out.println(orders);
//手动让 bean 实例销毁
context.close();
}
10、添加后置处理器之后,Bean的生命周期有7步:
(1)通过无参构造创建 Bean 实例
(2)调用set方法为 Bean 的属性设置值和对其他 bean 引用
(3)把 Bean 实例传递 Bean 后置处理器的方法 postProcessBeforeInitialization
(4)调用 Bean 的初始化的方法(配置初始化的方法)
(5)把 Bean 实例传递 Bean 后置处理器的方法 postProcessAfterInitialization
(6)使用Bean(对象获取到了)
(7)当容器关闭时候,调用 Bean的销毁的方法(配置销毁的方法)
演示过程如下:
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;
}
}
<!--配置后置处理器-->
<bean id="myBeanPost" class="com.company.dao.MyBeanPost"></bean>
Bean的装配方式:
Bean 的装配可以理解为将 Bean 依赖注入到 Spring 容器中,Bean 的装配方式即 Bean 依赖注入的方式。Spring 容器支持基于 XML 配置的装配、基于注解的装配以及自动装配等多种装配方式,其中最受亲睐的还是基于注解的装配。
一、基于xml自动装配
(1)什么是自动装配?
根据指定的装配规则【属性名称或者属性类型】,spring自动将匹配的属性进行注入;
(2)操作演示如下:
<1>根据属性名称自动注入;
autowire中的byName是根据属性名注入,要注意注入值bean的id值要和类中的属性名称一样;
<!--实现自动装配
bean 标签属性 autowire,配置自动装配
autowire 属性常用两个值:
byName 根据属性名称注入 ,注入值 bean 的 id 值和类属性名称一样
byType 根据属性类型注入
-->
<bean id="emp" class="com.company.dao.Emp" autowire="byName"></bean>
<2>根据属性类型自动注入
<!--实现自动装配
bean 标签属性 autowire,配置自动装配
autowire 属性常用两个值:
byName 根据属性名称注入 ,注入值 bean 的 id 值和类属性名称一样
byType 根据属性类型注入
-->
<!--autowire自动装配-->
<bean id="emp" class="com.company.dao.Emp" autowire="byType">
<!--手动装配-->
<property name="dept" ref="dept"></property>
</bean>
<bean id="dept" class="com.company.dao.Dept"></bean>
@Test
public void test4(){
ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
Emp emp = context.getBean("emp", Emp.class);
System.out.println(emp);
}
二、基于注解的装配
1、注解格式、作用范围、作用:
(1)格式:@注解名称(属性名称=属性值,...)
(2)作用范围:注解作用在类上面,方法上面,属性上面
(3)作用:简化 xml 配置
2、Spring 针对 Bean 管理中创建Bean对象提供如下注解:
(1)@Component:实现Bean的注入;以下3个是@Component的衍生注解(功能一样)
(2)@Service:service层
(3)@Controller:web层
(4)@Repository:dao层
3、基于注解方式实现对象创建:
<1>引入依赖
<2>在xml配置文件中开启组件扫描:
<!--开启组件扫描
1 如果扫描多个包,多个包使用逗号隔开
2 扫描包上层目录
-->
<context:component-scan base-package="com.company"></context:component-scan>
<3>创建类,在类上面添加创建对象注解:
//在注解里面 value 属性值可以省略不写,
//默认值是类名称,首字母小写
//UserService -- userService
@Component//<bean id="userService" class=".."/>
public class UserService {
//创建 UserDao 类型属性,生成对应的set方法
private UserDao userDao;
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
public void add() {
System.out.println("service add.........");
userDao.update();
}
}
<4>开启组件扫描细节配置:
<!--示例 1
use-default-filters="false" 表示现在不使用默认 filter,自己配置 filter
context:include-filter ,设置扫描哪些内容
-->
<context:component-scan base-package="com.company" use-default-filters="false">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
<!--示例 2
下面配置扫描包所有内容
context:exclude-filter: 设置哪些内容不进行扫描
-->
<context:component-scan base-package="com.company">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
4、基于注解方式实现属性注入:
(1)@Autowired:该注解可以对类成员变量、方法及构造方法进行标注,完成自动装配的工作。通过使用@Autowired 来消除 setter 和 getter 方法。默认按照 Bean 的类型进行装配。
<1>把 service 和 dao 对象创建,在 service 和 dao 类添加创建对象注解;
<2>在 service 注入 dao 对象,在 service 类添加 dao 类型属性,在属性上面使用注解;
@Service
public class UserService {
//定义 dao 类型属性
//不需要添加 set 方法
//添加注入属性注解
@Autowired
private UserDao userDao;
public void add() {
System.out.println("service add.......");
userDao.add();
}
}
(2)@Qualifier:该注解与 @Autowired 注解配合使用。当@Autowired 注解需要按照名称来装配注入时需要和该注解一起使用,Bean 的实例名称由@Qualifier 注解的参数指定。
@Service
public class UserService {
//定义 dao 类型属性
//不需要添加 set 方法
//添加注入属性注解
@Autowired //根据类型进行注入
@Qualifier(value = "userDaoImpl1") //根据名称进行注入
private UserDao userDao;
public void add() {
System.out.println("service add.......");
userDao.add();
}
}
(3)@Resource:该注解与@Autowired 的功能一样,区别在于该注解默认是按照名称来装配注入的,只有当找不到名称匹配的 Bean 时才会按照类型来装配注入;而@Autowired 默认按照 Bean 的类型进行装配,如果想按照名称来装配注入,则需要和@Qualifier 注解一起使用;
@Resource 注解有两个属性,name和type。name 属性指定 Bean 实例名称,即按照名称来装配注入;type 属性指定 Bean 的类型,即按照 Bean 的类型进行装配。
@Resource(name = "userDaoImpl1") //根据名称进行注入
private UserDao userDao;
(4)@Value:注入普通类型属性
@Value(value = "abc")
private String name;
AOP详解:【重点】
1、什么是AOP?
AOP即面向切面编程,它与OOP面向对象编程相辅相成,提供了与 OOP 不同的抽象软件结构的视角。在 OOP 中,以类作为程序的基本单元,而 AOP 中的基本单元是Aspect(切面)。Struts2 的拦截器设计就是基于 AOP 的思想。
2、为什么使用AOP?
利用 AOP 可以对业务逻辑的各个部分进行隔离,从而使业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
也就是说,我们可以不修改源代码,在主干功能里面添加新功能即可。
3、AOP底层原理(动态代理):
(1)有接口情况,使用 JDK 动态代理:
实现动态代理的步骤:
<1>创建接口,定义目标类要完成的功能
<2>创建目标类实现接口
<3>创建InvocationHandler接口的实现类,在invoke方法中完成代理类的功能
(1)调用目标方法
(2)增强功能
<4>使用Proxy类的静态方法,创建代理对象。 并把返回值转为接口类型。
<5>通过代理调用方法
创建接口:【需要动态代理的接口】
public interface UserDao {
public int add(int a,int b);
public String update(String id);
}
创建接口实现类:【需要代理的实际对象】
public class UserDaoImpl implements UserDao {
@Override
public int add(int a, int b) {
return a+b;
}
@Override
public String update(String id) {
return id;
}
}
调用处理器实现类:每次生成动态代理类对象时都需要指定一个实现了该接口的调用处理器对象。
//创建代理对象代码
class UserDaoProxy implements InvocationHandler {
//这个就是我们要代理的真实对象
private Object obj;
//有参构造,给我们要代理的真实对象赋初值
public UserDaoProxy(Object obj) {
this.obj = obj;
}
/**
* 该方法负责集中处理动态代理类上的所有方法调用。
* 调用处理器根据这三个参数进行预处理或分派到委托类实例上反射执行
* @param proxy 被代理的对象
* @param method 将要执行的方法
* @param args 执行方法时需要的参数
* @return 返回代理结果
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//在代理真实对象前我们可以添加一些自己的操作
System.out.println("方法之前执行...."+method.getName()+" :传递的参数..."+ Arrays.toString(args));
//当代理对象调用真实对象的方法时,其会自动的跳转到代理对象关联的handler对象的invoke方法来进行调用
Object res = method.invoke(obj, args);
//在代理真实对象后我们也可以添加一些自己的操作
System.out.println("方法之后执行...."+obj);
return res;
}
}
测试:
public class JDKProxy {
public static void main(String[] args) {
//代理的真正对象
UserDao userDao = new UserDaoImpl();
/**
* UserDaoProxy 实现了 InvocationHandler 接口
* 要代理哪个真实对象,就将该对象传进去,最后是通过该真实对象来调用其方法
*/
InvocationHandler handler = new UserDaoProxy(userDao);
//该方法用于为指定类装载器、接口及调用处理器生成动态代理类实例
UserDao userDao1= (UserDao) Proxy.newProxyInstance(userDao.getClass().getClassLoader(), userDao.getClass().getInterfaces(), handler);
System.out.println("动态代理对象的类型:"+userDao1.getClass().getName());
int result = userDao1.add(1, 2);
System.out.println("result:"+result);
}
}
(2)没有接口情况,使用 CGLIB 动态代理(了解):
实现一个业务类,注意,这个业务类并没有实现任何接口:
public class HelloService {
public HelloService() {
System.out.println("HelloService构造");
}
//该方法不能被子类覆盖,Cglib是无法代理final修饰的方法的
final public String sayOthers(String name) {
System.out.println("HelloService:sayOthers>>"+name);
return null;
}
public void sayHello() {
System.out.println("HelloService:sayHello");
}
}
自定义MethodInterceptor:
public class MyMethodInterceptor implements MethodInterceptor{
/**
* sub:cglib生成的代理对象
* method:被代理对象方法
* objects:方法入参
* methodProxy: 代理方法
*/
@Override
public Object intercept(Object sub, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("增强前");
Object object = methodProxy.invokeSuper(sub, objects);
System.out.println("增强后");
return object;
}
}
生成CGLIB代理对象调用目标方法:
public class Client {
public static void main(String[] args) {
// 代理类class文件存入本地磁盘方便我们反编译查看源码
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\\code");
// 通过CGLIB动态代理获取代理对象的过程
Enhancer enhancer = new Enhancer();
// 设置enhancer对象的父类
enhancer.setSuperclass(HelloService.class);
// 设置enhancer的回调对象
enhancer.setCallback(new MyMethodInterceptor());
// 创建代理对象
HelloService proxy= (HelloService)enhancer.create();
// 通过代理对象调用目标方法
proxy.sayHello();
}
}
4、AOP常用的术语:
(1)切面:
切面是一个动作:把通知应用到切入点的过程。
(2)连接点:
类里面哪些方法可以被增强,这些方法被称为连接点。
(3)切入点:
实际被真正增强的方法称为切入点。
(4)通知:
实际增强的逻辑部分被称为通知。
常用类型:
环绕通知:在目标方法执行前和执行后实施增强,可应用于日志记录、事物处理等功能。
前置通知:前置通知是在目标方法执行前实施增强,可应用于权限管理等功能。
后置通知:在目标方法成功执行后实施增强,可应用于关闭流、删除临时文件等功能。
5、AspectJ
Spring框架一般都是基于AspectJ来实现AOP的操作:
(1)什么是AspectJ?
- AspectJ是一个AOP的框架,它不属于Spring;
- 定义了AOP语法,是为了方便写AOP代码实现的;
- 扩展了Java语言
(2)引入AOP相关依赖
使用AspectJ 需要导入Spring AOP和 AspectJ相关jar包。
6、切入点表达式
作用:知道对哪个类里面的哪个方法进行增强;
语法结构: execution([权限修饰符] [返回类型] [类全路径] [方法名称]([参数列表]) )
例 1:对 com.company.dao.BookDao 类里面的 add 进行增强;
execution(* com.company.dao.BookDao.add(..))
例 2:对 com.company.dao.BookDao 类里面的所有的方法进行增强;
execution(* com.company.dao.BookDao.* (..))
例 3:对 com.company.dao 包里面所有类,类里面所有方法进行增强;
execution(* com.company.dao.*.* (..))
7、AspectJ注解实现AOP
(1)创建增强类,在里面创建方法,让不同方法代表不同通知类型:
//增强的类
@Component
@Aspect //生成代理对象
public class UserProxy {
//前置通知
//@Before注解表示作为前置通知
@Before(value = "execution(* com.company.User.add(..))")
public void before() {
System.out.println("before.........");
}
}
(2)进行通知的配置:
在 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 http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 开启注解扫描 -->
<context:component-scan base-package="com.company"></context:component-scan>
<!-- 开启 Aspect 生成代理对象-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
(3)配置不同类型的通知:
在增强类的里面,在作为通知方法上面添加通知类型注解,使用切入点表达式配置;
//增强的类
@Component
@Aspect //生成代理对象
public class UserProxy {
//前置通知
//@Before注解表示作为前置通知
@Before(value = "execution(* com.company.User.add(..))")
public void before() {
System.out.println("before.........");
}
//后置通知(返回通知)
@AfterReturning(value = "execution(* com.company.User.add(..))")
public void afterReturning() {
System.out.println("afterReturning.........");
}
//最终通知
@After(value = "execution(* com.company.User.add(..))")
public void after() {
System.out.println("after.........");
}
//异常通知
@AfterThrowing(value = "execution(* com.company.User.add(..))")
public void afterThrowing() {
System.out.println("afterThrowing.........");
}
//环绕通知
@Around(value = "execution(* com.company.User.add(..))")
public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("环绕之前.........");
//被增强的方法执行
proceedingJoinPoint.proceed();
System.out.println("环绕之后.........");
}
}
(4)相同的切入点抽取:
//相同切入点抽取
@Pointcut(value = "execution(* com.company.User.add(..))")
public void point() {
}
//前置通知
//@Before 注解表示作为前置通知
@Before(value = "point()")
public void before() {
System.out.println("before.........");
}
......
(5)有多个增强类对同一个方法进行增强,设置增强类优先级:
在增强类上面添加注解 @Order(数字类型值),数字类型值越小优先级越高;
//增强的类
@Component
@Aspect //生成代理对象
@Order(1)
public class UserProxy {
//前置通知
//@Before注解表示作为前置通知
@Before(value = "execution(* com.company.User.add(..))")
public void before() {
System.out.println("before.........");
}
......
}
8、xml配置实现Aop
(1)创建通知类,添加需要的方法;
public class User{
// 前置通知
public void before() {
System.out.println("前置通知");
}
// 后置通知 始终会执行
public void after() {
System.out.println("后置通知");
}
// 环绕通知
public void around(ProceedingJoinPoint proceedingJoinPoint ) throws Throwable {
System.out.println("环绕通知前");
proceedingJoinPoint .proceed();
System.out.println("环绕通知后");
}
// 后置 发生异常时不会执行
public void returning() {
System.out.println("后置通知");
}
// 发生异常
public void throwing() {
System.out.println("发生异常");
}
}
(2)在配置文件中添加通知类的Bean
<bean name="user" class="com.company.dao.User"></bean>
(3)配置xml配置文件,添加<aop:config>,添加子标签
<!-- 开启AOP自动代理 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
<!-- 准备通知对象 -->
<bean name="user" class="com.company.dao.User"></bean>
<!-- 配置AOP增强 -->
<aop:config>
<!--切入点-->
<aop:pointcut expression="execution(* com.company.*.*(..))" id="p"/>
<!-- 配置切面 -->
<aop:aspect ref="user">
<!-- 增强作用在具体方法上,按顺序写: 前 环绕 后 后置 异常 -->
<aop:before method="before" pointcut-ref="p"/>
<aop:around method="around" pointcut-ref="p"/>
<aop:after method="after" pointcut-ref="p"/>
<aop:after-returning method="returning" pointcut-ref="p"/>
<aop:after-throwing method="throwing" pointcut-ref="p"/>
</aop:aspect>
</aop:config>
9、JdbcTemplate
(1)什么是JdbcTemplate?
我们都知道使用原始的JDBC在操作数据库是比较麻烦的,所以Spring为了提高开发的效率,把JDBC封装、改造了一番,而JdbcTemplate就是Spring对原始JDBC封装之后提供的一个操作数据库的工具类。
我们可以借助JdbcTemplate来完成所有数据库操作,比如:增删改查等。改造之后的JdbcTemplate主要提供以下三种类型的方法:
- executeXxx() : 执行任何SQL语句,对数据库、表进行创建、修改、删除操作;
- updateXxx() : 执行增加、修改、删除等语句;
- queryXxx() : 执行查询相关的语句;
"用之不强,但弃之可惜",为什么这么说呢?
虽然JdbcTemplate算是最简单的数据持久层方案,属于另一种操作数据库的方式;但是从另一方面来说,实际开发过程中我们用的更多的是拥有更加强大的持久化框架来访问数据库,比如MyBatis、MyBatis Plus等。
(2)引入JdbcTemplate
<1>首先引入所需要的jar包
<2>在数据库配置文件中配置数据库连接池
<!-- 数据库连接池 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"destroy-method="close">
<property name="url" value="jdbc:mysql:///user_db" />
<property name="username" value="root" />
<property name="password" value="root" />
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
</bean>
<3>配置JdbcTemplate对象,注入DataAource
<!-- JdbcTemplate 对象 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!--注入 dataSource-->
<property name="dataSource" ref="dataSource"></property>
</bean>
<4>创建service类,创建dao,在dao里面注入JdbcTemplate对象
BookDao类:
public class BookDao {
}
BookDao的实现类:
@Repository
public class BookDaoImpl implements BookDao {
//注入 JdbcTemplate
@Autowired
private JdbcTemplate jdbcTemplate;
}
BookService服务层:
@Service
public class BookService {
//注入 dao
@Autowired
private BookDao bookDao;
}
XML配置组件扫描:
<!-- 组件扫描 -->
<context:component-scan base-package="com.company"></context:component-scan>
(3)JdbcTemplate添加操作
<1>对应数据库创建实体类
public class User {
private String username;
private String userId;
private String ustatus;
public User() {
}
public User(String username, String userId, String ustatus) {
this.username = username;
this.userId = userId;
this.ustatus = ustatus;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getUserId() { return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getUstatus() {
return ustatus;
}
public void setUstatus(String ustatus) {
this.ustatus = ustatus;
}
@Override
public String toString() {
return "User{" +
"username='" + username + '\'' +
", userId='" + userId + '\'' +
", ustatus='" + ustatus + '\'' +
'}';
}
}
<2>编写dao和service
- 在dao进行数据库添加
- 调用 JdbcTemplate 对象里面 update 方法实现添加操作
public class BookDao {
//添加的方法
public void add(Book book);
}
@Repository
public class BookDaoImpl implements BookDao {
//注入 JdbcTemplate
@Autowired
private JdbcTemplate jdbcTemplate;
//添加的方法
@Override
public void add(Book book) {
//1 创建 sql 语句
String sql = "insert into t_book values(?,?,?)";
//2 调用方法实现
Object[] args = {book.getUserId(), book.getUsername(),book.getUstatus()};
int update = jdbcTemplate.update(sql,args);
System.out.println(update);
}
}
<3>测试
@Test
public void testJdbcTemplate() {
ApplicationContext context =new ClassPathXmlApplicationContext("bean.xml");
BookService bookService = context.getBean("bookService",BookService.class);
Book book = new Book();
book.setUserId("1");
book.setUsername("java");
book.setUstatus("a");
bookService.addBook(book);
}
(4)JdbcTemplate删除和修改操作
<1>修改:
@Override
public void updateBook(Book book) {
String sql = "update t_book set username=?,ustatus=? where user_id=?";
Object[] args = {book.getUsername(), book.getUstatus(),book.getUserId()};
int update = jdbcTemplate.update(sql, args);
System.out.println(update);
}
<2>删除:
@Override
public void delete(String id) {
String sql = "delete from t_book where user_id=?";
int update = jdbcTemplate.update(sql, id);
System.out.println(update);
}
(5)JdbcTemplate查询返回某个值
queryForObject:
- 第一个参数:sql 语句
- 第二个参数:返回类型 Class
//查询表里面有多少条记录
@Override
public int selectCount() {
String sql = "select count(*) from t_book";
Integer count = jdbcTemplate.queryForObject(sql, Integer.class);
return count;
}
(6)JdbcTemplate查询返回对象
queryForObject:
参数1:sql 语句
参数2:RowMapper 是接口,针对返回不同类型数据,使用这个接口里面实现类完成数据封装
参数3:sql 语句值
//查询返回对象
@Override
public Book findBookInfo(String id) {
String sql = "select * from t_book where user_id=?";
//调用方法
Book book = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<Book>(Book.class),id);
return book;
}
(7)JdbcTemplate查询返回集合
query:
参数1:sql 语句
参数2:RowMapper 是接口,针对返回不同类型数据,使用这个接口里面实现类完成数据封装
参数3:sql 语句值
//查询返回集合
@Override
public List<Book> findAllBook() {
String sql = "select * from t_book";
//调用方法
List<Book> bookList = jdbcTemplate.query(sql, new BeanPropertyRowMapper<Book>(Book.class));
return bookList;
}
(8)JdbcTemplate批量添加
//批量添加
@Override
public void batchAddBook(List<Object[]> batchArgs) {
String sql = "insert into t_book values(?,?,?)";
int[] ints = jdbcTemplate.batchUpdate(sql, batchArgs);
System.out.println(Arrays.toString(ints));
}
//批量添加测试
List<Object[]> batchArgs = new ArrayList<>();
Object[] o1 = {"3","java","a"};
Object[] o2 = {"4","c++","b"};
Object[] o3 = {"5","MySQL","c"};
batchArgs.add(o1);
batchArgs.add(o2);
batchArgs.add(o3);
//调用批量添加
bookService.batchAdd(batchArgs);
(9)JdbcTemplate批量修改
//批量修改
@Override
public void batchUpdateBook(List<Object[]> batchArgs) {
String sql = "update t_book set username=?,ustatus=? where user_id=?";
int[] ints = jdbcTemplate.batchUpdate(sql, batchArgs);
System.out.println(Arrays.toString(ints));
}
测试:
//批量修改
List<Object[]> batchArgs = new ArrayList<>();
Object[] o1 = {"java0909","a3","3"};
Object[] o2 = {"c++1010","b4","4"};
Object[] o3 = {"MySQL1111","c5","5"};
batchArgs.add(o1);
batchArgs.add(o2);
batchArgs.add(o3);
//调用方法实现批量修改
bookService.batchUpdate(batchArgs);
(10)JdbcTemplate批量删除
//批量删除
@Override
public void batchDeleteBook(List<Object[]> batchArgs) {
String sql = "delete from t_book where user_id=?";
int[] ints = jdbcTemplate.batchUpdate(sql, batchArgs);
System.out.println(Arrays.toString(ints));
}
测试:
//批量删除
List<Object[]> batchArgs = new ArrayList<>();
Object[] o1 = {"3"};
Object[] o2 = {"4"};
batchArgs.add(o1);
batchArgs.add(o2);
//调用方法实现批量删除
bookService.batchDelete(batchArgs);
10、事务操作
(1)概念
事务是指逻辑上的一组操作,组成这组操作的各个单元,要么全部成功,要么全部不成功。
(2)ACID特性
- 原子性:一个事务中的操作要么全部成功,要么全部失败。
- 一致性:指的是数据库总是从一个一致性的状态转换到另一个一致性的状态。
比如A转账给B100块钱,假设B只有90块,支付之前我们数据库里面的数据都是符合约束的,但是如果事务执行成功了,我们的数据库数据就破坏约束了,因此事务不能执行成功,所以说事务提供了一致性的保证。
- 隔离性:一个事务的修改在最终提交前,对其他事务是不可见的。
- 持久性:一旦事务提交,所做的修改就会永久保存到数据库中。
(3)搭建事务操作环境
<1>创建数据库表,添加记录:
<2>创建 service,搭建 dao,完成对象创建和注入关系
UserDao接口:
public interface UserDao {
//少钱
public void reduceMoney();
//多钱
public void addMoney() ;
}
UserDao实现类:
@Repository
//@Repository用在持久层接口上,将接口的实现类交给spring来进行管理
public class UserDaoImpl implements UserDao {
@Autowired
private JdbcTemplate jdbcTemplate;
//lucy 转账 100 给 mary
//少钱
@Override
public void reduceMoney() {
String sql = "update t_account set money=money-? where username=?";
jdbcTemplate.update(sql,100,"lucy");
}
//多钱
@Override
public void addMoney() {
String sql = "update t_account set money=money+? where username=?";
jdbcTemplate.update(sql,100,"mary");
}
}
创建UserService服务层,在里面创建转账的方法:
@Service
public class UserService {
//注入 dao
@Autowired
private UserDao userDao;
//转账的方法
public void accountMoney() {
//lucy 少 100
userDao.reduceMoney();
//mary 多 100
userDao.addMoney();
}
}
<3>上面代码,如果正常执行没有问题的,但是如果代码执行过程中出现异常,就会出现问题
@Service
public class UserService {
//注入 dao
@Autowired
private UserDao userDao;
//转账的方法
public void accountMoney() {
//lucy 少 100
userDao.reduceMoney();
//模拟异常
int i=10/0;
//mary 多 100
userDao.addMoney();
}
}
用事务来解决上述出现的异常:
public void accountMoney() {
try {
//第一步:开启事务
//第二步:进行业务操作
//lucy 少 100
userDao.reduceMoney();
//模拟异常
int i=10/0;
//mary 多 100
userDao.addMoney();
//第三步:没有发生异常,提交事务
}catch (Exception e){
//第四步:发生异常,事务回滚
}
}
(4)spring事务管理介绍
<1>事务添加到 JavaEE 三层结构里面 Service 层(业务逻辑层)
<2>在 Spring 进行事务管理操作有两种方式:编程式事务管理和声明式事务管理(使用)
<3>声明式事务管理 (1)基于注解方式(使用) (2)基于 xml 配置文件方式
<4>在 Spring 进行声明式事务管理,底层使用 AOP 原理
(5)注解声明事务管理
<1>在spring文件中配置事务管理器
<!--创建事务管理器-->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--注入数据源-->
<property name="dataSource" ref="dataSource"></property>
</bean>
<2>在spring配置文件中开启事务注解
<!--开启事务注解-->
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
<3>在 service 类上面(或者 service 类里面方法上面)添加事务注解
@Transactional,这个注解添加到类上面,也可以添加方法上面 ;
如果把这个注解添加类上面,这个类里面所有的方法都添加事务 ;
如果把这个注解添加方法上面,为这个方法添加事务
@Service
@Transactional
public class UserService {
//注入 dao
@Autowired
private UserDao userDao;
//转账的方法
public void accountMoney() {
//lucy 少 100
userDao.reduceMoney();
//模拟异常
int i=10/0;
//mary 多 100
userDao.addMoney();
}
}
(6)声明事务管理参数配置
<1>在 service 类上面添加注解@Transactional,在这个注解里面可以配置事务相关参数。
- propagation:事务传播行为
//格式
@Transactional(propagation = Propagation.REQUIRED)
REQUIRED(默认) | 如果存在一个事务在运行,则支持当前事务;如果没有则开启一个新的事务 |
MANDATORY | 支持当前事务,如果没有正在运行的事务,就抛出异常 |
NEVER | 当前的方法不应该运行在事务中,如果有运行的事务,就抛出异常 |
NOT_SUPPORTED | 当前的方法不应该运行在事务中;如果当前存在事务,将它挂起; |
REQUIRED_NEW | 当前方法必须启动新事务,并在它自己的事务内运行;如果当前存在事务正在运行,则当前事务挂起; |
SUPPORTS | 支持当前事务,如果当前没有事务,就以非事务方式执行 |
NESTED | 支持当前事务,新增savepoint点,与当前事务同步提交或回滚 |
- ioslation:事务隔离级别
脏读:一个未提交事务读取到另一个未提交事务的数据,产生脏读。
不重复读:一个未提交事务读取到另一提交事务修改数据
幻读:一个未提交事务读取到另一提交事务添加数据,产生幻读。
通过设置事务隔离级别,解决以上读问题:
//格式
@Transactional(propagation = Propagation.REQUIRED,isolation=Isolation.REPEATABLE_READ)
脏读 | 不可重复读 | 幻读 | |
RED_UNCOMMITTED (读未提交) | 有 | 有 | 有 |
READ_COMMITTED (读已提交) | 无 | 有 | 有 |
REPEATABLE_READ (可重复读) | 无 | 无 | 有 |
SERIALIZABLE (串行化) | 无 | 无 | 无 |
- timeout:超时时间
事务需要在一定时间内进行提交,如果不提交进行回滚;
默认值是 -1 ,设置时间以秒为单位进行计算;
- readOnly:是否只读
读:查询操作,写:添加修改删除操作;
readOnly 默认值 false,表示可以查询,可以添加修改删除操作;
设置 readOnly 值是 true,设置成 true 之后,只能查询;
- rollbackFor:回滚
设置出现哪些异常进行事务回滚;
- noRollbackFor:不回滚
设置出现哪些异常不进行事务回滚;
(7)XML声明事务管理
<1>配置事务管理器
<!--创建事务管理器-->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--注入数据源-->
<property name="dataSource" ref="dataSource"></property>
</bean>
<2>配置通知
<!--配置通知-->
<tx:advice id="txadvice">
<!--配置事务参数-->
<tx:attributes>
<!--指定哪种规则的方法上面添加事务-->
<tx:method name="accountMoney" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<3>配置切入点和切面
<!--配置切入点和切面-->
<aop:config>
<!--配置切入点-->
<aop:pointcut id="pt" expression="execution(*com.company.service.UserService.*(..))"/>
<!--配置切面-->
<aop:advisor advice-ref="txadvice" pointcut-ref="pt"/>
</aop:config>
spring5框架新增功能
.......................