Spring框架基础入门

一、Spring概述与环境搭建

 
Spring是一个轻量级控制反转(IoC)和面向切面(AOP)的容器框架。
Spring 是最受欢迎的企业级 Java 应用程序开发框架,数以百万的来自世界各地的开发人员使用 Spring 框架来创建性能好、易于测试、可重用的代码。

Spring 框架是一个开源的 Java 平台,它最初是由 Rod Johnson编写的,并且于 2003 年 6 月首次在 Apache 2.0 许可下发布。

Spring环境搭建

Spring5至少要JDK8,Servlet3.1,Tomcat8.5+。请在实际开发时针对自己的 开发环境,选择Spring版本,Spring4支持JDK6/7/8。

创建工程
配置pom.xml引入Spring依赖包

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.1.3.RELEASE</version>
        </dependency>
    </dependencies>

测试是否正常运行
创建一个类

package pojo;

public class Master { 
public void sayHello(){ 
System.out.println("hello world"); } 
}

spring.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">

</beans>

配置spring.xml配置文件

<bean id="master" class="pojo.Master"></bean> 

创建一个测试类

@Test 
public void testSpring() { 
ApplicationContext context= new ClassPathXmlApplicationContext("spring.xml"); 
Master master = (Master)context.getBean("master"); master.sayHello(); 
}
//运行结果
//hello world

运行结果成功表示环境配置成功

集成Junit

	<!-- JUnit单元测试框架     --> 
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
    </dependency>
    <!--集成Junit-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-test</artifactId>
        <version>5.1.3.RELEASE</version>
    </dependency>

测试类代码

//测试类使用@Autowired自动注入时,就需要使用@RunWith注解和@ContextConfiguration注解
@RunWith(SpringJUnit4ClassRunner.class)//让测试运行于Spring测试环境,以便在测试开始的时候自动创建Spring的应用上下文
@ContextConfiguration({"classpath:spring.xml"})//获取配置文件spring.xml中的bean
public class TestUnit {
    @Resource
    private Master master;
    @Test
    public void springtest(){
        master.hell();
    }
}

@runWith注解作用:

  • @RunWith就是一个运行器
  • @RunWith(JUnit4.class)就是指用JUnit4来运行
  • @RunWith(SpringJUnit4ClassRunner.class),让测试运行于Spring测试环境,以便在测试开始的时候自动创建Spring的应用上下文
  • @RunWith(Suite.class)的话就是一套测试集合

@ContextConfiguration注解作用:

  • @ContextConfiguration({“classpath:spring.xml”})获取配置文件spring.xml中的bean
  • @ContextConfiguration(classes = ApplicationConfig.class)获取配置文件ApplicationConfig的bean

 

二、控制反转IoC

 
在传统的方式中,如果我们要用到一个类的对象,需要自己new一个出来,如:

    @Test
    public void testMaster() {
        Master master = new Master();
        master.sayHello();
    }

Master的对象master在testMaster方法中创建,这时master对象的控制权是属于testMaster的。而在Spring中,我们看不到new Master()的操作。而是通过Spring的ApplicationContext获得:

ApplicationContext context= new ClassPathXmlApplicationContext("spring.xml"); 
//不是自己new的对象,而是从ApplicationContext中获得 Master master = (Master) context.getBean("master");

其中:

context.getBean("master") 

getBean方法的参数值master,对应的是spring.xml中的bean的id:

<bean id="master" class="pojo.Master"></bean> 

可见master对象是由Spring创建的,其控制权属于Sping而不属于使用者。 这种设计叫做“控制反转(Inversion of Control,英文缩写为IoC)”。

为什么要控制反转
IoC把对象生成放在了XML里定义,所以当我们需要换一个实现子类将会变成很简单(一般这样的对象都是实现于某种接口的),只要修改XML就可以了。
IoC的缺点是,生成一个对象的步骤变复杂了,对于不习惯这种方式的人, 会觉得有些别扭和不直观。对象生成因为是使用反射编程,在效率上有些损耗。但相对于IoC提高的维护性和灵活性来说,这点损耗是微不足道的, 除非某对象的生成对效率要求特别高。

 

三、多种方式实现依赖注入

 

1、属性注入

属性注入是通过属性的setter方法进行注入,需要注入的属性必须有setter 方法且命名必须符合规范。如属性名是abc,那么setter方法应为setAbc()

基本数据类型

<property name="属性名" value="基本类型的属性值"/> 

示例:

<bean id="master" class="pojo.Master">
	<property name="name" value="张三"/>
</bean>

相当于

Master master = new Master();
master.setName="张三";

注入对象类型

需要先创建对象bean

<property name="属性名" ref="对象的bean的id"/>

示例:

<bean id="master" class="pojo.Master">
	<property name="pet" ref="pet"/> 
</bean>

注入集合和数组类型

list和数组注入方式相同

    <!--给数组或者List赋值-->
    <property name="stringList">
        <list>
            <!--普通类型-->
            <value>abc</value>
            <value>efg</value>
            <!--注入对象类型-->
            <!--<ref bean="其他bean的id"/>-->
        </list>
    </property>
    <!--注入Set类型-->
    <property name="stringSet">
        <set>
            <!--普通类型-->
            <value>abc</value>
            <value>abc2</value>
            <!--注入对象类型-->
            <!--<ref bean="其他bean的id"/>-->
        </set>
    </property>
    <!--注入Map-->
    <property name="objectMap">
        <map>
            <!--注入普通类型-->
            <entry key="a" value="aa"/>
            <!--注入对象类型-->
            <!--<entry key="b" value-ref="其它bean的id"/>-->
        </map>
    </property>

2、构造方法注入

Spring创建对象默认使用的是类的无参构造方法。所以在某个类添加带参构造函数的时候,记得将无参构造写出来,否则会导致某些情况下Spring无法创建对象。

按类型注入

constructor-arg标签用于给构造方法注入参数,type是参数类型,value是参数值。
对于有特殊字符的属性,在value子节点中只用CDATA。如示例中,注入的 String类型参数值为“<sansan>”,其中<>是xml中的节点符号,属于特殊字符。
注入对象类型,使用ref引用其他bean的id。

<constructor-arg type="属性类型" value="基本类型属性值"/>
<constructor-arg type="属性类型" ref="对象的bean的id"/>

示例:

    <bean id="petBean" class="pojo.Cat"></bean>
    <bean id="master" class="pojo.Master">
        <!--注入基本数据类型-->
        <constructor-arg type="java.lang.Integer" value="20"/> <!-- 对于包含特殊字符的属性值,可以在value子节点使用CDATA -->
        <constructor-arg type="java.lang.String">
            <value><![CDATA[<sansan>]]></value>
        </constructor-arg>
        <!--ref用于注入对象类型-->
        <constructor-arg type="pojo.Pet" ref="petBean"/>
    </bean>

按位置注入

如果构造方法里有多个参数的类型是相同的,可以使用按位置注入的方式
constructor-arg 的index属性用于指定构造方法中参数的索引,从0开始。
示例:

    <bean id="petBean" class="pojo.Cat"></bean>
    <bean id="master" class="pojo.Master">
        <constructor-arg value="20" index="1"/>
        <constructor-arg value="张三" index="0"/>
        <constructor-arg ref="pet" index="2"/>
    </bean>

按名称注入(最常用)

    <bean id="pet" class="pojo.Cat"></bean>
    <bean id="master" class="pojo.Master">
        <!--注入基本数据类型-->
        <constructor-arg name="age" value="20"/>
        <!-- 对于包含特殊字符的属性值,可以在value子节点使用CDATA -->
        <constructor-arg name="name">
            <value><![CDATA[<sansan>]]></value>
        </constructor-arg>
        <!--ref用于注入对象类型-->
        <constructor-arg name="pet" ref="pet"/>
    </bean>

P命名空间注入

P命名空间注入需要先引入头文件:

xmlns:p="http://www.springframework.org/schema/p"

在标签上使用p:属性名=“属性值”进行值注入。使用p:属性名-ref=”bean的id” 进行对象注入。
示例:

<bean class="pojo.Master" id="master5" p:name="筑梦者" p:age="18" p:cat-ref="pet"/>

工厂方法注入

静态工厂注入
工厂bean的class都是工厂类型,但实际的bean的对象是通过factory- method获得的。getCat()和getDog()返回的都是Pet类型的对象。所以 catBean和dogBean都是Pet类型的。

PetShop类:

package pojo;

public class PetShop {
    public static Pet getDog() {
        return new Dog();
    }

    public static Pet getCat() {
        return new Cat();
    }

    public static Pet getPet(String name) {
        if (name == "cat") {
            return new Cat();
        } else {
            return new Dog();
        }
    }

配置文件写法

    <!--创建工厂bean,调用不带参数的方法-->
    <bean id="catBean" class="pojo.PetShop" factory- method="getCat"/>
    <!--创建工厂bean,调用带参数的方法-->
    <bean id="dogBean" class="pojo.PetShop" factory- method="getPet">
        <constructor-arg value="dog"/>
    </bean>
    <!--可以给master分别注入catBean和dogBean,看运行结果-->
    <bean id="master" class="pojo.Master">
        <property name="pet" ref="dogBean"/>
    </bean>

测试

@Test 
public void testFactory() { 
ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml"); 
Master master = (Master) context.getBean("master"); master.getPet().shout(); 
}

实例工厂注入

实例工厂和静态工厂的区别在于实例工厂必须先初始化工厂对象,工厂中的方法没有static
示例:

package pojo;

public class PetShop {
    public Pet getDog() {
        return new Dog();
    }

    public Pet getCat() {
        return new Cat();
    }

    public Pet getPet(String name) {
        if (name == "cat") {
            return new Cat();
        } else {
            return new Dog();
        }
    }
} 

配置文件

    <bean id="shopBean" class="pojo.PetShop"></bean>
    <bean id="petBean" factory-bean="shopBean" factory- method="getCat"></bean>
    <bean id="master" class="pojo.Master">
        <property name="pet" ref="petBean"/>
    </bean>

3、使用注解

使用注解可以批量生成bean,扫描注解修饰的类。需要引入头文件。

xmlns:context="http://www.springframework.org/schema/context"
//不指定id,默认id是类名首字母小写 
@Component("person")
public class Person { 
@Qualifier("cat")//注入其它bean 
private Pet pet; 
//getter/setter方法略 
}

@Component注解修饰一个类,这个类会创建成bean。@Component(“bean 的id”),如果不指定id,默认的id是类名首字母小写。

@Autowired注解是按类型注入,默认情况下它要求依赖对象必须存在,如 果允许null值,可以设置它的required属性为false。当有多个类型一样的 bean存在时,会出现异常。

@Qualifier注解用于注入其它bean(按bean的id注入)。 一般作为@Autowired的修饰,不能单独使用注入属性

@Resource注解是java的注解,spring提供了支持。它可以实现按名称注入 和按类型注入。

//@Resource(name = "cat")//按名称注入 
@Resource(type = Cat.class)//按类型注入 
private Pet pet;

@Qualifier, @Autowired, @Resource可以用在属性上,也可以放在setter方法 上。

其它注解:
@Controller 分层开发中用于修饰web层的类。

@Service 分层开发时用于修饰service层的类。

@Repository 分层开发时用于修饰dao层的类。

这三个注解都是对于@Component更具体的实现。

@PostConstruct 依赖注入成功后被调用。构造方法->@Autowire- >@PostConstruct

@PreDestroy 销毁前执行。

Spring4之后新增一系列注解可以代替xml文件。

import org.springframework.context.annotation.*;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;
import pojo.Cat;
import pojo.Dog;
import pojo.Pet;

@Configuration//相当于代替一个xml文件 
@ComponentScan(basePackages = "service",
        excludeFilters =
        @ComponentScan.Filter(Repository.class),
        includeFilters = @ComponentScan.Filter(Service.class), useDefaultFilters = false)
public class AppConfig {
    @Bean//默认单例 
    public Pet cat() {
        return new Cat();
    }

    @Bean
    @Scope("prototype")//每次都获得一个新的实例 
    public Dog dog() {
        return new Dog();
    }
} 

注解示例:
创建一个Bird类,并使用@Component注解,默认类名首字母小写,相当于在配置文件创建了一个bean

@Component("bird")
public class Bird {
    private String name;
    private Integer age;
    public void run(){
        System.out.println("一只小小鸟,自由自在的飞啊飞~~~~");
    }
    //略

测试类

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = Bird.class)//加载bean
public class TestUnit {
    @Autowired
    private Bird bird;

    @Test
    public void test(){
        bird.run();
    }
}

运行结果:

一只小小鸟,自由自在的飞啊飞~~~~

这样在类明上加@Component注解只能创建一个bean,如果想要多个bean可以使用配置文件类

@Configuration//相当于spring.xml
public class ApplicationConfig {
    //创建多个bean
    @Bean
    public Bird bird1(){
        return new Bird();
    }
    @Bean
    public Bird bird2(){
        return new Bird();
    }
}

测试类:
@Autowired是按类型注入,当有多个相同类型的对象,需要注意变量名,这里的bird名要和配置文件类中的方法名一致,否则会报错,如配置文件第一个方法名是bird1,加载bean也要这个变量

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = ApplicationConfig.class)//加载bean
public class TestUnit {
    @Autowired
    private Bird bird1;
    @Autowired
    private Bird bird2;

    @Test
    public void test(){
        System.out.println(bird1);
        System.out.println(bird2);
    }
}

运行结果:

com.booy.pojo.anon.Bird@3c419631
com.booy.pojo.anon.Bird@418e7838

可以通过@Resource注解指定id或者@Autowired配合@Qualifier指定id

@Resource注解指定id

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = ApplicationConfig.class)//加载bean
public class TestUnit {
    @Resource(name = "bird1")
    private Bird bird;
    
    @Test
    public void test(){
        bird.run();
    }
}

@Autowired配合@Qualifier指定id

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = ApplicationConfig.class)//加载bean
public class TestUnit {
    @Autowired
    @Qualifier("bird1")
    private Bird bird;

    @Test
    public void test(){
        bird.run();
    }
}

运行结果:

一只小小鸟,自由自在的飞啊飞~~~~

@ComponentScan:定义扫描的路径从中找出标识了需要装配的类自动装配到spring的bean容器中
定义一个pserson类,pserson养了一只鸟

@Component
@ComponentScan(value = "com.booy.pojo.anon")//扫描bean
public class Person {
    private String name;
    private Integer age;
    @Resource
    private Bird bird;

测试:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = Person.class)//加载bean
public class TestUnit {
    @Autowired
    private Person person;

    @Test
    public void test(){
        person.getBird().run();
    }
}

运行结果:

一只小小鸟,自由自在的飞啊飞~~~~

 

四、Spring工作原理

 

Spring特点

a) 方便解耦,简化开发:对象之间的依赖关系交给Spring进行控制。
b) AOP切面编程:对OOP的有利补充。
c) 声明式事务支持:通过声明的方式灵活的进行事务的管理。
d) 集成各种框架

Spring容器

Spring有两个核心接口BeanFactoryApplicationContext

ApplicationContext实例即是Spring容器,它默认会实例化所有的singleton bean(单例bean)。

ApplicationContext是BeanFactory的子接口。它们都可以代表Spring容器。 BeanFactory在需要bean实例的时候才会创建bean,而ApplicationContext在初始化容器的时候实例化所有的singleton bean。单例bean可能在初始化的时候需要更多的系统资源,一旦创建成功,应用的响应速度会很快。因此,建议使用ApplicationContext作为Spring容器。

Spring容器是生成和管理Bean示例的工厂,bean是Spring中的基本单位,在基于Spring的java工程中,所有的组件都是bean。

BeanFactory
BeanFactory:是IOC容器的核心接口, 它定义了IOC的基本功能,我们看到它主要定义了getBean方法。getBean方法是IOC容器获取bean对象和引发依赖注入的起点。方法的功能是返回特定的名称的Bean。

BeanFactory用于初始化 Bean和调用它们生命周期方法。注意, BeanFactory 只能管理单例(Singleton)Bean的生命周期。它不能管理原型(prototype,非单例)Bean 的生命周期。这是因为原型 Bean实例被创建之后便被传给了客户端,容器失去了对它们的引用。

BeanFactory接口结构图
在这里插入图片描述
BeanFactory的源码:

public interface BeanFactory {

    //用来引用一个实例,或把它和工厂产生的Bean区分开,
    //如果一个FactoryBean的名字为a,那么,&a会得到那个Factory
    String FACTORY_BEAN_PREFIX = "&";

    //四个不同形式的getBean方法,获取实例 
    Object getBean(String name) throws BeansException;

    <T> T getBean(String name, Class<T> requiredType) throws BeansException;

    <T> T getBean(Class<T> requiredType) throws BeansException;

    Object getBean(String name, Object... args) throws BeansException;

    boolean containsBean(String name); // 是否存在 

    boolean isSingleton(String name) throws NoSuchBeanDefinitionException;// 是否为单实例 

    boolean isPrototype(String name) throws NoSuchBeanDefinitionException;// 是否为原型(多实例) 

    boolean isTypeMatch(String name, Class<?> targetType) throws NoSuchBeanDefinitionException;// 名称、类型是否匹配

    Class<?> getType(String name) throws NoSuchBeanDefinitionException; // 获取类型 

    String[] getAliases(String name);// 根据实例的名字获取实例的别名
}

使用XmlBeanfactory

Resource resource = new ClassPathResource("spring- config.xml"); 
BeanFactory beanFactory = new XmlBeanFactory(resource); 
  1. 容器启动阶段,首先读取bean的xml配置文件,解析xml中各种bean的定义,每一个会封装成一个BeanDefinition对象。可以参考 AbstractBeanDefinition类的代码。

  2. 通过BeanDefinitionRegistry将这些bean注册到beanFactory中。 DefaultListableBeanFactory是BeanDefinitionRegistry的实现类, BeanDefinition被保存到DefaultListableBeanFactory的一个 ConcurrentHashMap中,所以bean是通过key-value的形式存储在工厂中的。

  3. 接下来进入bean的实例化阶段,通过实现各种Aware接口,如 BeanFactoryAware, ApplicationContextAware,Spring会注入对应的 BeanFactory,ApplicationContext等。
    BeanFactroy采用的是延迟加载形式来注入Bean的,即只有在使用到某个Bean时(调用getBean()),才对该Bean进行加载实例化。
    Spring还有一个FactoryBean接口,其特点是,spring会在使用getBean()调用获得该bean时,会自动调用该bean的getObject()方法,所以返回的不是 factory这个bean,而是这个bean.getOjbect()方法的返回值。不要把 FactoryBean和BeanFactory混淆。

ApplicationContext

ApplicationContext接口作为BeanFactory的派生,因而提供BeanFactory所有的功能。而且ApplicationContext还在功能上做了扩展,相较于BeanFactorty,ApplicationContext还提供了以下的功能:
(1)MessageSource, 提供国际化的消息访问
(2)资源访问,如URL和文件
(3)事件传播特性,即支持aop特性
(4)载入多个(有继承关系)上下文 ,使得每一个上下文都专注于一个 特定的层次,比如应用的web层

ApplicationContext有多个实现类:
1) AnnotationConfigApplicationContext:从一个或多个基于java的配置类中加载上下文定义,适用于java注解的方式;
2) ClassPathXmlApplicationContext:从类路径下的一个或多个xml配置文件中加载上下文定义,适用于xml配置的方式;
3) FileSystemXmlApplicationContext:从文件系统下的一个或多个xml配置文件中加载上下文定义,也就是说系统盘符中加载xml配置文件;
4) AnnotationConfigWebApplicationContext:专门为web应用准备的,适用于注解方式;
5) XmlWebApplicationContext:从web应用下的一个或多个xml配置文件加载上下文定义,适用于xml配置方式。

 

五、Bean的生命周期和作用域

 

单例Bean的生命周期

在这里插入图片描述
1、实例化一个Bean--也就是我们常说的new;

2、按照Spring上下文对实例化的Bean进行配置--也就是IOC注入;

3、如果这个Bean已经实现了BeanNameAware接口,会调用它实现的 setBeanName(String)方法,此处传递的就是Spring配置文件中Bean的 id值

4、如果这个Bean已经实现了BeanFactoryAware接口,会调用它实现的setBeanFactory(setBeanFactory(BeanFactory)传递的是Spring工厂自身
(可以用这个方式来获取其它Bean,只需在Spring配置文件中配置一 个普通的Bean就可以);

5、 如果这个Bean已经实现了ApplicationContextAware接口,会调用setApplicationContext(ApplicationContext)方法,传入Spring上下文
(同样这个方式也可以实现步骤4的内容,但比4更好,因为 ApplicationContext是BeanFactory的子接口,有更多的实现方法);

6、如果这个Bean关联了BeanPostProcessor接口,将会调用postProcessBeforeInitialization(Object obj, String s)方法, BeanPostProcessor经常被用作是Bean内容的更改,并且由于这个是在 Bean初始化结束时调用那个的方法,也可以被应用于内存或缓存技术;

7、 如果Bean在Spring配置文件中配置了init-method属性会自动调用其配置的初始化方法。

8、如果这个Bean关联了BeanPostProcessor接口,将会调用 postProcessA

9、当Bean不再需要时,会经过清理阶段,如果Bean实现了DisposableBean 这个接口,会调用那个其实现的destroy()方法;

10、最后,如果这个Bean的Spring配置中配置了destroy-method属性,会自动调用其配置的销毁方法。

个人对生命周期的理解
主要四个部分:实例化、属性注入、初始化、销毁
首先spring容器启动时会先实例化一个bean,其次属性注入,Spring的依赖注入中所有的Bean对Spring容器的存在是没有意识的,当我们要使用到容器中的资源,就可以实现相关Aware接口获取到spring容器本身的资源,如果实现了BeanNameAware、BeanFactoryAware、ApplicationContextAware接口会调用到相关接口的方法,接下来就是初始化,如果关联了BeanPostProcessor接口会在初始化前后进行处理工作,当bean不需要时,如果实现了DisposableBean接口就会调用destroy()方法,如果配置了destroy-method属性,会自动调用其配置的销毁方法。

 

Bean的作用域

当通过Spring容器创建一个Bean实例时,不仅可以完成Bean实例的实例化,还可以为Bean指定特定的作用域。Spring支持如下5种作用域:
singleton:单例模式,在整个Spring IoC容器中,使用singleton定义的Bean将只有一个实例
prototype:原型模式,每次通过容器的getBean方法获取prototype定义的 Bean时,都将产生一个新的Bean实例。Bean完全交给客户端管理,容器不在跟踪生命周期。
request:对于每次HTTP请求,使用request定义的Bean都将产生一个新实例,即每次HTTP请求将会产生不同的Bean实例。只有在Web应用中使用 Spring时,该作用域才有效
session:对于每次HTTP Session,使用session定义的Bean都将产生一个新实例。同样只有在Web应用中使用Spring时,该作用域才有效
application:web环境中,一个ServletContext生命周期中的单例对象。
websocket:WebSocket环境中使用。

其中比较常用的是singleton和prototype两种作用域。对于singleton作用域的Bean,每次请求该Bean都将获得相同的实例。容器负责跟踪Bean实例的状态,负责维护Bean实例的生命周期行为;如果一个Bean被设置成 prototype作用域,程序每次请求该id的Bean,Spring都会新建一个Bean实 例,然后返回给程序。在这种情况下,Spring容器仅仅使用new关键字创建Bean实例,一旦创建成功,容器不在跟踪实例,也不会维护Bean实例的状态。

如果不指定Bean的作用域,Spring默认使用singleton作用域。Java在创建Java实例时,需要进行内存申请;销毁实例时,需要完成垃圾回收,这些工作都会导致系统开销的增加。因此,prototype作用域Bean的创建、销毁代价比较大。而singleton作用域的Bean实例一旦创建成功,可以重复使用。因此,除非必要,否则尽量避免将Bean被设置成prototype作用域。

 

六、面向切面AOP

 

1、核心概念

a)、Aspect 切面,一个模块化的关注点,它横跨多个类。事务管理是企业 Java应用程序中横切关注点的一个很好的例子。在Spring AOP中,切面通过使用常规类(基于模式的方法)或通过@Aspect注解修饰的类来实现。

b)、Join point 连接点。程序执行过程中的一个点,如在方法上进行统一的异 常处理。SpringAOP中,某个方法执行前、执行后、抛出异常后等一些具有 边界性质的特定点,称作连接点。

c)、Advice 增强。在一个特定切入点上采取的动作。

d)、Pointcut 切点。某个特定的连接点就是切点。

e)、Introduction 引介。引介是一种特殊的增强,它为类添加一些属性和方法. 这样,即使一个业务类原本没有实现某一个接口,通过AOP的引介功能,也可以 动态地为该业务类添加接口的实现逻辑.让业务类成为这个接口的实现类。

f)、Target object 目标对象。增强逻辑的织入目标类。如果没有AOP,那么目标 业务类需要自己实现所有逻辑,如果使用AOP可以把一些非逻辑性代码通过 AOP织入到主程序代码上。由于通过使用运行时代理来实现Spring AOP,所 以该对象始终是代理对象。

g)、AOP proxy AOP框架为了实现切面增强而创建的对象。在Spring框架中, AOP代理是JDK动态代理或CGLIB代理。

h)、Weaving 织入。织入是将增强添加到目标类具体链接点上的过程

Spring中的增强有五种:

1、前置增强(Before): 在方法执行前调用。
2、后置增强(AfterReturning): 在方法执行后调用(方法正常结束,没有异
常退出)。
3、异常抛出增强(AfterThrowing): 在方法抛出异常时调用。。
4、环绕增强(Around): 在方法执行前后都调用。。
5、最终增强(After): 在方法执行后执行(不管是正常退出还是异常退出)
 

2、手写动态代理

动态代理并不存在代理类, 代理对象直接由代理生成工具动态生成

Jdk代理

JDK动态代理代理实现接口的类。

主业务接口

package jdk;

//主业务接口
public interface ActorInerface{
    void play();
}

主业务具体实现

package jdk;

public class MovieActor implements ActorInerface {
    @Override
    public void play() {
        System.out.println("一个演员正在表演");
    }
}

代理类:

package jdk;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

//代理类
public class JdkProxy {
    private MovieActor target;

    public JdkProxy() {
        this.target = new MovieActor();
    }
    //创建代理
    public Object getProxyInstance(){
        //创建target类信息对象
        Class aClass = target.getClass();
        //Proxy.newProxyInstance()创建动态代理方法
        //loader:一个classloader对象,定义了由哪个classloader对象对生成的代理类进行加载
        //interfaces:一个interface对象数组,表示我们将要给我们的代理对象提供一组什么样的接口,那么也就是声明了代理类实现了这些接口,代理类就可以调用接口中声明的所有方法。
        //h:一个InvocationHandler对象,表示的是当动态代理对象调用方法的时候会关联到哪一个InvocationHandler对象上,并最终由其调用。
        return Proxy.newProxyInstance(aClass.getClassLoader(), aClass.getInterfaces(), new InvocationHandler() {
            //代理对象要执行的方法
            //proxy:代理对象
            //method:被代理要执行的方法
            //args:被代理对象的参数
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("合作洽谈,签订合同");//前置增强
                //返回反射调用后的返回值
                Object invoke = method.invoke(target, args);
                System.out.println("履行合同收款!");//后置增强
                return invoke;
            }
        });
    }
}

测试类:

package jdk;

public class Test{
    public static void main(String[] args) {
        JdkProxy jdkProxy = new JdkProxy();
        ActorInerface proxyInstance = (ActorInerface)jdkProxy.getProxyInstance();
        proxyInstance.play();
    }
}
//运行结果:
//合作洽谈,签订合同
//一个演员正在表演
//履行合同收款!

Cglib代理

cglib针对类来实现代理的,对指定目标类生成一个子类,并覆盖其中方法实现增强,但因为采用的是继承,所以不能对final修饰的类进行代理。

需要引入依赖包

        <dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib</artifactId>
            <version>3.2.4</version>
        </dependency>

具体类:

package cglib;

import jdk.ActorInerface;

public class MovieActor implements ActorInerface {
    @Override
    public void play() {
        System.out.println("一个演员正在表演");
    }
}

代理类:

package cglib;

import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class CglibProxy implements MethodInterceptor {
    private MovieActor target;

    public CglibProxy() {
        this.target=new MovieActor();
    }
    //创建代理对象
    public Object getProxyInstance(){
        Enhancer en = new Enhancer();//工具类
        en.setSuperclass(target.getClass());//设置父类
        en.setCallback(this);//设置回调函数
        return en.create();//设置子类
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("合作洽谈,签订合同");
        Object invoke = method.invoke(target, objects);
        System.out.println("履行合同收款!");
        return invoke;
    }
}

测试类:

package cglib;

public class Test {
    public static void main(String[] args) {
        CglibProxy cglibProxy = new CglibProxy();
        MovieActor proxyInstance = (MovieActor)cglibProxy.getProxyInstance();
        proxyInstance.play();
    }
}
//运行结果:
//合作洽谈,签订合同
//一个演员正在表演
//履行合同收款!

模拟切面

主业务接口

package aop;

public interface IActorService {
    void play();
    void run();
}

主业务实现

package aop;

public class ActorServiceImpl implements IActorService {
    @Override
    public void play() {
        System.out.println("演员正在表演!");
    }

    @Override
    public void run() {
        System.out.println("去感受生活");
    }
}

aop基础类

package aop;

import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class BaseAspect implements MethodInterceptor {
    @Override
    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        Object res = null;
        //开始增强
        begin();
            try {
                if(isIntercept(method,args)){//判断是否需要增强
                //前置增强
                before();
                res = methodProxy.invokeSuper(o, args);
                //后置增强
                after();
                }else{
                    res = methodProxy.invokeSuper(o, args);
                }
            } catch (Exception e) {
                //异常增强
                error(e);
                throw e;
            }finally {
                //最终增强
                end();
            }
        return res;
    }
    //开始增强
    public void begin(){
    }
    //前置增强
    public void before(){}
    //后置增强
    public void after(){}
    //最终增强
    public void end(){}
    //异常增强
    public void error(Throwable e){}
    //根据参数判断方法是否需要增强,默认true
    public boolean isIntercept(Method method,Object[] args){
        return true;
    }
}

切面类

package aop;

import java.lang.reflect.Method;

public class ActorAspect extends BaseAspect {
    @Override
    public boolean isIntercept(Method method, Object[] args) {
        return method.getName().equals("play");
    }

    @Override
    public void before() {
        System.out.println("表演前准备");
    }

    @Override
    public void after() {
        System.out.println("表演后吃大餐");
    }
}

创建代理对象

package aop;

import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;

public class ProxyFactory {
    //创建代理对象
    public static <T> T creatProxy(final Class<?> targetClass, final MethodInterceptor methodInterceptor){
        return (T) Enhancer.create(targetClass,methodInterceptor);
    }
}

测试类:

package aop;

public class Test {
    public static void main(String[] args) {
        //创建目标对象
        IActorService target = new ActorServiceImpl();
        //创建切面对象
        BaseAspect accountAspect = new ActorAspect();
        //创建代理对象
        IActorService proxy = ProxyFactory.creatProxy(target.getClass(), accountAspect);
        proxy.play();
        proxy.run();
    }
}
//运行结果:
//表演前准备
//演员正在表演!
//表演后吃大餐
//去感受生活

 

3、自动代理实现AOP

使用Spring实现aop,需要在头文件中引入aop的命名空间:

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:aop="http://www.springframework.org/schema/aop"
       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
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop-4.2.xsd">

pom.xml中引入包

        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.2</version>
        </dependency>

前置增强

主业务

package booy;

public class Actor {
    public void play(){
        System.out.println("演员正在表演");
    }
}

前置增强

package aop;

import org.springframework.aop.MethodBeforeAdvice;

import java.lang.reflect.Method;

public class BeforeService implements MethodBeforeAdvice {
    @Override
    public void before(Method method, Object[] objects, Object o) throws Throwable {
        System.out.println("开始前置增强");
        System.out.println(o+"的"+method.getName()+"方法");
        System.out.println("前置增强结束");
    }
}

测试类:

import booy.Actor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class SpringTest {
    public static void main(String[] args) {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
        Actor bean = (Actor)applicationContext.getBean("actor");
        bean.play();
    }
}
/*运行结果
开始前置增强
booy.Actor@2ddc8ecb的play方法
前置增强结束
演员正在表演
*/

配置文件spring.xml

    <bean id="beforeService" class="aop.BeforeService" />
    <bean id="actor" class="booy.Actor" />
    <aop:config>
        <!--确定需要增强的切入点-->
        <aop:pointcut id="point" expression=" execution(* booy..*(..))"/>
        <!--增强的切入点增强的类型配置-->
        <aop:advisor advice-ref="beforeService" pointcut-ref="point"/>
    </aop:config>

后置增强

主业务

package booy;

public class Actor {
    public void play(){
        System.out.println("演员正在表演");
    }
}

后置增强

package aop;

import org.springframework.aop.AfterReturningAdvice;

import java.lang.reflect.Method;

public class AfterService implements AfterReturningAdvice {
    @Override
    public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable {
        System.out.println("====我是后置增强====");
    }
}

配置文件spring.xml

    <bean id="afterService" class="aop.AfterService" />
    <bean id="actor" class="booy.Actor" />
    <aop:config>
        <!--确定需要增强的切入点-->
        <aop:pointcut id="point" expression=" execution(* booy..*(..))"/>
        <!--增强的切入点增强的类型配置-->
        <aop:advisor advice-ref="afterService" pointcut-ref="point"/>
    </aop:config>

测试类:

import booy.Actor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class SpringTest {
    public static void main(String[] args) {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
        Actor bean = (Actor)applicationContext.getBean("actor");
        bean.play();
    }
}
/*运行结果
演员正在表演
====我是后置增强====
*/

异常抛出增强

主业务

package booy;

public class Actor {
    public void play(){
        System.out.println("演员正在表演");
    }
}

异常抛出增强

package aop;

import org.springframework.aop.ThrowsAdvice;

import java.lang.reflect.Method;

public class ExceptionService implements ThrowsAdvice {
    public void afterThrowing(Method method, Object[] args,
                              Object target, Exception e) {
        System.out.println("------异常抛出增强Start------"); System.out.println(target + "的" + method.getName()
                + "方法发生异常:" + e.getMessage());
        System.out.println("------异常抛出增强End--------"); }
}

配置文件spring.xml

    <bean id="beforeService" class="aop.BeforeService" />
    <bean id="afterService" class="aop.AfterService" />
    <bean id="exceptionService" class="aop.ExceptionService" />
    <bean id="actor" class="booy.Actor" />
    <aop:config>
        <!--确定需要增强的切入点-->
        <aop:pointcut id="point" expression=" execution(* booy..*(..))"/>
        <!--增强的切入点增强的类型配置-->
        <aop:advisor advice-ref="afterService" pointcut-ref="point"/>
        <aop:advisor advice-ref="exceptionService" pointcut-ref="point"/>
    </aop:config>

测试类:

import booy.Actor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class SpringTest {
    public static void main(String[] args) {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
        Actor bean = (Actor)applicationContext.getBean("actor");
        try {
            bean.play("aa");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
/*运行结果:
aa演员正在表演
------异常抛出增强Start------
booy.Actor@1ffe63b9的play方法发生异常:手动异常
------异常抛出增强End--------
java.lang.Exception: 手动异常
	at booy.Actor.play(Actor.java:9)
	......
*/

环绕增强

主业务

package booy;

public class Actor {
    public void play(){
        System.out.println("演员正在表演");
    }
}

环绕增强

package aop;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

import java.lang.reflect.Method;

public class AroundService implements MethodInterceptor {
    @Override
    public Object invoke(MethodInvocation methodInvocation) throws Throwable {
        //被代理的对象
        Object target = methodInvocation.getThis();
        //被代理对象的方法
        Method method = methodInvocation.getMethod();
        Object[] arguments = methodInvocation.getArguments();

        try {
            System.out.println("=====前置增强=====");
            Object proceed = methodInvocation.proceed();
            System.out.println(target+"的"+method.getName()+"方法");
            System.out.println("=====后置增强=====");
            return proceed;
        } catch (Exception e) {
            System.out.println("异常增强");
            throw e;
        } finally {
            System.out.println("finally");
        }
    }
}

配置文件spring.xml

    <bean id="beforeService" class="aop.BeforeService" />
    <bean id="afterService" class="aop.AfterService" />
    <bean id="exceptionService" class="aop.ExceptionService" />
    <bean id="aroundService" class="aop.AroundService"/>
    <bean id="actor" class="booy.Actor" />
    <aop:config>
        <!--确定需要增强的切入点-->
        <aop:pointcut id="point" expression=" execution(* booy..*(..))"/>
        <!--增强的切入点增强的类型配置-->
        <aop:advisor advice-ref="aroundService" pointcut-ref="point"/>
    </aop:config>

测试类

import booy.Actor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class SpringTest {
    public static void main(String[] args) {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
        Actor bean = (Actor)applicationContext.getBean("actor");
            bean.play();
    }
}
/*运行结果:
=====前置增强=====
演员正在表演
booy.Actor@7c469c48的play方法
=====后置增强=====
finally
*/

最终增强

增强类同时实现后置增强和异常抛出增强。

引介增强

引介增强是类级别的增强,可以给目标类增加方法和属性。

主业务

package booy;

public class Actor {
    public void play(){
        System.out.println("演员正在表演");
    }
}

普通javaBean

package aop;

public class MyAdvice {
    public void test(){
        System.out.println("这个一个普通javaBean");
    }
}

配置文件spring.xml

    <bean id="actor" class="booy.Actor" />
    <bean class="aop.MyAdvice" id="advice"/>
    <aop:config>
        <!--确定需要增强的切入点-->
        <aop:pointcut id="point" expression=" execution(* booy..*(..))"/>
        <!--增强的切入点增强的类型配置-->
        <aop:aspect ref="advice">
            <aop:before method="test" pointcut-ref="point"/>
        </aop:aspect>
    </aop:config>

测试类:

import booy.Actor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class SpringTest {
    public static void main(String[] args) {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
        Actor bean = (Actor)applicationContext.getBean("actor");
            bean.play();
    }
}
/*运行结果:
这个一个普通javaBean
演员正在表演
*/

 

4、execution表达式

execution(* com.sample.service.impl.. . (..)) 

解释如下:
execution():表达式的主体;

第一个*符号:表示返回值的类型是任意类型
com.sample.service.impl:AOP所切的服务的包名,即我们的业务部分 包名后面的…:表示当前包及子包

第二个 :表示类名,即所有类。此处可以自定义,下文有举例
.*(..):表示任何方法名,括号表示参数,两个点表示任何参数类型

基本语法格式:
execution(<修饰符模式>?<返回类型模式><方法名模式>(<参数模式>)<异常 模式>?) 除了返回类型模式、方法名模式和参数模式外,其它项都是可选 的。

1)、通过方法签名定义切点

execution(public * * (..))
匹配所有目标类的public方法,第一个*代表返回类型,第二个*代表方法 名,而..代表任意入参的方法;

execution(* *To(..))
匹配目标类所有以To为后缀的方法。第一个*代表返回类型,而To代表任意 以To为后缀的方法;

2)、通过类定义切点

execution( com.baobaotao.Waiter. (..))
匹配Waiter接口的所有方法。第一个 代表返回任意类型,
com.baobaotao.Waiter. 代表Waiter接口中的所有方法;

execution( com.baobaotao.Waiter+. (..))
匹配Waiter接口及其所有实现类的方法

3)、通过类包定义切点
在类名模式串中,“. ”表示包下的所有类,而“..”表示包、子孙包下的所有类。
execution(* com.baobaotao.*(..))
匹配com.baobaotao包下所有类的所有方法;

execution(* com.baobaotao..*(..))
匹配com.baobaotao包、子孙包下所有类的所有方法,如 com.baobaotao.dao,com.baobaotao.servier以及com.baobaotao.dao.user包下的所有类的所有方法都匹配。“..”出现在类名中时,后面必须跟“*”,表示 包、子孙包下的所有类;

execution(* com.. . Dao.find*(..))
匹配包名前缀为com的任何包下类名后缀为Dao的方法,方法名必须以find 为前缀。如com.baobaotao.UserDao#findByUserId()、com.baobaotao.dao.ForumDao#findById()的方法都匹配切点。

4)、通过方法入参定义切点
切点表达式中方法入参部分比较复杂,可以使用“ ”和“..”通配符,其中“ ”表示任意类型的参数,而“..”表示任意类型参数且参数个数不限。
execution(* joke(String,int))
匹配joke(String,int)方法,且joke()方法的第一个入参是String,第二个入参 是int。joke(String,int)方法。如果方法中的入参类型是java.lang包下的类, 可以直接使用类名,否则必须使用全限定类名,如joke(java.util.List,int);

execution(* joke(String,*))匹配目标类中的joke()方法,该方法第一个入参为String,第二个入参可以 是任意类型,如joke(Strings1,String s2)和joke(String s1,double d2)都匹配, 但joke(String s1,doubled2,String s3)则不匹配;

execution(* joke(String,..))
匹配目标类中的joke()方法,该方法第 一个入参为String,后面可以有任意 个入参且入参类型不限,如joke(Strings1)、joke(String s1,String s2)和 joke(String s1,double d2,Strings3)都匹配。

execution(* joke(Object+))
匹配目标类中的joke()方法,方法拥有一个入参,且入参是Object类型或该 类的子类。它匹配joke(Strings1)和joke(Client c)。如果我们定义的切点是 execution(*joke(Object)),则只匹配joke(Object object)而不匹配joke(String cc)或joke(Client c)。
 

5、AspectJ实现AOP

主业务类

package aspectj;

public class AspectjAdviceTarget {
     //测试方法,用于测试前置增强、后置增强、环绕增强、最终增强
    public String testAdvice(String param1, Integer param2) {
        System.out.println("我是测试类的方法,参数是param1="
                + param1 + ";param2=" + param2);
        return "测试类返回";
    }    
    //测试方法,用于测试异常抛出增强
    public void testException() throws Exception {
        throw new Exception("我是测试故意抛出的异常");
    }
}

切面增强类

package aspectj;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import java.util.Arrays;

//@Aspect作用是把当前类标识为一个切面供容器读取
@Aspect
public class AspectjAdvice {
    //定义切入点表达式
    @Pointcut("execution(* aspectj.AspectjAdviceTarget.testAdvice(..))")
    public void myPoint() {
    }

    @Before("myPoint()")
    public void before(JoinPoint point) {
        System.out.println("------前置增强Start--------");
        System.out.println("连接点对象:" +
                point.getTarget().getClass());
        System.out.println("连接点方法:" +
                point.getSignature());
        System.out.println("连接点参数:" +
                Arrays.toString(point.getArgs()));
        System.out.println("------前置增强End--------");
    }

    @AfterReturning(pointcut = "myPoint()", returning =
            "returnVal")
    public void afterReturning(Object returnVal) {
        System.out.println("--------后置增强Start---------");
        System.out.println("返回值是" + returnVal);
        System.out.println("--------后置增强End-----------");
    }
    //异常抛出增强
    @AfterThrowing(pointcut = "execution(* aspectj.AspectjAdviceTarget.testException(..))", throwing = " ex")
    public void afterThrowing(Exception ex){
        System.out.println("------异常抛出增强Start--------");System.out.println("方法发生异常:"+ex.getMessage());System.out.println("------异常抛出增强End--------");
    }
    //最终增强,相当于后置增强和抛出增强的结合
    @After("myPoint()")
    public void after(){
        System.out.println("------After--------");
    }
    @Around("myPoint()")
    public void around(ProceedingJoinPoint joinPoint){
        try{
            System.out.println("------环绕增强Start------");System.out.println("连接点对象:"+
                    joinPoint.getTarget().getClass());
            System.out.println("连接点方法:"+
                    joinPoint.getSignature());
            System.out.println("连接点参数:"+
                    Arrays.toString(joinPoint.getArgs()));
            joinPoint.proceed();
            System.out.println("------环绕增强End------");
        }catch(Throwable throwable){
            System.out.println("------环绕增强捕获异常------");throwable.printStackTrace();
        }
    }
}

配置文件spring-aop.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">

    <aop:aspectj-autoproxy/>
    <bean id="aspectjAdviceTarget" class="aspectj.AspectjAdviceTarget"/>
    <bean id="aspectjAdvice" class="aspectj.AspectjAdvice"/>
</beans>

测试类

package aspectj;

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class AspectjAdviceTest {
     //前置增强、后置增强、环绕增强、最终增强都可以用这个方法测试 
    @Test
    public void testBefore() {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-aop.xml");
        AspectjAdviceTarget target = (AspectjAdviceTarget)applicationContext.getBean("aspectjAdviceTarget");
        target.testAdvice("a", 0);
    }
    // 测试异常抛出增强
    @Test
    public void testException() {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-aop.xml");
        AspectjAdviceTarget target = (AspectjAdviceTarget)applicationContext.getBean("aspectjAdviceTarget");
        try {
            target.testException();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值