常用的与 Spring 整合的框架
SSH:Struct2 + Spring + Hibernate
SSM:SpringMvc + Spring + MyBatis
官网:https://spring.io/
官方下载地址: http://repo.spring.io/release/org/springframework/spring
GitHub地址:https://github.com/spring-projects/spring-framework
常用依赖配置
<dependencies>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-webmvcartifactId>
<version>5.2.3.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-jdbcartifactId>
<version>5.2.3.RELEASEversion>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.12version>
dependency>
<dependency>
<groupId>org.aspectjgroupId>
<artifactId>aspectjweaverartifactId>
<version>LATESTversion>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>LATESTversion>
dependency>
<dependency>
<groupId>org.mybatisgroupId>
<artifactId>mybatis-springartifactId>
<version>2.0.3version>
dependency>
dependencies>
总结一句话:Spring 就是一个轻量级的控制反转(IoC)和面向切面编程(AOP)的框架!
在 Spring的官网有这个介绍,现代化的 java开发!说白了就是基于 Spring 的开发!
现在大多数公司都在使用 SpringBoot 进行快速开发。学习 SpringBoot 的前提,需要完全掌握 Spring 及 SpringMVC 。承上启下的作用!
发展了太久之后,违背了原来的理念!配置十分繁琐,人称配置地狱!
以前的写法
/**
* 用户Dao
*/
public interface UserDao {
void getUser();
}
/**
* 用户Dao实现类
*/
public class UserDaoImpl implements UserDao {
@Override
public void getUser() {
System.out.println("获取用户数据");
}
}
/**
* 用户业务接口
*/
public interface UserService {
void getUser();
}
/**
* 用户业务层实现类
*/
public class UserServiceImpl implements UserService {
private UserDao userDao = new UserDaoImpl();
@Override
public void getUser() {
userDao.getUser();
}
}
public class UserServiceTest {
@Test
public void getUser() {
UserService userService = new UserServiceImpl();
userService.getUser();
}
}
但是,当我们需要增加实现类的时候,如增加 UserDaoMySqlImpl 类时,我们要去修改源代码来实现 UserDaoMySqlImpl 类的功能
/**
* 增加的 UserMySqlImpl 实现类
*/
public class UserMySqlImpl implements UserDao {
@Override
public void getUser() {
System.out.println("mysql");
}
}
这时我们需要在 UserServiceImpl 类中修改代码
private UserDao userDao = new UserMySqlImpl();
如果我们还需要增加新的实现类 UserDaoOracleImpl ,则仍需要修改源代码。很明显,这样的代码并不是优秀的代码。当代码量十分大的时候,修改一次代码的成本代价就会十分昂贵,牵一发而动全身,这并不是我们想要的。
现在,我们使用一个Set接口实现,这时已经发生了革命性的变化!
// 聚合UserDao接口
private UserDao userDao ;
// 利用Set动态实现值的注入
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
增加Set方法的前后对比
在没有增加这个Set方法时,程序是主动创建对象的。控制权在程序员手上!
使用了Set注入后,程序不再具有主动性,而是变成了被动接收对象!这就是最开始的控制反转,这就是IoC的原型
**很明显,增加了 set 方法后,代码变得灵活了。**对象不再是程序员来控制或者修改,而是将主动权交由用户,按照用户的需要去创建和修改对象。
这种思想从本质上解决了问题,我们程序员不用再去管理对象的创建了。系统的耦合性大大降低,可以更加专注的在业务的实现上。
控制反转 IoC (Inversion of Control),是一种设计思想,DI(依赖注入)是实现 IoC 的一种方法,也有人认为 DI 只是 IoC 的另一种说法。没有 IoC 的程序中,我们使用面向对象编程,对象的创建与对象间的依赖关系完全硬编码在程序中,对象的创建由程序自己控制。控制反转后,将对象的创建转移给第三方。个人认为所谓控制反转,就是:获得依赖对象的方式反转了。
采用XML方式配置 Bean 的时候,Bean的定义信息是和实现分离的,而采用注解的方式可以把两者合为一体。Bean 的定义信息直接以注解的形式定义在实现类中,从而达到了零配置的目的。
控制反转是一种一种通过描述(XML或注解)并通过第三方去生产或获取特定对象的方式。在 Spring 中实现控制反转的是 IoC 容器,其实现方法是依赖注入(Dependency Injection,DI)。
<dependencies>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-webmvcartifactId>
<version>5.2.3.RELEASEversion>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.12version>
dependency>
dependencies>
public class Hello {
private String str;
public Hello() {
}
public Hello(String str) {
this.str = str;
}
public String getStr() {
return str;
}
public void setStr(String str) {
this.str = str;
}
@Override
public String toString() {
return "Hello{" +
"str='" + str + '\'' +
'}';
}
}
在 resource 目录下创建 applicationContext.xml,并配置Bean
<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 class="com.xp.pojo.Hello" id="hello">
<property name="str" value="Spring"/>
bean>
beans>
public class MyTest {
@Test
public void main(){
// 获取Spring上下文对象
ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
// 我们的对象现在都在Spring中被管理了,我们要使用,直接去里面取出来就可以了
Hello hello = context.getBean("hello", Hello.class);
System.out.println(hello.toString());
}
}
将之前的项目修改成Spring项目
<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="userDaoImpl" name="user" class="com.xp.dao.impl.UserDaoImpl"/>
<bean id="userDaoMySqlImpl" name="mysql" class="com.xp.dao.impl.UserMySqlImpl"/>
<bean id="userDaoOracleImpl" name="oracle" class="com.xp.dao.impl.UserOracleImpl"/>
<bean id="userServiceImpl" name="userServiceImpl" class="com.xp.service.impl.UserServiceImpl">
<property name="userDao" ref="mysql"/>
bean>
beans>
public class MyTest {
public static void main(String[] args) {
// 获取ApplicationContext,拿到Spring的IoC容器
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
// 容器在手,天下我有,需要什么,就直接get什么
UserService userService = context.getBean("userServiceImpl", UserServiceImpl.class);
userService.getUser();
}
}
由上可以看出,我们创建对象不需要自己手动 new 一个对象出来,而是通过配置 Bean ,交由 Spring 的 IoC 容器去创建。并且创建哪个对象这是由用户决定的,用户不需要修改代码,只需要在 xml 配置文件中修改属性即可。
到了现在,我们彻底不用在程序中改动了,要实现不同的操作,只需要在 xml 配置文件中进行修改,所谓的 IoC ,一句话搞定:对象由 Spring 来创建,管理,装配!
实体类如下
public class User {
private String name;
public User(){
System.out.println("User 的无参构造");
}
public User(String name) {
this.name = name;
System.out.println("User 的有参构造");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
使用无参构造创建对象,(默认)
<bean id="user" name="user" class="com.xp.pojo.User">
<property name="name" value="xp"/>
bean>
要使用有参构造创建对象
下标赋值
<bean id="user" class="com.xp.pojo.User">
<constructor-arg index="0" value="java"/>
bean>
类型赋值
这种方法具有局限性,不适用有多个同类型的参数的实体类
<bean id="user" class="com.xp.pojo.User">
<constructor-arg type="java.lang.String" value="string"/>
bean>
参数名赋值
<bean id="user" class="com.xp.pojo.User">
<constructor-arg name="name" value="name"/>
bean>
在配置文件加载的时候,容器中管理的对象就已经初始化了
配置文件 applicationContext.xml 配置如下
<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="user1" class="com.xp.pojo.User">
<constructor-arg index="0" value="java"/>
bean>
<bean id="user2" class="com.xp.pojo.User">
<constructor-arg name="name" value="name"/>
bean>
beans>
测试类如下
public class MyTest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
User user = context.getBean("user1",User.class);
System.out.println(user.getName());
}
}
控制台输出如下
有参构造调用了两次,代表我们刚刚配置的两个 Bean 全部被 Spring 创建了
debug时参数
IoC 容器默认会创建、管理的所有 Bean 的单例对象
可以看出,只要是在配置文件中配置了 Bean 并交给Spring管理,则在配置文件加载时,容器中管理的对象就已经初始化了
<alias name="user1" alias="user"/>
<bean id="user2" class="com.xp.pojo.User" name="user"/>
一般用于团队开发使用,他可以将多个配置文件,导入合并为一个
假设:现在项目中有多个人开发,这三个人负责不同的类开发,不同类需要注册在不同的 Bean 中,我们可以利用 import 将所有人的 bean.xml 合并为一个总的
张三开发 bean1.xml
李四开发 bean2.xml
王五开发 bean3.xml
applicationContext.xml
<import resource="bean1.xml"/>
<import resource="bean2.xml"/>
<import resource="bean3.xml"/>
使用的时候,使用总的配置 applicationContext.xml 就可以了
上面第四点 4. IoC创建对象的方式 已经讲了
创建实体类
Student.java
@data
public class Student {
private String name;
private Address address;
private String[] books;
private List<String> hobbys;
private Map<String,String> card;
private Set<String> games;
private String wife;
private Properties info;
}
Address.java
@data
public class Address {
private String address;
}
配置 Bean
<beans>
<bean id="student" class="com.xp.pojo.Student">
<property name="name" value="xp"/>
<property ref="address" name="address"/>
<property name="books">
<array>
<value>红楼梦value>
<value>西游记value>
<value>水浒传value>
<value>三国演义value>
array>
property>
<property name="hobbys">
<list>
<value>听歌value>
<value>敲代码value>
<value>看电影value>
list>
property>
<property name="card">
<map>
<entry key="身份证" value="441622111111111111"/>
<entry key="银行卡" value="111111111111111111"/>
map>
property>
<property name="games">
<set>
<value>LOLvalue>
<value>DNFvalue>
<value>CFvalue>
set>
property>
<property name="wife">
<null/>
property>
<property name="info">
<props>
<prop key="学号">1740111111prop>
<prop key="年龄">22222222prop>
<prop key="性别">男prop>
props>
property>
bean>
<bean id="address" class="com.xp.pojo.Address">
<property name="address" value="广东省"/>
bean>
beans>
测试
public class MyTest2 {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
Student student = context.getBean("student", Student.class);
System.out.println(student.toString());
}
}
我们可以使用 p 命名空间和 c 命名空间进行注入
xmlns:p="http://www.springframework.org/schema/p"
<bean id="user" class="com.xp.pojo.User" p:name="p namespace"/>
public class MyTest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
User user = context.getBean("user",User.class);
System.out.println(user.getName());
}
}
xmlns:p="http://www.springframework.org/schema/p"
<bean id="user0" class="com.xp.pojo.User" c:name="c namespace"/>
public class MyTest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
User user = context.getBean("user0",User.class);
System.out.println(user.getName());
}
}
注意点:p 命名空间和 c 命名空间不能直接使用,需要导入 xml 约束
作用域 | 描述 |
---|---|
singleton(单例) | (默认)singleton(单例)的 Bean 作用域为每个 Spring IoC 容器的单个对象实例 |
prototype(原型) | 将单个 Bean 的作用域限定为任意数量的对象 |
request(请求) | 将单个 Bean 的作用域限定为单个 HTTP 请求的声明周期。也就是说,每个HTTP请求都有自己的 Bean 实例,这些实例是在单个 Bean 定义的基础上创建的。仅在可感知web的Spring应用程序上下文中有效。 |
session(会话) | 将单个 Bean 作用域限定为 HTTP Session 的生命周期。仅在可感知 web 的 Spring 应用程序上下文中有效。 |
application(应用) | 将单个 Bean 作用域限定为 ServletContext 的生命周期。仅在可感知 web 的 Spring 应用程序上下文中有效。 |
websocket(web套接字) | 将单个 Bean 作用域限定为 websocket 的生命周期。仅在可感知 web 的 Spring 应用程序上下文中有效。 |
官网原文
Spring 配置的 Bean 默认是单例模式
<bean id="user" class="com.xp.pojo.User" p:name="p namespace" scope="singleton"/>
测试
public class MyTest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
User user = context.getBean("user",User.class);
User user1 = context.getBean("user",User.class);
System.out.println(user == user1);
}
}
当 Bean 的作用域设置为单例模式(singleton)或默认不设置作用域时,通过 getBean() 方法获取的 Bean 一直是同一个对象,控制台输出为 true
每次从容器中 get 的时候,都会产生一个新对象
<bean id="user0" class="com.xp.pojo.User" c:name="c namespace" scope="prototype"/>
测试
public class MyTest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
User user = context.getBean("user0",User.class);
User user1 = context.getBean("user0",User.class);
System.out.println(user == user1);
}
}
当 Bean 的作用域设置为原型模式(prototype)时,每次调用 getBean() 方法获取的 Bean 都是不同的对象。控制台输出为 false。
这些只能在 web 开发中使用!
在Spring中有三种装配方式
一个人有两个宠物,宠物都有 shot 方法
创建实体类
/**
* 猫
*/
class Cat {
public void shot(){
System.out.println("miao~");
}
}
/**
* 狗
*/
class Dog {
public void shot(){
System.out.println("wang~");
}
}
/**
* 人
*/
public class People {
private String name;
private Cat cat;
private Dog dog;
public People() {
}
public People(String name, Cat cat) {
this.name = name;
this.cat = cat;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Cat getCat() {
return cat;
}
public void setCat(Cat cat) {
this.cat = cat;
}
public Dog getDog() {
return dog;
}
public void setDog(Dog dog) {
this.dog = dog;
}
@Override
public String toString() {
return "People{" +
"name='" + name + '\'' +
", cat=" + cat +
", dog=" + dog +
'}';
}
}
<bean id="dog" class="com.xp.pojo.Dog"/>
<bean id="cat" class="com.xp.pojo.Cat"/>
<bean id="person" class="com.xp.pojo.People" autowire="byName">
<property name="name" value="xp"/>
bean>
需要保证所有 Bean 的id唯一,并且这个 Bean 需要和自动注入的属性的 set 方法的值一致
<bean id="dog22" class="com.xp.pojo.Dog"/>
<bean id="cat11" class="com.xp.pojo.Cat"/>
<bean id="person" class="com.xp.pojo.People" autowire="byType">
<property name="name" value="xp"/>
bean>
需要保证所有 Bean 的 class 唯一,并且这个 Bean 需要和自动注入的属性的类型一致
jdk 1.5 开始支持注解,Spring 2.5 开始支持注解。
Are annotations better than XML for configuring Spring?
The introduction of annotation-based configuration raised the question of whether this approach is “better” than XML. The short answer is “it depends.” The long answer is that each approach has its pros and cons, and, usually, it is up to the developer to decide which strategy suits them better. Due to the way they are defined, annotations provide a lot of context in their declaration, leading to shorter and more concise configuration. However, XML excels at wiring up components without touching their source code or recompiling them. Some developers prefer having the wiring close to the source while others argue that annotated classes are no longer POJOs and, furthermore, that the configuration becomes decentralized and harder to control.
No matter the choice, Spring can accommodate both styles and even mix them together. It is worth pointing out that through its JavaConfig option, Spring lets annotations be used in a non-invasive way, without touching the target components source code and that, in terms of tooling, all configuration styles are supported by the Spring Tools for Eclipse.
在配置Spring时,注释是否比XML更好?
基于注释的配置的引入提出了一个问题,即这种方法是否优于 XML 。简而言之,这要视情况而定。长篇大论的回答是,每种方法都有其优点和缺点,通常由开发人员决定哪种策略更适合他们。由于它们的定义方式,注释在其声明中提供了大量上下文,从而使配置更短、更简洁。然而,XML擅长在不接触源代码或不重新编译它们的情况下连接组件。然而,XML擅长在不接触源代码或不重新编译它们的情况下连接组件。一些开发人员喜欢将连接放在接近源的地方,而另一些人则认为带注释的类不再是 pojo,而且配置变得分散,更难控制。
无论选择什么,Spring 都可以容纳两种样式,甚至可以将它们混合在一起。 值得指出的是,通过其 JavaConfig 选项,Spring 允许以非侵入方式使用批注,而无需接触目标组件的源代码,并且就工具而言,Spring Tools for Eclipse 支持所有配置样式。
<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.xp"/>
beans>
直接在属性上使用,也可在set方法上使用
使用 Autowired 我们可以不用编写 set 方法了,前提是你这个自动装配的属性 在 IoC(Spring)容器中存在,且符合 byName 名字
/**
* 人
*/
public class People {
private String name;
@Autowired
private Cat cat;
@Autowired
private Dog dog;
public Cat getCat() {
return cat;
}
public Dog getDog() {
return dog;
}
}
<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/>
<bean id="dog" class="com.xp.pojo.Dog"/>
<bean id="cat" class="com.xp.pojo.Cat"/>
<bean id="people" class="com.xp.pojo.People">
<property name="name" value="xp"/>
bean>
beans>
public class MyTest3 {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("beans2.xml");
People people = context.getBean("people", People.class);
people.getCat().shot();
people.getDog().shot();
}
}
科普
@Nullable 字段标记了这个注解,说明这个字段可以为null
进入 @Autowired 源码,里面有个 required 属性,默认为true。
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {
boolean required() default true;
}
如果显示定义了 Autowired 的 required 属性为 false,说明这个对象可以为 null,否则不允许为空
@Autowired(required = false)
private Cat cat;
@Autowired 和 @Qualifier(value=“xxx”) 结合使用
如果 @Autowired 自动装配的环境比较复杂,自动装配无法通过一个注解【@Autowired】完成的时候,我们可以使用 @Qualifier(value=“xxx”) 去配置
给实体类增加 @Qualifier(value=“xxx”) 注解
@Autowired
@Qualifier(value = "dog11")
private Dog dog;
xml 中 Bean 的配置
<context:annotation-config/>
<bean id="dog11" class="com.xp.pojo.Dog"/>
<bean id="cat" class="com.xp.pojo.Cat"/>
<bean id="people" class="com.xp.pojo.People">
<property name="name" value="xp"/>
bean>
@Resource(name = "cat11")
private Cat cat;
@Resource
private Dog dog;
@Resource 注解和 @Autowired 的区别
都是用来自动装配的,都可以放在属性字段上
@Autowired 默认通过 byType 的方式注入的,但当类型大于1时,会以 byName 的方式注入。必须保证该对象存在
@Resource 默认通过 byName 的方式注入,如果找不到名字,则通过 byType 注入。如果两个都找不到的情况下,就报错
在 Spring4 之后,要使用注解开发,必须要保证导入了 AOP 的依赖
在使用注解需要导入context约束,增加注解的支持
bean
@Component
public class User {
// java代码
}
属性如何注入
直接在属性上面添加 @value 注解
// 等价于
@Value("xp")
private String name;
@Value("admin")
private String account;
@Value("123")
private String password;
在 set 方法上面添加 @value 注解
// 等价于
@Value("xp")
public void setName(String name) {
this.name = name;
}
注:若使用 xml 配置 Bean,属性上面添加 @value 注解的优先级高于在 set 方法上添加 @value 注解。若使用配置类配置 Bean,则 set 方法上的@value 注解优先级高于在属性上面添加 @value
衍生的注解
@Component 有几个衍生的注解,我们在 web 开发中,会按照 mvc 三层架构分层
@Repository --> dao 层注解
@Repository
public class UserDao {
// java 代码
}
@Service --> service 层注解
@Service
public class UserService {
// java 代码
}
@Controller --> web 层注解
@Controller
public class UserController {
// java代码
}
这四个注解功能都是一样的,都是代表将某个类注册到 Spring 中,自动装配 Bean
自动装配
@Autowired:自动装配通过类型、名字
如果 Autowired 不能唯一自动装配上属性,则需要通过 @Qualifier(value=“xxx”)
@Nullable 字段标记了这个注解,说明这个字段可以为null
@Resource:自动装配通过名字、类型
作用域
@scope 设置作用域,相当于
// 相当于
@Scope("prototype")
public class User {
// java代码
}
小结
xml 与 注解:
xml 和注解的最佳时间:
我们下载完全不适用 Spring 的 xml 配置了,全权交给 Java 来做
JavaConfig 是Spring 的一个子项目,在 Spring4 之后,它成为了一个核心功能!
配置类
// 这个也会被Spring容器托管,注册到容器中,因为他本来就是一个@Componment
// Configuration代表这是一个配置类,就和我们之前看得applicationContext.xml
@Configuration
public class SpringConfig {
// 注册一个Bean,就相当于我们之前写的一个bean标签
// 这个方法的名字,就相当于bean标签中的id属性
// 这个方法的返回值,就相当于bean标签中的class属性
@Bean
public User getUser(){
return new User();
}
}
测试
public class ConfigTest {
public static void main(String[] args) {
// 如果完全使用了配置类的方式去做,我们就只能通过 ApplicationContext 上下问来获取容器,通过配置类的class对象加载
ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
User user = context.getBean("getUser", User.class);
System.out.println(user.toString());
}
}
为什么要学习代理模式?
因为这就是 Spring AOC 的底层!
代理模式的分类:
角色分析:
代码步骤:
接口
public interface Service {
void rent();
}
真实角色
public class Host implements Service {
@Override
public void rent() {
System.out.println("房东出租房子");
}
}
代理角色
public class Proxy implements Service {
private Host host;
public Proxy() {
}
public Proxy(Host host) {
this.host = host;
}
@Override
public void rent() {
host.rent();
}
// 看房
public void seeHouse(){
System.out.println("中介带客户看房");
}
// 收中介费
public void fare(){
System.out.println("收中介费");
}
// 签合同
public void sign(){
System.out.println("签合同");
}
}
客户端访问代理角色
public class Client {
public static void main(String[] args) {
// 房东要出租房子
Host host = new Host();
// 代理,中介帮房东出租房子,但是,代理角色一般会有一些附属操作
Proxy proxy = new Proxy(host);
// 不需要面对房东,直接找中介即可
proxy.rent();
}
}
代理模式的好处:
代理模式的缺点:
在以前的开发中,我们是进行一个纵向开发,从 dao 层到 service 层,从 service 层到 controller 层,从 controller 到前端页面。当我们开发完成后,需要加入新的需求。如果我们继续按照纵向开发的方式去修改原来的代码,则会非常繁琐,代码量巨大。而且,**修改原来的代码是公司的大忌!**这时候使用 AOP 进行切面编程的横向开发就显得非常友好了。我们只需要横向增加一个代理,将增加的功能交由代理去完成。
需要了解两个类:Proxy(代理),InvocationHandler(调用处理程序)
接口
public interface Service {
void rent();
}
动态代理类
public class ProxyInvocationHandler implements InvocationHandler {
// 被代理的接口
private Service service;
public void setService(Service service) {
this.service = service;
}
// 生成得到代理类
public Object getProxy() {
return Proxy.newProxyInstance(this.getClass().getClassLoader(), service.getClass().getInterfaces(), this);
}
// 处理代理实例,并返回结果
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
seeHouse();
// 动态代理的本质,就是使用反射机制实现
Object result = method.invoke(service,args);
fare();
return result;
}
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();
// 代理角色:现在没有
ProxyInvocationHandler pih = new ProxyInvocationHandler();
// 通过调用程序处理角色来处理我们要调用的接口对象
pih.setService(host);
Service proxy = (Service) pih.getProxy();// 这里的proxy就是动态生成的,我们并没有写
proxy.rent();
}
}
根据上面说写的,我们可以将动态代理的类改成一个通用模板。只需将聚合的的类型改成 Object 类型
public class ProxyInvocationHandler implements InvocationHandler {
// 被代理的接口
private Object proxy;
public void setService(Object proxy) {
this.proxy = proxy;
}
// 生成得到代理类
public Object getProxy() {
return Proxy.newProxyInstance(this.getClass().getClassLoader(), proxy.getClass().getInterfaces(), this);
}
// 处理代理实例,并返回结果
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
seeHouse();
// 动态代理的本质,就是使用反射机制实现
Object result = method.invoke(proxy,args);
fare();
return result;
}
private void seeHouse(){
System.out.println("中介带看房");
}
private void fare(){
System.out.println("收中介费");
}
}
动态代理的好处:
AOP(Aspect Oriented Programming) 意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。AOP 是 OOP 的延续,是软件开发中的一个热点,也是 Spring 框架中的一个重要内容,是函数式编程的一种衍生泛型。利用 AOP 可以兑业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
SpringAOP 中,通过 Advice 定义横切逻辑,Spring 中支持5中类型的 Advice:
通知类型 | 连接点 | 实现接口 |
---|---|---|
前置通知 | 方法前 | org.springframework.aop.MethodBeforeAdvice |
后置通知 | 方法后 | org.springframework.aop.AfterReturningAdvice |
环境通知 | 方法前后 | org.aopalliance.intercept.MethodInterceptor |
异常抛出通知 | 方法抛出异常 | org.springframework.aop.ThrowsAdvice |
引介通知 | 类中增加新的方法属性 | org.springframework.aop.IntroductionInterceptor |
即 AOP 在不改变原有代码得情况下,去增加新的功能
使用AOP前,需要导入织入依赖包
<dependency>
<groupId>org.aspectjgroupId>
<artifactId>aspectjweaverartifactId>
<version>1.9.5version>
dependency>
方式一:使用Spring的API接口实现AOP
创建接口
public interface UserService {
void add();
void delete();
void update();
void select();
}
创建实现类
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 select() {
System.out.println("查询");
}
}
创建切面
public class BeforeLog implements MethodBeforeAdvice {
/**
*
* @param method 需要执行得目表对象的方法
* @param objects 参数
* @param o 目标对象
* @throws Throwable
*/
@Override
public void before(Method method, Object[] objects, Object o) throws Throwable {
System.out.println(o.getClass().getName()+" 的 "+method.getName()+" 被执行了");
}
}
public class AfterLog implements AfterReturningAdvice {
// o : 返回值
@Override
public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable {
System.out.println(o1.getClass().getName()+" 的 "+method.getName()+" 被执行了,返回结果为"+o);
}
}
注册 Bean 和 配置 AOP
<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 id="afterLog" class="com.xp.log.BeforeLog"/>
<bean id="beforeLog" class="com.xp.log.BeforeLog"/>
<bean id="userService" class="com.xp.service.UserServiceImpl"/>
<aop:config>
<aop:pointcut id="pointCut" expression="execution(* com.xp.service.UserServiceImpl.*(..))"/>
<aop:advisor advice-ref="afterLog" pointcut-ref="pointCut"/>
<aop:advisor advice-ref="beforeLog" pointcut-ref="pointCut"/>
aop:config>
beans>
测试
public class Test {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
// 代理的是接口,所以返回的是接口而不是实现类
UserService userService = context.getBean("userService", UserService.class);
userService.add();
}
}
方式二:自定义实现AOP的类
和方式一不同的地方
创建切入面的类
public class DiyPointCut {
public void before(){
System.out.println("---------方法执行前---------");
}
public void after(){
System.out.println("---------方法执行后---------");
}
}
<bean id="diy" class="com.xp.diy.DiyPointCut"/>
<aop:config>
<aop:aspect ref="diy">
<aop:pointcut id="pointcut" expression="execution(* com.xp.service.UserServiceImpl.*(..))"/>
<aop:before method="before" pointcut-ref="pointcut"/>
<aop:after method="after" pointcut-ref="pointcut"/>
aop:aspect>
aop:config>
方式三:使用注解方式实现 AOP
和前两种方式不同的地方
注册 Bean 和配置 aop
<bean id="annotationPointCut" class="com.xp.annotation.AnnotationPointCut"/>
<aop:aspectj-autoproxy proxy-target-class="false"/>
增加注解
@Aspect // 标注这个类是一个切面
public class AnnotationPointCut {
@Before("execution(* com.xp.service.UserServiceImpl.*(..))")
public void before(){
System.out.println("``````````方法执行前``````````");
}
@After("execution(* com.xp.service.UserServiceImpl.*(..))")
public void after(){
System.out.println("``````````方法执行后``````````");
}
@Around("execution(* com.xp.service.UserServiceImpl.*(..))")
public void around(ProceedingJoinPoint procedingJoinPoint) throws Throwable {
System.out.println("环绕前");
Object proceed = procedingJoinPoint.proceed();
System.out.println("环绕后");
System.out.println(proceed);
}
}
导入相关 jar包
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>LATESTversion>
dependency>
<dependency>
<groupId>org.mybatisgroupId>
<artifactId>mybatis-springartifactId>
<version>2.0.3version>
dependency>
编写实体类
// 使用了lombok插件,也可以自己手动写getter,setter和toString方法
@Data
public class User {
private Integer id;
private String name;
private String password;
}
编写核心配置文件
<configuration>
<properties resource="db.properties"/>
<typeAliases>
<package name="com.xp.pojo"/>
typeAliases>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
dataSource>
environment>
environments>
<mappers>
<mapper resource="mapper/UserMapper.xml"/>
mappers>
configuration>
编写接口
public interface UserMapper {
User getUserById(int id);
}
编写 mapper.xml
<mapper namespace="com.xp.mapper.UserMapper">
<select id="getUserById" resultType="user">
select * from user where id = #{id};
select>
mapper>
测试
public class MyBatisSpringTest {
public static void main(String[] args) {
String resources = "mybatis-config.xml";
try {
// 读取核心配置文件输入流
InputStream in = Resources.getResourceAsStream(resources);
// 通过 SqlSessionFactoryBuilder 获取 SqlSessionFactory对象
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(in);
// 获取SqlSession对象
SqlSession sqlSession = sqlSessionFactory.openSession();
// 获取mapper,并执行sql语句
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.getUserById(2);
System.out.println(user);
// 关闭sqlSession
sqlSession.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
什么是 MyBatis-Spring?
MyBatis-Spring 会帮助你将 MyBatis 代码无缝地整合到 Spring中。它将允许MyBatis 参与到 Spring 的事务管理之中,创建映射器 mapper 和 sqlSession
并注入到 Bean 中,以及将 MyBatis 的异常转换为 Spring 的 DataAccessException
。最终,可以做到应用代码不依赖于 MyBatis,Spring 或 MyBatis-Spring。
官网
http://mybatis.org/spring/zh/index.html
动机
Spring 2.0 只支持 iBatis 2.0。那么我们就想 MyBatis3 的支持添加到 Spring 3.0 中(参见 Spring Jira 中的问题)。不幸的是,Spring 3.0 的开发在 MyBatis 3.0官方发布前就结束了。由于 SPringle 开发团队不想发布一个基于未发布版的 MyBatis 的整合支持,如果要获得Spring 官方的支持,只能等待下一次的发布了。基于 Spring 中对 MyBatis 提供支持的兴趣,MyBatis社区认为,应该开始召集又兴趣参与其中的贡献者们,将对Spring 的集成作为 MyBatis 的一个社区子项目
基础知识
在开始使用 MyBatis-Spring 之前,你需要线熟悉 Spring 和 MyBatis 这两个框架和它们的术语。
MyBatis-Spring 需要以下版本:
MyBatis-Spring | MyBatis | Spring框架 | Spring Batch | Java |
---|---|---|---|---|
2.0 | 3.5+ | 5.0+ | 4.0+ | Java 8+ |
1.3 | 3.4+ | 3.2.2+ | 2.1+ | Java 6+ |
编写数据源配置
<bean id="datasource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/study?useTimezone=true&serverTimezone=GMT%2b8&useUnicode=true&characterEncoding=UTF-8&useSSL=false"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
bean>
SqlSessionFactory
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="datasource"/>
<property name="configLocation" value="classpath:mybatis-config.xml"/>
<property name="mapperLocations" value="classpath:mapper/UserMapper.xml"/>
bean>
SqlSessionTemplate
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg index="0" ref="sqlSessionFactory"/>
bean>
绑定mapper
<bean id="userMapper" class="com.xp.mapper.impl.UserMapperImpl">
<property name="sqlSession" ref="sqlSession"/>
bean>
给接口加实现类
public class UserMapperImpl implements UserMapper {
// 在以前,我们所有操作都是使用sqlSession来执行,现在都使用SqlSessionTemplate
private SqlSessionTemplate sqlSession;
public void setSqlSession(SqlSessionTemplate sqlSession) {
this.sqlSession = sqlSession;
}
@Override
public User getUserById(int id) {
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
return mapper.getUserById(id);
}
}
将自己写的实现类注入到 Spring 中,测试
@Test
public void getUserById(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserMapper userMapper = context.getBean("userMapper", UserMapper.class);
User user = userMapper.getUserById(3);
System.out.println(user);
}
在MyBatis中,你可以使用 SqlSessionFactory
来创建 SqlSession
。一旦你获得一个 session 之后,你可以使用它来执行映射的语句,提交或回滚连接,最后,当不再需要它的时候,你可以关闭 session。使用 MyBatis-Spring 之后,你不再需要直接使用 SqlSessionFactory
了,因为你的 bean 可以被注入一个**线程安全**的 Sqlsession
,它能基于SPringle的事务配置来自动提交、回滚、关闭 session。
SqlSessionTemplate
SqlSessionTemplate
是 MyBatis-Spring 的核心,作为 SqlSession
的一个实现,这意味着可以使用它无缝代替你代码中已经使用的 Sqlsession
。 SqlSessionTemplate
是线程安全的,可以被多个 Dao 或映射器共享使用。
当调用 SQL 方法时(包括由 getMapper()
方法返回的映射器中的方法),SqlSessionTemplate
将会保证使用的 SqlSession
与当前 Spring 的事务相关。此外,它管理 session 的生命周期,包含必要的关闭、提交或回滚操作。另外,它也负责将 MyBatis 的异常翻译成 Spring 中的 DataAccessExceptions
。
由于模板可以参与到 Spring 的事务管理中,并且由于其是线程安全的,可以供多个映射器类使用,你应该总是用 SqlSessionTmplate
来替换 MyBatis 默认的 DefaultSqlSession
实现。在同一应用程序中的不同类之间混杂使用可能会引起数据一致性的问题。
可以使用 SqlSessionFactory
作为构造方法的参数来创建 SqlSessionTemplate
对象
事务的ACID原则:
思考:
为什么需要事务?
事务管理配置
<bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
bean>
<tx:advice id="txAdvice" transaction-manager="dataSourceTransactionManager">
<tx:attributes>
<tx:method name="selectUser" read-only="true"/>
<tx:method name="*" propagation="REQUIRED"/>
tx:attributes>
tx:advice>
<aop:config>
<aop:pointcut id="txPoint" expression="execution(* com.xp.mapper.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPoint"/>
aop:config>