Spring

简介

行业春天

解决企业级开发的复杂性,使现有的技术更容易使用,本身是个大杂烩,整合了现有的技术框架

不要重复造轮子

  • SSH:Struct2+Spring+Hibernate
  • SSM:SpringMVC+Spring+Mybatis

官网:https://spring.io/projects/spring-framework#overiew

官方下载地址:https://repo.spring.io/release/org/springframework/spring/
可以下载到所有的版本

GitHub:https://github.com/spring-projects/spring-framework

SpringWebMVC依赖文本

<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
<dependency><groupId>org.springframework</groupId><artifactId>spring-webmvc</artifactId><version>5.2.7.RELEASE</version>
</dependency><!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
<dependency><groupId>org.springframework</groupId><artifactId>spring-jdbc</artifactId><version>5.2.7.RELEASE</version>
</dependency>

两个包:

  • springWebMVC
  • springJDBC

Spring优点

  • Spring是一个开源的免费的框架(容器)
  • Spring是一个轻量级,非入侵式框架!
    • 不会对原来项目有影响
  • 控制反转(IOC),面向切面编程(AOP)
  • 支持事务处理,对框架整合的支持

Spring是一个轻量级的控制反转(IOC)和面向切面编程(AOP)的框架

Spring组成

spring组成的七大模块

  • 核心容器(SpringCore)
    • 核心容器提供Spring框架的基本功能。spring以bean的方式组织和管理Java应用的各个组件及其关系,spring使用BeanFactory来产生和管理Bean,是工厂模式的实现,BeanFactory使用控制反转(IoC)模式将应用的配置和依赖性规范与实际的应用程序代码分开
  • 应用上下文(Spring Context)
    • Spring上下文是一个配置文件,向spring提供上下文信息,spring上下文包括企业服务、、、、
  • Spring面向切面编程(Spring AOP)
    • AOP(Aspect Oriented Programming)
    • 通过配置管理特性,SpringAOP模块直接将面向方法的编程功能集成在了Spring框架中,Spring管理的任何对象都支持AOP,SpringAOP模块基于Spring的应用程序中的对象提供了事务管理服务,通过使用SpringAOP,不用依赖EJB组件,就可以将声明性事务管理集成在应用程序中
  • JDBC和DAO模块(Spring DAO)
    • Dao(Data Access Object)
    • JDBC、DAO的抽象层,提供了有意义的异常层次结构实现,可用该结构来管理异常处理,和不同数据库提供商抛出的错误信息,异常层次结构简化了错误处理,并且极大的降低了需要编写的代码数量,比如打开和关闭链接。
  • 对象实体映射(Spring ORM)
    • ORM(Object Relational Mapping)
    • Spring插入了若干个ORM框架,提供了ORM对象的关系工具,其中包括Hibernate,JDO和IBatisSQL Map等,所有这些都遵从Spring的通用事务和DAO异常层次结构
  • Web模块(Spring Web)
    • web上下文模块建立应用程序上下文模块之上,基于web的应用程序提供了上下文,所以spring框架支持与Struts集成,web模块还简化了处理多部分请求以及将请求参数绑定到域对象的工作
  • MVC模块(SpringWebMVC)
    • MVC(Model View Controller)
    • MVC框架是一个全功能的构建Web应用程序的MVC实现,通过策略接口,MVC框架编程高度可配置的,MVC容纳了大量视图技术,其中包括JSP,POI等,模型由JavaBean来构成,存放于m当中,而视图是一个接口,负责实现模型,控制器表示逻辑代码,由c的事情。spring框架的功能可以用在任何J2EE服务器当中,大多数功能也适用于不受管理的环境,spring的核心要点就是支持不绑定到特定J2EE服务的可重用业务和数据的访问对象,毫无疑问这样的对象可以在不同的J2EE环境,独立应用程序和测试环境之间重用

Spring介绍

  • SpringBoot
    • 快速开发脚手架
    • 可以快速开发单个微服务,把一个功能单独提取出来就是一个微服务
    • 约定大于配置,主要学习他的配置
    • 学习SpringBoot的前提是要掌握Spring和SpringMVC
  • SpringCloud
    • SpringCloud基于SpringBoot实现的
IOC理论推导

之前使用的流程:

  1. UserDao接口
  2. UserDaoImpl实现类
  3. UserService业务接口
  4. UserServiceImpl业务实现类

在之前的业务中,用户的需求会影响到我们原来的代码,需要根据用户需求去修改源代码,如果程序代码量巨大,修改一次的成本十分昂贵

使用set接口来实现也是一种动态实现 值的注入

  • 之前,程序是主动创建对象,控制权在程序员手上
  • 使用了set注入后,程序不再具有主动性,而是变成了被动的接受对象

这种思想,本质上解决了问题,程序员不用再去管理对象的创建,系统的耦合性大大的降低,可以更加专注的在业务的实现上,这是IOC的原型

以前的架构

现在的架构

IOC本质

  • 是一种设计思想,DI(依赖注入)是实现IOC的一种方法,原先创建对象由程序自己控制,控制反转后,创建转移给第三方,获得依赖对象的方式反转了
  • IoC是Spring框架的核心内容
    • 可以使用XML配置
    • 可以使用注解
    • 不配置也行,自动装填
  • 控制反转时一种通过描述(XML或注解)并通过第三方生产或获取特定对象的方式,在Spring中实现控制反转的时IoC容器,其实现方法是依赖注入(Dependency Injection ,DI)
HelloSpring

制作一个简单演示,如何将new对象交给第三方来做,用户可以通过修改xml文件来实现IoC操作

  • 导入包
  • 配置类加载路径应用上下文xml文件
  • 编写JavaBean
  • 测试,通过上下文获取bean来new对象
<dependency><groupId>org.springframework</groupId><artifactId>spring-webmvc</artifactId><version>5.2.7.RELEASE</version>
</dependency>
<?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/beanshttps://www.springframework.org/schema/beans/spring-beans.xsd"><bean id="..." class="...">  <!-- collaborators and configuration for this bean go here --></bean><bean id="..." class="..."><!-- collaborators and configuration for this bean go here --></bean><!-- more bean definitions go here --></beans>
@Data
public class Hello {private String str;
}
<?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/beanshttps://www.springframework.org/schema/beans/spring-beans.xsd"><bean id="Hello" class="com.haoyun.POJO.Hello"><property name="str" value="haoyun"/></bean>
</beans>
public class MyTest {@Testpublic void BeanTest(){ApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("Beans.xml");Hello hello = (Hello) classPathXmlApplicationContext.getBean("Hello");System.out.println(hello.toString());}
}
IOC创建对象的方式
@Data
public class Hello {private String name;public Hello(String name){}
}
<bean id="Hello" class="com.haoyun.POJO.Hello"><property name="name" value="sdafsdf"/>
</bean>

依赖注入的两种变体

DI依赖注入存在两种主要变体:ConstructorSetter 的两种依赖注入

Constructor

<beans><bean id="thingOne" class="x.y.ThingOne"><constructor-arg ref="thingTwo"/><constructor-arg ref="thingThree"/></bean><bean id="thingTwo" class="x.y.ThingTwo"/><bean id="thingThree" class="x.y.ThingThree"/>
</beans>
<bean id="exampleBean" class="examples.ExampleBean"><constructor-arg type="int" value="7500000"/><constructor-arg type="java.lang.String" value="42"/>
</bean>
<bean id="exampleBean" class="examples.ExampleBean"><constructor-arg index="0" value="7500000"/><constructor-arg index="1" value="42"/>
</bean>
<bean id="exampleBean" class="examples.ExampleBean"><constructor-arg name="years" value="7500000"/><constructor-arg name="ultimateAnswer" value="42"/>
</bean>

Setter

调用no-argument构造函数,或static工厂方法来实例化Bean

ApplicationContext管理Beans的Constructor和setter的DI,

可以使用properties的格式,但是一般使用XML,或者注解@Controller等

Constructor和Setter是可以混合使用的

Setter注入应该要有合理的默认值,否则,应该添加not-null检查

  • 可以由xml、JavaCode、annotations指定
  • 每个property(属性)或构造函数参数都要设置value的实际定义,或是容器的另一个引用,如参数不是基本类型,就要找到容器中对应bean的reference(引用)

循环依赖

  • classA通过构造函数实注入实现classB,classB又通过构造函数注入实现classA,这种配置bean的相互注入,会被SpringIoC判定为循环reference,并抛出BeanCurrentlyInCreationException
  • 要求beanA和beanB之间必须有一个被完全初始化,再注入另一个,典型的先有鸡还是先有蛋的问题

Setter-Based example

public class ExampleBean {private AnotherBean beanOne;private YetAnotherBean beanTwo;private int i;public void setBeanOne(AnotherBean beanOne) {this.beanOne = beanOne;}public void setBeanTwo(YetAnotherBean beanTwo) {this.beanTwo = beanTwo;}public void setIntegerProperty(int i) {this.i = i;}
}
<bean id="exampleBean" class="examples.ExampleBean"><!-- setter injection using the nested ref element --><property name="beanOne"><ref bean="anotherExampleBean"/></property><!-- setter injection using the neater ref attribute --><property name="beanTwo" ref="yetAnotherBean"/><property name="integerProperty" value="1"/>
</bean><bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
<bean id="exampleBean" class="examples.ExampleBean"><!-- constructor injection using the nested ref element --><constructor-arg><ref bean="anotherExampleBean"/></constructor-arg><!-- constructor injection using the neater ref attribute --><constructor-arg ref="yetAnotherBean"/><constructor-arg type="int" value="1"/>
</bean><bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>

最简单就直接

<bean id="Hello" class="com.haoyun.POJO.Hello"><constructor-arg name="name" value="haoyun"/>
</bean>

真是没事找事

别名alias

<bean id="Hello" class="com.haoyun.POJO.Hello"><constructor-arg name="name" value="haoyun"/>
</bean>
<alias name="Hello" alias="safsdf"/>

原来的名字也能用,别名也能用

这个又是没用的,直接再bean标签使用name也能起别名,里面的内容,用逗号或分号或空格都能起别名

<bean id="Hello" class="com.haoyun.POJO.Hello" name="hello afsdfaf,sadfasdf">

import配置文件

之前官网说一个正规的beans命名为applicationContext.xml,这是一个总的配置文件,项目中可以有多个配置文件,这样的好处是,可以由多个人编写,最后汇总,汇总到主配置文件applicationContext.xml

ApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("applicationContext.xml","Beans.xml");

创建ApplicationContext实例的时候可以选用多个配置文件

或者只选择applicationContext.xml配置文件,将其他的配置文件导入applicationContext.xml

<import resource="Beans.xml"/>
<import resource="Beans1.xml"/>
<import resource="Beans2.xml"/>

多个Beans中配置相同的内容是没关系的

DI依赖注入

几种方式注入,Constructor注入,setter注入,拓展方式注入

Set方式注入

  • 依赖注入:set注入
    • 依赖:Bean对象的创建依赖于容器
    • 注入:Bean对象中的说有属性,由容器来注入

搭建环境

  1. 复杂类型
  2. 真实测试对象

直接把官网的举例的所有类型一次做出来

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns="http://www.springframework.org/schema/beans"xsi:schemaLocation="http://www.springframework.org/schema/beanshttps://www.springframework.org/schema/beans/spring-beans.xsd"><!--    <import resource="Beans.xml"/>--><!--    <import resource="Beans1.xml"/>--><!--    <import resource="Beans2.xml"/>--><bean id="address" class="com.haoyun.POJO.Address"/><bean id="student" class="com.haoyun.POJO.Student"><!--普通值注入,value--><property name="name" value="asdfafd"/><!--Bean注入,ref--><property name="address" ref="address"/><!--数组注入--><property name="books"><array><value>asdfsdf</value><value>asdfasd</value><value>sdfasdfa</value><value>asdfasdfqwer</value></array></property><!--list--><property name="hobbys"><list><value>adf</value><value>wer</value><value>trey</value></list></property><!--map--><property name="card"><map><entry key="asdf" value="asdfsf"/><entry key="asdfsdf" value="asdfsf"/><entry key="asddsaff" value="asdfsf"/></map></property><!--set--><property name="games"><set><value>sadf</value><value>ssadf</value><value>sdadf</value></set></property><!--null--><property name="wife"><null/></property><!--properties--><property name="info"><props><prop key="sadf">werqwer</prop><prop key="sasdf">werqwer</prop><prop key="saddf">werqwer</prop></props></property></bean></beans>
  • value
  • ref
  • list、map、set、properties
  • null
  • 其他类型

p-namespace、c-namespace

命名空间,这两个能进行的操作其他的操作也能实现,还不一定简便了,要先添加配置支持扩展使用命名空间

xmlns:p="http://www.springframework.org/schema/p"
xmlns:c="http://www.springframework.org/schema/c"

添加在applicationContext.xml上,才可以使用,p和c标签

p表示property,用于有无参构造函数的bean对象的依赖注入操作

c表示Constructor,用于有 有参构造函数的bean对象的依赖注入操作

<!--c:constructor   需要有参构造器-->
<bean id="User" class="com.haoyun.POJO.User" c:age="12" c:name="asdfsf"/><!--p:property  需要无参构造器-->
<bean id="User2" class="com.haoyun.POJO.User" p:age="13" p:name="sdf"/>

测试

@Test
public void BeanTest(){ApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");//Student student = (Student) classPathXmlApplicationContext.getBean("student");User user = (User) classPathXmlApplicationContext.getBean("User");User user2 = (User) classPathXmlApplicationContext.getBean("User2");System.out.println(user.toString());System.out.println(user2.toString());}
Bean-Scopes作用域
  • Singleton 单例
  • Prototype 原型
  • request 请求
  • session 会话
  • application 应用
  • WebSocket 连接

主要使用前两种

Singleton 单例模式

单例模式是Spring的默认机制

只管理Singleton bean 的一个共享实例,带有一个或多个match bean定义的id到导致只返回一个特定的bean实例

定义的bean限定为singleton时,只创建该bean定义的object的一个实例,存储在singleton缓存中,后续的请求都引用缓存中的实例

只创建一个实例
这个共享实例被共享到每个协作对象中

Spring中的singleton bean概念于singleton pettern的概念不同,如果在单个 Spring 容器中为特定 class 定义一个 bean,则 Spring 容器将创建该_ bean 定义所定义的 class 的一个且仅一个实例。

<bean id="accountService" class="com.something.DefaultAccountService"/><!-- the following is equivalent, though redundant (singleton scope is the default) -->
<bean id="accountService" class="com.something.DefaultAccountService" scope="singleton"/>

Demo

<bean id="studentTest" class="com.haoyun.POJO.Student" ><property name="address" ref="address"/>
</bean>
<bean id="studentTest2" class="com.haoyun.POJO.Student" ><property name="address" ref="address"/>
</bean>
<bean id="address" class="com.haoyun.POJO.Address" p:address="address" scope="singleton" />
@Test
public void BeanTest(){ApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");Student studentTest = (Student) classPathXmlApplicationContext.getBean("studentTest");Student studentTest2 = (Student) classPathXmlApplicationContext.getBean("studentTest2");System.out.println(studentTest);System.out.println(studentTest2);System.out.println(studentTest == studentTest2);System.out.println(studentTest.getAddress() == studentTest2.getAddress());
}

ProtoType 原型模式

bean部署的不是单例模式的原型范围都会创建一个新的bean实例,并对该特定bean发出请求,

每个bean都是一个单独的对象

配置和组装object交给client,client code必须清理prototype-scoped object释放原型beans所拥有的昂贵资源,之后可以尝试自定义bean post-processor,释放资源

<bean id="accountService" class="com.something.DefaultAccountService" scope="prototype"/>

每次从容器中get的时候都会产生一个新的对象

其余的request,session,application,这些只能在web开发中使用到,自行理解

Bean的自动装配
  • 自动装配,spring满足bean依赖的一种方式
  • autowire
  • spring会在上下文中自动寻找,并自动给bean装配属性

在spring中有三种装配方式

  1. 在xml中显示的配置
  2. 在java中显示的配置
  3. 隐式 的自动装配bean【重要】

XML Configuration Demo

<bean id="cat" class="com.haoyun.POJO.Cat"/>
<bean id="dog" class="com.haoyun.POJO.Dog"/><bean id="People" class="com.haoyun.POJO.People"><property name="name" value="haoyun"/><property name="cat" ref="cat"/><property name="dog" ref="dog"/>
</bean>
@Test
public void CatDogTest(){ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");People people = (People) classPathXmlApplicationContext.getBean("People");people.getCat().shout();people.getDog().shout();classPathXmlApplicationContext.registerShutdownHook();
}
<bean id="cat" class="com.haoyun.POJO.Cat"/><bean id="dog" class="com.haoyun.POJO.Dog"/>
<!--    <bean id="dog1" class="com.haoyun.POJO.Dog"/>如果这里的id更改,就找不到了--><!--byName:会自动再容器上下文查找,和自己对象set方法后面值对应的bean id--><bean id="People" class="com.haoyun.POJO.People" autowire="byName"><property name="name" value="haoyun"/></bean>
<bean id="cat" class="com.haoyun.POJO.Cat"/><bean class="com.haoyun.POJO.Dog"/>
<!--    <bean id="dog" class="com.haoyun.POJO.Dog"/> 但是不能配重,全局只能有一个--><!--连bean-id都可以不用--><!--byType:自动在容器的上下文查找,和自己对象相同类型bean--><bean id="People" class="com.haoyun.POJO.People" autowire="byType"><property name="name" value="haoyun"/></bean>

Annotation-based Container Configuration

使用annotation配置更加简洁,Spring都支持这两种配置风格

spring2.0引用了@Required
spring2.5采用通用的方法来驱动spring依赖注入@Autowired
2.5还添加了JSR-250注释的支持,@PostConstruct和@PreDestroy
(初始化方法)(销毁方法)

需要注册单独的Bean定义,和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/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsd"><context:annotation-config/></beans>

主要是添加这三句话

@Autowired

public class MovieRecommender {private final CustomerPreferenceDao customerPreferenceDao;@Autowiredpublic MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {this.customerPreferenceDao = customerPreferenceDao;}// ...
}
public class SimpleMovieLister {private MovieFinder movieFinder;@Autowiredpublic void setMovieFinder(MovieFinder movieFinder) {this.movieFinder = movieFinder;}// ...
}
public class MovieRecommender {private MovieCatalog movieCatalog;private CustomerPreferenceDao customerPreferenceDao;@Autowiredpublic void (MovieCatalog movieCatalog,CustomerPreferenceDao customerPreferenceDao) {this.movieCatalog = movieCatalog;this.customerPreferenceDao = customerPreferenceDao;}// ...
}
public class MovieRecommender {private final CustomerPreferenceDao customerPreferenceDao;@Autowiredprivate MovieCatalog movieCatalog;@Autowiredpublic MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {this.customerPreferenceDao = customerPreferenceDao;}// ...
}
<context:annotation-config/><bean id="cat" class="com.haoyun.POJO.Cat"/>
<bean id="dog" class="com.haoyun.POJO.Dog"/><bean id="People" class="com.haoyun.POJO.People" p:name="asdfadf"/>
public class MovieRecommender {@Autowiredprivate MovieCatalog[] movieCatalogs;// ...
}
public class MovieRecommender {private Set<MovieCatalog> movieCatalogs;@Autowiredpublic void setMovieCatalogs(Set<MovieCatalog> movieCatalogs) {this.movieCatalogs = movieCatalogs;}// ...
}
public class MovieRecommender {private Map<String, MovieCatalog> movieCatalogs;@Autowiredpublic void setMovieCatalogs(Map<String, MovieCatalog> movieCatalogs) {this.movieCatalogs = movieCatalogs;}// ...
}
public class SimpleMovieLister {private MovieFinder movieFinder;@Autowired(required = false)public void setMovieFinder(MovieFinder movieFinder) {this.movieFinder = movieFinder;}// ...
}
public class SimpleMovieLister {@Autowiredpublic void setMovieFinder(@Nullable MovieFinder movieFinder) {...}
}
public class MovieRecommender {@Autowired@Qualifier("main")private MovieCatalog movieCatalog;// ...
}
<bean  class="com.haoyun.POJO.Cat"/><bean  class="com.haoyun.POJO.Dog"/><bean id="People" class="com.haoyun.POJO.People" p:name="asdfadf"/>
<bean  id="cat1" class="com.haoyun.POJO.Cat"/>
<bean  id="cat2" class="com.haoyun.POJO.Cat"/><bean id="dog1" class="com.haoyun.POJO.Dog"/>
<bean id="dog2" class="com.haoyun.POJO.Dog"/><bean id="People" class="com.haoyun.POJO.People" p:name="asdfadf"/>
@Resource(type = Dog.class)
@Resource(name = "cat1")
使用注解开发

在Spring4之后,要使用注解开发,必须要保证AOP的包导入了

<!--指定要扫描的包,这个包小的注解就会生效-->
<context:component-scan base-package="com.haoyun.POJO"/>
@Repository
public class User {@Value("haoyun")private String name ;
}
@Service
@Controller
@Scope("singleton")
使用Java的方式配置Spring

完全不使用XML配置文件,全部使用Java的方式来做

@Configuration
@ComponentScan("com.haoyun.POJO")
public class HaoyunConfig {@Beanpublic User UserBean() {return new User();}}
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {@AliasFor(annotation = Component.class)String value() default "";boolean proxyBeanMethods() default true;
}
@Test
public void UserTest() {AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(HaoyunConfig.class);//"com.haoyun.controller"/*要与HaoyunConfig中@Bean的method方法名对应*/User user = (User) context.getBean("UserBean");System.out.println(user.getName());}
@Configuration
@ComponentScan("com.haoyun.POJO")
@Import(config2.class)
public class HaoyunConfig {@Beanpublic User UserBean() {return new User();}}

JavaConfig是spring的一个子项目,是spring4之后的核心功能

代理模式

SpringAOP的底层,SpringAOP和SpringMVC必问

代理模式:

  • 静态代理
  • 动态代理

静态代理

  • 抽象角色:一般会使用接口或者抽象类来解决
  • 真实角色:被代理的角色
  • 代理角色:代理真实角色,代理真实角色后,我们一般会做一些附属操作
  • 客户:访问代理对象的人

租房例子

客户找中介,中介找房东,实现租房的方法,还能添加一些附属操作

代码步骤:

  • 接口
  • 真实角色
  • 代理角色
  • 客户端访问角色

代理模式的好处:

  • 可以使真实角色的操作更加存粹,不用去关注一些公共的业务
  • 公共业务交给代理角色,实现业务分工
  • 公共业务发生扩展时,方便管理

缺点:

  • 一个真实角色就会产生一个代理角色,代码量增多

如果一条线已经做出来了,突然要在中间加一个功能,不能去修改原有的业务代码,在公司中是大忌,只能在原有的基础上添加功能,通过代理,调用的是原有的功能,但是在调用原有的功能的同时能像上面的租房的例子一样,增加一些功能,要去学习下面向对象七大特性

动态代理

静态代理每代理一个被代理角色就要重新编写一个静态代理对象,代码量也十分庞大

动态代理的代理类是动态生成的,不是直接写好的

动态代理分两大类:

  • 基于接口的动态代理
    • JDK原生的动态代理【这里使用】
  • 基于类的动态代理
    • 基于类的cglib
  • 但是现在用的比较多的是基于java字节码来实现的JavaAssist

需要了解两个类:Proxy,InvocationHandle

  • Proxy代理
  • InvocationHandle调用处理程序

通过反射的方式,判断被代理对象的类型,处理代理实例

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 {Object result  = method.invoke(target,args);return result;}
}

这个代理对象能添加功能,也能执行被代理对象的方法

AOP

不改变原有的业务,增加功能

AOP在Spring中的作用

  • 横切关注点:跨越应用程序多个模块的方法或功能,于我们业务逻辑无关,但需要关注的部分
  • 切面(aspect):横切关注点被模块化的特殊对象,是一个类
  • 通知(Adivce):切面必须要完成的工作,类中的一个方法
  • 目标(Target):被通知的对象
  • 代理(Proxy):向目标对象应用通知后创建的对象
  • 切入点(PointCut):切面通知 执行的“地点”的定义
  • 连接点(JointPoint):于切入点匹配的执行点

SpringAOP中,通过Advice定义横切逻辑,支持五种类型的的advice

AOP不改变原有代码的情况下,去增加新的功能

使用Spring实现AOP

使用Spring的API接口

<dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId><version>1.9.5</version>
</dependency>
xmlns:aop="http://www.springframework.org/schema/aop"
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd
public interface UserService {public void add();public void delete();public void update();public void select();
}
public class UserServiceImpl implements UserService{public void add() {System.out.println("add");}public void delete() {System.out.println("delete");}public void update() {System.out.println("update");}public void select() {System.out.println("select");}
}
public class Log implements MethodBeforeAdvice {///**// * @param method:要执行目标对象的方法// * @param args:参数// * @param target:目标对象// * @throws Throwable// */public void before(Method method, Object[] args, Object target) throws Throwable {System.out.println(target.getClass().getName()+"的"+method.getName()+"被执行");}
}
<bean id="log" class="com.haoyun.log.Log"/>
<bean id="UserServiceImpl" class="com.haoyun.service.UserServiceImpl"/>
<aop:config><!--切入点:expression:表达式(要执行的位置,UserServiceImpl.要切入的方法)--><aop:pointcut id="pointcut" expression="execution(* com.haoyun.service.UserServiceImpl.*(..))"/><!--执行环绕增加--><!--把log切片,切入到pointcut切入点上--><aop:advisor advice-ref="log" pointcut-ref="pointcut"/>
</aop:config>
@Test
public void ProxyTest() {ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");/*代理的是一个接口*/UserService userserviceImpl = (UserService) context.getBean("UserServiceImpl",UserService.class);userserviceImpl.add();
}

使用自定义类

public class aspectDiy {public void beforeDiy(){System.out.println("beforeDiy");}public void afterDiy(){System.out.println("afterDiy");}
}
<aop:config><aop:aspect ref="aspectDip"><!--切入点--><aop:pointcut id="pointcut" expression="execution(* com.haoyun.service.UserServiceImpl.*(..))"/><!--通知,切入到哪个点上--><aop:before method="beforeDiy" pointcut-ref="pointcut"/><aop:after method="afterDiy" pointcut-ref="pointcut"/></aop:aspect></aop:config>
beforeDiy
add
afterDiy

使用注解实现

    <aop:aspectj-autoproxy/><context:component-scan base-package="com.haoyun.pointCut"/>
@Aspect
@Component
public class AnnotationPointCut {@Before("execution(* com.haoyun.service.UserServiceImpl.*(..))")public void before(){System.out.println(AnnotationPointCut.class.getName()+"   before");}@After("execution(* com.haoyun.service.UserServiceImpl.*(..))")public void after(){System.out.println(AnnotationPointCut.class.getName()+"   after");}}

规范点的化使用第一种方法吧,切面小的话使用第二种方法,第三种方法也不是很简便,要设置切入点还挺麻烦,第一和第二种方法的切入点都可以复用,第三种方法切入点不好复用,差点意思

整合Mybatis

回顾Mybatis

步骤:

<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>8.0.11</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-aop</artifactId><version>5.2.7.RELEASE</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.12</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-webmvc</artifactId><version>5.2.7.RELEASE</version></dependency><dependency><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId><version>3.5.2</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-jdbc</artifactId><version>5.2.7.RELEASE</version></dependency><dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId><version>1.9.5</version></dependency><dependency><groupId>org.mybatis</groupId><artifactId>mybatis-spring</artifactId><version>2.0.5</version></dependency></dependencies><build><resources><resource><directory>src/main/resources</directory><includes><include>**/*.properties</include><include>**/*.xml</include></includes><filtering>true</filtering></resource><resource><directory>src/main/java</directory><includes><include>**/*.properties</include><include>**/*.xml</include></includes><filtering>true</filtering></resource></resources></build>
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configurationPUBLIC "-//mybatis.org//DTD Config 3.0//EN""http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration><typeAliases><package name="com.haoyun.POJO"/></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=false&amp;useUnicode=true&amp;characterEncoding=UTF-8"/><property name="username" value="root"/><property name="password" value="123456"/></dataSource></environment></environments><mappers><package name="com.haoyun.mapper"/></mappers>
</configuration>
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {private int id;private String name;private String pwd;
}
public interface UserMapper {List<User> selectUser();}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Config 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.haoyun.mapper.UserMapper"><select id="selectUser" resultType="user">select * from user</select>
</mapper>
@Test
public void UserMapper(){String resource  ="mybatis-config.xml";InputStream inputStream = null  ;try {inputStream = Resources.getResourceAsStream(resource);} catch (IOException e) {e.printStackTrace();}SqlSessionFactory build = new SqlSessionFactoryBuilder().build(inputStream);SqlSession sqlSession = build.openSession();UserMapper mapper = sqlSession.getMapper(UserMapper.class);List<User> users = mapper.selectUser();for (User user : users) {System.out.println(user);}sqlSession.close();
}

mybatis-spring官方文档

http://mybatis.org/spring/zh/index.html

这里面涉及到mybatis-spring包的版本问题,如果spring版本和mybatis是5.0和3.5版本以上的,要使用Mybatis-spring2.0以上的包

最好就通过阅读mybatis-spring文档来学习

http://mybatis.org/spring/zh/getting-started.html

整合Mybatis方式一

spring的IoC特性,原先的Mybatis中的new对象方法都将制作为bean,进行依赖注入

<!--DateSource:使用spring的数据源替换mybatis的设置:c3p0、dbcp、druid-->
<bean id="dateSource" 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=false&amp;useUnicode=true&amp;characterEncoding=UTF-8"/><property name="username" value="root"/><property name="password" value="123456"/>
</bean>
<!--sqlSessionFactory-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"><!--配置数据源--><property name="dataSource" ref="dateSource" /><!--mybatis的核心配置文件,但是可以不写--><property name="configLocation" value="classpath:mybatis-config.xml"/><!--相当于mappe package 添加mybatis.xml配置文件--><property name="mapperLocations" value="classpath:com/haoyun/mapper/UserMapper.xml"/><!--mybatis配置文件中能配置的在这都能配置-->
</bean>
<!--SqlSessionTemplate:就是SqlSession-->
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate"><!--只能使用构造器注入sqlSessionFactory,因为SqlSessionTemplate没有set方法--><constructor-arg index="0" ref="sqlSessionFactory"/>
</bean>
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configurationPUBLIC "-//mybatis.org//DTD Config 3.0//EN""http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration><typeAliases><package name="com.haoyun.POJO"/></typeAliases>
<!--    <settings>-->
<!--        <setting name="" value=""/>-->
<!--    </settings>--><!--一般会留下几个设置,证明使用了mybatis--><!--    <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=false&amp;useUnicode=true&amp;characterEncoding=UTF-8"/>-->
<!--                <property name="username" value="root"/>-->
<!--                <property name="password" value="123456"/>-->
<!--            </dataSource>-->
<!--        </environment>-->
<!--    </environments>-->
<!--    <mappers>-->
<!--        <package name="com.haoyun.mapper"/>-->
<!--    </mappers>-->
</configuration>
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
public class UserMapperImpl implements UserMapper {private SqlSessionTemplate sqlSession;public void setSqlSession(SqlSessionTemplate sqlSession) {this.sqlSession = sqlSession;}public List<User> selectUser() {UserMapper mapper = sqlSession.getMapper(UserMapper.class);return mapper.selectUser();}
}
<mapper namespace="com.haoyun.mapper.UserMapper"><select id="selectUser" resultType="user">select * from user</select>
</mapper>
<?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/beanshttps://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/aophttps://www.springframework.org/schema/aop/spring-aop.xsd"><import resource="spring-dao.xml"/><bean id="userMapper" class="com.haoyun.mapper.UserMapperImpl"><property name="sqlSession" ref="sqlSession"/></bean>
</beans>a
@Test
public void UserMapper(){ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");UserMapper userMapper = context.getBean("userMapper", UserMapper.class);List<User> users = userMapper.selectUser();for (User user : users) {System.out.println(user);}
}

整合Mybatis方式二

    <!--SqlSessionTemplate:就是SqlSession-->
<!--    <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">-->
<!--        &lt;!&ndash;只能使用构造器注入sqlSessionFactory,因为SqlSessionTemplate没有set方法&ndash;&gt;-->
<!--        <constructor-arg index="0" ref="sqlSessionFactory"/>-->
<!--    </bean>-->
public class UserMapperImpl2 extends SqlSessionDaoSupport implements UserMapper{/*省去了注入的过程*/public List<User> selectUser() {return getSqlSession().getMapper(UserMapper.class).selectUser();}
}
<bean id="UserMapperImpl2" class="com.haoyun.mapper.UserMapperImpl2"><property name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>

声明式事务

回顾事务transaction

  • 事务在项目开发中十分重要,涉及到数据的一致性
  • 事务ACID原则
    • 原子性
    • 一致性
    • 隔离性
      • 多个业务可能操作同一个资源,防止数据损坏
    • 持久性
      • 事务一旦提交,无论系统发生什么问题,结果都不会被影响

这里举了一个例子,进行insert操作和delete操作,insert操作成功,delete操作失败,根据ACID原则,这一组业务应该一起成功或者一起失败

在官方文档中介绍了两种事务的实现类型

http://mybatis.org/spring/zh/transactions.html#configuration

  • 交由容器管理事务
  • 编程式事务管理
    • 根据AOP的特性,最好以横切关注点的方式将切片切入,而不是修改已经写好的业务代码,所以编程式事务管理了解下就行,少用

交由容器管理事务

<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"><constructor-arg ref="dataSource" />
</bean>
<tx:jta-transaction-manager />
  <property name="transactionFactory"><bean class="org.apache.ibatis.transaction.managed.ManagedTransactionFactory" /></property>  

但是举例是结合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"xmlns:tx="http://www.springframework.org/schema/tx"xsi:schemaLocation="http://www.springframework.org/schema/beanshttps://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/aophttps://www.springframework.org/schema/aop/spring-aop.xsdhttp://www.springframework.org/schema/txhttp://www.springframework.org/schema/tx/spring-tx.xsd">
<tx:jta-transaction-manager/>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"><constructor-arg ref="dateSource" />
</bean>
<!--结合AOP实现事务织入-->
<!--配置事务的传播特性,这个属性在晚上搜,事务的传播特性-->
<tx:advice id="txAdvice" transaction-manager="transactionManager"><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.haoyun.mapper.*.*(..))"/><aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>
</aop:config>
<select id="selectUser" resultType="user">select * from user
</select>
<insert id="insertUser" parameterType="user">insert into mybatis.user (id,name,pwd) values (#{id},#{name},#{pwd})
</insert>
<delete id="deleteUser" parameterType="int">deletes from user where id = #{id}
</delete>
public interface UserMapper {List<User> selectUser();int insertUser( User user);int deleteUser( int id);
}
public class UserMapperImpl2 extends SqlSessionDaoSupport implements UserMapper{/*省去了注入的过程*/public List<User> selectUser() {User user = new User();user.setId(14);user.setName("haoyun");user.setPwd("123");UserMapper mapper = getSqlSession().getMapper(UserMapper.class);mapper.insertUser(user);mapper.deleteUser(5);return getSqlSession().getMapper(UserMapper.class).selectUser();}public int insertUser(User user) {return getSqlSession().getMapper(UserMapper.class).insertUser(user);}public int deleteUser(int id) {return getSqlSession().getMapper(UserMapper.class).deleteUser(id);}
}