第十章SSM_Spring
1.spring概述
框架(Framework):
框(指其约束性)架(指其支撑性),在软件设计中指为解决一个 开放性问题 而 设计的具有一定约束性的支撑结构。
在此结构上可以根据具体问题扩展、安插更多的组成部分,从而更 迅速和方便地构建完整的解决问题的方案。
Spring :
J2EE 的春天,是一个是分层的 Java SE/EE full-stack 开源的轻量级的 Java 开发框架。
Spring具有==控制反转(IoC)和面向切面(AOP)==两大核心。Java Spring 框架通过声明式方式灵活地进行事务的管理,提高开发效率和质量。
Spring 框架还是一个超级粘合平台,除了自己提供功能外,还提供粘合其他技术和框架的能力。
Spring优势:
- 1、方便解耦,简化开发
- 2、方便集成各种优秀框架
- 3、降低 Java EE API 的使用难度
- 4、方便解耦,简化开发
- 5、方便集成各种优秀框架
- 6、降低 Java EE API 的使用难度
Spring的体系结构:
Spring 为我们提供了一站式解决方案,而且Spring 是模块化的,允许咱们挑选和选择适用于项目的模块, 不需要把剩余部分也引入。Spring 框架提供约 20 个模块,可以根据应用程序的要求来选择。
2.Spring核心之IoC
2.1 IoC的概念
Ioc—Inversion of Control,即“控制反转”,不是什么技术,而是一种设计思想。
IoC 是指在程序开发中,实例的创建不再由调用者管理,而是由 Spring 容器创建。Spring 容器会负责控制程序之间的关系,而不是由程序代码直接控制,因此,控制权由程序代码转移到了 Spring 容器中,控制权发生了反转,这就是 Spring 的 IoC 思想。
2.2 IoC入门案例
为了将多个项目创建在一个项目文件当中,我们先去创建一个EmptyProject,再在里面new modules即可。
原有创建对象写法:
package com.czy.pojo;
public class Team {
private Integer id;
private String name;
private String location;
public Team() {
System.out.println("Team - 默认的构造方法 id="+id+",name="+name+",location="+location);
}
}
package com.czy.test;
import com.czy.pojo.Team;
import org.junit.Test;
public class Test01 {
@Test
public void test01(){
Team team = new Team();//原有写法,程序员自己创建对象
}
}
试着应用IoC思想,不去硬编码,而是通过配置文件的方式,让程序自己去创建对象。先在pom.xml文件中添加一段代码,然后如下图操作新建一个SpringConfig文件,取名为application.xml
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<?xml version="1.0" encoding="UTF-8"?>
<!--spring配置文件
beans:根标签
spring中java的对象称为java bean
spring-beans.xsd是一个约束文件,约束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 http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--创建对象:声明bean,告知spring容器创建哪些对象
一个bean标签表示一个对象
id=""对象名,要求唯一值
class="完全限定名" spring底层是通过反射的机制去创建对象,不能写接口
相当于com.czy.pojo.Team team1 = new com.czy.pojo.Team();,然后将创建的对象放入spring容器的一个集合map中
springMap.put(id,对象) 例如:springMap.put(team1,new Team()); 既然它都帮我们new了对象,那么一定可以看到那行构造方法的输出结果
-->
<bean id="team1" class="com.czy.pojo.Team"></bean>
</beans>
在Test01.java中新写一段代码,用IoC的方式同样成功创建了对象。去获取spring容器的时候,容器根据我们在application.xml里面配置好的这些bean,会帮我们把我们需要的这些对象创建好了。
Spring 提供了两种 IoC 容器:BeanFactory 和 ApplicationContext。
上面演示的是最常用的ApplicationContext方式,BeanFactory 不太常用就不演示了。
ApplicationContext 是 BeanFactory 的子接口,也被称为应用上下文。它不仅提供了 BeanFactory 的所有功能,还添加了对 i18n(国际化)、资源访问、事件传播等方面的良好支持。
ClassPathXmlApplicationContext是ApplicationContext 最常用的实现类:该类从类路径 ClassPath 中寻找指定的 XML 配置文件,找到并装载完成 ApplicationContext 的实例化工作
使用方式:ApplicationContext applicationContext=new ClassPathXmlApplicationContext(Spring配置文件的名称);
我们还可以创建非自定义对象
在application.xml文件中加入下面这行bean:
<!--创建非自定义的对象-->
<bean id="date1" class="java.util.Date"></bean>
除了上面演示的bean标签的属性class,name,id之外,还有一些bean标签的属性需要我们去学习。
属性 | 说明 |
---|---|
class | 指定bean对应类的全路径 |
name | name是bean对应对象的一个标识,可同名,但尽量不同名 |
scope | 执行bean对象创建模式和生命周期,分为两种:scope=“singleton”(单例,也是默认值)和scope=“prototype”(多例) |
id | id是bean对象的唯一标识,不能添加特别字符 |
lazy-init | 是否延时加载,只针对单例有效(多例本来就懒加载) |
init-method | 对象创建完毕之后立即调用的初始化方法 |
destroy- method | 对象销毁方法,调用spring容器destroy方法的时候执行 |
tips:
- scope的singleton:单例(默认值),在容器启动的时候就已经创建了对象,而且容器中只有唯一的一个对象
- scope的prototype:多例,在使用对象的时候才创建对象,每次使用都创建新的对象
- lazy-init的false:不懒(默认值),不延迟创建对象,容器加载的时候立即创建
- lazy-init的true:真懒,默认加载,使用对象的时候才去创建对象
2.3 Spring容器创建对象的方式
- 使用默认的构造方法
- 使用带参数的构造方法
- 使用工厂类
前两种方法:
右键resources新建一个新的配置文件,名为createType.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容器创建对象的方式:
1、通过默认的构造方法
2、通过带参数的构造方法
3、通过工厂方法:实例方法,静态方法-->
<!--1、通过默认构造方法-->
<bean id="team1" class="com.czy.pojo.Team"></bean>
<!-- 2、通过带参数的构造方法-->
<bean id="team2" class="com.czy.pojo.Team">
<!--name:表示参数的名称-->
<constructor-arg name="id" value="1001"/>
<constructor-arg name="name" value="勇士"/>
<constructor-arg name="location" value="金州"/>
</bean>
<bean id="team3" class="com.czy.pojo.Team">
<!--index:表示参数的下标索引-->
<constructor-arg index="0" value="1002"/>
<constructor-arg index="1" value="热火"/>
<constructor-arg index="2" value="迈阿密"/>
</bean>
</beans>
重开一个测试类,名为CreateTypeTest.java
package com.czy.test;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class CreateTypeTest {
@Test
public void test01(){
String springConfig="createType.xml";
//获取spring容器,获取到了之后容器就会帮我们去创建配置文件中的那些对象
ApplicationContext applicationContext = new ClassPathXmlApplicationContext(springConfig);
}
}
第三种方法:
在通过Spring容器创建对象的第三个方式——使用工厂类 之前,我们先去新建一个MyFactory.java
package com.czy.pojo;
public class MyFactory {
/**
* 实例方法(通过对象打点去调用)
* @return Team
*/
public Team instanceFun() {
System.out.println("MyFactory---instanceFun");
return new Team(1003, "实例方法创建的球队名", "实例方法创建的地名");
}
/**
* 静态方法
* @return Team
*/
public static Team staticFun() {
System.out.println("MyFactory---staticFun");
return new Team(1004, "静态方法创建的球队名", "静态方法创建的地名");
}
}
在createType.xml里面新加上这两个bean
<!--3、通过工厂方法:
3.1 实例方法
参考这句话MyFactory factory=new MyFactory(); Team team = factory.instanceFun();-->
<bean id="factory" class="com.czy.pojo.MyFactory"></bean>
<bean id="instanceTeam" factory-bean="factory" factory-method="instanceFun"></bean>
<!--3、通过工厂方法:
3.2 静态方法
参考这句话Team team1 = MyFactory.staticFun();-->
<bean id="staticTeam" class="com.czy.pojo.MyFactory" factory-method="staticFun"></bean>
2.4基于XML的DI
DI—Dependency Injection,即“依赖注入”:是组件之间依赖关系由容器在运行期决定,形象的说,即由容器动态的将某个依赖关系注入到组件之中。
依赖注入的目的并非为软件系统带来更多功能,而是为 了提升组件重用的频率,并为系统搭建一个灵活、可扩展的平台。
通过依赖注入机制,我们只需要通过简单的配置,而无需任何代码就可指定目标需要的资源,完成自身的业务逻辑,而不需要关心具体的资源来自何处,由谁实现。
IoC是一个概念,是一种思想,其实现方式多种多样。依赖注入就是其中用的比较多的一种方式。
Ioc和DI是同一个概念的不同角度描述。IoC是一种思想,概念,DI是实现它的手段。Spring框架使用依 赖注入DI实现IoC.
通俗讲法:
本来我接受各种参数来构造一个对象,现在只接受一个参数——已经实例化的对象。
也就是说我对 对象的『依赖是注入进来的』,而和它的构造方式解耦了。构造和销毁这些『控制』操作也交给了第三方,也就是『控制反转』。
2.4.1注入分类-通过set方法
set 注入也叫设值注入,是指通过 setter 方法传入被调用者的实例。这种注入方式简单、直观,因而在Spring 的依赖注入中大量使用。
先在src-main-java下创建com.czy.dao的包,新建TeamDao类
package com.czy.dao;
public class TeamDao {
public void add(){
System.out.println("TeamDao--- add ---");
}
}
同上创建service包,新建TeamService类
package com.czy.service;
import com.czy.dao.TeamDao;
public class TeamService {
private TeamDao teamDao;
public TeamDao getTeamDao() {
return teamDao;
}
public void setTeamDao(TeamDao teamDao) {
this.teamDao = teamDao;
}
public void add(){
teamDao.add();
System.out.println("TeamService--- add ---");
}
}
右键resources新建自动注入的配置文件DI.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">
<bean id="teamDao" class="com.czy.dao.TeamDao"></bean>
<bean id="teamService" class="com.czy.service.TeamService">
<!--使用set方法注入属性值,name是属性名,ref是引用-->
<property name="teamDao" ref="teamDao"></property>
</bean>
</beans>
编写测试方法
@Test
public void test02() {
//通过加载配置文件DI.xml获取spring容器
ApplicationContext ac = new ClassPathXmlApplicationContext("DI.xml");
//通过指定id(teamService)获取指定bean对象
TeamService teamService = (TeamService) ac.getBean("teamService");
//通过teamService对象打点调用add方法
teamService.add();
}
控制台输出,经验证成功创建
2.4.2注入分类-通过构造方法
构造注入,是指在构造调用者实例的同时,完成被调用者的实例化,使用构造器设置依赖关系。
在TeamService里面添加属性teamSao的构造方法(有参和无参),在DI.xml里面添加一个bean如下
<bean id="teamService2" class="com.czy.service.TeamService">
<!--使用构造方法注入属性值,name是属性名,ref是引用-->
<constructor-arg name="teamDao" ref="teamDao"></constructor-arg>
</bean>
这种方式与set方法注入属性值很相似,同样可以成功。
2.4.3注入分类-自动注入
在DI.xml里面新加入这两个自动注入的bean
<!--按名称自动注入:查找容器中id名与TeamService类中的属性名一致的对象进行注入-->
<bean id="teamService3" class="com.czy.service.TeamService" autowire="byName">
</bean>
<!--按类型自动注入:查找容器中类型与TeamService类中的属性类型相同或者符合is-a关系的对象进行注入,但是要求类型相同的对象唯一,否则抛出异常:不知道用哪一个匹配-->
<bean id="teamService4" class="com.czy.service.TeamService" autowire="byType">
</bean>
2.5基于注解实现IoC(重要)
对于 DI 使用注解,将不再需要在 Spring 配置文件中声明 bean 实例。Spring 中使用注解,需要在原有Spring 运行环境基础上再做一些改变。
2.5.1声明Bean的注解@Component
重新编写TeamDao.java
package com.czy.dao;
import org.springframework.stereotype.Component;
//@Component 注解标识在类上,表示该类对象的创建交给Spring容器了,value属性表示创建的id值(id值可省略),默认是类名的首字母小写
@Component(value = "teamDao")//相当于<bean id="teamDao" class="com.czy.dao.TeamDao"></bean>
public class TeamDao {
public void add(){
System.out.println("TeamDao--- add");
}
public TeamDao() {
System.out.println("TeamDao--- 默认的构造方法");
}
}
右键resources新建annotation.xml,注意不要忘记在beans标签中新添命名空间
<?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
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!--表示告知spring要扫描的包
这些包以及子包的类上如果添加了@Component注释,这些添加了注解的类就交给了spring容器创建对象-->
<context:component-scan base-package="com.czy.dao"></context:component-scan>
</beans>
编写测试类,控制台输出成功创建对象
梳理流程:
获取spring容器→加载配置文件annotation.xml→annotation.xml告知spring要扫描的包为com.czy.dao→扫描到dao包中,TeamDao类标注了@Component,表示该类对象的创建交给Spring容器了(即com.czy.dao.TeamDao这个类就自动注入到了spring容器当中,实现DI)
除此之外,Spring中还提供了其他3个用于创建对象的注解:
- @Repository : 用于dao实现类的的注解
- @Service: 用户service实现类的注解
- @Controller: 用于controller实现类的注解
但这三个注解还有其他的含义:
@Service创建业务层对象,业务层对象可以加入事务功能,@Controller 注解创建的对象可以作为处理器接收用户的请求…
总结:
@Repository,@Service,@Controller 是对@Component 注解的细化,标注不同层的对象。即持久层对象,业务层对象,控制层对象。
2.5.2包扫描
需要在 Spring 配置文件中配置组件扫描器,用于在指定的基本包中扫描注解。如果没有报扫描,添加的创建对象的注解不生效。
如果要扫描的包有多个,可以有以下方式扫描:
- 使用多个context:component-scan指定不同的包路径
2.指定 base-package的值使用分隔符
<!--多个包的扫描: 方式2 : base-package中直接声明要扫描的多个包 ,多个值用逗号,分号或者空格分割,但是空格不推荐-->
<context:component-scan base- package="com.kkb.dao,com.kkb.service,com.kkb.controller"></context:component- scan>
3.base-package是指定到父包名
不建议使用顶级的父包,扫描的路径比较多,导致容器启动时间变慢。指定到合适的目标包,即注解所在包全路径。
<!--多个包的扫描: 方式3: base-package中直接声明要扫描的多个包的父包-->
<context:component-scan base-package="com.kkb"></context:component-scan>
2.5.3属性注入@Value
需要在属性上使用注解@Value,该注解的 value 属性用于指定要注入的值。使用该注解完成属性注入时,类中无需 setter。当然,若属性有 setter,则也可将其加到 setter 上。
2.5.4byType自动注入@Autowired
需要在引用属性上使用注解@Autowired,该注解默认使用按类型自动装配 Bean 的方式。使用该注解完成属性注入时,类中无需 setter。当然,若属性有 setter,则也可将其加到 setter 上。
2.5.5byName自动注入@Autowired和@Qualifier
需要在引用属性上联合使用注解@Autowired 与@Qualifier。@Qualifier 的 value 属性用于指定要匹配的 Bean 的 id 值。类中无需 set 方法,也可加到 set 方法上。
@Autowired 还有一个属性 required,默认值为 true,表示当匹配失败后,会终止程序运行。若将其值设置为 false,则匹配失败,将被忽略,未匹配的属性值为 null。(了解即可)
2.5.6自动注入@Resource
Spring提供了对 jdk中@Resource注解的支持。@Resource 注解既可以按名称匹配Bean,也可以按类型匹配 Bean。默认是按名称注入。使用该注解,要求 JDK 必须是 6 及以上版本。@Resource 可在属性上,也可在 set 方法上。
1.byType注入引用类型属性
@Resource 注解若不带任何参数,采用默认按名称的方式注入,按名称不能注入 bean,则会按照类型进行 Bean 的匹配注入。
2.byName注入引用类型属性
@Resource 注解指定其 name 属性,则 name 的值即为按照名称进行匹配的 Bean 的 id。
3.Spring核心之AOP
3.1什么是AOP
AOP为Aspect Oriented Programming的缩写,意思为面向切面编程,是通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。
AOP的作用是在不修改源码的情况下,程序运行期间对方法进行功能增强好处:
- 减少代码的重复,提高开发效率,便于维护。
- 专注核心业务的开发。
核心业务和服务性代码混合在一起。
开发的时候各自做自己擅长的事情,运行的时候再将服务性代码织入到核心业务中。
通过spring工厂自动实现将服务性代码以切面的方式加入到核心业务代码中。
3.2静态代理
原有方式:核心业务和服务方法都编写在一起
package com.czy.service;
public class TeamService {
public void add() {
try {
System.out.println("开始事务");
System.out.println("TeamService---- add----");// 核心业务
System.out.println("提交事务");
} catch (Exception e) {
e.printStackTrace();
System.out.println("回滚事务");
}
}
}
3.2.1基于类的静态代理
将服务性代码分离出来,核心业务–保存业务中只有保存功能
package com.czy.service;
public class TeamService {
public void add() {
System.out.println("TeamService---- add----");// 核心业务
}
}
package com.czy.staticproxy;
import com.czy.service.TeamService;
/**
* 基于类的静态代理:
* 要求继承被代理的类
* 弊端:每次只能代理一个类
*/
public class ProxyTeamService extends TeamService {
public void add() {
try {
System.out.println("开始事务");
super.add();//核心业务就是由被代理对象完成 ;其他服务功能由代理类完成
System.out.println("提交事务");
} catch (Exception e) {
System.out.println("回滚事务");
}
}
}
public static void main(String[] args) {
TeamService ser = new ProxyTeamService();
ser.add();
}
**基于类的静态代理弊端:**要求继承被代理的类,代理类只能代理一个类
3.2.2基于接口的静态代理
为核心业务(保存add)创建一个接口,通过接口暴露被代理的方法
要求:代理类和被代理类都实现了同一个接口
1.声明这同一个接口,里面定义核心的方法
package com.czy.service;
/*** 接口定义核心方法 */
public interface IService {
void add();
}
2.创建被代理类TeamService和UserService,被代理类都去实现上面那个接口,并在里面重写add方法
package com.czy.service;
public class TeamService implements IService {
@Override
public void add() {
System.out.println("TeamService---- add");// TeamService的核心业务
}
}
package com.czy.service;
public class UserService implements IService {
@Override
public void add() {
System.out.println("UserService---- add");// UserService的核心业务
}
}
3.新建一个代理类ProxyTranService,负责服务功能中的事务处理;新建一个代理类ProxyLogService ,负责服务功能中的日志处理。代理类和被代理类一样,都要去实现那同一个接口
package com.czy.staticproxy;
import com.czy.service.IService;
/**
* 基于接口的静态代理:
* 代理类和被代理类实现同一个接口
*/
public class ProxyTranService implements IService {
private IService service;//被代理的对象
//全参构造方法
public ProxyTranService(IService service) {
this.service = service;
}
@Override
public void add() {
try {
System.out.println("开始事务");
service.add();//核心业务就是由被代理对象完成 ;其他服务功能由代理类完成
System.out.println("提交事务");
} catch (Exception e) {
System.out.println("回滚事务");
}
}
}
package com.czy.staticproxy;
import com.czy.service.IService;
public class ProxyLogService implements IService {
private IService service;//被代理对象
//全参构造方法
public ProxyLogService(IService service) {
this.service = service;
}
@Override
public void add() {
try {
System.out.println("开始日志");
service.add();//核心业务就是由被代理对象完成 ;其他服务功能由代理类完成
System.out.println("结束日志");
} catch (Exception e) {
System.out.println("异常日志");
}
}
}
最后编写测试类:
public static void main(String[]args){
TeamService teamService=new TeamService();//被代理对象teamService
UserService userService=new UserService();//被代理对象userService
ProxyTranService tranService=new ProxyTranService(userService);//事务代理对象--一级代理
//tranService.add();//代理对象干活
ProxyLogService logService=new ProxyLogService(tranService);//日志的代理对象--二级代理
logService.add();
}
二级代理流程分析:
1.从main开始,logService打点add调用函数,而logService是ProxyLogService代理类,到下一步
2.到ProxyLogService类中,输出**“开始日志”**→service.add(); 这个service是tranService,而tranService是ProxyTranService类(又是一个代理类),到下一步
3.到ProxyTranService类中,输出**“开始事务”**→service.add(); 这个service是userService,而userService是UserService类(终于是被代理类了),到下一步
4.到UserService类中,输出**"UserService---- add"→回到第三步service.add();这句结束→输出"提交事务"→输出“结束日志”**
示意图:
3.2.3提切面作接口(AOP)
1.声明AOP接口作为切面
package com.czy.aop;
/**
* 切面:服务代码,切入到核心代码中,切入到哪里,给了四个位置
*/
public interface AOP {
void before();
void after();
void exception();
void myFinally();
}
2.声明专门管理事务的TranAOP 和管理日志的LogAOP,都去实现上面那个总接口
package com.czy.aop;
public class TranAOP implements AOP {
@Override
public void before() {
System.out.println("事务 before");
}
@Override
public void after() {
System.out.println("事务 after");
}
@Override
public void exception() {
System.out.println("事务 exception");
}
@Override
public void myFinally() {
System.out.println("事务 myFinally");
}
}
package com.czy.aop;
public class LogAop implements AOP {
@Override
public void before() {
System.out.println("日志 before");
}
@Override
public void after() {
System.out.println("日志 after");
}
@Override
public void exception() {
System.out.println("日志 exception");
}
@Override
public void myFinally() {
System.out.println("日志 myFinally");
}
}
3.声明代理类ProxyAOPService
package com.czy.staticproxy;
import com.czy.aop.AOP;
import com.czy.service.IService;
public class ProxyAOPService implements IService {
private IService service;//被代理对象
private AOP aop;//要加入切面
public ProxyAOPService(IService service, AOP aop) {
this.service = service;
this.aop = aop;
}
@Override
public void add() {
try {
aop.before();
service.add();//被代理对象干活aop.after();
} catch (Exception e) {
aop.exception();
} finally {
aop.myFinally();
}
}
}
编写测试类:
@Test
public void test02(){
IService teamService=new TeamService();//被代理对象--核心内容
AOP tranAop=new TranAOP();//切面-服务内容(事务)
AOP logAop=new LogAop();//切面-服务内容(日志)
IService service=new ProxyAOPService(teamService,logAop); //代理对象--一级代理
IService service2=new ProxyAOPService(service,tranAop);//代理对象--二级代理
service2.add();
}
示意图:
总结静态代理:
1)可以做到在不修改目标对象的功能前提下,对目标对象功能扩展。
2)缺点:
因为代理对象,需要与目标对象实现一样的接口。所以会有很多代理类,类太多。 一旦接口增加方法,目标对象与代理对象都要维护。
3.3动态代理
3.3.1基于JDK的动态代理
public class MyJDKProxy {
public static void main(String[] args) {
//目标对象--被代理对象
TeamService teamService = new TeamService();
//返回代理对象 调用JDK中Proxy类中的静态方法newProxyInstance获取动态代理类的实例
IService proxyService = (IService) Proxy.newProxyInstance(
teamService.getClass().getClassLoader(),
teamService.getClass().getInterfaces(),
new InvocationHandler() {//回调函数 编写代理规则
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
System.out.println("开始事务");
Object invoke = method.invoke(teamService, args);//核心业务
System.out.println("提交事务");
return invoke;
} catch (Exception e) {
System.out.println("回滚事务");
e.printStackTrace();
throw e;
} finally {
System.out.println("finally ");
}
}
}
);
//代理对象干活
proxyService.add();
}
}
后续也可以进行结构化处理,此处省略。
3.3.2基于CGLIB的动态代理
上面说到基于JDK的动态代理,代理对象不需要实现接口,但是被代理对象一定要实现接口,否则不能用JDK动态代理。如果想要功能扩展,但被代理对象没有实现接口,怎样功能扩展?
答:子类的方式实现代理CGLIB。
Cglib代理,也叫做子类代理。在内存中构建一个子类对象从而实现对被代理对象功能的扩展。
- JDK的动态代理有一个限制,就是使用动态代理的对象必须实现一个或多个接口。如果想代理 没有实现接口的类,就可以使用CGLIB实现。
- CGLIB是一个强大的高性能的代码生成包,它可以在运行期扩展Java类与实现Java接口。它广泛的被许多AOP的框架使用,例如Spring AOP和dynaop,为他们提供方法的interception。
- CGLIB包的底层是通过使用一个小而快的字节码处理框架ASM,来转换字节码并生成新的类。不鼓励直接使用ASM,因为它要求你必须对JVM内部结构包括class文件的格式和指令集都很熟悉。
public static void main(String[]args){
//目标对象:没有接口
NBAService nbaService=new NBAService();
//创建代理对象:选择cglib动态代理
NBAService proxyService= (NBAService)
Enhancer.create(nbaService.getClass(),
new MethodInterceptor(){//回调函数编写代理规则
@Override
public Object intercept(Object o,Method method,Object[]objects,
MethodProxy methodProxy)throws Throwable{
try{
System.out.println("开始事务");
Object invoke=methodProxy.invokeSuper(o,objects);//核心System.out.println("提交事务");
return invoke;
}catch(Exception e){
System.out.println("事务回滚");
throw e;
}finally{
System.out.println("finally ");
}
}
});
//代理对象干活
int res=proxyService.add("湖人",1001);System.out.println(res);
}
后续也可以进行结构化处理,此处省略。
3.4SpringAOP
3.4.1SpringAOP相关概念
Spring的AOP实现底层就是对上面的动态代理的代码进行了封装,封装后我们只需要对需要关注的部分 进行代码编写,并通过配置的方式完成指定目标的方法增强。
我们先来介绍AOP的相关术语:
1.Target(被代理对象)
要被增强的对象,一般是业务逻辑类的对象。
2.Proxy(代理)
一个类被 AOP 织入增强后,就产生一个结果代理类。
3.Aspect(切面)
表示增强的功能,就是一些代码完成的某个功能,非业务功能。是切入点和通知的结合。
4.Joinpoint(连接点)
所谓连接点是指那些被拦截到的点。在Spring中,这些点指的是方法(一般是类中的业务方法),因为
Spring只支持方法类型的连接点。
5.PointCut(切入点)
切入点指声明的一个或多个连接点的集合。通过切入点指定一组方法。
被标记为 final 的方法是不能作为连接点与切入点的。因为最终的是不能被修改的,不能被增强的。
6.Advice(通知/增强)
所谓通知是指拦截到 Joinpoint 之后所要做的事情。通知定义了增强代码切入到目标代码的时间点,是目标方法执行之前执行,还是之后执行等。通知类型不同,切入时间不同。
通知的类型:前置通知,后置通知,异常通知,最终通知,环绕通知。
切入点定义切入的位置,通知定义切入的时间。
7.Weaving(织入)
是指把增强应用到目标对象来创建新的代理对象的过程。 spring 采用动态代理织入,而 AspectJ 采用编译期织入和类装载期织入。
切面的三个关键因素:
1、切面的功能–切面能干啥
2、切面的执行位置–使用Pointcut表示切面执行的位置
3、切面的执行时间–使用Advice表示时间,在目标方法之前还是之后执行。
3.4.2AspectJ对AOP的实现
对于 AOP 这种编程思想,很多框架都进行了实现。Spring 就是其中之一。AspectJ 也实现了 AOP 的功能,且其实现方式更为简捷而且还支持注解式开发。
Spring 将AspectJ 的对于 AOP 的实现也引入到了自己的框架中。在 Spring 中使用 AOP 开发时,一般使用 AspectJ 的实现方式
AspectJ 是一个优秀面向切面的框架,它扩展了 Java 语言,提供了强大的切面实现。
AspectJ 中常用的通知有5种类型:
- 前置通知
- 后置通知
- 环绕通知
- 异常通知
- 最终通知
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 表达式中就是方法的签名。
PS:表达式中非加粗文字表示可省略部分,各部分间用空格分开。在其中可以使用以下符号:
符号 | 意义 |
---|---|
* | 0-多个任意字符 |
… | 用在方法参数中,表示任意个参数;用在包名后,表示当前及其子包路径 |
+ | 用在类名后,表示当前及其子类;用在接口后,表示当前接口及其实现类 |
示例:
-
execution(* com.kkb.service..(…))
指定切入点为:定义在 service 包里的任意类的任意方法。
-
execution(* com.kkb.service….(…))
指定切入点为:定义在 service 包或者子包里的任意类的任意方法。“…”出现在类名中时,后面必须跟“*”,表示包、子包下的所有类。
-
execution(* com.kkb.service.IUserService+.*(…))
指定切入点为:IUserService 若为接口,则为接口中的任意方法及其所有实现类中的任意方法;若为类, 则为该类及其子类中的任意方法。
3.4.3注解方式实现AOP
开发阶段:关注核心业务和AOP代码
运行阶段:spring框架会在运行的时候将核心业务和AOP代码通过动态代理的方式编织在一起
代理方式的选择:是否实现了接口。如果有接口就选择JDK动态代理,没有就选择CGLIB动态代理。
1、创建项目引入依赖 略
2、创建spring配置文件引入约束 略
3、创建核心业务类
/**
* 创建核心业务类-公共接口
*/
public interface IService {
void add(int id, String name);
boolean update(int num);
}
package com.czy.service;
import org.springframework.stereotype.Service;
/**
* 创建核心业务类-代理类
*/
@Service
public class TeamService implements IService {
@Override
public void add(int id, String name) {
System.out.println("TeamService----add");
}
@Override
public boolean update(int num) {
System.out.println("TeamService----update");
if(num>666){
return true;
}
return false;
}
}
package com.czy.service;
import org.springframework.stereotype.Service;
/**
* 创建核心业务类-代理类
*/
@Service("nbaService")
public class NBAService implements IService{
@Override
public void add(int id, String name) {
System.out.println("NBAService---- add----");
}
@Override
public boolean update(int num) {
System.out.println("NBAService---- update----");
if(num>666){
return true;
}
return false;
}
}
4、定义切面类
/**
* 切面类
*/
@Component //切面对象的创建权限依然交给spring容器
@Aspect //aspectj 框架的注解 标识当前类是一个切面类
public class MyAOP {
public void before(JoinPoint jp){
System.out.println("AOP前置通知:在目标方法执行之前被调用的通知");
}
public void afterReturn(Object result){
System.out.println("AOP后置通知:在目标方法执行之后被调用的通知,result="+result);
}
public Object around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("AOP环绕方法---目标方法的执行之前");
Object proceed = pjp.proceed();
System.out.println("AOP环绕方法---目标方法的执行之后");
return proceed;
}
public void exception(JoinPoint jp,Throwable ex){
//一般会把异常发生的时间、位置、原有都记录下来
System.out.println("AOP异常通知:在目标方法执行出现异常的时候才会别调用的通知,否则不执行");
System.out.println(jp.getSignature()+"方法出现异常,异常信息是:"+ex.getMessage());
}
public void myFinally(){
System.out.println("AOP最终通知:无论是否出现异常都是最后被调用的通知");
}
}
5、业务类于切面类添加注解
spring.xml配置文件中开启包扫描和注册aspectJ的自动代理
<!--包扫描-->
<context:component-scanbase-package="com.kkb.service,com.kkb.aop"/> <!--开启注解AOP的使用-->
<aop:aspectj-autoproxyproxy-target-class="true"/>
<!--aop:aspectj-autoproxy的底层是由AnnotationAwareAspectJAutoProxyCreator实现的,是基于AspectJ的注解适配自动代理生成器。
其工作原理是,aop:aspectj-autoproxy通过扫描找到@Aspect定义的切面类,再由切面类根据切入点找到目标类的目标方法,再由通知类型找到切入的时间点。-->
6、测试类
public class Test01 {
@Test
public void test01(){
ApplicationContext ac=new ClassPathXmlApplicationContext("spring.xml");
TeamService teamService = (TeamService) ac.getBean("teamService");
teamService.add(1001,"湖人队");
System.out.println("--------------------------");
boolean update = teamService.update(888);
System.out.println("update 结果="+update);
System.out.println("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
NBAService nbaService = (NBAService) ac.getBean("nbaService");
nbaService.add(1002,"热火");
System.out.println("--------------------------");
boolean update2 = teamService.update(888);
System.out.println("update 结果="+update2);
}
}
3.4.4XML方式实现AOP
/**
* 切面类
*/
@Component //切面对象的创建权限依然交给spring容器
@Aspect //aspectj 框架的注解 标识当前类是一个切面类
public class MyAOP {
public void before(JoinPoint jp){
System.out.println("AOP前置通知:在目标方法执行之前被调用的通知");
}
public void afterReturn(Object result){
System.out.println("AOP后置通知:在目标方法执行之后被调用的通知,result="+result);
}
public Object around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("AOP环绕方法---目标方法的执行之前");
Object proceed = pjp.proceed();
System.out.println("AOP环绕方法---目标方法的执行之后");
return proceed;
}
public void exception(JoinPoint jp,Throwable ex){
//一般会把异常发生的时间、位置、原有都记录下来
System.out.println("AOP异常通知:在目标方法执行出现异常的时候才会别调用的通知,否则不执行");
System.out.println(jp.getSignature()+"方法出现异常,异常信息是:"+ex.getMessage());
}
public void myFinally(){
System.out.println("AOP最终通知:无论是否出现异常都是最后被调用的通知");
}
}
<aop:config>
<!--声明切入点的表达式,可以声明多个-->
<aop:pointcut id="pt1" expression="execution(* com.kkb.service..*.add* (..))"/>
<aop:pointcut id="pt2" expression="execution(* com.kkb.service..*.update*(..))"/>
<aop:pointcut id="pt3" expression="execution(* com.kkb.service..*.del* (..))"/>
<aop:pointcut id="pt4" expression="execution(* com.kkb.service..*.insert*(..))"/>
<aop:aspect ref="myAOP">
<aop:before method="before" pointcut="execution(* com.kkb.service..*.*(..))"></aop:before>
<aop:after-returning method="afterReturn" pointcut-ref="pt2" returning="result"></aop:after-returning>
<aop:after-throwing method="exception" pointcut-ref="pt1" throwing="ex"></aop:after-throwing>
<aop:after method="myFinally" pointcut-ref="pt1"></aop:after>
<aop:around method="around" pointcut-ref="pt2"></aop:around>
</aop:aspect>
");
return proceed;
}
public void exception(JoinPoint jp,Throwable ex){
//一般会把异常发生的时间、位置、原有都记录下来
System.out.println("AOP异常通知:在目标方法执行出现异常的时候才会别调用的通知,否则不执行");
System.out.println(jp.getSignature()+"方法出现异常,异常信息是:"+ex.getMessage());
}
public void myFinally(){
System.out.println("AOP最终通知:无论是否出现异常都是最后被调用的通知");
}
}
```xml
<aop:config>
<!--声明切入点的表达式,可以声明多个-->
<aop:pointcut id="pt1" expression="execution(* com.kkb.service..*.add* (..))"/>
<aop:pointcut id="pt2" expression="execution(* com.kkb.service..*.update*(..))"/>
<aop:pointcut id="pt3" expression="execution(* com.kkb.service..*.del* (..))"/>
<aop:pointcut id="pt4" expression="execution(* com.kkb.service..*.insert*(..))"/>
<aop:aspect ref="myAOP">
<aop:before method="before" pointcut="execution(* com.kkb.service..*.*(..))"></aop:before>
<aop:after-returning method="afterReturn" pointcut-ref="pt2" returning="result"></aop:after-returning>
<aop:after-throwing method="exception" pointcut-ref="pt1" throwing="ex"></aop:after-throwing>
<aop:after method="myFinally" pointcut-ref="pt1"></aop:after>
<aop:around method="around" pointcut-ref="pt2"></aop:around>
</aop:aspect>