Spring
1、简介
- Spring:春天-----------------》给软件行业带来的春天
- Spring框架是由于软件开发的复杂性而创建的。Spring使用的是基本的JavaBean来完成以前只可能由EJB完成的事情。然而,Spring的用途不仅仅限于服务器端的开发。从简单性、可测试性和松耦合性角度而言,绝大部分Java应用都可以从Spring中受益。
- 目的:解决企业应用开发的复杂性
- 功能:使用基本的JavaBean代替EJB,并提供了更多的企业应用功能
- 范围:任何Java应用
- Spring是一个轻量级控制反转(IoC)和面向切面(AOP)的容器框架。
Spring理念 : 使现有的技术更加容易使用 , 本身就是一个==大杂烩== (里面什么东西都有) , 整合了现有的技术框架
- Spring Boot
- 一个快速开发的脚手架
- 基于Spring Boot可以快速的开发单个微服务
- 约定大于配置
- Spring Cloud
- SpringCloud是基于Spring Boot实现的
因为现在大多数公司都在使用SpringBoot进行快速开发,学习SpringBoot的前提,需要完全掌握Spring及SpringMvc! 承上启下的作用
弊端 : 发展太久之后,违背了原来的 理念 ! 越到后面配置越繁琐 , 人称: “配置地狱”
官方下载地址: http://repo.spring.io/release/org/springframework/spring
GitHub: https://github.com/spring-projects/spring-framework/releases
<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.12.RELEASE</version>
</dependency>
<!-- Spring操作数据库的话就需要一个spring-jdbc-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.12.RELEASE</version>
</dependency>
1.1、优点:
- spring是一个开源级的免费框架(容器)
- Spring是一个轻量级的 (本身很小) ,非入侵式的框架 (引入了Spring不会改变代码原来的情况)
- 控制反转 (IOC) , 面向切面编程 (AOP) !
- 支持事务的处理,对框架整合的支持 !
总结 : Spring就是一个轻量级的控制反转 (IOC) , 面向切面编程 (AOP) 的框架
1.2、组成
2、IOC理论推导
在业务中,用户的需求可能会影响我们原来的代码,我们需要根据用户的需求去修改原有的代码 ! 如果程序代码量十分大,修改一次的成本十分昂贵!
//每次需求都会去修改 实现的类
private UserDao userDao = new UserDaoImpl(); /*调用dao层,相当于写死了,耦合性很高。 如果要使用其他实现类的时候,又要去重新new */
public void getUser() {
userDao.getUser();
}
使用一个set接口实现,就发生了反转的作用
private UserDao userDao;
//利用Set进行动态实现值的注入
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
//通过bean容器 set 注入对象后, userDao 就代表注入的对象,
public void getUser() {
userDao.getUser();
}
<?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="userDao" class="com.xun.dao.UserDaoImpl"/>
<bean id="mySQL" class="com.xun.dao.UserDaoMySQLImpl"/>
<!-- 通过set注入 ref引用的哪个bean, userDao就等于哪个对象 -->
<bean id="userService" class="com.xun.service.UserServiceImpl">
<property name="userDao" ref="mySQL"/>
</bean>
<!--
ref: 引用Spring容器中创建好的对象
value: 具体的值,基本数据类型使用
-->
</beans>
- 之前,程序是需要主动创建对象的,控制权在程序员手中
- 使用set注入后 , 程序不在具有主动性了,而是变成了被动的接收对象!
这种思想 , 程序员就不需要在去管理对象的创建了 , 系统的耦合大大的降低了, 可以专注的在业务实现上! 这就是ioc的原型
IOC本质:
控制反转IOC (Inversion of Control),是一种设计思想,DI(依赖注入)是实现IOC的一种方法,也有人认为DI只是IOC的另一种说法。没有IOC的程序中,我们使用面向对象编程,对象的创建与对象间的依赖关系完全硬编码在程序中,对象的创建由程序自己控制,控制反转后将对象的创建移交给第三方,个人认为所谓控制反转就是:获得对象的方式 “反转了”。
采用XML方式配置Bean的时候,Bean的定义信息是和现实分离的,而采用注解的方式可以把两者合为一体,Bean的定义信息直接以注解的形式定义在实现类中,从而达到了零配置的目的。
控制反转是一种通过描述(XML或注解)并通过第三方去生产或获取待定对象的方式。在Spring中实现控制反转的是IOC容器。其实现方法就是依赖注入(Dependency Injection,DI)。
3、helloSpring
-
创建一个实体类
package com.xun.pojo; 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 + '\'' + '}'; } }
-
编写 application.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 传统的: 类型 变量名 = new 类型(); Hello hello = new Hello(); Spring: id = 变量名 class = new的对象; property 相当于给对象中的属性设置一个值 --> <bean id="hello" class="com.xun.pojo.Hello"> <!-- 通过set来注入属性 --> <property name="str" value="Spring"/> </bean> <!-- ref: 引用Spring容器中创建好的对象 value: 具体的值,基本数据类型使用 --> </beans>
-
测试
public class MyTest { public static void main(String[] args) { //获取Spring的上下文对象 拿到Spring的容器 ApplicationContext con = new ClassPathXmlApplicationContext("application.xml"); //我们的对象都已经在Spring中管理了,我们要使用直接去里面取出来就可以了 getBean("在bean容器中的id,看你要调用哪一个") Object bean = con.getBean("hello"); System.out.println(bean.toString()); } }
总结:
原本 的java 每需要用对象 就去new 。 然后现在使用了Spring,每需要用对象 就去容器里面调用
控制: 就是谁来控制对象的创建,传统应用程序的对象是由“程序本身控制创建”的,使用Spring后,对象是由“Spring来创建”的
反转:程序本身不创建对象,而变成被动的接收对象
依赖注入:就是利用set方法来进行注入的
彻底不用在程序中改动了,要实现不同的操作,只需要在xml配置文件中进行修改(应了那句话,Spring不会改变代码原来的情况,因为都是在配置文件中修改的),所谓的IOC , 一句话搞定:对象由Spring来创建,管理,装配!
4、IOC创建对象的方式
-
**默认 Spring是使用无参构造创建对象,**可以放置一个有参构造,让无参构造失效(测试看看),Spring容器里边的注册对象就会报错
-
假设我们要使用有参构造创建对象,就需要给有参构造的参数赋值
-
通过下标赋值
<!-- 使用下标赋值 有参构造里面,有多少个参数,就要写多少个,否则会报错--> <bean id="user" class="com.xun.pojo.User"> <constructor-arg index="0" value="1"/> <constructor-arg index="1" value="哈哈哈"/> </bean>
-
通过类型
<!-- 通过type类型来赋值 不建议使用 有参构造里面,有多少个参数,就要写多少个--> <bean id="user" class="com.xun.pojo.User"> <constructor-arg type="int" value="123"/> <!-- 基本类型不用--> <constructor-arg type="java.lang.String" value="哈哈哈"/> <!-- 引用类型都必须这样子--> </bean>
-
参数名
<!-- 直接通过参数名来设置 有参构造里面,有多少个参数,就要写多少个--> <bean id="user" class="com.xun.pojo.User"> <constructor-arg name="name" value="姓名"/> <constructor-arg name="gender" value="性别"/> </bean>
-
总结:在配置文件加载的时候,容器中管理的对象就已经初始化了!(如果容器里面有很多东西的时候估计加载会很慢)
5、Spring配置
5.1、别名
<!-- 如果添加了别名,我们也可以使用别名获取到这个对象-->
<alias name="user" alias="user2"/>
5.2、Bean的配置
<!-- id:bean 的唯一标识符,也就是相当于对象的变量名
class:bean 对象所对应的全限定名: 包名+类型
name:也是别名,而且name 可以同时取多个别名,分隔符也有多种-->
<bean id="UserTwo" class="com.xun.pojo.UserTwo" name="two two2,two3;two4">
<property name="name" value="注入"/>
</bean>
5.3、import
这个import,一般用于团队开发使用,他可以将多个配置文件,导入合并为一个!
假设,项目中有多个人开发,这多个人负责不同的类开发,不同的类需要注册在不同的bean中,这时就可以使用import将多个人的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">
<import resource="application.xml"/>
<import resource="beans.xml"/>
<import resource="beans1.xml"/>
</beans>
6、依赖注入
6.1、构造器注入
-
通过下标赋值
<!-- 使用下标赋值 有参构造里面,有多少个参数,就要写多少个,否则会报错--> <bean id="user" class="com.xun.pojo.User"> <constructor-arg index="0" value="1"/> <constructor-arg index="1" value="哈哈哈"/> </bean>
-
通过类型
<!-- 通过type类型来赋值 不建议使用 有参构造里面,有多少个参数,就要写多少个--> <bean id="user" class="com.xun.pojo.User"> <constructor-arg type="int" value="123"/> <!-- 基本类型不用--> <constructor-arg type="java.lang.String" value="哈哈哈"/> <!-- 引用类型都必须这样子--> </bean>
-
参数名
<!-- 直接通过参数名来设置 有参构造里面,有多少个参数,就要写多少个--> <bean id="user" class="com.xun.pojo.User"> <constructor-arg name="name" value="姓名"/> <constructor-arg name="gender" value="性别"/> </bean>
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; } @Override public String toString() { return "Address{" + "address='" + address + '\'' + '}'; } }
-
真实类型
public class Student { private String name; private Address address; //调用复杂类型 private String[] books; private List<String> hobBysList; private Map<String,String> cardMap; private Set<String> gamesSet; private String wife; private Properties info; } //省略了get、set、toString
-
applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="address" class="com.xun.pojo.Address"> <property name="address" value="北京市朝阳街360号"/> </bean> <bean id="student" class="com.xun.pojo.Student"> <!-- 第一种:普通值注入,value ,也可以向下面一样写,只不过闭合标签比较简便--> <property name="name" value="大锤"/> <!-- 第二种:bean注入(就是注入定义好了的bean),ref --> <property name="address" > <!--用闭合标签也可以--> <ref bean="address"/> </property> <!-- 第三种:数组注入,array --> <property name="books"> <array> <value>英语</value> <value>数学</value> <value>语文</value> </array> </property> <!-- 第四种:list集合注入,list --> <property name="hobBysList"> <list> <value>游泳</value> <value>打球</value> </list> </property> <!-- 第五种:Map集合注入,map --> <property name="cardMap"> <map> <!--map是以键值对的形式放值--> <entry key="手机号" value="13548756924"/> </map> </property> <!-- 第六种:Set集合注入,set --> <property name="gamesSet"> <set> <value>王者荣耀</value> </set> </property> <!-- 第七种:null注入, null --> <property name="wife" > <null/> </property> <!-- 第八种:Properties注入,props --> <property name="info"> <props> <!--跟map不一样,它的值是在尖括号中间定义的--> <prop key="driver">com.mysql.jdbc.Driver</prop> <prop key="url">(url)</prop> <prop key="username">root</prop> <prop key="password">123456</prop> </props> </property> </bean> </beans>
-
测试类
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); } }
总结一句话:只要打出 < 里面有的都可以拿出来用
6.3扩展方式注入
我们可以使用 p 命名空间 和 c命名空间进行注入
用时候需要在头部导入对应的xml约束
xmlns:p="http://www.springframework.org/schema/p"
xmlns:c="http://www.springframework.org/schema/c"
使用:
<?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:p="http://www.springframework.org/schema/p"
xmlns:c="http://www.springframework.org/schema/c"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- p命名空间注入,可以直接注入属性的值 : property -->
<bean class="com.xun.pojo.User" id="user" p:name="大锤" p:age="13"/>
<!-- c命名空间注入,通过构造器注入,需要有参构造 :constructor-arg -->
<bean class="com.xun.pojo.User" id="user2" c:name="大锤1" c:age="14"/>
</beans>
测试:
@Test
public void test(){
ApplicationContext context = new ClassPathXmlApplicationContext("userbeans.xml");
User user = context.getBean("user",User.class);
User user2 = context.getBean("user2",User.class);
System.out.println(user);
System.out.println(user2);
}
6.4、bean的作用域
-
单例模式(Spring默认机制) , 所有人都共享一个对象
<bean class="com.xun.pojo.User" id="user" scope="singleton"/>
-
原型模式:每次从容器中get的时候,都会产生一个新对象
<bean class="com.xun.pojo.User" id="user" scope="prototype"/>
-
其余的 request、session、application、websocket,这些只能再web开发中使用到
7、Bean的自动装配
- 自动装配是Spring满足bean依赖的一种方式!
- Spring会在上下文中自动查找,并自动给bean装配属性!
在Spring 中有三种装配的方式
- 在.xml中显示的配置
- 在java中显示配置
- 隐式的自动装配beam 【重要】
测试 没用自动装配:
环境搭建:一个人 有两个宠物!
<bean class="com.xun.pojo.Dog" id="dog"/>
<bean class="com.xun.pojo.Cat" id="cat"/>
<bean class="com.xun.pojo.Person" id="person">
<property name="name" value="大锤"/>
<property name="cat" ref="cat"/>
<property name="dog" ref="dog"/>
</bean>
7.1、ByName自动装配
<bean class="com.xun.pojo.Dog" id="dog"/>
<bean class="com.xun.pojo.Cat" id="cat"/>
<!-- byName:会自动在容器上下文中查找,和自己对象里面 set方法后面的值对应的 beanid(就是看上面的id中有没有和自己对象里面set方法后面的值对应上,有的话就自动装配,原理还是跟上面一样的 ) -->
<bean class="com.xun.pojo.Person" id="person" autowire="byName">
<property name="name" value="大锤"/>
</bean>
7.2、ByType自动装配
<!-- byType:会自动在容器上下文中查找,和自己对象 属性类型相同的bean (就比如这个bean 里面有两个属性,一个是dog,一个是cat,然后上下文中有跟属性类型相同的bean) ,保证属性唯一的情况下才能使用-->
<bean class="com.xun.pojo.Person" id="person" autowire="byType">
<property name="name" value="大锤"/>
</bean>
小结:
-
ByName的时候,需要保证所有bean的 id 唯一,并且和自己对象里面 set方法后面的值 对应上的 beanid
-
ByType的时候,需要保证所有bean的 class 唯一,并且和自己对象里面 需要注入的属性类型一致
<!--以下就是id不对应,class又不唯一 -->
<bean class="com.xun.pojo.Dog" id="dog1"/>
<bean class="com.xun.pojo.Dog" id="dog11"/>
<bean class="com.xun.pojo.Cat" id="cat1"/>
<bean class="com.xun.pojo.Cat" id="cat11"/>
7.3使用注解实现自动装配
jdk1.5支持注解,Spring2.5支持注解
要使用注解须知:
-
导入约束 需要导入 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/> </beans>
-
配置注解的支持 【注意点】
<!-- 开启注解的支持--> <context:annotation-config/>
@Autowired
直接在属性上使用即可,也可以在set方法上使用!
使用Autowired后,可以不用编些Set方法了,前提是你这个自动配置 的属性在IOC(Spring)容器中存在,且符合名字 ByName 或者 符合 ByType 其中一个即可
科普:
@Nullable // 字段标记了这个注解,说名这个字段可以为null
测试:
public class Person {
//如果显示定义了@Autowired的required属性为false,说明这个对象可以为null,否则不允许为空
@Autowired(required = false)
private Cat cat;
如果 @Autowired 自动装配的环境比较复杂(就是既不符合 ByName,又不符合 ByType。说白了就是bean id名字既不一样,又有多个bean class相同)自动装配无法通过注解 @Autowired 完成的时候,可以使用 @Qualifier(value = “要指定的bean id”) 去配合 @Autowired 的使用,指定一个唯一的bean对象注入
public class Person {
@Autowired
@Qualifier(value = "cat111")
private Cat cat;
@Autowired
@Qualifier(value = "dog1")
private Dog dog;
private String name;
}
@Resource
public class Person {
@Resource
private Cat cat;
@Resource(name = "dog1") //可以根据 name 来指定bean id
private Dog dog;
private String name;
}
跟上面的@Autowired 一样也是自动装配,但是当 ByType 、ByName 都不满足的时候,可以在自己里面的参数 @Resource(name = “dog1”) 来指定bean id
8、使用注解开发
在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:component-scan base-package="com.xun"/>
<!-- 开启注解的支持-->
<context:annotation-config/>
</beans>
-
bean
// 等价于 <bean class="com.xun.pojo.User" id="user"/> // 组件 @Component //放在类上,说明这个类被Spring管理了, 就是Bean public class User { public String name; }
-
属性如何注入
@Component public class User { private String name; public String getName() { return name; } //等价于 <property name="name" value="大锤"/> @Value("大锤") //放在公有属性上,可以不需要set public void setName(String name){ this.name = name; } }
-
衍生的注解
@Component 有几个衍生的注解,在web开发中,会按照mvc三层架构分层!
-
dao 【@Repository】 但是一般dao层的东西都习惯用 @Repository 来标注 ,功能是一样的都会注册到bean里面
-
service 【@Service】 跟上面都是一样的
-
controller 【@Controller】 跟上面都是一样的
这四个注解功能都是一样的,都是代表将某个类注册到Spring容器中,装配Bean
-
-
自动装配置
/* @Autowired:通过ByType、ByName 实现 自动装配 如果既不满足 ByType 又不满足 ByName (说白了就是bean id名字既不一样,又有多个bean class相同),自动装配无法通过注解 @Autowired 完成的时候,可以使用 @Qualifier (value = "要指定的bean id") 去配合 @Autowired 的使用,指定一个唯一的bean对象注入 @Qualifie:当自动装配 @Autowired 无法完成的时候。配合 @Autowired 使用。参数 value 指定一个唯一的bean对象注入 @Resource: 跟上面的@Autowired是一样的,也是自动装配 但是当 ByType 、ByName 都不满足的时候,可以在自己里面的参数 @Resource(name = "dog1") 来指定bean id @Nullable : 字段标记了这个注解,说名这个字段可以为null */
-
作用域
//标记 Bean的作用域 @Scope("prototype") // singleton:单例模式 prototype:原型模式 ,在上面bean的作用域里都有讲解 public class User { private String name; }
-
小结
xml 与 注解:
- xml 更加万能,适用于任何场合!维护简单方便
- 注解 不是自己的类用不了(因为使用注解,每个类都是一个bean),维护相当复杂!
xml 与 注解 最佳实践:
-
xml 用来管理 bean
-
注解只负责完成属性的注入;
-
我们在使用的过程中,只需要注意一个问题:必须让注解生效,就需要开启注解的支持
<!-- 指定要扫描的包,这个包下的注解就会生效--> <context:component-scan base-package="com.xun"/> <!-- 开启注解的支持--> <context:annotation-config/>
9、使用Java的方式配置Spring
现在要完全不使用Spring的xml配置了,全权交给Java来做!
JavaConfig是Spring的一个子项目,在Spring4之后,他成为了一个核心的功能!
实体类:
//这个注解的意思就是说明这个类被Spring接管了,注册到容器中
@Component
public class User {
private String name;
public String getName() {
return name;
}
@Value("大锤") //给属性注入值
public void setName(String name) {
this.name = name;
}
}
配置文件:
// 这个类也会被Spring容器托管,注册到容器中,因为它本身就是一个@Component
// @Configuration 代表这是一个配置类,就和之前看的application.xml一样的,所有配置文件里面能指定的他也能指定
@Configuration
@ComponentScan("com.xun.pojo") //指定它的扫描包 注解
@Import(xunConfig.class) /*指定它的Import 引入配置类(就相当于引入其他的Bean),之前是引入配置类的地址,现在是引入配置 类的class对象*/
public class MyConfig {
// 注册一个bean,就相当于我们之前写的一个bean标签
//这个方法的名字,就相当于bean标签中的id属性
//这个方法的返回值,就相当于bean标签中的class属性
@Bean
public User user(){
return new User(); //就是返回要注入到bean的对象
}
}
测试类:
public class MyTest {
public static void main(String[] args) {
//如果完全使用了配置类方式去做,我们就只能通过 AnnotationConfig 上下文来获取容器,通过配置类的class对象加载!
ApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class);
User user= context.getBean("user",User.class);
System.out.println(user.getName());
}
}
这种纯 java 的配置方式,在 SpringBoot 中随处可见!
10、代理模式
代理,就是帮别人做事情
你只需要去做你的事情就可以了,其他的事情交给代理来做
为什么要学习代理模式? 因为这就是SpringAOP的底层! 【面试高频 :SpringAOP,SpringMVC】
代理模式的分类:
- 静态代理
- 动态代理
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JeY4xHRA-1611833231746)(Spring.assets/image-20201230141341876.png)]
真实对象 跟 代理对象 要去实现 租房接口
10.1、静态代理
角色分析:
-
抽象角色:一般会用接口或者抽象类来解决
//租房 public interface Rent { public void rent(); }
-
真实角色:被代理的角色
//房东 public class Host implements Rent{ public void rent() { System.out.println("房东要出租房子"); } }
-
代理角色:代理真实角色,代理后一般会做一些附属操作
//代理角色 public class Proxy implements Rent{ // 首先要代理房东出租房子 private Host host; public Proxy(){ } public Proxy(Host host) { this.host = host; } public void rent() { seeHouse(); host.rent(); fare(); } //代理有很多自己的方法 public void seeHouse(){ System.out.println("带用户去看房"); } public void fare(){ System.out.println("收中介费"); } }
-
客户:访问代理对象的人
//用户 public class Client { public static void main(String[] args) { //房东要出租房子 Host host = new Host(); //代理房东出租房子,但是代理角色一般会有一些附属操作 Proxy proxy = new Proxy(host); //通过代理租房子,不需要面对房东 proxy.rent(); } } /* 一开始是直接去找房东租房子 现在很懒,想在网上找房子,就很难去找房东租房子了 然后就去找到 中介 租房子。 中介帮房东出租房子 */
静态代理模式的好处:
- 可以使真实角色的操作更加纯粹!不用去关注一些公共的业务
- 公共业务也就交给代理角色!实现了业务的分工
- 公共业务发生扩展的时候,方便集中管理!
缺点:
- 一个真实角色就会产生对应的代理角色,如果有多个真实角色 代码量会翻倍~开发效率会变低
10.2、加深理解
代理模式:就是不改变原有的业务情况下,给他增加一些功能、扩展一些功能
- 抽象角色
//抽象角色
public interface UserService {
public void add();
public void update();
public void delete();
public void select();
}
-
真实业务
//真实业务 public class UserServiceImpl implements UserService{ public void add() { System.out.println("新增一个方法"); } public void update() { System.out.println("修改一个方法"); } public void delete() { System.out.println("删除一个方法"); } public void select() { System.out.println("查询一个方法"); } }
-
代理类
public class UserServiceProxy implements UserService{ //要代理的对象 private UserServiceImpl userService; public void setUserService(UserServiceImpl userService){ this.userService = userService; } public void add() { log("add"); userService.add(); } public void update() { log("update"); userService.update(); } public void delete() { log("delete"); userService.delete(); } public void select() { log("select"); userService.select(); } public void log(String aa){ System.out.println("输出了"+aa+"日志"); } }
-
测试
public class Client { public static void main(String[] args) { UserServiceImpl userService = new UserServiceImpl(); UserServiceProxy userServiceProxy = new UserServiceProxy(); userServiceProxy.setUserService(userService); userServiceProxy.add(); } }
AOP的底层就是代理模式,再不改变源码的情况写增加一些功能
-
10.3、动态代理
- 动态代理和静态代理角色一样
- 动态代理的代理类是动态生成的,不是我们直接写好的
- 动态代理分为两大类:基于接口的动态代理,基于类的动态代理
- 基于接口: JDK动态代理 【下面使用这个】
- 基于类: cglib
- java字节码实现: javasist
需要了解两个类:Proxy 代理,InvocationHandler:调用处理程序
-
Proxy :提供了创建动态代理类和实例的静态方法
-
InvocationHandler: 调用处理程序实现的接口
动态代理的好处:
- 可以使真实角色的操作更加纯粹!不用去关注一些公共的业务
- 公共业务也就交给代理角色!实现了业务的分工
- 公共业务发生扩展的时候,方便集中管理!
- 一个动态代理类代理的是一个接口,一般就是对应的一类业务
- 一个代理类可以代理多个类,只要实现了同一个接口即可
//用这个类自动生成代理类
public class ProxyInvocationHandler implements InvocationHandler {
//被代理的接口
private Object target; //代理谁
public void setTarget(Object target) {
this.target = target;
}
//生成代理类
public Object getProxy(){
/* Proxy.newProxyInstance:生成一个代理对象
this.getClass().getClassLoader():加载得到 类在哪个位置
target.getClass().getInterfaces():表示要代理的接口是哪一个接口
this:代表它自己的 InvocationHandler
*/
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+"日志");
}
}
测试:
public class Client {
public static void main(String[] args) {
//真实角色
UserServiceImpl userService = new UserServiceImpl();
//代理角色,不存在,需要通过 ProxyInvocationHandler去生成一个代理类。 因为它实现了InvocationHandler
ProxyInvocationHandler pih = new ProxyInvocationHandler();
pih.setTarget(userService); //设置要代理的对象
//设置动态生成代理类
UserService proxy = (UserService) pih.getProxy();
proxy.delete();
}
}
11、AOP
11.1、什么是AOP
在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
在不影响原来业务的状态下实现动态增强
11.2、Aop在Spring中的作用
提供声明式事务;允许用户自定义切面
- 横切关注点:跨越应用程序多个模块的方法或功能。即使,与我们的业务逻辑无关的,但是我们需要关注的部分,就是横切关注点,如日志,安全、缓存、事务等等…
- 切面(ASPECT):横切关注点被模块化的特殊对象。 他是一个类
- 通知(Advice):切面必须要完成的工作。他是类中的一个方法
- 目标(Target):被通知对象
- 代理(Proxy):向目标对象应用通知之后创建的对象。
- 切入点(PointCut):切面通知 执行的地点的定义
- 连接点(JoinPoint):与切入点匹配的执行点
SpringAOP中,通过Advice定义横切逻辑,Spring中支持5种类型的Advice
即,AOP在不改变原有代码的情况下,去增加新的功能
11.3、使用Spring实现AOP
【重点】 使用AOP植入,需要导入一个依赖包
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.6</version>
</dependency>
配置AOP需要导入AOP的约束
<?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">
方式一:使用Spring 的API接口 【主要SpringAPI接口实现】
1、创建一个接口
package com.xun.service;
//接口
public interface UserService {
public void add();
public void update();
public void delete();
public void select();
}
2、创建一个真实对象
package com.xun.service;
//真实角色
public class UserServiceImpl implements UserService{
public void add() {
System.out.println("新增了一个方法");
}
public void update() {
System.out.println("修改了一个方法");
}
public void delete() {
System.out.println("删除了一个方法");
}
public void select() {
System.out.println("查询了一个方法");
}
}
3、编写一个log类,前置通知(切入到方法之前)
package com.xun.log;
import org.aopalliance.aop.Advice;
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;
//创建一个日志类 前置通知,就是切入到切入点之前
public class Log implements MethodBeforeAdvice {
/* method:要执行的目标对象的方法
objects:参数
o:目标对象
*/
public void before(Method method, Object[] objects, Object tagert) throws Throwable {
System.out.println(tagert.getClass().getName()+"的"+method.getName()+"被执行了");
}
}
4、创建一个 后置通知 (切入到方法之后)
package com.xun.log;
import org.springframework.aop.AfterReturningAdvice;
import java.lang.reflect.Method;
//后置通知 ,就是切入到切入点之前
public class AfterLog implements AfterReturningAdvice {
//returnValue : 返回值
public void afterReturning(Object returnValue, Method method, Object[] ages, Object target) throws Throwable {
System.out.println("执行了"+method.getName()+"方法,返回结果为:"+returnValue);
}
}
5、方式一:使用原生Spring API接口
<?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 class="com.xun.service.UserServiceImpl" id="service"/>
<bean class="com.xun.log.Log" id="log"/>
<bean class="com.xun.log.AfterLog" id="afterLog"/>
<!-- 方式一:使用原生Spring API接口-->
<!-- 配置aop : 需要导入aop的约束-->
<aop:config>
<!-- 切入点 :就是要在哪个地方执行
expression :表达式,execution(要执行的位置(切入点)! *(修饰词) *(返回值) *(类名) *(方法名) *(参数) ) -->
<aop:pointcut id="aaa" expression="execution(* com.xun.service.UserServiceImpl.*(..))"/>
<!-- 执行环绕增加 下面的意思就是,把log这个beanid 切入到上面这个方法里面 前置通知-->
<aop:advisor advice-ref="log" pointcut-ref="aaa"/>
<!-- 执行环绕增加 下面的意思就是,把afterLog这个beanid 切入到上面这个方法里面 后置通知-->
<aop:advisor advice-ref="afterLog" pointcut-ref="aaa"/>
</aop:config>
6、测试
public class MyTest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
// 注意点:动态代理代理的是接口
UserService service = context.getBean("service", UserService.class);
service.add();
}
}
方式二:自定义类来实现AOP 【主要是切面定义】
1、定义一个类
package com.xun.diy;
//自定义切入点类
public class DiyPintCut {
public void before(){
System.out.println("=======方法执行前========");
}
public void after(){
System.out.println("=======方法执行后========");
}
}
2、自定义切面
<!-- 方式二:自定义类-->
<bean id="cut" class="com.xun.diy.DiyPintCut" />
<aop:config>
<!-- 自定义切面 ref要引用的类-->
<aop:aspect ref="cut" >
<!-- 切入点-->
<aop:pointcut id="qierudian" expression="execution( * com.xun.service.UserServiceImpl.*(..))"/>
<!-- 前置通知 把 before 这个方法,切入到 qie_ru_dian里面-->
<aop:before method="before" pointcut-ref="qierudian"/>
<!-- 后置通知 把 after 这个方法,切入到 qie_ru_dian里面-->
<aop:after method="after" pointcut-ref="qierudian"/>
</aop:aspect>
</aop:config>
</beans>
3、测试
import com.xun.service.UserService;
import com.xun.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 service = context.getBean("service", UserService.class);
service.add();
}
}
方式三:用注解实现
package com.xun.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;
//也可以在这里加@Component 直接标注被Spring托管,就不需要在配置类里注册了
@Aspect //标注这个类是一个切面(方式二需要定义类在配置文件标注为切面,现在直接用注解标注)
public class AnnotationPointCut {
// 标注这是一个 前通知,括号里面就是"切入点", 表示切入到这个切入点之前之前
@Before("execution( * com.xun.service.UserServiceImpl.*(..))")
public void before(){
System.out.println("方法执行前");
}
// 标注这是一个 后通知,括号里面就是"切入点", 表示切入到这个切入点之前之后
@After("execution( * com.xun.service.UserServiceImpl.*(..))")
public void after(){
System.out.println("方法执行后");
}
//环绕增强 ,在环绕增强中,我们可以给定一个参数,代表我们要获取处理切入的点(加不加都行)
@Around("execution( * com.xun.service.UserServiceImpl.*(..))")
public void around(ProceedingJoinPoint jp) throws Throwable {
System.out.println("方法环绕前");
//执行方法 , 可以 用 jp. 来看看都有什么可以调用的 方法,其实也就是获取这个方法的一些信息
Object proceed = jp.proceed(); //可以把它当成过滤器,只有经过这个后,才执行上面的通知
System.out.println("方法环绕后");
}
}
<!-- 方式三:使用注解, 就不需要在配置类里面定义切点,前通知后通知了 都交给注解来做了-->
<bean class="com.xun.diy.AnnotationPointCut" id="cut"/>
<!-- 需要开启注解支持-->
<aop:aspectj-autoproxy/>
12、整合Mybatis
步骤:
- 导入相关jar包
- junit
- mybatis
- mysql数据库
- spring相关的 spring-webmvc
- aop织入 aspectjweaver
- mybatis-spring
- 编写配置文件
- 测试
12.1、回忆Mybatis
- 编写实体类
- 编写配置类
- 编写util工具类读取配置类
- 编写Mapper接口
- 编写Mapper.xml
- 测试
12.2、Mybatis-Spring
-
编写数据源配置
<!-- DataSource:使用Spring的数据源替换Mybatis的配置 这里使用Spring提供的JDBC:org.springframework.jdbc.datasource --> <bean class="org.springframework.jdbc.datasource.DriverManagerDataSource" id="dataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC"/> <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配置文件 Mybatis配置文件里面有的这里都能配--> <property name="configLocation" value="mybatis-comfig.xml"/> <property name="mapperLocations" value="classpath:com/xun/dao/*.xml"/> </bean>
-
SqlSessionTemplate
<!-- SqlSessionTemplate:就是我们使用的sqlSession--> <bean class="org.mybatis.spring.SqlSessionTemplate" id="sqlSession"> <!-- 只能使用构造器注入SqlSessionFactory,因为他没有set方法--> <constructor-arg index="0" ref="sqlSessionFactory"/> </bean>
-
需要给接口加实现类,就相当于是Mybatis获取SqlSession一样
public class UserMapperImpl implements UserMapper{ // 我们所有的操作,在原来都是使用sqlSession。现在都使用SqlSessionFactory; private SqlSessionTemplate sqlSession; public void setSqlSession(SqlSessionTemplate sqlSession) { this.sqlSession = sqlSession; } public List<User> selectUser() { UserMapper mapper = sqlSession.getMapper(UserMapper.class); return mapper.selectUser(); } }
-
将自己写的实现类,注入到Spring中
<bean class="com.xun.dao.UserMapperImpl" id="userMapper"> <property name="sqlSession" ref="sqlSession"/> </bean>
-
测试使用即可
@Test
public void Test(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserMapper userMapper = context.getBean("userMapper", UserMapper.class);
for (User user : userMapper.selectUser()) {
System.out.println(user);
}
}
13、事务式声明
1、回顾事务
- 把一组业务当成一个事务来做;要么都成功要么都失败
- 事务在项目开发中十分的重要,涉及到数据的一致性问题,不能马虎!
- 确保完整性和一致性
事务ACID原则:
- 原子性
- 要么都成功,要么都失败
- 一致性
- 事物前后的数据完整性要保证一致
- 隔离性
- 多个业务可能操作同一个资源,防止数据损坏
- 持久性
- 事务一旦提交,无论系统发生什么问题,结果都不会再被影响,持久化的写到存储器中
2、Spring中的事务管理
-
声明式事务 :AOP 就是代码是横切进去的,不影响原有代码
<!-- 配置声明式事务--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> <!-- 结合AOP实现事务的织入--> <!-- 配置事务通知--> <tx:advice id="aaa" transaction-manager="transactionManager"> <!-- 给哪些方法配置事务--> <!-- 配置事务的传播特性:propagation=REQUIRED(默认的,可以不写。就是支持当前事务,如果没有当前事务,就新建一个事务)--> <tx:attributes> <!-- 需要导入 tx 约束 --> <tx:method name="addUser" propagation="REQUIRED"/> <tx:method name="deleteUser"propagation="REQUIRED"/> <!-- read-only="true 只读,就不能对数据库进行增删改操作--> <tx:method name="select" read-only="true"/> <!-- 给所有方法添加事务--> <tx:method name="*" propagation="REQUIRED"/> </tx:attributes> </tx:advice> <!-- 使用AOP配置事务的切入--> <aop:config> <!-- 定义切入点--> <aop:pointcut id="hanhan" expression="execution(* com.xun.mapper.*.*(..))"/> <!-- 切入 把什么切入到什么里面--> <aop:advisor advice-ref="aaa" pointcut-ref="hanhan"/> </aop:config>
-
编程式事务 :需要在代码中,进行事务的管理
为什么需要事务?
- 如果不配置事务,可能存在数据提交不一致的情况。
- 如果不在Spring中去配置声明式事务,我们就需要在代码中手动配置事务
- 事务在项目的开发中十分重要,设计到数据的一致性和完整性问题!