49 KiB
title | author | top | cover | toc | mathjax | summary | tags | categories | reprintPolicy | abbrlink | date | coverImg | img | password |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Spring5 | TianZD | true | true | true | false | SSM中的Spring5框架学习笔记,粗略学了一下,没有参考价值 | [Spring java 学习笔记] | [java] | cc_by | 7f09f5da | 2022-04-29 11:06:51 | <nil> | <nil> | <nil> |
[toc]
Spring
1、概述
概述
Spring框架是一个开放源代码的J2EE应用程序框架,由Rod Johnson发起,是针对bean的生命周期进行管理的轻量级容器(lightweight container)。 Spring解决了开发者在J2EE开发中遇到的许多常见的问题,提供了功能强大IOC、AOP及Web MVC等功能。Spring可以单独应用于构筑应用程序,也可以和Struts、Webwork、Tapestry等众多Web框架组合使用,并且可以与 Swing等桌面应用程序AP组合。因此, Spring不仅仅能应用于J2EE应用程序之中,也可以应用于桌面应用程序以及小应用程序之中。
理念
使现有的技术更加容易使用,本身是一个大杂烩,整合了现有的技术框架
SSH和SSM
SSH:Struct2+Spring+Hibernate
SSM:SpringMVC+Spring+Mybatis
优点
- 开放源代码的免费的J2EE应用程序框架(容器)
- 轻量级、非入侵式的框架
- 是针对bean的生命周期进行管理的轻量级容器
- 提供了功能强大IOC、AOP及Web MVC等功能
- 支持事务的处理
- Spring可以单独应用于构筑应用程序,也可以和Struts、Webwork、Tapestry等众多Web框架组合使用
总结:Spring就是一个轻量级的控制反转IOC和面向切面编程AOP的框架
缺点
- 配置十分繁琐,人称“配置地狱”
组成
Spring框架主要由七部分组成,分别是 Spring Core、 Spring AOP、 Spring ORM、 Spring DAO、Spring Context、 Spring Web和 Spring Web MVC。
扩展
现代化的java开发,就是基于spring的开发
- Spring Boot
快速开发的脚手架,基于springboot可以快速开发单个微服务
约定大于配置
- Spring Cloud
基于Spring Boot实现
现在大多数公司都在使用springboot进行快速开发
学习springboot需要完全掌握spring 和springmvc
2、IOC理论推导
原来方式
基础代码
- 编写Dao层UserDao接口
public interface UserDao {
void getUser();
}
- 编写Dao层UserDaoImpl实现类和UserDaoMysqlImpl实现类
public class UserDaoImpl implements UserDao {
@Override
public void getUser() {
System.out.println("默认获取用户数据");
}
}
public class UserDaoMysqlImpl implements UserDao{
@Override
public void getUser() {
System.out.println("Mysql获取用户数据");
}
}
- 编写Service层UserService接口
public interface UserService {
void getUser();
}
- 编写Service层UserServiceImpl实现类
public class UserServiceImpl implements UserService {
//创建dao层对应接口的实现类对象,从而调用dao层的方法
//弊端:新增或者改变需求时,dao层实现类增加或改变,需要更改这里的代码
private UserDao userDao = new UserDaoImpl();
@Override
public void getUser() {
//调用dao层对应方法
userDao.getUser();
}
}
- 编写用户代码MyTest类
public class MyTest {
public static void main(String[] args) {
//用户实际调用的是业务层,不接触dao层
UserService userService = new UserServiceImpl();
userService.getUser();
}
}
分析
用户实际调用的是业务层,不接触dao层,通过创建userservice
接口引用指向一个UserServiceImpl
实现类对象,调用UserServiceImpl
中的getUser()
方法。由于UserServiceImpl
中创建dao层对应接口的实现类对象,从而调用dao层的getUser()
方法
弊端:
在我们之前的业务中,用户的需求可能会影响我们原来的代码,需要根据用户的需求去修改源代码,如果程序代码量大,修改的成本十分昂贵
- 新增或者改变需求时,dao层实现类增加或改变,需要更改这里的代码,比如要调用
UserDaoMysqlImpl
中的方法,需要改变UserServiceImpl中的代码
改进-IOC原型
代码
- 改变Service层UserServiceImpl实现类
public class UserServiceImpl implements UserService {
private UserDao userDao;
//通过set进行动态实现值的注入
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
@Override
public void getUser() {
//调用dao层对应方法
userDao.getUser();
}
}
- 改变用户代码MyTest类
public class MyTest {
public static void main(String[] args) {
//用户实际调用的是业务层,不接触dao层
UserService userService = new UserServiceImpl();
// 实现UserDaoImpl类中的方法
((UserServiceImpl) userService).setUserDao(new UserDaoImpl());
userService.getUser();
//实现UserDaoMysqlImpl方法
((UserServiceImpl) userService).setUserDao(new UserDaoMysqlImpl());
userService.getUser();
}
}
分析
在新增或者改变需求时,只需要更改MyTest类中的代码
使用一个set接口实现,实现了革命性的变化
private UserDao userDao;
//通过set进行动态实现值的注入
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
- 之前程序是主动创建对象,控制权在程序员手上,用户访问业务层,由程序员代码决定用户调用什么
- 使用set注入后,程序不具有主动性,通过把接口给用户,用户访问业务层,主动权在用户,由用户选择调用什么,程序变成了被动接收对象(来自用户)
这种思想即控制反转IOC的原型,从本质上解决了问题,程序员不需要再管理对象的创建了,系统的耦合性大大降低,可以更加专注在业务的实现上
IOC本质
控制反转是一种通过描述(XML或者注解)并通过第三方去生产或获取特定对象的方式,在Spring中实现控制反转的是IOC容器,实现方式是依赖注入
-
控制反转IOC(inversion of control)是一种设计思想,DI(依赖注入)是实现IOC的一种方式。
-
在没有IOC的程序中,使用面向对象编程,对象的创建和对象间的依赖关系完全硬编码在程序中,对象的创建由程序自己控制
-
控制反转后,对象的创建转移给了第三方
-
控制反转就是获得依赖对象的方式反转了
IOC是Spring框架的核心内容,可以使用XML配置,也可以使用注解,新版本的Spring也可以零配置实现IOC。采用XML方式配置Bean的时候,Bean的的定义信息和实现是分离的,采用注解的方式可以把两者合为一体,Bean的定义信息直接以注解的形式定义在实现类中,从而达到了零配置的目的
Spring容器在初始化时先读取配置文件,根据配置文件或元数据创建于组织对象存入容器中,程序使用时再从IOC容器中取出需要的对象
3、HelloSpring
代码
创建实体类Hello.class
该类为后续要生成的对象的类,必须要有set方法(依赖注入要用)
public class Hello {
private String str;
public String getStr() {
return str;
}
public void setStr(String str) {
this.str = str;
}
public Hello() {
}
public Hello(String str) {
this.str = str;
}
public void helloSpring() {
System.out.println("hello " + str);
}
}
Spring配置文件Beans.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--使用Spring创建对象
类名 变量名 = new 类
正常创建对象:Hello hello = new Hello();
bean = 对象
property相当于给对象中的属性设置值
id = 变量名
class = new 的对象
-->
<bean id="hello" class="com.tian.pojo.Hello">
<property name="str" value="Spring"/>
</bean>
</beans>
业务代码MyTest.class
import com.tian.pojo.Hello;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest {
public static void main(String[] args) {
//获取Spring的上下文对象,固定语句
ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
//对象都在Spring中进行管理,要使用的化直接去里面取
Hello hello = (Hello) context.getBean("hello");
hello.helloSpring();
}
}
分析
对象由spring进行创建,对象的属性由Spring容器设置,这个过程就叫控制反转IOC,一句话说就是对象由Spring来创建、管理、装配
-
控制:传统应用程序的对象由程序本身控制创建,使用SPring后,对象由Spring进行创建
-
反转:程序本身不创建对象,变为被动的接收对象
-
依赖注入:利用set方法进行注入,对象必须要有set方法
4、IOC创建对象的方式
在xml中使用<bean>后,不管有没有使用,都会创建
使用无参构造创建对象,默认
必须有无参构造函数
<bean id="hello" class="com.tian.pojo.Hello">
<property name="str" value="Spring"/>
</bean>
有参构造函数——参数名
重点
<bean id="hello" class="com.tian.pojo.Hello">
<constructor-arg name="str" value="直接通过参数名"/>
</bean>
通过有参构造函数——参数下标
<bean id="hello" class="com.tian.pojo.Hello">
<constructor-arg index="0" value="参数下标"/>
</bean>
有参构造函数——参数类型
不建议使用:当类中有两个或以上属性类型一样时,会导致错误
<bean id="hello" class="com.tian.pojo.Hello">
<constructor-arg type="java.lang.String" value="有参类型"/>
</bean>
总结
在配置文件加载的时候,容器中管理的对象就已经初始化了
5、Spring配置
别名alias
起一个小名,两个名字hello和hello2都能用来创建对象
<alias name="hello" alias="hello2"/>
Bean的配置
<!--
id:bean的唯一标识符,也就是相当于对象名
class:bean对象对应的全限定名:包名+类型
name:也是别名,而且name可以取多个别名,可以用空格、分号;、逗号,分隔
-->
<bean id="hello" class="com.tian.pojo.Hello" name="hello3,hello4">
<constructor-arg name="str" value="直接通过参数名"/>
</bean>
import
一般用于多个团队开发,可以将多个配置文件导入合成一个
项目中有多个人开发,三个人负责不同的类的开发,不同的类需要注册在不同的bean中,可以利用import将所有的bean.xml合成一个总的。
<import resource="Beans.xml"></import>
6、DI依赖注入
构造器注入
见4、IOC创建对象的方式
set方式注入【重点】
依赖注入:set注入
- 依赖:bean对象的创建依赖于容器
- 注入:bean对象中的所有属性由容器注入
环境搭建
- 复杂类型
package com.tian.pojo;
public class Address {
private String address;
//get set tostring省略
}
- 真实类型
public class Student {
private String name;
private Address address;
private String[] books;
private List<String> hobbies;
private Map<String, String> card;
private Set<String> games;
private String wife;
private Properties info;
//省略了get 和set tostring
}
- 配置文件
<bean id="student" class="com.tian.pojo.Student">
<!--第一种注入,普通值注入,value= -->
<property name="name" value="tian"></property>
</bean>
- 测试代码
public class MyTest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
Student student = (Student) context.getBean("student");
System.out.println(student.getName());
}
}
注入方式
-
普通值注入value
-
bean注入ref
-
数组注入
-
list
-
map
-
properties
-
null
<bean id="address" class="com.tian.pojo.Address">
<property name="address" value="上海"/>
</bean>
<bean id="student" class="com.tian.pojo.Student">
<!--普通值注入,value= -->
<property name="name" value="tian"></property>
<!--bean注入,ref = -->
<property name="address" ref="address"/>
<!--数组注入 <array><value>-->
<property name="books">
<array>
<value>book1</value>
<value>book2</value>
<value>book3</value>
</array>
</property>
<!--list注入,<list><value>-->
<property name="hobbies">
<list>
<value>听歌</value>
<value>电影</value>
</list>
</property>
<!--map注入,<map><entry>-->
<property name="card">
<map>
<entry key="key1" value="val1"/>
<entry key="key2" value="val2"/>
</map>
</property>
<!--set注入,<set><value>-->
<property name="games">
<set>
<value>LOL</value>
<value>COD</value>
</set>
</property>
<!--null注入,<null>
空字符串的话,直接name,value=“”即可-->
<property name="wife">
<null></null>
</property>
<!--properties注入-->
<property name="info">
<props>
<prop key="driver">com.mysql.driver</prop>
<prop key="url">ual</prop>
<prop key="name">root</prop>
<prop key="password">123456</prop>
</props>
</property>
</bean>
扩展C\P命名空间注入
这个的使用要在bean.xml文件的头目录里面加上两行语句
xmlns:p="http://www.springframework.org/schema/p" xmlns:c="http://www.springframework.org/schema/c"
<!--p命名空间注入,可以直接设置注入属性的值:property-->
<bean id="address1" class="com.baidu.pojo.Address" p:name="陕西省"/>
<!--c命名空间注入,通过构造器注入:construct-args-->
<bean id="address2" class="com.baidu.pojo.Address" c:name="宝鸡市"/>
7、Bean的作用域scope
单例模式
scope="singleton"
默认就是单例,也可以手动设置
<bean id="address" class="com.tian.pojo.Address" scope="singleton">
<property name="address" value="上海"/>
</bean>
原型模式
scope="prototype"
每次从容器中get的时候,都会产生一个新对象
<bean id="address" class="com.tian.pojo.Address" scope="prototype">
<property name="address" value="上海"/>
</bean>
其余的request、session、application
只能在web开发中使用
8、Bean的自动装配
自动装配:Spring会在上下文中自动寻找、装配属性
装配方式:
- 在xml中显式的配置
- 在java中显式的配置
- 隐式的自动装配Bean【重要】
搭建环境
一个人有两个从宠物,dog和cat
xml配置
<bean id="cat" class="com.tian.pojo.Cat"/>
<bean id="dog" class="com.tian.pojo.Dog"/>
<bean id="person" class="com.tian.pojo.Person">
<property name="name" value="tian"/>
<property name="cat" ref="cat"/>
<property name="dog" ref="dog"/>
</bean>
byName、byType自动装配
byName
byname:会自动在容器上下文中查找和自己对象中set方法后面中的值对应的bean id,如果dog改成dog222,则不会成功
<bean id="cat" class="com.tian.pojo.Cat"/>
<bean id="dog" class="com.tian.pojo.Dog"/>
<!--byname:会自动在容器上下文中查找和自己对象中set方法后面中的值对应的bean id-->
<bean id="person" class="com.tian.pojo.Person" autowire="byName">
<property name="name" value="tian"/>
</bean>
byType
byType:会自动在容器上下文中查找和自己对象属性类型相同的bean
如果dog改成dog222 ,依然会成功,甚至注册dog时不需要id属性<bean class="com.tian.pojo.Dog"/>
注意,如果dog注册了多个对象,即同一个类型有两个对象,则bytype不可以使用
<bean id="cat" class="com.tian.pojo.Cat"/>
<bean id="dog222" class="com.tian.pojo.Dog"/>
<!--byType:会自动在容器上下文中查找和自己对象属性类型相同的bean-->
<bean id="person" class="com.tian.pojo.Person" autowire="byType">
<property name="name" value="tian"/>
</bean>
总结
- byname:需要保证所有bean的id唯一,并且bean需要和自动注入的属性的set方法的值一致
- byType:需要保证所有bean的class唯一,并且bean需要和自动注入的属性类型一致
使用注解实现自动装配
Spring2.5开始支持注解
使用注解:
- 导入约束:context约束
xmlns:context="http://www.springframework.org/schema/context"
- 配置注解的支持:
<context:annotation-config/>
<?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:annotation-config/>
</beans>
- xml配置
<bean id="cat" class="com.tian.pojo.Cat"/>
<bean id="dog" class="com.tian.pojo.Dog"/>
<bean id="person" class="com.tian.pojo.Person"/>
-
默认为byType方式
-
直接在属性上使用或者在set方法上使用
-
可以不需要set方法
public class Person {
@Autowired
private Cat cat;
@Autowired
private Dog dog;
private String name;
}
- 扩展:autowired有一个属性,required,默认为true,如果显式的定义了其为false,说明这个对象允许为null
@Autowired(required = false)
Autowired默认为byType方式,如果IOC容器中同一个类型注册了多个对象,那么会出现问题,需要配置Qualifier使用,指定具体的对象
<bean id="cat1" class="com.tian.pojo.Cat"/>
<bean id="cat2" class="com.tian.pojo.Cat"/>
public class Person {
@Autowired
@Qualifier(value = "cat1")
private Cat cat;
}
Resource是java自带的注解
通过byName方式
public class Person {
//@Autowired
//@Qualifier(value = "cat1")
@Resource(name="cat1")
private Cat cat;
}
9、使用注解开发
Bean
- 导包,必须有aop的包,
pop.xml
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.9</version>
</dependency>
</dependencies>
- 导入context约束,增加注解支持,
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"
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.tian.pojo"/>
<!--注解支持-->
<context:annotation-config/>
</beans>
- 实体类@Component
在实体类上使用@Component
注解等价于<bean id="user" class="com.tian.pojo.User"/>
,id默认为类的名字小写
//等价于<bean id="user" class="com.tian.pojo.User"/>
// id默认为类的名字小写
@Component
public class User {
private String name;
//get set
}
属性的注入@Value
适用于简单的,复杂的还是通过配置文件
//等价于<bean id="user" class="com.tian.pojo.User"/>
// id默认为类的名字小写
@Component
public class User {
@Value("tianzd")
private String name;
//get set
}
衍生的注解
@Component有几个衍生注解,在web开发中会按照MVC三层架构开发
- @Component 用于pojo层
- @Service 用于service业务层
- @Controller 用于Controller层
- @Repository 用于Dao层
四个功能一样,都是代表将某个类注册到spring中,装配Bean
自动装配注解
作用域注解@Scope
在某个类上标注,修改称单例或者多例模式
小结
xml与注解
-
xml更加万能,适用于任何场景,维护简单方便
-
注解不是自己的类使用不了,维护复杂
最佳实践
- xml用来管理Bean
- 注解只负责完成属性注入@Value
10、使用java的方式配置Spring
舍弃xml配置文件,使用config类实现
实体类
package com.tian.pojo;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
//这个注解的意思就是说明这个类被注册到了容器中
@Component
public class User {
private String name;
public String getName() {
return name;
}
@Value("tianzhendong")
public void setName(String name) {
this.name = name;
}
}
配置类
package com.tian.config;
import com.tian.pojo.User;
import org.springframework.context.annotation.*;
//使其成为配置类,同beans.xml
//这个也会被spring容器托管,注册到容器中,因为他也是一个@component
@Configuration
@ComponentScan("com.tian")
//@Import(MyConfig1.class) 合并配置类,相当于xml中的import
public class MyConfig {
//注册一个bean,相当于一个bean标签
//方法的名字相当于id属性
//返回值类型,相当于class属性
//返回值就是要注入到bean中的对象
@Bean(name = "user1")
@Scope(value = "singleton")
public User getUser() {
return new User();
}
}
测试类
import com.tian.config.MyConfig;
import com.tian.pojo.User;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class);
User user = context.getBean("user1", User.class);
System.out.println(user.getName());
}
}
11、代理模式
中介
为什么学习代理模式
在业务增删改时,为了避免更改底层的代码
代理模式是SpringAop的底层
SpringAOP的底层,面试重点
分类
- 静态代理
- 动态代理
静态代理
租房:中介,房东,客户,出租房接口(中介和房东的)
角色分析
- 抽象角色:一般会使用接口或者抽象类解决
- 真实角色:被代理的角色,房东
- 代理角色:代理真实角色,中介,代理真实角色后,一般会做一些附加操作
- 客户:访问代理对象的角色
抽象接口
package com.tian.demo1;
public interface Rent {
public void rent();
}
实现类
package com.tian.demo1;
public class LandLord implements Rent{
@Override
public void rent() {
System.out.println("房子租出去了");
}
}
代理
package com.tian.demo1;
public class Proxy implements Rent{
private Rent landLord;
public Proxy(LandLord landLord) {
this.landLord = landLord;
}
public Proxy() {
}
@Override
public void rent() {
landLord.rent();
}
}
客户端
package com.tian.demo1;
public class Client {
public static void main(String[] args) {
Proxy proxy = new Proxy(new LandLord());
proxy.rent();
}
}
优点
- 可以使真实角色的操作更加纯粹,不用去关注一些公共的业务
- 公共业务交给代理,实现了业务的分工
- 公共业务发生扩展时,方便集中管理
缺点
- 代理类和委托类实现了相同的接口,代理类通过委托类实现了相同的方法。这样就出现了大量的代码重复。如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。
- 代理对象只服务于一种类型的对象,如果要服务多类型的对象。势必要为每一种对象都进行代理,静态代理在程序规模稍大时就无法胜任了。
动态代理
每个代理类只能为一个接口服务,这样程序开发中必然会产生许多的代理类,为了弥补静态代理的缺点:一个真实角色就会产生一个代理,代码量翻倍,开发效率降低
概述
动态代理是在运行时,通过反射机制实现动态代理,并且能够代理各种类型的对象
- 角色和静态代理一样
- 动态代理的代理类是动态生成的,不是直接写好的
分类
- 基于接口的动态代理——JDK动态代理,这里使用这种
- 基于类——cglib
- java字节码——javasist
JDK动态代理
在Java中要想实现动态代理机制,需要java.lang.reflect.InvocationHandler接口和 java.lang.reflect.Proxy 类的支持
- Proxy类:生成动态代理实例
- InvocationHandler接口:调用处理程序并返回一个结果
InvocationHandler接口
每一个代理实例都有一个关联的调用处理程序,InvocationHandler是由代理实例的调用处理程序实现的接口
方法:invoke(object, object[])
//Object proxy:被代理的对象
//Method method:要调用的方法
//Object[] args:方法调用时所需要参数
public interface InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}
Proxy类
有个静态方法,可以创建动态代理类的实例
//CLassLoader loader:类的加载器
//Class<?> interfaces:得到全部的接口
//InvocationHandler h:得到InvocationHandler接口的子类的实例
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException
动态创建代理对象的类
- 动态代理类只能代理接口(不支持抽象类),代理类都需要实现InvocationHandler类,实现invoke方法
- invoke方法就是调用被代理接口的所有方法时需要调用的,该invoke方法返回的值是被代理接口的一个实现类
//动态代理类只能代理接口(不支持抽象类),代理类都需要实现InvocationHandler类,实现invoke方法。
//invoke方法就是调用被代理接口的所有方法时需要调用的,该invoke方法返回的值是被代理接口的一个实现类
public class LogHandler implements InvocationHandler {
// 目标对象
private Object targetObject;
//绑定关系,也就是关联到哪个接口(与具体的实现类绑定)的哪些方法将被调用时,执行invoke方法。
public Object newProxyInstance(Object targetObject){
this.targetObject=targetObject;
//该方法用于为指定类装载器、一组接口及调用处理器生成动态代理类实例
//第一个参数指定产生代理对象的类加载器,需要将其指定为和目标对象同一个类加载器
//第二个参数要实现和目标对象一样的接口,所以只需要拿到目标对象的实现接口
//第三个参数表明这些被拦截的方法在被拦截时需要执行哪个InvocationHandler的invoke方法
//根据传入的目标返回一个代理对象
return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(),
targetObject.getClass().getInterfaces(),this);
}
@Override
//关联的这个实现类的方法被调用时将被执行
/*InvocationHandler接口的方法,proxy表示代理,method表示原对象被调用的方法,args表示方法的参数*/
//invoke方法中实现了代理类要扩展的公共功能。
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{
//代理扩展逻辑,实现代理类要扩展的公共功能
System.out.println("proxy do");
return method.invoke(targetObject, args);
}
}
优点
- 可以使真实角色的操作更加纯粹,不用去关注一些公共的业务
- 公共业务交给代理,实现了业务的分工
- 公共业务发生扩展时,方便集中管理
- 一个动态代理类代理的是一个接口,一般就是对应一个业务
- 一个动态代理类可以代理多个类,只要是实现了同一个接口即可
12、AOP
简介
什么是AOP
面向切面编程,AOP是能够让我们在不影响原有功能的前提下,为软件横向扩展功能。 那么横向扩展怎么理解呢,我们在WEB项目开发中,通常都遵守三层原则,包括控制层(Controller)->业务层(Service)->数据层(dao),那么从这个结构下来的为纵向,它具体的某一层就是我们所说的横向。我们的AOP就是可以作用于这某一个横向模块当中的所有方法。
和oop区别
AOP和OOP的区别:AOP是OOP的补充,当我们需要为多个对象引入一个公共行为,比如日志,操作记录等,就需要在每个对象中引用公共行为,这样程序就产生了大量的重复代码,使用AOP可以完美解决这个问题。
AOP在Spring中的作用
提供声明式事务,允许用户自定义切面
- 横切关注点:跨越应用程序多个模块的方法或功能,即:与我们业务逻辑无关的,但我们需要关注的部分,如日志,安全,缓存,事务等
- 切面:拦截器类,其中会定义切点以及通知
- 切点:具体拦截的某个业务点
- 通知:切面当中的方法,声明通知方法在目标业务层的执行位置,通知类型如下:
- 前置通知:@Before 在目标业务方法执行之前执行
- 后置通知:@After 在目标业务方法执行之后执行
- 返回通知:@AfterReturning 在目标业务方法返回结果之后执行
- 异常通知:@AfterThrowing 在目标业务方法抛出异常之后
- 环绕通知:@Around 功能强大,可代替以上四种通知,还可以控制目标业务方法是否执行以及何时执行
方式1:使用Spring的API接口实现AOP
导包:注意
<dependencies>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.7</version>
</dependency>
</dependencies>
接口
public interface UserService {
public void add();
public void delete();
public void update();
public void selete();
}
接口实现类
public class UserServiceImpl implements UserService{
@Override
public void add() {
System.out.println("add");
}
@Override
public void delete() {
System.out.println("delete");
}
@Override
public void update() {
System.out.println("update");
}
@Override
public void selete() {
System.out.println("selete");
}
}
增强方法log:注意
package com.tian.log;
import org.springframework.aop.MethodBeforeAdvice;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
@Component
public class Log implements MethodBeforeAdvice {
@Override
//method:要执行的目标对象的方法
//objects: 参数
//target:目标对象
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("目标对象为:" + target.getClass().getName());
System.out.println("执行的方法为:" + method.getName());
for (int i = 0; i < args.length; i++) {
System.out.println("第"+i+"个参数为:"+args[i]);
}
}
}
xml配置:注意
<!--注册bean-->
<bean id="userService" class="com.tian.service.UserServiceImpl"/>
<bean id="log" class="com.tian.log.Log"/>
<!--方式1,使用原生API接口-->
<!--配置aop,需要导入aop约束-->
<aop:config>
<!--切入点, expression是表达式,execution(要执行的位置 * * * *)-->
<aop:pointcut id="pointcut" expression="execution(* com.tian.service.UserServiceImpl.*(..))"/>
<!--执行环绕增加-->
<aop:advisor advice-ref="log" pointcut-ref="pointcut"/>
</aop:config>
测试:注意
import com.tian.service.UserService;
import com.tian.service.UserServiceImpl;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
//动态代理代理的是接口
UserService userService = context.getBean("userService", UserService.class);
userService.add();
}
}
方式2:自定义切入类
切入类
public class DiyPointCut {
public void before() {
System.out.println("---------方法执行前---------");
}
public void after() {
System.out.println("---------方法执行后---------");
}
}
配置
<bean id="userservice" class="com.service.UserServiceImpl"/>
<bean id="diy" class="com.diy.DiyPointCut"></bean>
<aop:config> <!--第二种方式:使用AOP的标签实现-->
<aop:aspect ref="diy">
<aop:pointcut id="diyPonitcut" expression="execution(* com.service.UserServiceImpl.*(..))"/>
<!-- pointcut-ref关联的切入点 , method切入的方法 -->
<aop:before pointcut-ref="diyPonitcut" method="before"/>
<aop:after pointcut-ref="diyPonitcut" method="after"/>
</aop:aspect>
</aop:config>
方式3:注解
增强类
//声明该类是一个切面
@Aspect
public class AnnotationPointCut {
//声明前置方法
@Before("execution(* com.service.UserServiceImpl.*(..))")
public void before() {
System.out.println("这是使用注解的前置增强");
}
//声明后置方法
@After("execution(* com.service.UserServiceImpl.*(..))")
public void after() {
System.out.println("使用注解的后置增强");
}
//环绕增强的优先级更高
@Around("execution(* com.service.UserServiceImpl.*(..))")
public void around(ProceedingJoinPoint jp) throws Throwable {
System.out.println("环绕前");
System.out.println("签名:" + jp.getSignature());
//执行目标方法proceed
Object proceed = jp.proceed();
System.out.println("环绕后");
System.out.println("proceed对象:"+proceed);
}
}
配置
<bean id="userservice" class="com.service.UserServiceImpl"/>
<bean id="annotationPointcut" class="com.diy.AnnotationPointCut"/>
<aop:aspectj-autoproxy/>
13、整合Mybatis
步骤:
- 导入jar包
- junit单元测试
- mybatis
- mysql
- spring
- aop
- mybatis-spring(新包,用于整合)
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.25</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.7</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.9</version>
</dependency>
<!--spring操作数据库-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.9</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.7</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.2</version>
</dependency>
</dependencies>
-
编写配置文件
-
测试
回忆mybatis
数据:
- 编写实体类
package com.tian.pojo;
/**
* @program: SpringStudy
* @description:
* @author: TianZD
* @create: 2021-08-10 21:00
**/
public class User {
private int id;
private String name;
private String password;
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", password='" + password + '\'' +
'}';
}
public int getId() {
return id;
}
public void setId(int 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;
}
public User() {
}
public User(int id, String name, String password) {
this.id = id;
this.name = name;
this.password = password;
}
}
- 编写核心配置文件
<?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>
<package name="com.tian"/>
</typeAliases>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSl=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="com/tian/dao/UserMapper.xml"/>
</mappers>
</configuration>
- 编写核心配置类
package com.tian.utils;
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;
/**
* @program: SpringStudy
* @description:
* @author: TianZD
* @create: 2021-08-10 21:07
**/
public class MybatisUtils {
public static SqlSessionFactory sqlSessionFactory;
static {
try {
String resources = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resources);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
}
public static SqlSession getSqlSession() {
return sqlSessionFactory.openSession();
}
}
- 编写接口
package com.tian.dao;
import com.tian.pojo.User;
import javax.sound.midi.VoiceStatus;
import java.util.List;
import java.util.Map;
/**
* @program: SpringStudy
* @description:
* @author: TianZD
* @create: 2021-08-10 21:18
**/
public interface UserMapper {
public int insert(User user);
public int delete(int id);
public int update(User user);
public User select(int id);
}
- 编写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.tian.dao.UserMapper">
<resultMap id="map" type="User">
<result column="pwd" property="password"/>
</resultMap>
<select id="select" resultType="user">
select *
from mybatis.user;
</select>
<delete id="delete" parameterType="int">
delete
from mybatis.user
where id = #{id};
</delete>
<update id="update" parameterType="map">
update mybatis.user
set name = #{name},pwd = #{password}
where id =#{id};
</update>
<insert id="insert" parameterType="map">
insert into mybatis.user (id, name, pwd)
values (#{id},#{name},#{password});
</insert>
</mapper>
- 测试
Mybatis-Spring
- 不变内容:
实体类User.class
UserMapper.interface接口
- 编写数据源
<!--dataSource-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<property name="url"
value="jdbc:mysql://localhost:3306/mybatis?useSSl=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</bean>
- SqlSessionFactory
<!--sqlSessionFactory-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<!--绑定mybatis-config.xml-->
<property name="configLocation" value="classpath:mybatis-config.xml"/>
<property name="mapperLocations" value="classpath:com/tian/dao/UserMapper.xml"/>
<property name="typeAliasesPackage" value="com.tian"/>
</bean>
- SqlSessionTemplate
<!--SqlSessionTemplate-->
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
<!--只能通过构造函数注入-->
<constructor-arg index="0" ref="sqlSessionFactory"/>
</bean>
- 给接口加实现类
package com.tian.dao;
import com.tian.pojo.User;
import org.mybatis.spring.SqlSessionTemplate;
/**
* @program: SpringStudy
* @description:
* @author: TianZD
* @create: 2021-08-10 22:44
**/
public class UserMapperImpl implements UserMapper{
UserMapper userMapper = null;
private SqlSessionTemplate sqlSessionTemplate;
public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) {
this.sqlSessionTemplate = sqlSessionTemplate;
}
@Override
public int insert(User user) {
userMapper = sqlSessionTemplate.getMapper(UserMapper.class);
return userMapper.insert(user);
}
@Override
public int delete(int id) {
userMapper = sqlSessionTemplate.getMapper(UserMapper.class);
return userMapper.delete(id);
}
@Override
public int update(User user) {
userMapper = sqlSessionTemplate.getMapper(UserMapper.class);
return userMapper.update(user);
}
@Override
public User select(int id) {
userMapper = sqlSessionTemplate.getMapper(UserMapper.class);
return userMapper.select(id);
}
}
- 将实现类注入到spring中
<bean id="userMapperImpl" class="com.tian.dao.UserMapperImpl">
<property name="sqlSessionTemplate" ref="sqlSession"/>
</bean>
- 测试
import com.tian.dao.UserMapper;
import com.tian.pojo.User;
import com.tian.utils.MybatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @program: SpringStudy
* @description:
* @author: TianZD
* @create: 2021-08-10 21:33
**/
public class MyTest {
@Test
public void userMapperTest() {
ApplicationContext context = new ClassPathXmlApplicationContext("spring-mybatis.xml");
UserMapper userMapper = context.getBean("userMapperImpl", UserMapper.class);
System.out.println(userMapper.select(2));
}
}
进一步简化:SqlSessionDaoSupport
SqlSessionDaoSupport
是一个抽象的支持类,用来为你提供 SqlSession
。调用 getSqlSession()
方法你会得到一个 SqlSessionTemplate
,之后可以用于执行 SQL 方法
package com.tian.dao;
import com.tian.pojo.User;
import org.apache.ibatis.session.SqlSession;
import org.mybatis.spring.support.SqlSessionDaoSupport;
/**
* @program: SpringStudy
* @description:
* @author: TianZD
* @create: 2021-08-10 23:17
**/
public class UserMapperImpl_2 extends SqlSessionDaoSupport implements UserMapper{
SqlSession sqlSession = getSqlSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
@Override
public int insert(User user) {
return userMapper.insert(user);
}
@Override
public int delete(int id) {
return userMapper.delete(id);
}
@Override
public int update(User user) {
return userMapper.update(user);
}
@Override
public User select(int id) {
return userMapper.select(id);
}
}
注册:略去了sqlsessiontemplate注册
需要注册userMapperimpl2,SqlSessionDaoSupport需要通过属性设置一个
sqlSessionFactory或
SqlSessionTemplate
<!--dataSource-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<property name="url"
value="jdbc:mysql://localhost:3306/mybatis?useSSl=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</bean>
<!--sqlSessionFactory-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<!--绑定mybatis-config.xml-->
<property name="configLocation" value="classpath:mybatis-config.xml"/>
<property name="mapperLocations" value="classpath:com/tian/dao/UserMapper.xml"/>
<property name="typeAliasesPackage" value="com.tian"/>
</bean>
<bean id="userMapperImpl2" class="com.tian.dao.UserMapperImpl_2">
<property name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>
14、声明式事务
事务分为:
- 声明式事务:AOP实现
- 编程式事务
使用声明式事务
配置声明式事务
要开启 Spring 的事务处理功能,在 Spring 的配置文件中创建一个 DataSourceTransactionManager
对象:
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<constructor-arg name="dataSource" ref="dataSource" />
</bean>
aop实现事务
<!--结合AOP实现事务的注入-->
<!--配置事务通知-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<!--给哪些方法配置事务-->
<!--配置事务的传播特性:new propagation-->
<tx:attributes>
<tx:method name="update" propagation="REQUIRED"/>
<tx:method name="delete" propagation="REQUIRED"/>
<tx:method name="insert" propagation="REQUIRED"/>
<tx:method name="select" read-only="true"/>
</tx:attributes>
</tx:advice>
<!--配置事务切入-->
<aop:config>
<aop:pointcut id="txPointcut" expression="execution(* com.tian.dao.*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
</aop:config>
事务的传播特性
事务传播特性,就是多个事务方法调用时如何定义方法间事务的传播。Spring 定义了 7 种传播行为:
- propagation_requierd:如果当前没有事务,就新建一个事务,如果已存在一个事务中,加入到这个事务中,这是Spring默认的选择。
- propagation_supports:支持当前事务,如果没有当前事务,就以非事务方法执行。
- propagation_mandatory:使用当前事务,如果没有当前事务,就抛出异常。
- propagation_required_new:新建事务,如果当前存在事务,把当前事务挂起。
- propagation_not_supported:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
- propagation_never:以非事务方式执行操作,如果当前事务存在则抛出异常。
- propagation_nested:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与propagation_required类似的操作。