1、Spring
1.1、简介
- Spring:给软件行业带来了春天!
- 2002年,首次推出了Spring框架的雏形:interface 21框架
- 2004年3月24号,Spring框架以interface21为基础,发布了正式版本1.0版本
- Rod Johnson,Spring framework创始人
- spring理念:使现有的技术更加容易使用,本身是一个大杂烩,整合了现有的技术框架!
通过切面和模版减少样板式代码
- SSH:Struct2 + Spring + Hibernate
- SSM:SpringMVC+Spring+Mybatis
下载地址:https://repo.spring.io/release/org/springframework/spring/
Github:https://github.com/spring-projects/spring-framework
<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.9.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.9.RELEASE</version>
</dependency>
1.2、优点
- Spring是一个开源的免费的框架(容器)!
- Spring是一个轻量级的、非入侵式的框架!
- 控制反转,面向切面编程(AOP)
- 支持事务的处理,对框架整合的支持
总结:Spring就是一个轻量级的控制反转(IOC)和面向切面编程(AOP)的框架
1.3、Spring的组成
1.4、拓展
Spring官网介绍:现代化的java开发!说白了就是基于spring的开发
- Spring Boot:
- 一个快速开发的脚手架。
- 基于Spring Boot可以快速的开发单个微服务
- 约定大于配置!
- Spring Cloud
- SpringCloud是基于SpringBoot实现的。
目前大多数公司都在使用SpringBoot进行快速开发,学习SpringBoot的前提,需要完全掌握Spring及SpringMVC!承上启下的作用!
弊端:发展了太久之后,违背了原来的理念!配置十分繁琐,人称:“配置地狱”
2、IOC理论的推导
- UserDao接口
- UserDaoImpl实现类
- UserService业务接口
- UserServiceImpl实现类
在之前的业务中,用户的需求可能会影响我们原来的代码,我们需要根据用户的需求去修改原代码!
假设程序代码量十分大,修改一次代码的成本价十分昂贵!
例如,如果我们需要实现从Oracle数据库获取数据而不是Mysql数据库获取数据,那么按照原来的代码接口,我们不但需要去创建一个Oracle的Dao接口还需要在Service的实现类中new一个Oracle的实现类,最后再到Servlet层修改代码,这样的话需要修改的代码量非常大!!
解决方法:
我们使用一个set接口实现
private UserDao userDao;
//利用set进行动态实现值的注入!
public void setUserDao(userDao userDao){
this.userDao = userDao;
}
- 之前,程序是主动创建对象!控制权在程序员手上
- 使用set注入后,程序不再具有主动性,而是变成了被动的接受对象。
那么我们去测试类实现一下这个方法
public class myTest {
public static void main(String[] args) {
UserService userService = new UserServiceImpl();
//根据需求创建Dao接口
((UserServiceImpl) userService).setUserDao(new MysqlDaoImpl());
userService.getUser();
}
}
这种思想,从本质上解决了问题,程序员不用再去管理对象的创建了,系统的耦合性大大降低,可以更加专注在业务的实现上。这是IOC的原型!
移交主动权(程序—->用户)
IOC的本质
对象的创建交给外部容器完成,这就叫做控制反转
控制反转IoC(Inversion of Control),是一种设计思想,DI(依赖注入)是实现IoC的一种方法,也有人认为DI只是IoC的另一种说法。没有IoC的程序中 , 我们使用面向对象编程 , 对象的创建与对象间的依赖关系完全硬编码在程序中,对象的创建由程序自己控制,控制反转后将对象的创建转移给第三方,个人认为所谓控制反转就是:获得依赖对象的方式反转了。
采用XML方式配置Bean的时候,Bean的定义信息是和实现分离的,而采用注解的方式可以把两者合为一体,Bean的定义信息直接以注解的形式定义在实现类中,从而达到了零配置的目的。
控制反转是一种通过描述(XML或注解)并通过第三方去生产或获取特定对象的方式。在Spring中实现控制反转的是IoC容器,其实现方法是依赖注入(Dependency Injection,DI)。
3、HelloSpring
Hello.java
public class Hello {
private String str;
public String getStr() {
return str;
}
public void setStr(String str) {
this.str = str;
}
@Override
public String toString() {
return "Hello{" +
"str='" + str + '\'' +
'}';
}
}
beans.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--使用Spring来创建对象,在Spring中这些都成为Bean
类型 变量名 = new 类型();
Hello hello = new Hello();
id = 变量名
class = new的对象
property 相当于给对象中的属性设置一个值
bean = 对象 new Hello();
-->
<bean id="hello" class="com.roderick.pojo.Hello">
<property name="str" value="Spring"/>
</bean>
</beans>
myTest.java
public class myTest {
public static void main(String[] args) {
//获取Spring的上下文对象
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
//我们的对象都在Spring中管理了,我们要使用,直接去里面取出来就可以!
Hello hello = (Hello) context.getBean("hello");
System.out.println(hello.toString());
}
}
输出:
Hello{str='Spring'}
思考:
- Hello 对象是谁创建的 ?
- hello 对象是由Spring创建的
- Hello 对象的属性是怎么设置的 ?
- hello 对象的属性是由Spring容器设置的
这个过程就叫控制反转 :
- 控制 : 谁来控制对象的创建 , 传统应用程序的对象是由程序本身控制创建的 , 使用Spring后 , 对象是由Spring来创建的
- 反转 : 程序本身不创建对象 , 而变成被动的接收对象 .
依赖注入 : 就是利用set方法来进行注入的.
IOC是一种编程思想,由主动的编程变成被动的接收
到了现在,我们彻底不用在程序中去改动了,要实现不同的操作,只需要在xml配置文件中进行修改,所谓的IOC,就是:对象由Spring来创建,管理,装配!
4、IOC创建对象的方式
使用无参构造创建对象,默认!
<bean id="hello" class="com.roderick.pojo.Hello"> <property name="name" value="无参构造"/> </bean>
假设我们要使用有参构造方法
下标赋值(例如有参构造函数中有多个变量,按照顺序下标从0开始
<!--第一种,下标赋值--> <bean id="user" class="com.roderick.pojo.User"> <constructor-arg index="0" value="雨落2"/> </bean>
类型赋值(不建议使用)
<!--第二种,类型赋值--> <bean id="user" class="com.roderick.pojo.User"> <constructor-arg type="java.lang.String" value="yuluo"/> </bean>
直接通过参数名赋值
<bean id="user" class="com.roderick.pojo.User"> <constructor-arg name="name" value="name赋值"/> </bean>
beans.xml
<bean id="user" class="com.roderick.pojo.User">
<constructor-arg name="name" value="name赋值"/>
</bean>
<bean id="user2" class="com.roderick.pojo.UserT">
</bean>
public class myTest {
public static void main(String[] args) {
//Spring容器,
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
User user = (User) context.getBean("user");
User user2= (User) context.getBean("user");
user.show();
System.out.println(user==user2);
}
}
输出结果:
userT被创建了
User:name赋值有惨构造
true
- 这里获取Spring容器后,只获取了user的bean对象而没有获取userT的bean对象
- 但是在输出结果中,userT的无参构造方法也被调用了
- 并且创建两次userBean,他们进行==判断后返回的结果是true
- 说明在获取Spring容器后,内存中就只有一个userBean提供获取【单例模式】
总结:在配置文件加载的时候,容器中管理的对象就已经初始化了!【单例模式的对象创建时间6.4】
5、Spring配置
5.1、别名
<bean id="user" class="com.roderick.pojo.User">
<constructor-arg name="name" value="name赋值"/>
</bean>
<alias name="user" alias="NickName"/>
User user = (User) context.getBean("user");
User user2= (User) context.getBean("NickName");
System.out.println(user==user2);
输出结果:true
5.2、Bean的配置
<!--
id:bean的唯一标识符,也就是相当于对象名
class:bean对象所对应的全限定名: 包名+类型
name:相当于别名,可以同时取多个别名
-->
<bean id="userT" class="com.roderick.pojo.UserT" name="user2,u2,userT2">
<property name="name" value=":)"/>
</bean>
5.3、import
import一般用于团队开发,可以将多个配置文件,导入合并为一个
- 假设,项目中有多个成员进行开发,不同成员使用了多个不同的bean.xml
- 我们可以使用import,将所有beans.xml合并到applicationContext.xml
- 最后我们使用applicationContext即可
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<import resource="beans.xml"/>
<import resource="beans2.xml"/>
....
</beans>
6、依赖注入
Dependency Injection
对象之间的依赖关系,其实就是给对象上的属性赋值!因为对象上有其他对象的变量,因此存在了依赖。
6.1、构造器注入
Constructor
<bean id="user" class="com.roderick.pojo.User">
<constructor-arg name="name" value="name赋值"/>
</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; } }
真实测试对象
public class Student { private String name; private Address address; private String[] books; private List<String> hobby; private Map<String,String> card; private Set<String> games; private Properties info; private String wife; }
beans.xml ```xml <?xml version=”1.0” encoding=”UTF-8”?>
《java入门到入土》 《spring》 《mybatis》 打游戏 滑板 看电影 Lol CSGO speed 1 2 3 4
4. 测试类
```java
public class myTest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
Student student = (Student) context.getBean("student");
System.out.println(student.toString());
/*
Student{
name='雨落',
address=Address{address='浙江'},
books=[《java入门到入土》, 《spring》, 《mybatis》],
hobby=[打游戏, 滑板, 看电影],
card={身份证=2131231332232, 学生证=201805023333},
games=[Lol, CSGO, speed],
info={password=4, url=2, driver=1, username=3},
wife='null'
}
*/
}
}
6.3、拓展方式注入(c/p命名空间)
我们可以使用p命名空间和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 http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--p命名空间注入,可以直接注入属性值-->
<!--p命名空间对应set注入-->
<bean id="user" class="com.roderick.pojo.User"
p:name="雨落"
p:age="22"
/>
<!--c命名空间注入,可以通过构造器注入constructor-args-->
<!--c命名空间对应构造器注入-->
<bean id="user2" class="com.roderick.pojo.User"
c:age="222"
c:name="雨落"
/>
测试:
@Test
public void test2(){
ApplicationContext context = new ClassPathXmlApplicationContext("UserBean.xml");
// User user = (User) context.getBean("user");
User user = context.getBean("user2", User.class);
System.out.println(user);
}
注意点
不能直接使用,需要导入xml约束
xmlns:p="http://www.springframework.org/schema/p"
xmlns:c="http://www.springframework.org/schema/c"
6.4、Bean Scope
- singleton 单例模式(Spring默认机制
- 内存中只有一个对象被用于获取
- prototype 原型模式
- 每次从容器中get的时候都会产生一个新对象
- 其余的request、session、application、websocket 这些只能在web开发中使用
Tips:
scope属性除了控制对象是单例还是多例的,还控制着对象创建的时间!
- 当使用singleton【单例】的时候,对象在获得IOC容器之前就已经创建了
- 当使用prototype的时候,对象在使用的时候才创建
6.5、Bean创建细节总结
/**
* 1) 对象创建: 单例/多例
* scope="singleton", 默认值, 即 默认是单例 【service/dao/工具类】
* scope="prototype", 多例; 【Action对象】
*
* 2) 什么时候创建?
* scope="prototype" 在用到对象的时候,才创建对象。
* scope="singleton" 在启动(容器初始化之前), 就已经创建了bean,且整个应用只有一个。
* 3)是否延迟创建
* lazy-init="false" 默认为false, 不延迟创建,即在启动时候就创建对象
* lazy-init="true" 延迟初始化, 在用到对象的时候才创建对象
* (只对单例有效)
* 4) 创建对象之后,初始化/销毁
* init-method="init_user" 【对应对象的init_user方法,在对象创建之后执行 】
* destroy-method="destroy_user" 【在调用容器对象的destroy方法时候执行,(容器用实现类)】
*/
7、Bean的自动装配
- 自动装配是Spring满足Bean依赖的一种方式!
- Spring会在上下文中自动寻找,并自动给bean装配属性
在Spring中有三种自动装配的方式
- 在xml中显示的配置
- 在java中显示配置
- 隐式的自动装配bean【重要!】
7.1、测试
- 搭建环境
- 一个人的对象拥有两个宠物对象
7.2、ByName自动装配
bean id必须和set方法参数一致
<bean id="cat233" class="com.roderick.pojo.Cat"/>
<bean id="cat" class="com.roderick.pojo.Cat"/>
<bean id="dog233" class="com.roderick.pojo.Dog"/>
<bean id="dog" class="com.roderick.pojo.Dog"/>
<!--
byName原理:会自动在容器上下文查找,和自己对象set方法后面的值对应的beanID
-->
<bean id="people" class="com.roderick.pojo.People" autowire="byName">
<property name="name" value="雨落1"/>
</bean>
如上例子,通过byName自动装配,他会在配置文件中寻找id为cat和dog的bean来对应People类中的cat和dog变量
即使该配置文件中有多个class为Cat和Dog的bean,但是byName只会匹配id与cat和dog对应的bean
7.3、ByType自动装配
<!-- <bean id="cat233" class="com.roderick.pojo.Cat"/>-->
<bean id="cat" class="com.roderick.pojo.Cat"/>
<!-- <bean id="dog233" class="com.roderick.pojo.Dog"/>-->
<bean id="dog" class="com.roderick.pojo.Dog"/>
<!--
byType原理:会自动在容器上下文寻找,和自己对象属性类型相同的Bean
-->
<bean id="people" class="com.roderick.pojo.People" autowire="byType">
<property name="name" value="雨落1"/>
</bean>
byType自动装配会匹配与dog和cat相同class的bean,但是在配置文件中类型必须是唯一的
如果有两个class相同但是id不同的bean,就无法通过byType来自动装配
小结:
- byName的时候,需要保证所有bean的id唯一,并且beanID需要和自动注入的属性的set方法值一致
- byType的时候,需要保证Bean的class唯一,并且bean需要和自动注入的属性的类型一致!
7.4、使用注解实现自动装配
jdk1.5支持的注解,Spring2.5支持注解
要使用注解须知:
- 导入约束. context约束
配置注解的支持
<conntext:annotation-config/>
【重点】<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"> <context:annotation-config/> </beans>
@Autowired
直接在类中的属性上使用即可!也可以在set方式上使用!
使用Autowired 我们可以不用编写Set方法,前提是这个自动装配的属性在IOC(Spring)容器中存在,且符合byName。【比如上诉的Cat和Dog,他们都在Spring中被注册为bean】
科普:
@Nullable 字段标记了这个注解,说明这个字段可以为null
public @interface Autowired {
boolean required() default true;
}
测试代码:
//如果显示的定义了Autowired属性为false,说明这个对象可以为null。否则不允许为空
@Autowired(required = false)
private Cat cat;
@Autowired
private Dog dog;
- 如果有多个类型相同的bean
<bean id="dog233" class="com.roderick.pojo.Dog"/>
<bean id="dog" class="com.roderick.pojo.Dog"/>
- 那么可以在CLass中@Autowired下添加@Qualifier(value = xxx)指定一个唯一的bean对象
@Autowired
@Qualifier(value = "dog233")
private Dog dog;
拓展
- @Resource(name = xxx)注解
- 支持byName和byType查找
- 如果有多个bean名字不同他会寻找name符合的那个
- 如果所有bean名字都不同,他会寻找唯一的类型符合的那个(未加(name=xxx)的情况下)
@Resource(name = "cat233")
private Cat cat;
@Autowired
@Qualifier(value = "dog233")
private Dog dog;
private String name;
小结:
@Resource和@Autowired的区别
- 都是用来自动装配的,都可以放在属性字段上
- @Autowired通过byType的方式实现,而且必须要求这个对象存在
- @Resource默认通过byName方式存在,如果找不到名字,就通过byType实现!如果两个都找不到的情况下就报错!
- 执行顺序不同:
- @Autowired通过byType
- @Resource通过byName再通过byType
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.roderick.pojo"/>
<context:annotation-config/>
</beans>
bean
//等价于<bean id="user" class="com.roderick.pojo.User"/> @Component //组件 public class User { ... }
属性如何注入
//等价于<bean id="user" class="com.roderick.pojo.User"/> @Component //组件 public class User { public String name; //相当于bean标签里的<property name="name" value="Roderick"/> @Value("Roderick") public void setName(String name) { this.name = name; } }
衍生的注解
- @Component有几个衍生注解,我们在web开发中会按照mvc三层架构分层
- 扫描包后这些注解功能等价于@Component,都是将类注入到Spring容器中装配Bean
- dao 【@Repository】
- service 【@Service】
- controller 【@Controller】
- 自动装配 ```
@Autowired:自动装配通过类型、名字
如果Autowired不能唯一自动装配上属性,则需要通过@Qualifier(value = xx)Nullable 字段标记了这个注解,说明这个字段可以为null
@Resource : 自动装配通过名字、类型
可添加(name = xxx)指定唯一bean ```作用域
@Scope("singleton") //prototype... public class User { ... }
小结
- xml与注解:
- xml更加万能,适用于任何场合!维护简单方便
- 注解 不是自己的类使用不了,维护相对复杂
- xml与注解的最佳实践:
- xml用来管理bean
- 注解只负责完成属性的注入
- 我们在使用的过程中,只需要注意个问题:要让注解生效,必须要开启注解支持以及包的扫描
<!--指定要扫描的包,这个包下的注解就会生效--> <context:component-scan base-package="com.roderick.pojo"/> <context:annotation-config/>
- xml与注解:
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;
}
toString...
}
- 配置文件
//相当于<beans>... </beans>标签
@Configuration //也会被Spring容器托管,注册到容器中,因为他本身就带有@Component
@ComponentScan("com.roderick.pojo")
@Import(myConfig2.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 getUser = context.getBean("user", User.class);
System.out.println(getUser.getName());
}
}
总结:
- 使用Java方式配置有两种途径
- 一是创建一个Config.java,在里面添加@Bean的方法获取对象
- 二是在User类中加上@Component注解,然后在Config.java中加上扫描包的注解@ComponentScan(“com.roderick.pojo”)
- 这种纯java的配置方式,在SpringBoot中随处可见!
10、代理模式
为什么要学习代理模式?因为这就是SpringAOP的底层
代理模式的分类:
- 静态代理
- 动态代理
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(); fee(); } //看房 public void seeHouse(){ System.out.println("中介带你看房"); } //收中介费 public void fee(){ System.out.println("收中介费"); } }
客户端访问代理角色
public class Client { public static void main(String[] args) { //房东要出租房子 Host host=new Host(); //代理(中介帮房东租房子)(代理角色一般会有一些附属操作) Proxy proxy = new Proxy(host); //不用面对房东,直接找中介租房即可 proxy.rent(); } }
优缺点
代理模式的好处:
- 可以使真实角色的操作更加纯粹!不用去关注一些公共的业务
- 公业业务就交给了代理角色!实现了业务的分工
- 公共业务发生扩展,方便集中管理!
缺点:
- 一个真实角色就会产生一个代理角色;代码量会翻倍,开发效率会变低
详解
聊聊aop
代码:
public interface UserService {
public void add();
public void delete();
public void update();
public void query();
}
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 query() {
System.out.println("query");
}
}
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 delete() {
log("delete");
userService.delete();
}
public void update() {
log("update");
userService.update();
}
public void query() {
log("query");
userService.query();
}
//日志方法
public void log(String msg){
System.out.println("[Debug]执行了:"+msg);
}
}
public class Client {
public static void main(String[] args) {
UserServiceImpl service = new UserServiceImpl();
UserServiceProxy proxy = new UserServiceProxy();
proxy.setUserService(service);
proxy.add();
}
}
10.2、动态代理
- 动态代理和静态代理角色一样
- 动态代理的代理类是动态生成的,不是我们直接写好的!
- 动态代理分为两大类:基于接口的动态代理,基于类的动态代理
- 基于接口——JDK动态代理 【这里使用】
- 基于类:cglib
- java字节码实现:JAVAssit
需要了解两个类: Proxy(代理)、InvocationHandler(调用处理程序)
InvocationHandler
java.lang.reflect. [Interface]
- InvocationHandler 是由代理实例的
Proxy
java.lang.reflect
- Proxy提供了一个静态方法用来创建动态代理类和实例
实现:
//用这个类,自动生成代理类!
public class ProxyInvocationHandler implements InvocationHandler {
//被代理的接口
private Object target;
public void setTarget(Object target) {
this.target = target;
}
//生成得到代理类
public Object getProxy() {
return Proxy.newProxyInstance(this.getClass().getClassLoader(),
target.getClass().getInterfaces(), this);
}
//处理代理实例并返回结果
//method是指在Client中使用的方法,args获取他传递的参数,proxy获取调用方法的代理类
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+"方法");
}
}
测试:
- 在测试类中用到了UserServiceImpl和UserService
- 他们的关系是,UserServiceImpl是实现UserService接口的实现类
- pih.getProxy方法就是根据真实角色实现的接口来得到代理类的!!
public class Client {
public static void main(String[] args) {
//真实角色
UserServiceImpl userService = new UserServiceImpl();
//代理处理类
ProxyInvocationHandler pih = new ProxyInvocationHandler();
//获得代理角色
pih.setTarget(userService); //设置要代理的对象
/*
通过pih.setTarget(userService)传入的userService设置target
*/
//动态生成代理类
UserService proxy = (UserService) pih.getProxy();
// return Proxy.newProxyInstance(this.getClass().getClassLoader(),target.getClass().getInterfaces(), this);
// pih中的getProxy方法return类上面的代码,其中参数通过target.getClass().getInterfaces()获取到被代理的接口
proxy.add();
}
}
动态代理的好处
- 可以使真实角色的操作更加纯粹!不用去关注一些公共的业务
- 公业业务就交给了代理角色!实现了业务的分工
- 公共业务发生扩展,方便集中管理!
- 一个动态代理类代理的是一个接口,一般就对应的一类业务
- 一个动态代理类可以代理多个类,只要是实现了同一个接口即可
11、AOP
11.1、什么是AOP
AOP可以简单理解为:在执行某些代码前,执行另外的代码
11.2、Aop在Spring中的作用
提供声明式事务;允许用户自定义切面
11.3、使用Spring实现Aop
【重点】使用AOP织入,需要导入一个依赖包
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
- 方式一:使用Spring的API接口【主要是SpringAPI接口实现
切入方法
import org.springframework.aop.MethodBeforeAdvice;
public class Log implements MethodBeforeAdvice {
//method:要执行的目标对象的方法
//args:参数
//target:目标对象
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("【log】"+target.getClass().getName()+"的"+method.getName()+"被执行了");
}
}
import org.springframework.aop.AfterReturningAdvice;
import java.lang.reflect.Method;
public class AfterLog implements AfterReturningAdvice {
//returnValue: 返回值
public void afterReturning(Object returnValue, Method method, Object[] objects, Object o1) throws Throwable {
System.out.println("【AfterLog】执行了"+method.getName()+"方法,返回结果为:"+returnValue);
}
}
applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--注册bean-->
<bean id="userServiceImpl" class="com.roderick.service.UserServiceImpl"/>
<bean id="log" class="com.roderick.log.Log"/>
<bean id="afterLog" class="com.roderick.log.AfterLog"/>
<!--方式一:使用衍生Spring API接口-->
<!--配置aop:需要导入aop的约束-->
<aop:config>
<!--切入点: expression:表达式
execution(要执行的位置! * * * * *)->修饰词 返回值 类名 方法名 参数
-->
<aop:pointcut id="pointcut" expression="execution(* com.roderick.service.UserServiceImpl.*(..))"/>
<!--执行环绕增强!-->
<aop:advisor advice-ref="log" pointcut-ref="pointcut"/>
<aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
</aop:config>
</beans>
测试类
public class myText {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
//aop动态代理代理的是接口,
UserService userService = (UserService) context.getBean("userServiceImpl");
userService.add();
}
}
输出结果:
【log】com.roderick.service.UserServiceImpl的add被执行了
add...
【AfterLog】执行了add方法,返回结果为:null
- 方式二:用自定义类来实现aop【主要是切面定义
public class DiyPointCut {
public void before(){
System.out.println("=====方法执行前=====");
}
public void after(){
System.out.println("=====方法执行后=====");
}
}
<!--方式二:自定义类-->
<bean id="diy" class="com.roderick.diy.DiyPointCut"/>
<aop:config>
<!--自定义切面: ref 要引用的类-->
<aop:aspect ref="diy">
<!--切入点pointcut (需要切入的位置)-->
<aop:pointcut id="point" expression="execution(* com.roderick.service.UserServiceImpl.*(..))"/>
<!--通知(执行的方法)-->
<aop:before method="before" pointcut-ref="point"/>
<aop:after method="after" pointcut-ref="point"/>
</aop:aspect>
</aop:config>
- 方式三:使用注解实现
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
//方式三: 使用注解方式实现AOP
@Aspect //标注这个类为一个切面
public class AnnotationPointCut {
@Before("execution(* com.roderick.service.UserServiceImpl.*(..))")
public void before(){
System.out.println("===方法执行前===");
}
@After("execution(* com.roderick.service.UserServiceImpl.*(..))")
public void after(){
System.out.println("===方法执行后===");
}
//在环绕增强中,我们可以给定一个参数,代表我们要获取处理切入的点
@Around("execution(* com.roderick.service.UserServiceImpl.*(..))")
public void around(ProceedingJoinPoint jp) throws Throwable {
System.out.println("环绕前");
System.out.println(jp.getSignature());//获得签名 输出
//执行方法
Object proceed = jp.proceed();
System.out.println("环绕后");
}
}
<!--方式三-->
<bean id="annotationPointCut" class="com.roderick.diy.AnnotationPointCut"/>
<!--开启注解支持 JDK(默认)[proxy-target-class="false"] cglib[proxy-target-class="true"]-->
<aop:aspectj-autoproxy />
注意:
- 注解实现aop同样需要在配置文件中注册bean,并且需要开启注解支持
- 注解实现aop需要在切面类上面添加@Aspect注解
- 注解实现aop每一个方法都需要进行execution【缺点
小记:
execution的参数
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern)throws-pattern?)
- 修饰符匹配(modifier-pattern?)
- 返回值匹配(ret-type-pattern)可以为*表示任何返回值,全路径的类名等
- 类路径匹配(declaring-type-pattern?)
- 方法名匹配(name-pattern)可以指定方法名 或者 代表所有, set 代表以set开头的所有方法
- 参数匹配((param-pattern))可以指定具体的参数类型,多个参数间用“,”隔开,各个参数也可以用“”来表示匹配任意类型的参数,如(String)表示匹配一个String参数的方法;(,String) 表示匹配有两个参数的方法,第一个参数可以是任意类型,而第二个参数是String类型;可以用(..)表示零个或多个任意参数
- 异常类型匹配(throws-pattern?)
- 其中后面跟着“?”的是可选项
execution(public * com.roderick.service.UserService.*(..))
//表示匹配com.roderick.server.UserService中所有的公有方法
12、整合Mybatis
步骤
导入相关jar包
- junit
- mybatis
- mysql数据库
- spring相关
- aop织入
- Mybatis-spring【新知识】
<dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.20</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.2</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.2.9.RELEASE</version> </dependency> <!--Spring操作数据库还需要一个Spring-jdbc--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.1.9.RELEASE</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.4</version> </dependency> <!-- https://mvnrepository.com/artifact/org.mybatis/mybatis-spring --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>2.0.3</version> </dependency> </dependencies>
编写配置文件
- 测试
12.1、回忆mybatis
- 编写工具类
- 编写实体类
- 编写核心配置文件
- 编写接口
- 编写Mapper.xml
- 测试
12.2、Mybatis-Spring
MyBatis-Spring能将Mybatis代码无缝的整合到Spring中,允许Mybatis参与到Spring的事务管理中
方式一
- 编写数据源
<!--DataSource:使用Spring的数据源替换Mybatis的配置 (c3p0,dbcp druid)
这里使用Spring提供的JDBC org.springframework.jdbc.datasource
-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=false&useUnicode=true&characterEncoding=utf-8"/>
<property name="username" value="root"/>
<property name="password" value="luoyu-3-233"/>
</bean>
- sqlSessionFactory
<!--sqlSessionFactory-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<!--拓展:绑定Mybatis配置文件-->
<property name="configLocation" value="classpath:mybatis-config.xml"/>
<property name="mapperLocations" value="classpath:com/roderick/mapper/*.xml"/>
</bean>
- sqlSessionTemplate
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
<!--构造器注入sqlSessionFactory,因为没有set方法,只能构造器注入-->
<constructor-arg index="0" ref="sqlSessionFactory"/>
</bean>
- 给接口加实现类
public class UserMapperImpl implements UserMapper {
//相当于将原先测试类中new一个sqlsession然后getMapper的操作放到该实现类中了
//以前我们所有的操作都使用SqlSession来操作,现在都使用sqlSessionTemplate
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中【applicationContext.xml】
<!--通过该文件来注册Mapper实现类-->
<bean id="userMapper" class="com.roderick.mapper.UserMapperImpl">
<property name="sqlSession" ref="sqlSession"/>
</bean>
- get实现类的bean来执行方法
public static void main(String[] args) throws IOException {
ApplicationContext context = new ClassPathXmlApplicationContext("spring-dao.xml");
UserMapper userMapper = context.getBean("userMapper", UserMapper.class);
for (User user : userMapper.selectUser()) {
System.out.println(user);
}
}
注意:
Spring-dao.xml 数据源、sqlSessionFactory、sqlSessionTemplate的注入在此xml文件中进行【myBatis配置】
applicationContext.xml 【Mapper接口实现类的注册在此文件中进行】
<!--通过import方法导入dao的配置-->
<!--这样就可以实现dao的配置专注于配置mybatis-->
<import resource="spring-dao.xml"/>
<!-- <import resource="sprint-mvc.xml"/>-->
<!--通过该文件来注册Mapper-->
<bean id="userMapper" class="com.roderick.mapper.UserMapperImpl">
<property name="sqlSession" ref="sqlSession"/>
</bean>
方式二
SqlSessionDaoSupport
public class UserMapperImpl2 extends SqlSessionDaoSupport implements UserMapper {
public List<User> selectUser() {
// SqlSession sqlSession = getSqlSession();
// UserMapper mapper = sqlSession.getMapper(UserMapper.class);
// return mapper.selectUser();
return getSqlSession().getMapper(UserMapper.class).selectUser();
}
}
<bean id="userMapper2" class="com.roderick.mapper.UserMapperImpl2">
<!--该方法实现类没有sqlSession的成员变量,但是他继承的方法需要sqlSessionFactory-->
<property name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>
新建一个实现类实现Mapper接口,该实现类继承SqlSessionDaoSupport类
这样就不需要在实现类中创建一个私有成员sqlSession然后set他了,可以直接用getSqlSession()方法来得到,
但是SqlSessionDaoSupport需要SqlSessionFactory
所以在Spring配置文件中写该实现类的bean时要配置变量传入sqlSessionFactory
13、声明式事务
13.1、回顾事务
- 把一组业务当成一个业务来做;要么都成功,要么都失败
- 事务在项目开发中,十分的重要,涉及到数据的一致性问题,不能马虎!
- 确保完整性和一致性
事务的ACID原则:
- 原子性
- 一致性
- 隔离性
- 多个业务可能操作同一个资源,防止数据损坏
- 持久性
- 事务一旦提交,无论系统发生什么问题,结果都不会再被影响,被持久化的写到存储器中!
13.2、Spring中的事务管理
- 声明式事务: AOP
- 编程式事务:需要在代码中进行事务的管理(try catch回滚
步骤:
- 首先在Spring中配置声明式事务
<!--配置声明式事务-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
- 配置事务的通知(Advice) —>给哪些方法配置事务
<!--配置事务通知:-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<!--给哪些方法配置事务-->
<!--配置事务的传播特性:new-->
<tx:attributes>
<tx:method name="*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
- 注意要使用tx需要导入约束
xmlns:tx="http://www.springframework.org/schema/tx"
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
- 配置事务的切入
<!--配置事务切入-->
<aop:config>
<!--切入点-->
<aop:pointcut id="txPointCut" expression="execution(* com.roderick.mapper.*.*(..))"/>
<!--切入-->
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>
</aop:config>
为什么需要事务?
- 如果不配置事务,可能存在数据提交不一致的情况;
- 如果我们不在Spring中去配置声明式事务,我们就需要在代码中手动配置事务
- 事务在项目的开发中十分重要,涉及到数据的一致性和完整性问题,不容马虎。