Spring详解
1、Spring
1.1、简介
- 2002,首次推出了Spring框架的雏形:interface21框架
- Spring框架以interface21框架为基础,并不断丰富其内涵,于2004年3月24日,发布了1.0正式版
- Rod johson ,Spring Framework创始人,他是悉尼大学的博士,然而他的专业不是计算机,而是音乐学
- Spring理念:使现有的技术更加容易使用,本身是一个大杂烩,整合了现有的技术框架。
- SSM:SpringMVC+Spring+Mybatis
官网:https://docs.spring.io/spring-framework/docs/current/reference/html/index.html
- 需要的maven依赖
<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.19</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.19</version>
</dependency>
1.2、优点
- Spring是一个开源的免费的框架(容器)!
- Spring是一个轻量级的、非入侵式的框架!
- 控制反转(IOC),面向切面编程(AOP)
- 支持事务的处理,对框架整合的支持
Spring就是一个轻量级的控制反转(IOC)和面向切面编程(AOP)的框架!
1.3、组成
- 七大模块:Spring Core,AOP,ORM,DAO,MVC,WEB,Content
- 核心容器(Spring core)
核心容器提供Spring框架的基本功能。Spring以bean的方式组织和管理Java应用中的各个组件及其关系。Spring使用BeanFactory来产生和管理Bean,它是工厂模式的实现。BeanFactory使用控制反转(IOC)模式将应用的配置和依赖性规范与实际的应用程序代码分开。BeanFactory使用依赖注入的方式提供给组件依赖。
- Spring上下文(Spring context)
Spring上下文是一个配置文件,向Spring框架提供上下文信息。Spring上下文包括企业服务,如JNDI、EJB、电子邮件、国际化、校验和调度功能。
Spring面向切面编程(Spring AOP)
通过配置管理特性,Spring AOP 模块直接将面向方面的编程功能集成到了 Spring框架中。所以,可以很容易地使 Spring框架管理的任何对象支持 AOP。Spring AOP 模块为基于 Spring 的应用程序中的对象提供了事务管理服务。通过使用 Spring AOP,不用依赖 EJB 组件,就可以将声明性事务管理集成到应用程序中。
- Spring DAO模块
DAO模式主要目的是将持久层相关问题与一般的的业务规则和工作流隔离开来。Spring 中的DAO提供一致的方式访问数据库,不管采用何种持久化技术,Spring都提供一直的编程模型。Spring还对不同的持久层技术提供一致的DAO方式的异常层次结构。
- Spring ORM模块
Spring 与所有的主要的ORM映射框架都集成的很好,包括Hibernate、JDO实现、TopLink和IBatis SQL Map等。Spring为所有的这些框架提供了模板之类的辅助类,达成了一致的编程风格。
- Spring Web模块
Web上下文模块建立在应用程序上下文模块之上,为基于Web的应用程序提供了上下文。Web层使用Web层框架,可选的,可以是Spring自己的MVC框架,或者提供的Web框架,如Struts、Webwork、tapestry和jsf。
- Spring MVC框架(Spring WebMVC)
MVC框架是一个全功能的构建Web应用程序的MVC实现。通过策略接口,MVC框架变成为高度可配置的。Spring的MVC框架提供清晰的角色划分:控制器、验证器、命令对象、表单对象和模型对象、分发器、处理器映射和视图解析器。Spring支持多种视图技术。
1.4、扩展
-
Spring Boot
- 一个快速开发的脚手架
- 基于Spring Boot可以快速的开发单个微服务
- 约定大于配置
-
Spring Cloud
- Spring Cloud 是基于Spring Boot实现的
弊端:发展太久,配置十分繁琐,人称:配置地域~
2、IOC理念推导
- 过去:
- UserDao–>UserDaoImpl–>UserService—>UserServiceImpl—>servlet
- 在之前的业务中,用户的需求可能会影响我们原来的代码(修改service层代码),我们需要根据用户的需求去改变代码,如果程序代码量十分大,修改一次的成本代码十分昂贵
- 现在
- userserviceimpl
public class UserServiceImpl implements UserService{
private UserDao userDao;
//利用set进行动态实现值的注入
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
@Override
public void getUser() {
userDao.getUser();
}
}
- 用户调用业务层
public static void main(String[] args) {
//用户实际调用的是业务层,dao层不需要解除
UserService userService = new UserServiceImpl();
((UserServiceImpl)userService).setUserDao(new UserDaoImpl());
userService.getUser();
}
使用一个Set接口实现,当用户需要的service改变时,不需要改变service层代码,而是在业务层new
- 之前,程序是主动创建对象,控制权在程序员手上
- 使用set注入后,程序不再具有主动性,而是变成了被动的接收对象
原来: private UserDao userDao=new UserDaio();—写死了
现在:private UserDao;
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
} —对象创建可以在业务层实现
- 这种思想,从本质上解决了问题,我们程序员不用再去管理对象的创建了。系统的耦合性大大降低,可以更加专注的在业务的实现上。(servlet层),而不用回头去改服务层。
- IOC本质
控制反转(IOC)是一种设计思想,没有IOC的程序中,我们使用面向对象编程,对象的创建与对象间的依赖关系完全硬编码在程序中,对象的创建由程序自己控制,控制反转后将对象的创建转移给第三方,即获得依赖对象的方式反转了。
- 控制反转是一种通过(XML或注解)并通过第三方去生产或获取特定对象的方式。在Spring中实现控制反转的是IOC容器,其实现方法是依赖注入。
3、HelloSpring
- hello实体类
public class Hello {
private String str;
public String getStr() {
return str;
}
public void setStr(String str) {
this.str = str;
}
@Override
public String toString() {
return "Hello{" +
"str='" + str + '\'' +
'}';
}
}
- 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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 使用spring来创建对象,在spring这些都称为bean
bean=对象 相当于 new Hello()
id="hello" class="com.qian.pojo.Hello" id=对象名,class=new的对象,property 相当于给对象中的属性设置一个值
-->
<bean id="hello" class="com.qian.pojo.Hello">
<property name="str" value="Spring"/>
</bean>
</beans>
- 测试
public class MyTest {
public static void main(String[] args) {
//获取spring的上下文对象
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
//我们的对象现在都在spring中管理了,我们要使用,直接去里面取出来就可以
Hello hello = (Hello) context.getBean("hello");
System.out.println(hello.toString());
}
}
- 这个过程就叫控制反转:
- 控制:谁来控制对象的创建,传统应用程序的对象是由程序本身控制创建的,使用spring后,对象是由spring来创建的
- 反转:程序本身不创建对象,而变成被动的接收对象
- 依赖注入:就是利用set方法来进行注入,由主动的编程变成被动的接收
- 到了现在,我们彻底不用在程序中去改动了,要实现不同的操作,只需要在xml配置文件中进行修改,所谓的ioc,就是:对象由Spring来创建,管理,装配!
4、IOC创建对象方式
- 使用无参构造创建对象(默认实现)
public User(){
System.out.println("User 的无参构造");
}
- 假设我们要使用有参构造创建对象
- 下标赋值
<!-- 第一种,下标赋值-->
<bean id="user" class="com.qian.pojo.User">
<constructor-arg index="0" value="狂神说"/>
</bean>
- 通过类型创建
<!--第二种通过类型创建,不建议使用-->
<bean id="user" class="com.qian.pojo.User">
<constructor-arg type="java.lang.String" value="秦疆"/>
</bean>
- 通过参数名
<!--第三种,直接通过参数名-->
<bean id="user" class="com.qian.pojo.User">
<constructor-arg name="name" value="秦疆"/>
</bean>
- 只要写在bean中注册,即便测试里没有调用,仍然创建了对象,实例化了。**在配置文件加载的时候,容器中管理的对象就已经初始化了,且只有一种实例方式,调用时直接get就行
5、Spring配置
5.1、alias别名
<!-- 别名,如果添加了别名,也可以使用别名获取到这个对象-->
<alias name="user" alias="user1"/>
5.2、bean
<bean id="user" class="com.qian.pojo.User">
<constructor-arg name="name" value="秦疆"/>
</bean>
<!-- id:bean的唯一标识符,也就是相当于我们学的对象名
class: bean对象所对应的全限定名:包名+类型
name:也是别名,而且name可以同时取多个别名
-->
5.3、 import
- 一般用于团队开发使用,它可以将多个配置文件导入合并为一个
<import resource="beans1.xml"/>
使用的时候,直接用总的配置就可以
6、依赖注入
6.1、 构造器注入
之前讲过的,就是在实体类中
public class SimpleMovieLister {
// the SimpleMovieLister has a dependency on a MovieFinder
private final MovieFinder movieFinder;
// a constructor so that the Spring container can inject a MovieFinder
public SimpleMovieLister(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
6.2、通过set方式注入(重点)
- 依赖注入:set注入
- 依赖:bean对象的创建依赖于容器
- 注入:bean对象中的所有属性,由容器来注入
- 环境搭建
- 复杂类型
public class Address {
private String address;
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
- 真实测试对象
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;
}
- 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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="student" class="com.qian.pojo.Student">
<!-- 第一种,普通值注入,value-->
<property name="name" value="秦疆"/>
</bean>
</beans>
- 测试类
public class MyTest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
Student student =(Student) context.getBean("student");
System.out.println(student.getName());
}
}
- 所有类型的注入
- beans.xml
<bean id="address" class="com.qian.pojo.Address">
<property name="address" value="南昌"/>
</bean>
<bean id="student" class="com.qian.pojo.Student">
<!-- 第一种,普通值注入,value-->
<property name="name" value="秦疆"/>
<!-- 第二种,bean注入,ref-->
<property name="address" ref="address"/>
<!-- 第三种,数组注入,ref-->
<property name="books" >
<array>
<value>红楼梦</value>
<value>水浒传</value>
<value>三国演义</value>
<value>西游记</value>
</array>
</property>
<!-- 数组注入,list-->
<property name="hobbies">
<list>
<value>听歌</value>
<value>敲代码</value>
<value>看电影</value>
</list>
</property>
<!-- map注入-->
<property name="card">
<map>
<entry key="电话卡" value="111111111"/>
<entry key="银行卡" value="232323232323232"/>
</map>
</property>
<!-- set-->
<property name="games">
<set>
<value>LOL</value>
<value>CS</value>
<value>COC</value>
</set>
</property>
<!-- null-->
<property name="wife">
<null/>
</property>
<!-- properties
key=value
-->
<property name="info">
<props>
<prop key="学号">20190525</prop>
<prop key="性别">男</prop>
<prop key="姓名">小明</prop>
</props>
</property>
</bean>
- 测试
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
Student student =(Student) context.getBean("student");
System.out.println(student.toString());
//Student{name='秦疆', address=Address{address='南昌'}, books=[红楼梦, 水浒传, 三国演义, 西游记], hobbies=[听歌, 敲代码, 看电影], card={电话卡=111111111, 银行卡=232323232323232}, games=[LOL, CS, COC], wife='null', info={学号=20190525, 性别=男, 姓名=小明}}
}
6.3、其他方式注入
- P命名空间注入,依赖于 xmlns:p=“http://www.springframework.org/schema/p”,需要导入
xmlns:p="http://www.springframework.org/schema/p"
<!-- p命名空间注入,可以直接注入属性的值:property-->
<bean id="user" class="com.qian.pojo.User" p:name="秦疆" p:age="18"/>
- C命名空间注入,依赖于 xmlns:c=“http://www.springframework.org/schema/c”,需要导入
xmlns:c="http://www.springframework.org/schema/c"
<!-- p命名空间注入,通过构造器注入,construction-args-->
<bean id="user2" class="com.qian.pojo.User" c:name="王也" c:age="20"/>
注意:P命名和C命名空间不能直接使用,需要导入xml约束
xmlns:p="http://www.springframework.org/schema/p"
xmlns:c="http://www.springframework.org/schema/c"
7、Bean的作用域
- 六种作用域
- singleton
- prototype
- request
- session
- application
- websocket
- singleton单例模式
- 默认是单例,但是也可以显式的设置
<bean id="user2" class="com.qian.pojo.User" c:name="王也" c:age="20" scope="singleton"/>
- 测试
User user =(User) context.getBean("user2");
User user2 =(User) context.getBean("user2");
System.out.println(user==user2);
//true
- prototype原型模式
- xml中设置
<bean id="user2" class="com.qian.pojo.User" c:name="王也" c:age="20" scope="prototype"/>
- 测试
User user =(User) context.getBean("user2");
User user2 =(User) context.getBean("user2");
System.out.println(user==user2);
//false,每次从容器中get的时候都会产生一个新对象
8、Bean的自动装配
- 自动装配是Spring满足bean依赖的一种方式
- Spring会在上下文中自动寻找,并自动给bean装配属性
- 三种装配的方式
- 在beans.xml中显式的配置(前面一直用的)
- 在java中显式的配置
- 隐式的自动装配bean(重要的方式)
8.1、测试
- 环境搭建
<bean id="cat" class="com.qian.pojo.Cat"/>
<bean id="dog" class="com.qian.pojo.Dog"/>
<bean id="person" class="com.qian.pojo.Person">
<property name="name" value="狂神"/>
<property name="dog" ref="dog"/>
<property name="cat" ref="cat"/>
@Test
public void test1(){
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
Person person = context.getBean("person", Person.class);
person.getCat().shout();
person.getDog().shout();
}
8.2、ByName自动装配
<bean id="cat" class="com.qian.pojo.Cat"/>
<bean id="dog" class="com.qian.pojo.Dog"/>
<!-- byName:会自动在容器上下文中查找,和自己对象set方法后面的值对应的bean id-->
<bean id="person" class="com.qian.pojo.Person" autowire="byName">
<property name="name" value="狂神"/>
</bean>
- 如果id改了,就查不到。只会自动在bean中寻找和自己对象set方法后面值相同的id
- id要对应
8.3、ByType 自动装配
<bean class="com.qian.pojo.Cat"/>
<bean class="com.qian.pojo.Dog"/>
<!-- byType:会自动在容器上下文中查找,和自己对象属性类型相同的bean!-->
<bean id="person" class="com.qian.pojo.Person" autowire="byType">
<property name="name" value="狂神"/>
</bean>
- 必须保证这个类型全局唯一
- id名可以忽略
小结:
- byName的时候,需要保证所有bean的id唯一,并且这个bean需要和自动注入的属性的set方法的值一致
- byType的时候,需要保证所有bean的class唯一,并且这个bean需要和自动注入的属性的类型一致!
8.4、使用注解实现自动装配
- spring2.5就支持注解了
- 须知:
- 导入约束: 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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config/>
</beans>
- 直接在属性上使用,可以省略掉set方法(最常用)
@Autowired
private Cat cat;
@Autowired
private Dog dog;
private String name;
- 用于setter方法:看如下代码
@Autowired
public void setNotifyservice(NotifyService notifyservice) {
this.notifyservice = notifyservice;
}
- 用于构造函数
public class Order {
@Autowired
public Order(NotifyService notifyservice) {
this.notifyservice = notifyservice;
}
- beans.xml:不需要再注入属性等,很简洁
<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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<bean id="cat" class="com.qian.pojo.Cat"/>
<bean id="dog" class="com.qian.pojo.Dog"/>
<bean id="person" class="com.qian.pojo.Person"/>
<context:annotation-config/>
</beans>
- 注意:
- 使用@Autowired 我们可以不用编写set方法,前提是这个自动装配的属性在ioc(Spring)容器中存在,默认应该是按照byType来的(id名不一致同样可以查到说明是按照byType)
- 扩展。如果现实定义了@Autowired的required属性为false,说明这个对象可以为null,否则不允许为空
- @Autowired注解默认使用的是byType的方式向Bean里面注入相应的Bean
- 有多个相同类型的对象时不适用
- @Qualifier注解
@Autowired
@Qualifier(value = "cat22")
private Cat cat;
- @Qualifier注解采用的是byName的方式。 名字不符合时不适用
- @Resource
- 会根据情况选择根据bytype还是byname(相当于上面两种的结合)
- 当名字没有一致的,相同类型又不止一个,则附带属性name
<bean id="cat1" class="com.qian.pojo.Cat"/>
<bean id="cat2" class="com.qian.pojo.Cat"/>
//这种情况下既不可以通过bytype也不可以通过byname,所以只能指定一个name
@Resource(name = "cat1")
private Cat cat;
9、使用注解开发
- 在spring4之后,要使用注解开发,必须保证aop的包导入了
- 使用注解需要导入context约束,增加注解的支持
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config/>
<!-- 指定要扫描的包,这个包下的注解会生效-->
<context:component-scan base-package="com.qian.pojo"/>
</beans>
- bean
@Component组件
//等价于<bean id = "user" class = "com.qina.pojo.user"/>
@Component
public class User {
public String name = "秦疆";
}
-
属性如何注入
@Value() 相当于< property name=“” value=“”/>
@Value("kuangshen")
public String name ;
- 衍生的注解
@Component有几个衍生注解(意思都一样,是当做bean注册在spring中),我们在web开发中,会按照mvc三层架构分层!
- dao (@Repository)
- service(@Service)
- controller (@Controller)
这四个注册功能都是一样的,都是代表将某个类注册到Spring中,装配Bean
- 作用域(@Scope)
@Scope("singleton")
public class User {
@Value("kuangshen")
public String name ;
}
- 小结:
- xml更加万能,适用于任何场合,维护简单方便
- 注解 不是自己的类使用不了,维护比较复杂
xml与注解最佳实践:
- xml用来管理bean
- 注解只负责完成属性的注入
- 我们在使用的过程中,只需要注意一个问题:要让注解生效,需要开启注解的支持
<!-- 指定要扫描的包,这个包下的注解会生效-->
<context:component-scan base-package="com.qian"/>
<context:annotation-config/>
- 使用Java的方式配置Spring
-
完全不适用Spring的xml配置,全权交给java来做
-
JavaConfig 是Spring的一个子项目,爱Spring4之后,它成为了一个核心功能
-
使用
- 编写实体类
public class User { private String name; public String getName() { return name; } //属性注入值 @Value("秦疆") public void setName(String name) { this.name = name; } @Override public String toString() { return "User{" + "name='" + name + '\'' + '}'; } }
- 写config类(配置类)
@Configuration//这也也会Spring容器托管,注册到容器中,他也是一个@Component @Import(Config2.class) //当有多个config时,可以通过注解导入合并为一个 public class Config { //注册一个bean,就相当于我们之前写的一个bean标签‘ //这个方法的名字,就相当于bean标签中的id属性,这个方法的返回值,就相当于bean标签中的class属性 @Bean public User getUser(){ return new User();//就是返回要注入到bean的对象 } }
- 测试
public class MyTest { @Test public void test1(){ //如果完全使用了配置类方式去做,我们就只能通过AnnotationConfig 上下文来获取容器,通过配置类的class对象加载 ApplicationContext context = new AnnotationConfigApplicationContext(Config.class); User getUser = context.getBean("getUser", User.class); System.out.println(getUser.getName()); } }
-
这种纯java的配置方式,在SpringBoot中随处可见!
10、代理模式
- 为什么要学习代理模式?因为这就是SpringAOP的底层
- 代理模式的分类:
- 静态代理
- 动态代理
10.1、静态代理
- 角色分析:
- 抽象角色:一般会使用接口或者抽象类来解决(这是真实角色和代理角色之间的联系部分)
- 真实角色:被代理的角色
- 代理角色:代理真实角色,代理真实角色后,我们一般会做一些附属操作
- 客户:访问代理角色的人
- 代理模式的好处:
- 可以使真实角色的操作更加纯粹,不用去关注一些公共的业务
- 公共业务就交给了代理角色,实现了业务的分工!
- 公共业务发生扩展的时候,方便集中管理
- 缺点:
- 一个真实角色就会产生一个代理角色,如果有多个真实角色,代码量会翻倍,开发效率会变低
- 代码步骤:
- 接口(真实角色和代理角色的联系(比如房东和中介都是为了出租房子))
package com.qian.demo01;
public interface Rent {
public void rent();
}
- 真实角色(房东)
package com.qian.demo01;
//房东--真实角色
public class Host implements Rent {
@Override
public void rent() {
System.out.println("房东要出租房子!");
}
}
- 代理角色(中介)
package com.qian.demo01;
//中介----代理角色
public class Proxy implements Rent {
private Host host;
public Proxy() {
}
public Proxy(Host host) {
this.host = host;
}
@Override
public void rent() {
seeHouse();
host.rent(); //调的是房东的租房子
context();
fee();
}
//看房
public void seeHouse(){
System.out.println("中介带你看房");
}
//收费
public void fee(){
System.out.println("收中介费");
}
//签合同
public void context(){
System.out.println("签租赁合同");
}
}
- 客户
package com.qian.demo01;
public class Client {
public static void main(String[] args) {
//房东要出租房子
Host host=new Host();
//代理,中介帮房东出租房子,但是中介一般会有一些附属操作
Proxy proxy = new Proxy(host); //通过代理调用房东的出租房子
//不用面对房东,直接找中介租房即可
proxy.rent();
}
}
10.2、加深理解静态代理
- 接口
package com.qian.demo02;
public interface UserService {
public void add();
public void delete();
public void update();
public void query();
}
- 业务层
package com.qian.demo02;
public class UserServiceImpl implements UserService{
@Override
public void add() {
System.out.println("增加了一个用户");
}
@Override
public void delete() {
System.out.println("删除了一个用户");
}
@Override
public void update() {
System.out.println("修改了一个用户");
}
@Override
public void query() {
System.out.println("查询了一个用户");
}
}
- 客户层
package com.qian.demo02;
public class Client {
public static void main(String[] args) {
UserServiceImpl userService = new UserServiceImpl();
UserServiceProxy poxy = new UserServiceProxy();
poxy.setUserService(userService);
poxy.add();
poxy.query();
}
}
- 当需要添加需求时,可以增加一个代理类
package com.qian.demo02;
public class UserServiceProxy implements UserService{
UserServiceImpl userService;
public void setUserService(UserServiceImpl userService) {
this.userService = userService;
}
//用的还是真实角色的方法,但是是通过代理角色对象
@Override
public void add() {
log("add");
userService.add();
}
@Override
public void delete() {
log("delete");
userService.delete();
}
@Override
public void update() {
log("update");
userService.update();
}
@Override
public void query() {
log("query");
userService.query();
}
//添加一个日志方法 不需要在真实角色那里添加修改,直接在代理中完成
public void log(String msg){
System.out.println("使用了"+msg+"方法");
}
//新增一个代理类,可以保证不用改变业务代码(userServiceImpl)
}
- 为什么要添加一个代理类,而不是直接在业务层代码上添加功能?
- 保证原有代码(业务代码)不会改变,业务代码实现基本的功能,一些添加的需求在代理类中实现
- 解耦,这样耦合性降低。
- 在不改变原有代码的基础上,增加某个功能
10.3、动态代理
- 动态代理和静态代理角色一样
- 动态代理的代理类是动态生成的,不是我们直接写好的!
- 动态代理分为两大类:基于接口的动态代理,基于类的动态代理
- 基于接口—JDK动态代理
- 基于类的—cglib
- java字节码实现:javasist
- 需要了解两个类:
- Proxy:代理
- InvocationHandler:调用程序
- 步骤:
- 接口:
package com.qian.demo03;
public interface Rent {
public void rent();
}
- 真实角色
package com.qian.demo03;
//房东--真实角色
public class Host implements Rent {
@Override
public void rent() {
System.out.println("房东要出租房子!");
}
}
- 动态代理
package com.qian.demo03;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
//我们会用这个类自动生成代理类
public class ProxyInvocationHandler implements InvocationHandler {
//1.被代理的接口
private Rent rent;
public void setRent(Rent rent) {
this.rent = rent;
}
//2.生成得到代理类的方法
public Object getProxy() {
return Proxy.newProxyInstance(this.getClass().getClassLoader(),rent.getClass().getInterfaces(),this );
}
//3.处理代理实例,并返回结果
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//动态代理的本质,就是使用反射机制实现!
seeHouse(); //看房子
Object result = method.invoke(rent, args); //租房子
fee(); //收费
return result;
}
public void seeHouse(){
System.out.println("中介看房子");
}
public void fee(){
System.out.println("收中介费");
}
}
- 客户
package com.qian.demo03;
public class Client {
public static void main(String[] args) {
//真实角色
Host host=new Host();
//代理角色:获取
ProxyInvocationHandler pih = new ProxyInvocationHandler();
//通过调用程序处理角色来处理我们要调用的接口对象
pih.setRent(host);
Rent proxy =(Rent) pih.getProxy(); //获取代理角色类,这里的proxy就是动态生成的,我们并没有写
proxy.rent();
}
}
- 万能实现动态代理
- 动态代理类
package com.qian.demo04;
import com.qian.demo03.Rent;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
//我们会用这个类自动生成代理类
public class ProxyInvocationHandler implements InvocationHandler {
//被代理的接口
private Object target;
public void setTarget(Object target) {
this.target = target;
}
//生成得到代理类的方法
public Object getProxy() {
return Proxy.newProxyInstance(this.getClass().getClassLoader(),
target.getClass().getInterfaces(),this );
}
//处理代理实例,并返回结果
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//动态代理的本质,就是使用反射机制实现!
log(method.getName());
Object result = method.invoke(target, args); //租房子
return result;
}
public void log(String msg){
System.out.println("执行了"+msg+"方法");
}
}
- 客户类
package com.qian.demo04;
import com.qian.demo02.UserService;
import com.qian.demo02.UserServiceImpl;
public class Client {
public static void main(String[] args) {
//真实角色
UserServiceImpl userService = new UserServiceImpl();
//代理角色
ProxyInvocationHandler pih = new ProxyInvocationHandler();
pih.setTarget(userService);//设置要代理的对象
UserService proxy =(UserService) pih.getProxy();//动态生成代理类
proxy.add();
}
}
- 好处:
- 静态代理的好处都有
- 可以使真实角色的操作更加纯粹,不用去关注一些公共的业务
- 公共业务就交给了代理角色,实现了业务的分工!
- 公共业务发生扩展的时候,方便集中管理
- 一个动态代理类代理的是一个接口,可以对应一类业务。(对应多个业务类)
- 一个动态代理类可以代理多个类,只要是实现同一个接口即可
11、AOP
11.1、什么是AOP?
AOP(Aspect Oriented Programming)意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生泛型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
11.2、 AOP在Spring中的作用
提供声明式事务,允许用户自定义切面
- 横切关注点:跨越应用程序多个模块的方法或功能。即是,与我们业务逻辑无关的,但是我们需要关注的部分,就是横切关注点。如日志、安全、缓存、事务等等
- 切面(ASPECT):横切关注点 被模块化的对象,即,它是一个类 (LOG)
- 通知(Advice):切面必须要完成的工作,即,它是类中的一个方法(log中的方法)
- 目标(Target):被通知对象
- 代理(Proxy):向目标对象应用通知之后创建的对象
- 切入点(PointCut):切面通知 执行的“地点”的定义
- 连接点(JointPoint):与切入点匹配的执行点
11.3、使用Spring实现AOP
- 使用AOP注入,需要导入一个依赖包
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.9.1</version>
<scope>runtime</scope>
</dependency>
- 方式一:使用Spring的接口
- 接口
package com.qian.service;
public interface UserService {
public void add();
public void delete();
public void update();
public void query();
}
- 业务层
package com.qian.service;
public class UserServiceImpl implements UserService{
@Override
public void add() {
System.out.println("增加了一个用户");
}
@Override
public void delete() {
System.out.println("删除了一个用户");
}
@Override
public void update() {
System.out.println("删除了一个用户");
}
@Override
public void query() {
System.out.println("查询了一个用户");
}
}
- 日志层(执行前的,和执行后的)
package com.qian.log;
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;
public class BeforeLog implements MethodBeforeAdvice {
@Override
//method:要执行的目标对象的方法
//args:参数
//target:目标对象
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println(target.getClass().getName()+"的"+method.getName()+"被执行了");
}
}
package com.qian.log;
import org.springframework.aop.AfterReturningAdvice;
import java.lang.reflect.Method;
public class AfterLog implements AfterReturningAdvice {
@Override
//returnValue:返回值
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("执行了"+method.getName()+"方法,返回结果为:"+returnValue);
}
}
- 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:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 注册bean-->
<bean id="userService" class="com.qian.service.UserServiceImpl"/>
<bean id="beforeLog" class="com.qian.log.BeforeLog"/>
<bean id="afterLog" class="com.qian.log.AfterLog"/>
<!-- 配置aop:需要导入aop约束-->
<aop:config>
<!-- 切入点:expression:表达式 ,execution(要执行的位置!修饰词 返回值 类名 方法名 参数)-->
<aop:pointcut id="point" expression="execution(* com.qian.service.UserServiceImpl.*(..))"/>
<!-- 执行环绕增加-->
<aop:advisor advice-ref="beforeLog" pointcut-ref="point"/>
<aop:advisor advice-ref="afterLog" pointcut-ref="point"/>
</aop:config>
</beans>
- 测试
import com.qian.service.UserService;
import com.qian.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 = (UserService) context.getBean("userService");
userService.add();
}
}
- 方式二:自定义实现AOP [主要是切面]
- 自定义类
package com.qian.diy;
public class DiyPointCut {
public void before(){
System.out.println("=============方法执行前================");
}
public void after(){
System.out.println("=============方法执行后================");
}
}
- 自定义切面
<!-- 方式二:自定义类-->
<bean id="diy" class="com.qian.diy.DiyPointCut"/>
<aop:config>
<!-- 自定义切面,ref 要引用的类-->
<aop:aspect ref="diy">
<!-- 切入点-->
<!-- execution表达式:第一个是返回类型,*表示所有类型;第二个是包名,第三个是类名,第四个是方法名,*(..)表示所有方法-->
<aop:pointcut id="point" expression="execution(* com.qian.service.UserServiceImpl.*(..))"/>
<!-- 通知-->
<aop:before method="before" pointcut-ref="point"/>
<aop:after method="after" pointcut-ref="point"/>
</aop:aspect>
</aop:config>
- 测试
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
//动态代理代理的是接口
UserService userService = (UserService) context.getBean("userService");
userService.add();
userService.delete();
}
- 方式三:使用注解实现
- 写注解类
package com.qian.diy;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
//方式三:使用注解方式实现AOP
@Aspect //标注这个类是一个切面
public class AnnotationPointCut {
@Before("execution(* com.qian.service.UserServiceImpl.*(..))")
public void before(){
System.out.println("=============方法执行前============");
}
@After("execution(* com.qian.service.UserServiceImpl.*(..))")
public void after(){
System.out.println("=============方法执行后============");
}
//在环绕增强中,我们可以给定一个参数,代表我们要获取处理切入的点
@Around("execution(* com.qian.service.UserServiceImpl.*(..))")
public void around(ProceedingJoinPoint jp) throws Throwable {
System.out.println("环绕前");
//执行方法
Object proceed = jp.proceed();
System.out.println("环绕后");
}
}
- xml中配置
<!-- 方式三:使用注解实现AOP-->
<bean id="annotationPointCut" class="com.qian.diy.AnnotationPointCut"/>
<!-- 开启注解支持-->
<aop:aspectj-autoproxy/>
- 测试
public class MyTest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
//动态代理代理的是接口
UserService userService = (UserService) context.getBean("userService");
userService.add();
}
}
- 结果
环绕前
=============方法执行前============
增加了一个用户
=============方法执行后============
环绕后
环绕优先级更高。
- 都是为了实现增强功能但是不改变业务代码
12、整合Mybatis
12.1、步骤:
- 导入jar包
- junit
- mybatis
- mysql
- spring
- aop
- mybatis-spring
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.2</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.1.9.RELEASE</version>
</dependency>
<!-- 连接spring-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.1.9.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.9.1</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.2</version>
</dependency>
</dependencies>
- 编写配置文件
- 测试
12.2、 回忆Mybatis
- 编写实体类
package com.qian.pojo;
import lombok.Data;
@Data
public class User {
private int id;
private String name;
private String pwd;
}
- 编写核心配置文件
<?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.qian.pojo"/>
</typeAliases>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper class="com.qian.mapper.UserMapper"/>
</mappers>
</configuration>
- 编写接口
package com.qian.mapper;
import com.qian.pojo.User;
import java.util.List;
public interface UserMapper {
public List<User> selectUser();
}
- 编写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.qian.mapper.UserMapper">
<select id="selectUser" resultType="user">
select * from mybatis.user
</select>
</mapper>
- 测试
import com.qian.mapper.UserMapper;
import com.qian.pojo.User;
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 org.junit.Test;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
public class MyTest {
@Test
public void test() throws IOException {
String resources = "mybatis-config.xml";
InputStream in = Resources.getResourceAsStream(resources);
SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(in);
SqlSession sqlSession = sessionFactory.openSession(true);
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
for (User user : mapper.selectUser()) {
System.out.println(user);
}
}
}
- maven静态资源导入问题解决
<build>
<resources>
<resource>
<directory>src/main/java</directory><!--所在的目录-->
<includes><!--包括目录下的.properties , .xml文件都会扫描到-->
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<!-- filtering 选项 false 不启用过滤器, *.property 已经起到过滤的作用了 -->
<filtering>false</filtering>
</resource>
</resources>
</build>
12.3、 Mybatis-Spring
- MyBatis-Spring 会帮助你将 MyBatis 代码无缝地整合到 Spring 中。它将允许 MyBatis 参与到 Spring 的事务管理之中,创建映射器 mapper 和 SqlSession 并注入到 bean 中,以及将 Mybatis 的异常转换为 Spring 的 DataAccessException。
- 步骤
- 编写操作数据库的spring配置
- 编写数据源配置
- sqlSessionFactory
- 绑定mybatis
- sqlsession
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- DataSource:使用spring的数据源代替mybatis的配置
我们这里使用spring提供的jdbc
-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="username" value="root"/>
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=UTF-8"/>
<property name="password" value="123456"/>
</bean>
<!-- sqlSessionFactory-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<!-- 绑定Mybatis-->
<property name="configLocation" value="classpath:mybatis-config.xml"/>
<property name="mapperLocations" value="classpath:com/qian/mapper/*.xml"/>
</bean>
<!-- SqlSessionTemplate就是我们使用的sqlSession-->
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
<!-- 只能使用构造器来注入,因为它没有set方法-->
<constructor-arg index="0" ref="sqlSessionFactory"/>
</bean>
<bean id="userMapper" class="com.qian.mapper.UserMapperImpl">
<property name="sqlSession" ref="sqlSession"/>
</bean>
</beans>
- 给接口加一个实现类
public class UserMapperImpl implements UserMapper{
private SqlSessionTemplate sqlSession;
//注入sqlsession
public void setSqlSession(SqlSessionTemplate sqlSession) {
this.sqlSession = sqlSession;
}
@Override
public List<User> selectUser() {
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
return mapper.selectUser();
}
}
- 测试
import com.qian.mapper.UserMapper;
import com.qian.pojo.User;
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 org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
public class MyTest {
@Test
public void test() throws IOException {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserMapper userMapper = context.getBean("userMapper", UserMapper.class);
for (User user : userMapper.selectUser()) {
System.out.println(user);
}
}
}
方式二:
- 给接口加实现类时继承一个SqlSessionDaoSupport:免去注入sqlSession
public class UserMapperImpl2 extends SqlSessionDaoSupport implements UserMapper{
@Override
public List<User> selectUser() {
SqlSession sqlSession = getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
return mapper.selectUser();
}
}
- 在spring中配置这个实现类
<bean id="userMapper2" class="com.qian.mapper.UserMapperImpl2">
<property name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>
13、声明式事务
- 回顾事务
- 要么都成功,要么都失败
- 事务在项目开发中,十分重要,涉及到数据的一致性问题,不能马虎!
- 确保完整性和一致性
- 事务ACID原则
- 原子性
- 一致性
- 隔离性
- 多个业务可能操作同一个资源,防止数据损坏
- 持久性
- 事务一旦提交,无论系统发生什么问题,结果都不会再被影响,被持久化的写到存储器中
- spring中的事务管理
- 声明式事务:AOP(交由容器管理事务)
- 编程式事务:需要在代码中,进行事务的管理
- 声明式事务(在spring中结合AOP实现事务的织入):
- 写接口
public interface UserMapper {
public List<User> selectUser();
//添加一个用户
public int addUser(User user);
//删除一个用户
public int deleteUser(int id);
}
- 实现类
package com.qian.mapper;
import com.qian.pojo.User;
import org.apache.ibatis.session.SqlSession;
import org.mybatis.spring.support.SqlSessionDaoSupport;
import org.springframework.web.servlet.handler.UserRoleAuthorizationInterceptor;
import java.util.List;
public class UserMapperImpl2 extends SqlSessionDaoSupport implements UserMapper{
@Override
public List<User> selectUser() {
User user = new User(6,"小王","1234556");
SqlSession sqlSession = getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
mapper.addUser(user);
mapper.deleteUser(4);
return mapper.selectUser();
}
@Override
public int addUser(User user) {
return getSqlSession().getMapper(UserMapper.class).addUser(user);
}
@Override
public int deleteUser(int id) {
return getSqlSession().getMapper(UserMapper.class).deleteUser(id);
}
}
- mapper.xml写sql
<insert id="addUser" parameterType="user">
insert into mybatis.user(id, name, pwd) VALUES (#{id},#{name},#{pwd});
</insert>
<delete id="deleteUser" parameterType="int">
delete from mybatis.user where id = #{id};
</delete>
- 在spring中结合AOP实现事务的织入
<!-- 结合AOP实现事务的织入-->
<!-- 配置事务通知-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<!-- 给哪些方法配置事务,配置事务的传播特性 propagation-->
<tx:attributes>
<tx:method name="add" propagation="REQUIRED"/>
<tx:method name="delete" propagation="REQUIRED"/>
<tx:method name="update" propagation="REQUIRED"/>
<tx:method name="query" read-only="true"/>
<tx:method name="*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<!--配置事务切入-->
<aop:config>
<aop:pointcut id="txPointCut" expression="execution(* com.qian.mapper.*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>
</aop:config>
- 测试
@Test
public void test() throws IOException {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserMapper userMapper = context.getBean("userMapper2", UserMapper.class);
for (User user : userMapper.selectUser()) {
System.out.println(user);
}
}
- 思考:为什么需要事务?
- 如果不配置事务,可能存在数据提交不一致的情况;
- 如果我们不再spring中去配置声明式事务,我们就需要在代码中手动配置事务
- 事务在项目中的开发中十分重要,设计到数据的一致性和完整性问题,不容马虎!