java-spring

终于进入spring的学习

1. IoC控制反转

Ioc—Inversion of Control,即“控制反转”,不是什么技术,而是一种设计思想。
IoC 是指在程序开发中,实例的创建不再由调用者管理,而是由 Spring 容器创建。Spring 容器会负责控制程序之间的关系,而不是由程序代码直接控制,因此,控制权由程序代码转移到了 Spring 容器中,控制权发生了反转,这就是 Spring 的 IoC 思想。

比如:servlet只需要添加注解,没有实例化,更没有通过实例变量来执行doGet等实例方法,这就是tomcat容器帮我们创建并执行了!!

1.1 Spring入门案例

  • 创建实体类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    package com.kkb.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);
    }
    }
  • 创建Spring的配置文件application.xml

    image-20210705141236981

  • 使用Spring容器创建对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?xml version="1.0" encoding="UTF-8"?>
<!--spring的配置文件
1、beans: 根标签,spring中java的对象成为bean
2、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底层是反射机制创建对象,所以必须使用类名
相当于 Team team1=new Team();创建好的对象放入一个集合Map中
例如:springMap.put("team1",new Team()); -->

<bean id="team1" class="com.kkb.pojo.Team"></bean>
</beans>
  • 获取Spring容器

    • BeanFactory
      BeanFactory 是基础类型的 IoC 容器,是一个管理 Bean 的工厂,它主要负责初始化各种 Bean,并调用它们的生命周期方法。
      BeanFactory 接口有多个实现类,最常见的是
      org.Springframework.beans.factory.xml.XmlBeanFactory,它是根据 XML 配置文件中的定义装配Bean 的。
    1
    BeanFactory beanFactory = new XmlBeanFactory(new FileSystemResource(Spring配置文件的名称));
    • ApplicationContext

      ApplicationContext 是 BeanFactory 的子接口,也被称为应用上下文。它不仅提供了 BeanFactory 的所有功能,还添加了对 i18n(国际化)、资源访问、事件传播等方面的良好支持。ApplicationContext 接口有两个常用的实现类:

      • ClassPathXmlApplicationContext——常用

        该类从类路径 ClassPath 中寻找指定的 XML 配置文件,找到并装载完成 ApplicationContext 的实例化工作

        1
        ApplicationContext applicationContext=new ClassPathXmlApplicationContext(Spring配置文件的名称);
      • 它与 ClassPathXmlApplicationContext 的区别是:在读取 Spring 的配置文件时,

        FileSystemXmlApplicationContext 不再从类路径中读取配置文件,而是通过参数指定配置文件的位置,它可以获取类路径之外的资源,如“D:\application.xml”

        1
        ApplicationContext applicationContext = new FileSystemXmlApplicationContext(String configLocation);
  • 通过上下文对象获取容器中的对象

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    package com.kkb.test;
    import com.kkb.pojo.Team;
    import org.junit.Test;
    import org.springframework.beans.factory.BeanFactory;
    import org.springframework.beans.factory.xml.XmlBeanFactory;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    import org.springframework.context.support.FileSystemXmlApplicationContext;
    import org.springframework.core.io.FileSystemResource;
    import java.util.Date;
    public class Test1 {
    @Test
    public void test01(){
    //使用spring容器创建对象
    //1、指定spring配置文件的名称
    String springConfig="application.xml";
    //2、创建spring容器的对象:
    //方式1:不推荐,了解
    BeanFactory beanFactory = new XmlBeanFactory(new FileSystemResource("D:/workspaces/ideaProjects/MySpring/spring01/src/main/resources/application.xml"));
    beanFactory.getBean("team1");//根据ID从IOC容器获取对象,这时才创建

    //方式2:applicationContext--常用
    ApplicationContext applicationContext=new ClassPathXmlApplicationContext(springConfig);
    //这里执行完毕容器中的对象都已经创建完成

    //方式3:applicationContext--了解
    ApplicationContext applicationContext2 = new FileSystemXmlApplicationContext("D:/workspaces/ideaProjects/MySpring/spring01/src/main/resources/application.xml");
    //3、获取容器中的对象
    Team team1= (Team) applicationContext.getBean("team1");
    System.out.println("spring容器中对象的个数:"+beanDefinitionCount);
    String[] beanDefinitionNames =
    applicationContext.getBeanDefinitionNames();
    System.out.println("spring容器中所有对象的名称:");
    for (String name : beanDefinitionNames) {
    System.out.println(name);
    }
    }
    }
  • 容器其他api

    1
    2
    3
    4
    //容器中的api数量
    int beanDefinitionCount = applicationContext.getBeanDefinitionCount();
    //容器中的api名称
    String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();

1.2 创建非定义对象

1
2
3
pox.xml文件中补充
<!--创建非自定义的对象-->
<bean id="date" class="java.util.Date"></bean>

1.3 bean标签的对象

关键:对象的创建时机和创建时的伴生方法

image-20210705143304722

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!--
bean标签的属性:
id="自定义的对象名称" ,要求唯一
name="bean对于的一个标识“,一般使用id居多
class="类的完全限定名"
scope="singleton/prototype" 单例/多例
singleton:默认值,单例:在容器启动的时候就已经创建了对象,而且整个容器只有为一个的一个对象
prototype:多例,在使用对象的时候才创建对象,每次使用都创建新的对象
lazy-init="true/false" 是否延迟创建对象,只针对单例有效
true:不延迟创建对象,容器加载的时候立即创建
false:默认加载,使用对象的时候才去创建对象
init-method="创建对象之后执行的初始化方法"
destroy-method="对象销毁方法,调用容器destroy方法的时候执行"
-->
<bean id="team2" class="com.kkb.pojo.Team" scope="singleton" lazy-
init="true" init-method="init" destroy-method="destroy"/>
<bean id="team3" class="com.kkb.pojo.Team" scope="prototype" />

重点:

  • singleton:默认值,单例:在容器启动的时候就已经创建了对象,而且整个容器只有唯一的一个对象(引用)
  • prototype:多例,在使用对象的时候才创建对象,每次使用都创建新的对象
  • lazy-init=”true/false” 是否延迟创建对象,只针对单例有效(懒加载,即对象的创建时机是在get时才加载)

init和destroy方法的定义:

1
2
3
4
5
6
public void init(){
System.out.println("Team ---- init()");
}
public void destroy(){
System.out.println("Team ---- destroy()");
}

init和destroy方法的执行:

1
2
init()//对象创建即执行
applicationContext.close();//关闭容器

1.4 Spring容器创建对象的方式

1.4.1 使用默认的构造方法(无参)

1
2
<!--1、通过默认构造方法-->
<bean id="team1" class="com.kbb.pojo.Team"></bean>

1.4.2 有参构造

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!-- 2、通过带参数的构造方法-->
<!--属性名声明-->
<bean id="team2" class="com.kbb.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.kbb.pojo.Team">
<!--index:表示参数的下标索引-->
<constructor-arg index="0" value="1002"/>
<constructor-arg index="1" value="热火"/>
<constructor-arg index="2" value="迈阿密"/>
</bean>

1.4.3 工厂方法

1
2
3
4
5
6
7
8
9
10
11
12
13
<!--3、通过工厂方法:
3.1 静态方法
Team team1 = MyFactory.staticFun();-->

<bean id="staticTeam" class="com.kbb.pojo.MyFactory" factory-method="staticFun"></bean>

<!--3、通过工厂方法:
3.2 实例方法
1.MyFactory factory=new MyFactory(); 先声明工厂类
2.Team team = factory.instanceFun(); 调用实例制造机-->
<bean id="factory" class="com.kbb.pojo.MyFactory"></bean>
<bean id="instanceTeam" factory-bean="factory" factory-method="instanceFun">
</bean>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//工厂类
package com.kbb.pojo;
public class MyFactory {
/**
* 实例方法
* @return
*/
public Team instanceFun(){
System.out.println("MyFactory------instanceFun");
return new Team(1003,"湖人","洛杉矶");
}
/**
* 静态方法
* @return
*/
public static Team staticFun(){
System.out.println("MyFactory------staticFun");
return new Team(1004,"小牛","达拉斯");
}
public static void main(String[] args) {
Team team1 = MyFactory.staticFun();
MyFactory factory=new MyFactory();
Team team = factory.instanceFun();
}
}

1.5 基于XML的DI

DI—Dependency Injection,即“依赖注入”

是组件之间依赖关系由容器在运行期决定,形象的说,即由容器动态的将某个依赖关系注入到组件之中。依赖注入的目的并非为软件系统带来更多功能,而是为了提升组件重用的频率,并为系统搭建一个灵活、可扩展的平台。通过依赖注入机制,我们只需要通过简单的配置,而无需任何代码就可指定目标需要的资源,完成自身的业务逻辑,而不需要关心具体的资源来自何处,由谁实现。

IoC 是一个概念,是一种思想,其实现方式多种多样。依赖注入就是其中用的比较多的一种方式。
Ioc和DI是同一个概念的不同角度描述。IoC是一种思想,概念,DI是实现它的手段。Spring框架使用依赖注入实现IoC

Spring 容器是一个超级大工厂,负责创建、管理所有的 Java 对象,这些 Java 对象被称为 Bean。

Spring 容器管理着容器中 Bean 之间的依赖关系,Spring 使用“依赖注入”的方式来管理 Bean 之间的依赖关系。使用 IoC 实现对象之间的解耦和。

怎么解偶的?

不需要对象自己来new了啊!!!原来service里面new了一个dao对象,这俩就耦合了,想要换一个dao还得找到service里的dao进行重new,现在通过依赖注入的方式,只需要在xml里给dao改个名就行!!!

实例

dao:

1
2
3
4
5
public class TeamDao {
public void add(){
System.out.println("TeamDao---- add----");
}
}

service

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.kkb.service;
import com.kkb.dao.TeamDao;
public class TeamService {
private TeamDao teamDao;//=new TeamDao();
public void add(){
teamDao.add();
System.out.println("TeamService----add----");
}
public TeamService() {
}
public TeamService(TeamDao teamDao) {
this.teamDao = teamDao;
}
public TeamDao getTeamDao() {
return teamDao;
}
public void setTeamDao(TeamDao teamDao) {
this.teamDao = teamDao;
}
}

创建一个service参数dao的单例:

1
2
<bean id="teamDao" class="com.kkb.dao.TeamDao"></bean>
<!-- <bean id="teamDao1" class="com.kkb.dao.TeamDao"></bean>-->

下面就是看如何将这个单例注入service中了

1.5.1 set注入

set 注入也叫设值注入是指,通过 setter 方法传入被调用者的实例。这种注入方式简单、直观,因而在Spring 的依赖注入中大量使用。

1
2
3
4
<bean id="teamService" class="com.kkb.service.TeamService">
<!--使用set方法注入属性值-->
<property name="teamDao" ref="teamDao"></property>
</bean>

1.5.2 构造方法注入

构造注入是指,在构造调用者实例的同时,完成被调用者的实例化。使用构造器设置依赖关系

1
2
3
4
<bean id="teamService2" class="com.kkb.service.TeamService">
<!--使用构造方法注入属性值-->
<constructor-arg name="teamDao" ref="teamDao"></constructor-arg>
</bean>

类似于基础属性的注入,就是value变成了ref

1.5.3 自动注入

对于引用类型属性的注入也可不在配置文件中显示的注入。可以通过为标签设置 autowire 属性值,为引用类型属性进行隐式自动注入(默认是不自动注入引用类型属 性)。根据自动注入判断标准的不同,可以分为两种:

byName:根据名称自动注入
byType: 根据类型自动注入
1、 byName
当配置文件中被调用者 bean 的 id 值与代码中调用者 bean 类的属性名相同时,可使用byName 方式,让容器自动将被调用者 bean 注入给调用者 bean。容器是通过调用者的 bean类的属性名与配置文件的被调用者 bean 的 id 进行比较而实现自动注入的。
2、 byType
使用 byType 方式自动注入,要求:配置文件中被调用者 bean 的 class 属性指定的类,要与代码中调用者 bean 类的某引用类型属性类型同源。即要么相同,要么有 is-a 关系(子类,或是实现类)。但这样的同源的被调用 bean 只能有一个。多于一个,容器就不知该匹配哪一个了。

理解不了直接看例子:

1
2
3
4
5
6
7
8
</bean>
<!--按名称自动注入:查找容器中id名与属性名一致的对象进行注入-->
<bean id="teamService3" class="com.kkb.service.TeamService" autowire="byName">
</bean>
<!--按类型自动注入:查找容器中类型与属性类型相同或者符合is-a关系的对象进行注入,但是要求类
型相同的对象唯一,否则抛出异常:不知道用哪一个匹配-->
<bean id="teamService4" class="com.kkb.service.TeamService" autowire="byType">
</bean>

注意点:依赖类型的自动注入需要找准”com.kkb.dao.TeamDao”,像上面定义了两个TeamDao对象,则byType必然出错,而byName依赖的是id,可以很好的规避错误

1.6 基于注解实现IoC—重要

1.6.1 基于注解创建对象

对于 DI 使用注解,将不再需要在 Spring 配置文件中声明 bean 实例。Spring 中使用注解,需要在原有Spring 运行环境基础上再做一些改变。

目的:Bean类注解+xml配置包扫描,同样可以将对象交由spring创建

声明Bean的注解 @Component

在类上添加注解@Component表示该类创建对象的权限交给Spring容器。注解的value属性用于指定bean的id值,value可以省略。
@Component 不指定 value 属性,bean 的 id 是类名的首字母小写。

除此之外,Spring中还提供了其他3个用于创建对象的注解:

  • @Repository : 用于dao实现类的的注解
  • @Service: 用户service实现类的注解

  • @Controller: 用于controller实现类的注解

这三个注解与@Component 都可以创建对象,但这三个注解还有其他的含义:

@Service创建业务层对象,业务层对象可以加入事务功能,

@Controller 注解创建的对象可以作为处理器接收用户的请求。

@Repository,@Service,@Controller 是对@Component 注解的细化,标注不同层的对象。即持久层对象,业务层对象,控制层对象

包扫描

需要在 Spring 配置文件中配置组件扫描器,用于在指定的基本包中扫描注解。如果没有报扫描,添加的创建对象的注解不生效。

如果要扫描的包有多个,可以有以下方式扫描:

1、使用多个context:component-scan指定不同的包路径

image-20210705154941510

2、指定 base-package的值使用分隔符
分隔符可以使用逗号(,)分号(;)还可以使用空格,不建议使用空格。

1
2
3
4
<!--多个包的扫描: 方式2 : base-package中直接声明要扫描的多个包 ,多个值用逗号,分号或者空格分割,但是空格不推荐-->

<context:component-scan base-package="com.kkb.dao,com.kkb.service,com.kkb.controller">
</context:component-scan>

3、base-package是指定到父包名
base-package 的值表是基本包,容器启动会扫描包及其子包中的注解,当然也会扫描到子包下级的子包。所以 base-package 可以指定一个父包就可以。但不建议使用顶级的父包,扫描的路径比较多,导致容器启动时间变慢。指定到目标包和合适的。也就是注解所在包全路径。

1
2
<!--多个包的扫描: 方式3: base-package中直接声明要扫描的多个包的父包-->
<context:component-scan base-package="com.kkb"></context:component-scan>

1.6.2 基于注解实现属性注入(DI)

1、基本数据类型注入@Vaule

需要在属性上使用注解@Value,该注解的 value 属性用于指定要注入的值。使用该注解完成属性注入时,类中无需 setter。当然,若属性有 setter,则也可将其加到 setter 上。

image-20210705161949208

2、引用数据类型(对象)@Autowired、@Qualifier

  • byType自动注入@Autowired

    需要在引用属性上使用注解@Autowired,该注解默认使用按类型自动装配 Bean 的方式。使用该注解完成属性注入时,类中无需 setter。当然,若属性有 setter,则也可将其加到 setter 上。

    (前提是已经有该类型的类被创建,否则报空指针异常)

  • byName自动注入@Autowired和@Qualifier

    需要在引用属性上联合使用注解@Autowired 与@Qualifier。@Qualifier 的 value 属性用于指定要匹配的 Bean 的 id 值。类中无需 set 方法,也可加到 set 方法上。
    @Autowired 还有一个属性 required,默认值为 true,表示当匹配失败后,会终止程序运行。若将其值设置为 false,则匹配失败,将被忽略,未匹配的属性值为 null。

image-20210705162332775

3、自动注入@Resource

Spring提供了对 jdk中@Resource注解的支持。@Resource 注解既可以按名称匹配Bean,也可以按类型匹配 Bean。默认是按名称注

入。使用该注解,要求 JDK 必须是 6 及以上版本。@Resource 可在属性上,也可在 set 方法上。

  • byType注入引用类型属性
    @Resource 注解若不带任何参数,采用默认按名称的方式注入,按名称不能注入 bean,则会按照类型进行 Bean 的匹配注入。

  • byName注入引用类型属性
    @Resource 注解指定其 name 属性,则 name 的值即为按照名称进行匹配的 Bean 的 id。

image-20210705162710368

2. Spring核心之AOP

什么是AOP
AOP为Aspect Oriented Programming的缩写,意思为面向切面编程,是通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。

AOP的作用:不修改源码的情况下,程序运行期间对方法进行功能增强
好处:

1、减少代码的重复,提高开发效率,便于维护。
2、专注核心业务的开发。

核心业务和服务性代码混合在一起

开发中:各自做自己擅长的事情,运行的时候将服务性代码织入到核心业务中。

通过spring工厂自动实现将服务性代码以切面的方式加入到核心业务代码中。

2.1 AOP的实现机制-动态代理

什么是代理模式
代理:自己不做,找人帮你做。
代理模式:在一个原有功能的基础上添加新的功能。

分类:静态代理和动态代理。

2.1.1 静态代理

原有方式:核心业务和服务方法都编写在一起

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.kkb.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("回滚事务");
}
}
}

基于类的静态代理

将服务性代码分离出来,核心业务—保存业务中只有保存功能

1
2
3
4
5
6
package com.kkb.service;
public class TeamService {
public void add(){
System.out.println("TeamService---- add----");// 核心业务
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.kkb.staticproxy;
import com.kkb.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("回滚事务");
}
}
}
1
2
3
4
public static void main(String[] args) {
TeamService ser=new ProxyTeamService();
ser.add();
}

可以看到基于类的静态代理的弊端:代理类只能代理一个类

基于接口的静态代理

为核心业务(保存add)创建一个接口,通过接口暴露被代理的方法

要求:代理类和被代理类都实现了同一个接口

1
2
3
4
5
6
7
package com.kkb.service;
/**
* 接口定义核心方法
*/
public interface IService {
void add();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.kkb.service;
public class TeamService implements IService{
@Override
public void add(){
System.out.println("TeamService---- add----");// 核心业务
}
}
package com.kkb.service;
public class UserService implements IService{
@Override
public void add() {
System.out.println("UserService---- add-----");
}
}

一级代理类:这里可以认为是事务类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.kkb.staticproxy;
import com.kkb.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("回滚事务");
}
}
}

二级代理类:可以认为是在一级代理类的基础上进行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.kkb.staticproxy;
import com.kkb.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("异常日志");
}
}
}

测试类

1
2
3
4
5
6
7
8
9
10
public static void main(String[] args) {
TeamService teamService=new TeamService();//被代理对象
UserService userService=new UserService();//被代理对象
ProxyTranService tranService=new ProxyTranService(userService);//事务代理
对象--一级代理
//tranService.add();//代理对象干活
ProxyLogService logService=new ProxyLogService(tranService);//日志的代理对
象--二级代理
logService.add();
}

提取出切面代码,作为AOP接口

切面就可以理解为汉堡包的一层层的培根和蔬菜,包裹着核心的牛排

共有4个位置可以将切面代码编织进入核心业务代码中。

1
2
3
4
5
6
7
8
9
10
package com.kkb.aop;
/**
* 切面:服务代码,切入到核心代码中,切入到哪里,给了四个位置
*/
public interface AOP {
void before();
void after();
void exception();
void myFinally();
}

日志切面:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.kkb.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");
}
}

事务切面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.kkb.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");
}
}

如何将切面夹进去?

在代理类中将被代理对象和代理事务糅合起来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.kkb.staticproxy;
import com.kkb.aop.AOP;
import com.kkb.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();
}
}
}

测试类:

1
2
3
4
5
6
7
8
9
@Test
public void test02(){
IService teamService=new TeamService();//被代理对象--核心内容
AOP logAop=new LogAop();//切面-服务内容
AOP tranAop=new TranAOP();
IService service=new ProxyAOPService(teamService,logAop); //代理对象--一级代理
IService service2=new ProxyAOPService(service,tranAop);//代理对象--二级代理
service2.add();
}

2.1.2 总结静态代理

1)可以做到在不修改目标对象的功能前提下,对目标对象功能扩展。
2)缺点:

因为代理对象,需要与目标对象实现一样的接口。所以会有很多代理类,类太多。一旦接口增加方法,目标对象与代理对象都要维护。

2.2.1 动态代理

静态代理:要求代理类一定存在,

动态代理:程序运行的时候,根据要被代理的对象动态生成代理类。

1、基于JDK的动态代理

image-20210705184740593

1
2
3
4
5
6
7
8
9
/*newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandlerh)
ClassLoader :类加载器,因为动态代理类,借助别人的类加载器。一般使用被代理对象的类加载器。
Class<?>[] interfaces:接口类对象的集合,针对接口的代理,针对哪个接口做代理,一般使用的就是被代理对象的接口。
InvocationHandler:句柄,回调函数,编写代理的规则代码
public Object invoke(Object arg0, Method arg1, Object[] arg2)
Object arg0:代理对象
Method arg1:被代理的方法
Object[] arg2:被代理方法被执行的时候的参数的数组
*/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package com.kkb.dynamicproxy;
import com.kkb.service.IService;
import com.kkb.service.TeamService;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
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();
System.out.println(teamService.getClass());
System.out.println(proxyService.getClass()+"--------");
}
}

对上面的代码进行结构化

方式1:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class ProxyHandler implements InvocationHandler {
private IService service;//目标对象
private AOP aop;//切面
public ProxyHandler(IService service, AOP aop) {
this.service = service;
this.aop = aop;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
aop.before();
Object invoke = method.invoke(service, args);//核心业务
aop.after();
return invoke;
}catch (Exception e){
aop.exception();
e.printStackTrace();
throw e;
}finally {
aop.myFinally();
}
}
}

将已经编写好代理规则(回调函数)的对象作为参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public static void main2(String[] args) {
//目标对象--被代理对象
TeamService teamService=new TeamService();
//切面
AOP tranAop=new TranAOP();
//返回代理对象 基于JDK的动态代理
IService proxyService= (IService) Proxy.newProxyInstance(
teamService.getClass().getClassLoader(),
teamService.getClass().getInterfaces(),
new ProxyHandler(teamService,tranAop)
);
//代理对象干活
proxyService.add();
System.out.println(teamService.getClass());
System.out.println(proxyService.getClass()+"------");
}

方式2:代理工厂

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class ProxyFactory {
private IService service;//目标对象
private AOP aop;//切面
public ProxyFactory(IService service, AOP aop) {
this.service = service;
this.aop = aop;
}
/**
* 获取动态代理的示例
* @return
*/
public Object getProxyInstance() {
return Proxy.newProxyInstance(
service.getClass().getClassLoader(),
service.getClass().getInterfaces(),
new InvocationHandler() {//回调函数 编写代理规则
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
aop.before();
Object invoke = method.invoke(service, args);//核心业务
aop.after();
return invoke;
}catch (Exception e){
aop.exception();
e.printStackTrace();
throw e;
}finally {
aop.myFinally();
}
}
}
);
}
}

2、基于CGLIB的动态代理

Cglib代理,也叫做子类代理。在内存中构建一个子类对象从而实现对目标对象功能的扩展。

JDK的动态代理有一个限制,就是使用动态代理的对象必须实现一个或多个接口。如果想代理没有实现接口的类,就可以使用CGLIB实现。

CGLIB是一个强大的高性能的代码生成包,它可以在运行期扩展Java类与实现Java接口。它广泛的被许多AOP的框架使用,例如Spring AOP和dynaop,为他们提供方法的interception。CGLIB包的底层是通过使用一个小而快的字节码处理框架ASM,来转换字节码并生成新的类。不鼓励直接使用ASM,因为它要求你必须对JVM内部结构包括class文件的格式和指令集都很熟悉。

直接编写测试类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package com.kkb.cglibproxy;
//被代理类
public class NBAService {
public int add(String name,int id){
System.out.println("NBAService---- add----");
return id;
}
}
//创建代理类
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("huren",1001);
System.out.println(res);
}

结构化设计方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package com.kkb.cglibproxy;
import com.kkb.aop.AOP;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class CglibProxyFactory {
//目标对象
private NBAService nbaService;//没有实现接口的
//切面
private AOP aop;//切面
/**
* 创建代理对象
* @param nbaService
* @param aop
* @return
*/
public Object getProxyInstance(NBAService nbaService,AOP aop){
return Enhancer.create(nbaService.getClass(),new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
try {
aop.before();
Object o1 = methodProxy.invokeSuper(o, objects);
aop.after();
return o1;
}catch (Exception e){
aop.exception();
throw e;
} finally {
System.out.println("finally-----------");
}
}
});
}
}
public static void main(String[] args) {
//目标对象:没有接口
NBAService nbaService=new NBAService();
//创建切面
AOP tranAop=new TranAOP();
//创建代理对象:选择cglib动态代理
NBAService proxyInstance = (NBAService) new
CglibProxyFactory().getProxyInstance(nbaService, tranAop);
int res=proxyInstance.add("huren",1001);
System.out.println(res);
}

2.2 SpringAOP

Spring AOP相关概念

Spring的AOP实现底层就是对上面的动态代理的代码进行了封装,封装后我们只需要对需要关注的部分进行代码编写,并通过配置的方式完成指定目标的方法增强。

  • 我们先来介绍AOP的相关术语:
    Target(目标对象)

    要被增强的对象,一般是业务逻辑类的对象。

  • Proxy(代理)
    一个类被 AOP 织入增强后,就产生一个结果代理类。

  • Aspect(切面)
    表示增强的功能,就是一些代码完成的某个功能,非业务功能。是切入点和通知的结合。
  • Joinpoint(连接点)
    所谓连接点是指那些被拦截到的点。在Spring中,这些点指的是方法(一般是类中的业务方法),因为Spring只支持方法类型的连接点。
  • Pointcut(切入点)
    切入点指声明的一个或多个连接点的集合。通过切入点指定一组方法。被标记为 final 的方法是不能作为连接点与切入点的。因为最终的是不能被修改的,不能被增强的。
  • Advice(通知/增强)
    所谓通知是指拦截到 Joinpoint 之后所要做的事情就是通知。通知定义了增强代码切入到目标代码的时间点,是目标方法执行之前执行,还是之后执行等。通知类型不同,切入时间不同。
    • 通知的类型:前置通知,后置通知,异常通知,最终通知,环绕通知。切入点定义切入的位置,通知定义切入的时间。
  • Weaving(织入).
    是指把增强应用到目标对象来创建新的代理对象的过程。 spring 采用动态代理织入,而 AspectJ 采用编译期织入和类装载期织入。
    切面的三个关键因素:
    1、切面的功能—切面能干啥
    2、切面的执行位置—使用Pointcut表示切面执行的位置
    3、切面的执行时间—使用Advice表示时间,在目标方法之前还是之后执行。

2.3.1 AspectJ 对 AOP 的实现

对于 AOP 这种编程思想,很多框架都进行了实现。Spring 就是其中之一,可以完成面向切面编程。AspectJ 也实现了 AOP 的功能,且其实现方式更为简捷而且还支持注解式开发。所以,Spring 又将AspectJ 的对于 AOP 的实现也引入到了自己的框架中。

在 Spring 中使用 AOP 开发时,一般使用 AspectJ 的实现方式

AspectJ 是一个优秀面向切面的框架,它扩展了 Java 语言,提供了强大的切面实现。

符号
意义
*
0-多个任意字符
..
用在方法参数中,表示任意个参数;用在包名后,表示当前及其子包路径
+
用在类名后,表示当前及其子类;用在接口后,表示当前接口及其实现类
4.5.2 .1 AspectJ的通知类型
AspectJ 中常用的通知有5种类型:

  1. 前置通知
  2. 后置通知
  3. 环绕通知
  4. 异常通知
  5. 最终通知
    4.5.2.2 AspectJ的切入点表达式

3. SpringMVC

不用骨架创建项目后,如何添加webapp文件夹

image-20211128191723632

image-20211128191827601