参考
https://www.processon.com/view/5bbac847e4b015327afb89dc?fromnew=1
一、Spring概述
Spring简介
Spring : 春天 —->给软件行业带来了春天 2002年,Rod Jahnson首次推出了Spring框架雏形interface21框架。 2004年3月24日,Spring框架以interface21框架为基础,经过重新设计,发布了1.0正式版。 很难想象Rod Johnson的学历 , 他是悉尼大学的博士,然而他的专业不是计算机,而是音乐学。 Spring理念 : 使现有技术更加实用 . 本身就是一个大杂烩 , 整合现有的框架技术
官网 : http://spring.io/
官方下载地址 : https://repo.spring.io/libs-release-local/org/springframework/spring/
GitHub : https://github.com/spring-projects
Spring优点
1、Spring是一个开源免费的框架 (容器 )
2、Spring是一个轻量级的框架 , 非侵入式的
3、控制反转 IOC
4、面向切面 Aop
5、对事物的支持 , 对其他框架的支持
…
一句话概括:
Spring是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器(框架)
Spring体系结构
Spring 由 20 多个模块组成,它们可以分为:数据访问/集成(Data Access/Integration)、Web、面向切面编程(AOP, Aspects)、提供 JVM的代理(Instrumentation)、消息发送(Messaging)、核心容器(Core Container)和测试(Test)。
二、IOC容器
1、理解容器
控制反转(IOC,Inversion of Control),是一个概念,是一种思想。指将传统上由程序代码直接操控的对象调用权交给容器,通过容器来实现对象的装配和管理。控制反转就是对对象控制权的转移,从程序代码本身反转到了外部容器。通过容器实现对象的创建,属性赋值,依赖的管理。 IoC 是一个概念,是一种思想,其实现方式多种多样。当前比较流行 应用广泛的实现方式是依赖注入。
依赖:classA 类中含有 classB 的实例,在 classA 中调用 classB 的方法完成功能,即 classA对 classB 有依赖。
Ioc 的实现:
➢ 依赖注入:DI(Dependency Injection),程序代码不做定位查询,这些工作由容器自行完成。
依赖注入 DI 是指程序运行过程中,若需要调用另一个对象协助时,无须在代码中创建被调用者,而是依赖于外部容器,由外部容器创建后传递给程序。
Spring 的依赖注入对调用者与被调用者几乎没有任何要求,完全支持对象之间依赖关系的管理。
Spring 框架使用依赖注入(DI)实现 IoC。
Spring 容器是一个超级大工厂,负责创建、管理所有的 Java 对象,这些 Java 对象被称为 Bean。Spring 容器管理着容器中 Bean 之间的依赖关系,Spring 使用“依赖注入”的方式来管理 Bean 之间的依赖关系。使用 IoC 实现对象之间的解耦和。
2、快速入门-第一个Spring程序
1、创建 maven 项目
选择maven的 quickstart,创建一个标准的maven项目
2、pom.xml引入 maven 依赖
<!--spring依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<build>
<plugins>
<!--maven编译插件-->
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
3、定义接口与实体类
public interface SomeService {
void doSome();
}
public class SomeServiceImpl implements SomeService {
public SomeServiceImpl() {
super();
System.out.println("SomeServiceImpl无参数构造方法");
}
@Override
public void doSome() {
System.out.println("====业务方法doSome()===");
}
}
4、创建 Spring 配置文件
在 src/main/resources/目录现创建一个 xml 文件,文件名可以随意,但 Spring 建议的名称为 applicationContext.xml。
spring 配置中需要加入约束文件才能正常使用,约束文件是 xsd 扩展名。
<?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创建对象
声明bean , 就是告诉spring要创建某个类的对象
id:对象的自定义名称,唯一值。 spring通过这个名称找到对象
class:类的全限定名称(不能是接口,因为spring是反射机制创建对象,必须使用类)
spring就完成 SomeService someService = new SomeServiceImpl();
spring是把创建好的对象放入到map中, spring框架有一个map存放对象的。
springMap.put(id的值, 对象);
例如 springMap.put("someService", new SomeServiceImpl());
一个bean标签声明一个对象。
-->
<bean id="someService" class="com.bjpowernode.service.impl.SomeServiceImpl" />
<bean id="someService1" class="com.bjpowernode.service.impl.SomeServiceImpl" scope="prototype"/>
<!--
spring能创建一个非自定义类的对象吗, 创建一个存在的某个类的对象。
-->
<bean id="mydate" class="java.util.Date" />
</beans>
<!--
spring的配置文件
1.beans : 是根标签,spring把java对象成为bean。
2.spring-beans.xsd 是约束文件,和mybatis指定 dtd是一样的。
-->
5、定义测试类
String config="applicationContext.xml";
ApplicationContext ac = new ClassPathXmlApplicationContext(config);
//使用spring提供的方法, 获取容器中定义的对象的数量
int nums = ac.getBeanDefinitionCount();
System.out.println("容器中定义的对象数量:"+nums);
//容器中每个定义的对象的名称
String names [] = ac.getBeanDefinitionNames();
for(String name:names){
System.out.println(name);
}
//从容器中获取某个对象, 你要调用对象的方法
//getBean("配置文件中的bean的id值")
SomeService service = (SomeService) ac.getBean("someService");
service.doSome();
//获取一个非自定义的类对象
Date my = (Date) ac.getBean("mydate");
System.out.println("Date:"+my);
6、容器接口和实现类
ApplicationContext 用于加载 Spring 的配置文件,在程序中充当“容器”的角色
我们一般有两种方式获取容器对象,
1、ClassPathXmlApplicationContext(基于配置文件的)
新建一个配置文件(一般放在 resources/applicationContext.xml),在配置文件中通过标签(如
)向容器中注入实例对象
2、AnnotationConfigApplicationContext(基于配置类注解的)
新建一个配置类MyConfig.java(注解@Configuration),在配置类文件中通过注解向容器中注入实例对象
//配置类
@Configuration
public class MyBeanConfig {
@Bean
public Object myDate(){
return new Date();
}
}
//测试类
@Test
public void test05(){
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyBeanConfig.class);
Object myDate = context.getBean("myDate");
System.out.println(myDate.getClass().getName()); //java.util.Date
}
3、依赖注入DI-基于XML
DI(dependence injection)依赖注入
bean 实例在调用无参构造器创建对象后,就要对 bean 对象的属性进行初始化。初始化是由容器自动完成的,称为注入。
根据注入方式的不同,常用的有三种方式:构造器注入、Set方式注入(重点)、扩展方式注入
1、构造器注入
构造注入是指,在构造调用者实例的同时,完成被调用者的实例化。即,使用构造器设置依赖关系。
举例 1:
➢ name:指定参数名称。
➢ value:给参数注入的属性值
➢ index:指明该参数对应着构造器的第几个参数,从 0 开始。不过,该属性不要也行,但要注意,若参数类型相同,或之间有包含关系,则需要保证赋值顺序要与构造器中的参数顺序一致。
举例 2:
使用构造注入创建一个系统类 File 对象
2、Set方式注入
set 注入也叫设值注入,是指通过 setter 方法传入被调用者的实例。这种注入方式简单、直观,因而在 Spring 的依赖注入中大量使用。
1、创建Bean实体类
注意:实体类必须要有get set 方法(因为Spring容器DI时依赖setter注入的)
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Student {
private String name;
private Address address;
private String[] books;
private List<String> hobbys;
private Map<String,String> score;
private Set<String> games;
private String wife;
private Properties info;
}
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Address {
private String addr;
}
2、在xxx.xml中配置bean
<?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">
<bean id="address" class="org.xjt.di.domain.Address">
<property name="addr" value="shenzhen"></property>
</bean>
<bean id="student" class="org.xjt.di.domain.Student">
<property name="name" value="xiong"></property>
<property name="address" ref="address"></property>
<property name="books">
<array>
<value>语文</value>
<value>数学</value>
<value>英语</value>
</array>
</property>
<property name="hobbys">
<list>
<value>足球</value>
<value>篮球</value>
<value>羽毛球</value>
</list>
</property>
<property name="score">
<map>
<entry key="chinese" value="86"/>
<entry key="math" value="96"/>
<entry key="english" value="88"/>
</map>
</property>
<property name="games">
<set>
<value>三国杀</value>
<value>街头霸王</value>
<value>魂斗罗</value>
</set>
</property>
<property name="wife">
<null></null>
</property>
<property name="info">
<props>
<prop key="学号">2018112233</prop>
<prop key="班级">机械3班</prop>
<prop key="性别">男</prop>
</props>
</property>
</bean>
</beans>
3、扩展方式注入
p-命名空间
需要先导入约束:xmlns:p="http://www.springframework.org/schema/p"
<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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- set方法注入-->
<bean name="classic" class="com.example.ExampleBean">
<property name="email" value="someone@somewhere.com"/>
</bean>
<!-- p-命名空间方法注入(必须先导入依赖)-->
<bean name="p-namespace" class="com.example.ExampleBean"
p:email="someone@somewhere.com"/>
</beans>
c-命名空间
需要先导入约束:xmlns:c="http://www.springframework.org/schema/c"
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:c="http://www.springframework.org/schema/c"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="beanTwo" class="x.y.ThingTwo"/>
<bean id="beanThree" class="x.y.ThingThree"/>
<!-- traditional declaration with optional argument names -->
<bean id="beanOne" class="x.y.ThingOne">
<constructor-arg name="thingTwo" ref="beanTwo"/>
<constructor-arg name="thingThree" ref="beanThree"/>
<constructor-arg name="email" value="something@somewhere.com"/>
</bean>
<!-- 必须要有构造方法 -->
<!-- c-namespace declaration with argument names -->
<bean id="beanOne" class="x.y.ThingOne" c:thingTwo-ref="beanTwo"
c:thingThree-ref="beanThree" c:email="something@somewhere.com"/>
</beans>
4、依赖注入DI-基于注解(重要)
对于 DI 使用注解,将不再需要在 Spring 配置文件中声明 bean 实例。Spring 中使用注解需要在原有Spring 配置文件中配置组件扫描器,用于在指定的基本包中扫描注解
指定多个包的三种方式:
(1)使用多个 context:component-scan 指定不同的包路径
(2)指定 base-package 的值使用分隔符
分隔符可以使用逗号(,)分号(;)还可以使用空格,不建议使用空格。
(3)base-package 是指定到父包名
base-package 的值表是基本包,容器启动会扫描包及其子包中的注解,当然也会扫描到子包下级的子包。所以 base-package 可以指定一个父包就可以。
但不建议使用顶级的父包,扫描的路径比较多,导致容器启动时间变慢。指定到目标包和合适的。也就是注解所在包全路径。例如注解的类在 com.bjpowernode.beans 包中
定义Bean的注解_@_Component
@Component有几个衍生的注解,分别为@_Controller 、@Service、@_Repository
这4个注解功能是一样的,都是代表将某个类注入到Spring容器中 装配Bean
需要在bean类上使用注解@Component,该注解的 value 属性用于指定该 bean 的 id 值
另外@Component有几个衍生的注解 分别为:
➢ @Repository 用于对 DAO 实现类进行注解
➢ @Service 用于对 Service 实现类进行注解
➢ @Controller 用于对 Controller 实现类进行注解
这4个注解功能是一样的,都是代表将某个类注入到Spring容器中 装配Bean
但这三个注解还有其他的含义:
@Service创建业务层对象,业务层对象可以加入事务功能,
@Controller 注解创建的对象可以作为处理器接收用户的请求。
@Repository,@Service,@Controller 是对@Component 注解的细化,标注不同层的对象。即持久层对象,业务层对象,控制层对象。
简单类型属性注入_@_Value
需要在属性上使用注解@Value,该注解的 value 属性用于指定要注入的值。 使用该注解完成属性注入时,类中无需 setter。当然,若属性有 setter,则也可将其加
到 setter 上。
举例:
自动注入-byType
使用 byType 方式自动注入,要求:配置文件中被调用者 bean 的 class 属性指定的类,要与代码中调用者 bean 类的某引用类型属性类型同源。即要么相同,要么有 is-a 关系(子类,或是实现类)。但这样的同源的被调用 bean 只能有一个。多于一个,容器就不知该匹配哪一个了。
举例:
byType 自动注入@Autowired
需要在引用属性上使用注解@Autowired,该注解默认使用按类型自动装配 Bean 的方式。 使用该注解完成属性注入时,类中无需 setter。当然,若属性有 setter,则也可将其加到 setter 上。
举例:
自动注入-byName
当配置文件中被调用者 bean 的 id 值与代码中调用者 bean 类的属性名相同时,可使用byName 方式,让容器自动将被调用者 bean 注入给调用者 bean。容器是通过调用者的 bean类的属性名与配置文件的被调用者 bean 的 id 进行比较而实现自动注入的。
举例:
测试类:
byName自动注入@Autowired 与@Qualifier
需要在引用属性上联合使用注解@Autowired 与@Qualifier。@Qualifier 的 value 属性用于指定要匹配的 Bean 的 id 值。类中无需 set 方法,也可加到 set 方法上。
举例:
JDK自动注入注解-_@_Resource
Spring 提供了对 jdk 中@Resource 注解的支持。@Resource 注解既可以按名称匹配 Bean,也可以按类型匹配 Bean,默认是按名称注入。使用该注解,要求 JDK 必须是 6 及以上版本。@Resource 可在属性上,也可在 set 方法上。
(1)byType 注入引用类型属性
@Resource 注解若不带任何参数,采用默认按名称的方式注入,按名称不能注入 bean,则会按照类型进行 Bean 的匹配注入。
举例:
(2)byName 注入引用类型属性
@Resource 注解指定其 name 属性,则 name 的值即为按照名称进行匹配的 Bean 的 id。
举例:
注解与 XML 的对比
注解优点是:
- 方便
- 直观
高效(代码少,没有配置文件的书写那么复杂)。
其弊端也显而易见:以硬编码的方式写入到 Java 代码中,修改是需要重新编译代码的。
XML 方式优点是:
- 配置和代码是分离的
- 在 xml 中做修改,无需编译代码,只需重启服务器即可将新的配置加载。
5、依赖注入DI-使用javaConfig
javaConfig,是在 Spring 3.0 开始从一个独立的项目并入到 Spring 中的。javaConfig 可以看成一个用于完成 Bean 装配的 Spring 配置文件,一个类中只要标注了@Configuration注解,这个类就可以为spring容器提供Bean定义的信息了。标注了@Configuration和标注了@Component的类一样是一个Bean,可以被Spring的 context:component-scan
标签扫描到。类中的每个标注了@Bean的方法都相当于提供了一个Bean的定义信息。
@Configuration
public class AppConfig {
@Bean
public UserDao userDao() {
return new UserDaoImpl();
}
@Bean
public UserService userService() {
//这里不能声明接口类型
UserServiceImpl userService = new UserServiceImpl();
//配置依赖关系(需要set方法)
userService.setUserDao(userDao());
return userService;
}
}
测试类:
//基于xml
ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
UserService service = (UserService)context.getBean("userService");
User user = service.getUser();
System.out.println(user);
//基于javaConfig
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
UserService service = (UserService)context.getBean("userService");
User user = service.getUser();
System.out.println(user);
6、为应用指定多个Spring配置文件
在实际应用里,随着应用规模的增加,系统中 Bean 数量也大量增加,导致配置文件变得非常庞大、臃肿。为了避免这种情况的产生,提高配置文件的可读性与可维护性,可以将Spring 配置文件分解成多个配置文件。
包含关系的配置文件:
多个配置文件中有一个总文件,总配置文件将各其它子文件通过
举例:
7、Bean作用域(Scopes)
- 单例模式
<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"/>
- 原型模式
<bean id="accountService" class="com.something.DefaultAccountService" scope="prototype"/>
- Request, Session, Application, and WebSocket
三、面向切面编程AOP
1、不使用 AOP 的开发方式
Step1:项目 aop_leadin1
先定义好接口与一个实现类,该实现类中除了要实现接口中的方法外,还要再写两个非业务方法。非业务方法也称为交叉业务逻辑:
➢ doTransaction():用于事务处理
➢ doLog():用于日志处理
然后,再使接口方法调用它们。接口方法也称为主业务逻辑。
接口和实现类:
Step2:项目 aop_leadin2
当然,也可以有另一种解决方案:将这些交叉业务逻辑代码放到专门的工具类或处理类中,由主业务逻辑调用。
Step3:项目 aop_leadin3
以上的解决方案,还是存在弊端:交叉业务与主业务深度耦合在一起。当交叉业务逻辑较多时,在主业务代码中会出现大量的交叉业务逻辑代码调用语句,大大影响了主业务逻辑的可读性,降低了代码的可维护性,同时也增加了开发难度。 所以,可以采用动态代理方式。在不修改主业务逻辑的前提下,扩展和增强其功能。
基于JDK的动态代理需要实现接口InvocationHandler 重写invoke方法
测试类:
2、代理模式
http://c.biancheng.net/view/1359.html
代理模式的作用:
- 功能增强:在原有功能上 增加额外的功能(不改变原有功能的代码)
- 控制访问:代理类不让你访问目标,例如商家不让顾客直接访问厂家
实现代理的方式有两种:
- 静态代理:代理类是自己手动实现的 自己创建一个Java代理类,同时所要代理的目标类是确定的(实现简单 容易理解)
- 动态代理:在程序执行过程中,使用jdk的反射机制,创建代理类对象, 并动态的指定要代理目标类。换句话说: 动态代理是一种创
建java对象的能力,让你不用创建TaoBao类,就能创建代理类对象。
动态代理(原理): 基于反射机制
1、什么是动态代理 ?
使用jdk的反射机制,创建对象的能力, 创建的是代理类的对象。 而不用你创建类文件。不用写java文件。
动态:在程序执行时,调用jdk提供的方法才能创建代理类的对象。
jdk动态代理,必须有接口,目标类必须实现接口
没有接口时,需要使用cglib动态代理
2、动态代理能做什么 ?
可以在不改变原来目标方法功能的前提下, 可以在代理中增强自己的功能代码。
程序开发中的意思。
比如:你所在的项目中,有一个功能是其他人(公司的其它部门,其它小组的人)写好的,你可以使用。
GoNong.class , GoNong gn = new GoNong(), gn.print();
你发现这个功能,现在还缺点, 不能完全满足我项目的需要。 我需要在gn.print()执行后,需要自己在增加代码。
用代理实现 gn.print()调用时, 增加自己代码, 而不用去改原来的 GoNong文件。
1.代理 代购, 中介,换ip,商家等等 比如有一家美国的大学, 可以对全世界招生。 留学中介(代理) 留学中介(代理): 帮助这家美国的学校招生, 中介是学校的代理, 中介是代替学校完成招生功能。 代理特点:
- 中介和代理他们要做的事情是一致的: 招生。
- 中介是学校代理, 学校是目标。
- 家长—-中介(学校介绍,办入学手续)——美国学校。
- 中介是代理,不能白干活,需要收取费用。
- 代理不让你访问到目标。
为什么要找中介 ?
1. 中介是专业的, 方便
2. 家长现在不能自己去找学校。 家长没有能力访问学校。 或者美国学校不接收个人来访。买东西都是商家卖, 商家是某个商品的代理, 你个人买东西, 肯定不会让你接触到厂家的。
- 在开发中也会有这样的情况, 你有a类, 本来是调用c类的方法, 完成某个功能。 但是c不让a调用。
a ——-不能调用 c的方法。
在a 和 c 直接 创建一个 b 代理, c让b访问。
a —访问b—-访问c
实际的例子: 登录,注册有验证码, 验证码是手机短信。
中国移动, 联通能发短信。
中国移动, 联通能有子公司,或者关联公司,他们面向社会提供短信的发送功能
张三项目发送短信——子公司,或者关联公司——-中国移动, 联通3.使用代理模式的作用 1.功能增强: 在你原有的功能上,增加了额外的功能。 新增加的功能,叫做功能增强。 2.控制访问: 代理类不让你访问目标,例如商家不让用户访问厂家。 4.实现代理的方式 1.静态代理 : 1) 代理类是自己手工实现的,自己创建一个java类,表示代理类。 2)同时你所要代理的目标类是确定的。 特点: 1)实现简单 2)容易理解。 缺点: 当你的项目中,目标类和代理类很多时候,有以下的缺点: 1)当目标类增加了, 代理类可能也需要成倍的增加。 代理类数量过多。 2) 当你的接口中功能增加了, 或者修改了,会影响众多的实现类,厂家类,代理都需要修改。影响比较多。 模拟一个用户购买u盘的行为。 用户是客户端类 商家:代理,代理某个品牌的u盘。 厂家:目标类。 三者的关系: 用户(客户端)—-商家(代理)—-厂家(目标) 商家和厂家都是卖u盘的,他们完成的功能是一致的,都是卖u盘。 实现步骤:
- 创建一个接口,定义卖u盘的方法, 表示你的厂家和商家做的事情。
- 创建厂家类,实现1步骤的接口
- 创建商家,就是代理,也需要实现1步骤中的接口。
- 创建客户端类,调用商家的方法买一个u盘。 代理类完成的功能:
- 目标类中方法的调用
- 功能增强 2.动态代理 在静态代理中目标类很多时候,可以使用动态代理,避免静态代理的缺点。 动态代理中目标类即使很多, 1)代理类数量可以很少, 2)当你修改了接口中的方法时,不会影响代理类。 动态代理: 在程序执行过程中,使用jdk的反射机制,创建代理类对象, 并动态的指定要代理目标类。 换句话说: 动态代理是一种创建java对象的能力,让你不用创建TaoBao类,就能创建代理类对象。 在java中,要想创建对象: 1.创建类文件, java文件编译为class 2.使用构造方法,创建类的对象。 动态代理的实现:
- jdk动态代理(理解): 使用java反射包中的类和接口实现动态代理的功能。反射包 java.lang.reflect , 里面有三个类 :InvocationHandler , Method, Proxy.
- cglib动态代理(了解): cglib是第三方的工具库, 创建代理对象。 cglib的原理是继承, cglib通过继承目标类,创建它的子类,在子类中 重写父类中同名的方法, 实现功能的修改。
因为cglib是继承,重写方法,所以要求目标类不能是final的, 方法也不能是final的。
cglib的要求目标类比较宽松, 只要能继承就可以了。cglib在很多的框架中使用,
比如 mybatis ,spring框架中都有使用。
jdk动态代理:
- 反射, Method类,表示方法。类中的方法。 通过Method可以执行某个方法。
- jdk动态代理的实现
反射包 java.lang.reflect , 里面有三个类 : InvocationHandler , Method, Proxy.
1)InvocationHandler 接口(调用处理器):就一个方法invoke()
invoke():表示代理对象要执行的功能代码。你的代理类要完成的功能就写在
invoke()方法中。代理类完成的功能:
- 调用目标方法,执行目标方法的功能
- 功能增强,在目标方法调用时,增加功能。
方法原型:
参数: Object proxy:jdk创建的代理对象,无需赋值。
Method method:目标类中的方法,jdk提供method对象的
Object[] args:目标类中方法的参数, jdk提供的。
public Object invoke(Object proxy, Method method, Object[] args)
InvocationHandler 接口:表示你的代理要干什么。
怎么用: 1.创建类实现接口InvocationHandler
2.重写invoke()方法, 把原来静态代理中代理类要完成的功能,写在这。
2)Method类:表示方法的, 确切的说就是目标类中的方法。
作用:通过Method可以执行某个目标类的方法,Method.invoke();
method.invoke(目标对象,方法的参数)
Object ret = method.invoke(service2, "李四");
说明: method.invoke()就是用来执行目标方法的,等同于静态代理中的
//向厂家发送订单,告诉厂家,我买了u盘,厂家发货
float price = factory.sell(amount); //厂家的价格。
3)Proxy类:核心的对象,创建代理对象。之前创建对象都是 new 类的构造方法() 现在我们是使用Proxy类的方法,代替new的使用。 方法: 静态方法 newProxyInstance() 作用是: 创建代理对象, 等同于静态代理中的TaoBao taoBao = new TaoBao();
参数:
1. ClassLoader loader 类加载器,负责向内存中加载对象的。 使用反射获取对象的ClassLoader
类a , a.getCalss().getClassLoader(), 目标对象的类加载器
2. Class<?>[] interfaces: 接口, 目标对象实现的接口,也是反射获取的。
3. InvocationHandler h : 我们自己写的,代理类要完成的功能。
返回值:就是代理对象
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
- 实现动态代理的步骤:
- 创建接口,定义目标类要完成的功能
- 创建目标类实现接口
- 创建InvocationHandler接口的实现类,在invoke方法中完成代理类的功能
1.调用目标方法
2.增强功能4.使用Proxy类的静态方法,创建代理对象。 并把返回值转为接口类型。
示例:
com.xjt.handler.MyInvocationHandler.java
目标类和接口
基于Spring配置文件的aop
- 方法一:
使用原生是sping-api接口 在目标方法前置后置增加功能 该方法需要定义前置方法实现org.springframework.aop.MethodBeforeAdvice 定义后置方法实现org.springframework.aop.AfterReturningAdvice 优点:前后置方法中可获取到目标对象、执行的方法、参数,自定制程度化高
前置方法
package org.example.log;
import org.springframework.aop.AfterReturningAdvice;
import java.lang.reflect.Method;
public class AfterLog implements AfterReturningAdvice {
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println(target.getClass().getName()+"类的"+method.getName()+"方法被执行了,返回结果为:"+returnValue);
}
}
后置方法
package org.example.log;
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;
public class Log implements MethodBeforeAdvice {
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println(target.getClass().getName()+"类的"+method.getName()+"方法被执行了");
}
}
Spring的配置:applicationContext.xml
<bean id="userService" class="org.example.service.UserServiceImpl"/>
<bean id="log" class="org.example.log.Log"/>
<bean id="afterLog" class="org.example.log.AfterLog"/>
<aop:config>
<!--切入点 表达式-->
<aop:pointcut id="pointcut" expression="execution(* org.example.service.UserServiceImpl.*(..))"/>
<aop:advisor advice-ref="log" pointcut-ref="pointcut"/>
<aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
</aop:config>
- 方法二:
前后置方法可以定义在一个类中
package org.example.diy;
public class Diy {
void before(){
System.out.println("before===============目标方法执行前");
}
void after(){
System.out.println("after===============目标方法执行后");
}
}
Spring的配置:applicationContext.xml
<bean id="userService" class="org.example.service.UserServiceImpl"/>
<bean id="diy" class="org.example.diy.Diy"/>
<aop:config>
<aop:aspect ref="diy">
<aop:pointcut id="pointcut" expression="execution(* org.example.service.UserServiceImpl.*(..))"/>
<aop:before method="before" pointcut-ref="pointcut"/>
<aop:after method="after" pointcut-ref="pointcut"/>
</aop:aspect>
</aop:config>
执行效果:
public static void main( String[] args ){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService = (UserService) applicationContext.getBean("userService");
userService.add();
}
before===============目标方法执行前
执行了UserServiceImpl::add()
after===============目标方法执行后
基于注解的aop
切面类
package org.example.anno;
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;
@Aspect //声明当前类是切面类
public class AnnotationAop {
@Before("execution(* org.example.service.UserServiceImpl.*(..))")
void doBefore(){
System.out.println("==========方法执行前==========");
}
@After("execution(* org.example.service.UserServiceImpl.*(..))")
void doAfter(){
System.out.println("==========方法执行前==========");
}
@Around("execution(* org.example.service.UserServiceImpl.*(..))")
void doAround(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("环绕前。。。");
joinPoint.proceed();
System.out.println("环绕后。。。");
}
}
applicationContext.xml配置
<bean id="userService" class="org.example.service.UserServiceImpl"/>
<!--方法二:基于注解的实现-->
<bean id="annotationAop" class="org.example.anno.AnnotationAop"/>
<!--proxy-target-class 默认false 使用jdk动态代理,true使用cglib-->
<aop:aspectj-autoproxy proxy-target-class="false"/>
打印结果:
public static void main( String[] args ){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService = (UserService) applicationContext.getBean("userService");
userService.add();
}
环绕前。。。
==========方法执行前==========
执行了UserServiceImpl::add()
==========方法执行前==========
环绕后。。。
3、AOP 概述
AOP(Aspect Orient Programming),面向切面编程。面向切面编程是从动态角度考虑程序运行过程。
AOP 底层,就是采用动态代理模式实现的。采用了两种代理:JDK 的动态代理,与 CGLIB的动态代理。
AOP 为 Aspect Oriented Programming 的缩写,意为:面向切面编程,可通过运行期动态代理实现程序功能的统一维护的一种技术。AOP 是 Spring 框架中的一个重要内容。利用 AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
面向切面编程,就是将交叉业务逻辑封装成切面,利用 AOP 容器的功能将切面织入到主业务逻辑中。所谓交叉业务逻辑是指,通用的、与主业务逻辑无关的代码,如安全检查、事务、日志、缓存等。
若不使用 AOP,则会出现代码纠缠,即交叉业务逻辑与主业务逻辑混合在一起。这样,会使主业务逻辑变的混杂不清。
例如,转账,在真正转账业务逻辑前后,需要权限控制、日志记录、加载事务、结束事务等交叉业务逻辑,而这些业务逻辑与主业务逻辑间并无直接关系。但,它们的代码量所占比重能达到总代码量的一半甚至还多。它们的存在,不仅产生了大量的“冗余”代码,还大大干扰了主业务逻辑—-转账。
面向切面编程对有什么好处?
1.减少重复;
2.专注业务;
注意:面向切面编程只是面向对象编程的一种补充。
使用 AOP 减少重复代码,专注业务实现:
4、AOP 编程术语
(1)切面(Aspect)
切面泛指交叉业务逻辑。上例中的事务处理、日志处理就可以理解为切面。常用的切面是通知(Advice) 。实际就是对主业务逻辑的一种增强。
(2)连接点(JoinPoint)
连接点指可以被切面织入的具体方法。通常业务接口中的方法均为连接点。
(3)切入点(Pointcut)
切入点指声明的一个或多个连接点的集合。通过切入点指定一组方法。 被标记为 final 的方法是不能作为连接点与切入点的。因为最终的是不能被修改的,不能被增强的。
(4)目标对象(Target)
目 标 对 象 指 将 要 被 增 强 的 对 象 。 即 包 含 主 业 务 逻 辑 的 类 的 对 象 。 上例中 的StudentServiceImpl 的对象若被增强,则该类称为目标类,该类对象称为目标对象。当然不被增强,也就无所谓目标不目标了。
(5)通知(Advice)
通知表示切面的执行时间,Advice 也叫增强。上例中的 MyInvocationHandler 就可以理解为是一种通知。换个角度来说,通知定义了增强代码切入到目标代码的时间点,是目标方法执行之前执行,还是之后执行等。通知类型不同,切入时间不同。 切入点定义切入的位置,通知定义切入的时间。
5、AspectJ 对AOP的实现
对于 AOP 这种编程思想,很多框架都进行了实现。Spring 就是其中之一,可以完成面向切面编程。然而,AspectJ 也实现了 AOP 的功能,且其实现方式更为简捷,使用更为方便,而且还支持注解式开发。所以,Spring 又将 AspectJ 的对于 AOP 的实现也引入到了自己的框架中。 在 Spring 中使用 AOP 开发时,一般使用 AspectJ 的实现方式。
AspectJ简介
AspectJ 是一个优秀面向切面的框架,它扩展了 Java 语言,提供了强大的切面实现。官网地址:http://www.eclipse.org/aspectj/ AspetJ 是 Eclipse 的开源项目,官网介绍如下: a seamless aspect-oriented extension to the Javatm programming language(一种基于 Java 平台的面向切面编程的语言) Java platform compatible(兼容 Java 平台,可以无缝扩展) easy to learn and use(易学易用)
AspectJ 的通知类型
AspectJ 中常用的通知有五种类型:
(1)前置通知
(2)后置通知
(3)环绕通知
(4)异常通知
(5)最终通知
AspectJ 的切入点表达式
AspectJ 定义了专门的表达式用于指定切入点。表达式的原型是:
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)
解释:
modifiers-pattern] 访问权限类型
ret-type-pattern 返回值类型
declaring-type-pattern 包名类名
name-pattern(param-pattern) 方法名(参数类型和参数个数)
throws-pattern 抛出异常类型
?表示可选的部分
以上表达式共 4 个部分。
execution(访问权限 方法返回值 方法声明(参数) 异常类型)
切入点表达式要匹配的对象就是目标方法的方法名。所以,execution 表达式中明显就是方法的签名。注意,表达式中黑色文字表示可省略部分,各部分间用空格分开。在其中可以使用以下符号:
举例:
execution(public (..))
指定切入点为:任意公共方法。
execution( set(..))
指定切入点为:任何一个以“set”开始的方法。
execution( com.xyz.service..*(..))
指定切入点为:定义在 service 包里的任意类的任意方法。
execution( com.xyz.service...*(..))
指定切入点为:定义在 service 包或者子包里的任意类的任意方法。“..”出现在类名中时,后面必须跟“”,表示包、子包下的所有类。
**execution( ..service..(..))
指定所有包下的 serivce 子包下所有类(接口)中所有方法为切入点
execution( .service..(..))
指定只有一级包下的 serivce 子包下所有类(接口)中所有方法为切入点
execution( .ISomeService.(..))
指定只有一级包下的 ISomeSerivce 接口中所有方法为切入点
execution( ..ISomeService.(..))
指定所有包下的 ISomeSerivce 接口中所有方法为切入点
execution( com.xyz.service.IAccountService.(..))
指定切入点为:IAccountService 接口中的任意方法。
execution( com.xyz.service.IAccountService+.(..))
指定切入点为:IAccountService 若为接口,则为接口中的任意方法及其所有实现类中的任意方法;若为类,则为该类及其子类中的任意方法。
execution( joke(String,int)))
指定切入点为:所有的 joke(String,int)方法,且 joke()方法的第一个参数是 String,第二个参数是 int。如果方法中的参数类型是 java.lang 包下的类,可以直接使用类名,否则必须使用全限定类名,如 joke( java.util.List, int)。
execution( joke(String,)))
指定切入点为:所有的 joke()方法,该方法第一个参数为 String,第二个参数可以是任意类型,如 joke(String s1,String s2)和 joke(String s1,double d2)都是,但joke(String s1,double d2,String s3)不是。
execution( joke(String,..)))
指定切入点为:所有的 joke()方法,该方法第一个参数为 String,后面可以有任意个参数且参数类型不限,如 joke(String s1)、joke(String s1,String s2)和 joke(String s1,double d2,String s3)都是。
execution( joke(Object))
指定切入点为:所有的 joke()方法,方法拥有一个参数,且参数是 Object 类型。joke(Object ob)是,但,joke(String s)与 joke(User u)均不是。
execution( joke(Object+))) *
指定切入点为:所有的 joke()方法,方法拥有一个参数,且参数是 Object 类型或该类的子类。不仅 joke(Object ob)是,joke(String s)和 joke(User u)也是。
6、AspectJ 基于注解的 AOP 实现
基于注解的 AOP 实现步骤
1、maven 依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
2、引入 AOP 约束
在 AspectJ 实现 AOP 时,要引入 AOP 的约束。配置文件中使用的 AOP 约束中的标签,均是 AspectJ 框架使用的,而非 Spring 框架本身在实现 AOP 时使用的。
AspectJ 对于 AOP 的实现有注解和配置文件两种方式,常用是注解方式。
3、定义业务接口与实现类
4、定义切面类
类中定义了若干普通方法,将作为不同的通知方法,用来增强功能。
5、声明目标对象切面类对象
6、注册 AspectJ 的自动代理
在定义好切面 Aspect 后,需要通知 Spring 容器,让容器生成“目标类+ 切面”的代理对象。这个代理是由容器自动生成的。只需要在 Spring 配置文件中注册一个基于 aspectj 的自动代理生成器,其就会自动扫描到@Aspect 注解,并按通知类型与切入点,将其织入,并生成代理。<aop:aspectj-autoproxy/>
的底层是由AnnotationAwareAspectJAutoProxyCreator
实现的。
从其类名就可看出,是基于 AspectJ 的注解适配自动代理生成器。 其工作原理是,<aop:aspectj-autoproxy/>
通过扫描找到@Aspect 定义的切面类,再由切面类根据切入点找到目标类的目标方法,再由通知类型找到切入的时间点。
@Before 前置通知-方法有 JoinPoint 参数
在目标方法执行之前执行。被注解为前置通知的方法,可以包含一个 JoinPoint 类型参数。该类型的对象本身就是切入点表达式。通过该参数,可获取切入点表达式、方法签名、目标对象等。 不光前置通知的方法,可以包含一个 JoinPoint 类型参数,所有的通知方法均可包含该参数。
@AfterReturning 后置通知-注解有 returning 属性
在目标方法执行之后执行。由于是目标方法之后执行,所以可以获取到目标方法的返回值。该注解的 returning 属性就是用于指定接收方法返回值的变量名的。所以 被注解为后置通知的方法,除了可以包含 JoinPoint 参数外,还可以包含用于接收返回值的变量。该变量最好为 Object 类型,因为目标方法的返回值可能是任何类型。
接口增加方法:
实现方法:
定义切面:
@Around 环绕通知-增强方法有 ProceedingJoinPoint参数
在目标方法执行之前之后执行。被注解为环绕增强的方法要有返回值,Object 类型。并且方法可以包含一个 ProceedingJoinPoint 类型的参数。接口 ProceedingJoinPoint 其有一个proceed()方法,用于执行目标方法。若目标方法有返回值,则该方法的返回值就是目标方法的返回值。最后,环绕增强方法将其返回值返回。该增强方法实际是拦截了目标方法的执行。
接口增加方法:
接口方法的实现:
定义切面:
@AfterThrowing 异常通知-注解中有 throwing 属性
在目标方法抛出异常后执行。该注解的 throwing 属性用于指定所发生的异常类对象。当然,被注解为异常通知的方法可以包含一个参数 Throwable,参数名称为 throwing 指定的名称,表示发生的异常对象。
增加业务方法:
方法实现:
定义切面:
@After 最终通知
无论目标方法是否抛出异常,该增强均会被执行。
增加方法:
方法实现:
定义切面:
@Pointcut 定义切入点
当较多的通知增强方法使用相同的 execution 切入点表达式时,编写、维护均较为麻烦。
AspectJ 提供了@Pointcut 注解,用于定义 execution 切入点表达式。 其用法是,将@Pointcut 注解在一个方法之上,以后所有的 execution 的 value 属性值均可使用该方法名作为切入点。代表的就是@Pointcut 定义的切入点。这个使用@Pointcut 注解的方法一般使用 private 的标识方法,即没有实际作用的方法。
四、Spring 集成 MyBatis
将 MyBatis 与 Spring 进行整合,主要解决的问题就是将 SqlSessionFactory 对象交由 Spring来管理。所以,该整合,只需要将 SqlSessionFactory 的对象生成器 SqlSessionFactoryBean 注册在 Spring 容器中,再将其注入给 Dao 的实现类即可完成整合。 实现 Spring 与 MyBatis 的整合常用的方式:扫描的 Mapper 动态代理
Spring 像插线板一样,mybatis 框架是插头,可以容易的组合到一起。插线板 spring 插上 mybatis,两个框架就是一个整体。
所谓的Spring集成MyBatis,就是由Spring给MyBatis注入需要的组件。比如:DataSource(数据源)、SqlSessionFactory、sqlSession、事务管理对象等。
1、添加集成MyBatis的依赖
<dependencies>
<!--spring核心 IOC容器-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<!--spring事务-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<!--spring对jdbc的支持依赖包(关联spring-tx包) -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- mybatis-spring 依赖包 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.0</version>
</dependency>
<!--mybatis 依赖包 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.1</version>
</dependency>
<!-- mysql驱动 依赖包 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
<!-- alibaba数据源 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.12</version>
</dependency>
</dependencies>
2、创建Mapper
xxxMapper.java 和xxxMapper.xml 一般放在同一个目录中
下面是采用注解的方式写的sql
@Mapper
public interface EmpMapper {
@Select("select * from emp where empno=#{empno}")
public Emp getEmpById(int empno);
}
在Mapper接口上添加@Mapper注解,Spring容器扫描到此注解后,就会将SqlSession注入给Mapper组件。
3、添加db.properties文件
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/emp?characterEncoding=utf-8
jdbc.username=root
jdbc.password=123
4、Spring容器中添加组件
applicationContext.xml
<!--开启注解扫描,设置需要扫描的包(用于扫描测试时用的Service组件) -->
<context:component-scan base-package="com.neusoft.ms"/>
<!-- 引入db配置文件 -->
<context:property-placeholder location="classpath:db.properties" />
<!-- 配置dataSource数据源 -->
<bean id="dataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="${jdbc.driver}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
<!-- 创建SqlSessionFactory,并配置实体对象别名 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"></property>
<property name="typeAliasesPackage" value="com.neusoft.ms.po" />
</bean>
<!-- 配置Mapper,自动扫描Mapper接口,并为其注入SqlSessionFactory -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.neusoft.ms.mapper"></property>
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property>
</bean>
这里,配置了sqlSessionFactory组件,并将sqlSessionFactory组件注入给Mapper组件。
5、测试
创建Service组件,然后测试
@Service("empService")
public class EmpServiceImpl implements EmpService{
@Autowired
private EmpMapper empMapper;
@Override
public Emp getEmpById(int empno) {
return empMapper.getEmpById(empno);
}
}
public class MsTest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
EmpService service = (EmpService)context.getBean("empService");
Emp emp = service.getEmpById(7369);
System.out.println(emp);
}
}
6、添加事务管理
Spring为了支持事务管理,专门封装了事务管理对象。我们只要在Spring容器中配置这个对象,即可使用。
在Spring容器中添加事务管理的配置:
<!-- 配置Spring提供的事务管理器 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 开启注解事务 -->
<tx:annotation-driven transaction-manager="transactionManager" />
在Service组件中,使用@Transactional注解,就可以给业务方法添加事务管理。
@Transactional
public int transferAccounts(Emp emp1,Emp emp2) {
//需要事务管理的业务
}
注意:
- 需要事务管理的service,在方法上加上@Transactional 注解即可。
- 必须为public方法才行,不要捕捉异常,要让异常自动抛出,否则不能进行事务回滚。
7、事务传播行为
@Transactional 注解中的 propagation 属性,可以设置事务传播行为。属性值为:
- REQUIRED:如果当前没有事务,就新建一个事务,如果已经存在一个事务中,就加入到这个事务中。这是最常见的选择。
- SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行。
- MANDATORY:使用当前的事务,如果当前没有事务,就抛出异常。
- REQUIRES_NEW:新建事务,如果当前存在事务,把当前事务挂起。
- NOT_SUPPORTED:以非事务方式执行操作,如果存在事务,就把当前事务挂起。
- NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。
[@Override
@Transactional(propagation=Propagation.SUPPORTS)
public Dept getDeptById(Integer deptno) {
return deptMapper.getDeptById(deptno);
}