Spring注解驱动

前提知识:

  • bean是对象,对象不一定是bean
  • 只有经ioc容器接管的对象才能称为bean
  • ioc容器是一个存放bean的容器,可以想象为一个Map集合。
  • ioc容器中管理的bean都是默认单实例的。

1. 使用注解和使用配置文件对比。

1.1 使用配置文件

  1. 注入bean,扫描bean,为bean的属性赋值。
  • 指定bean的id和类型。设置属性值。
  • 需要写一个bean.xml配置文件。
<bean id="people" class="com.rm.pojo.People">
    <property name="name" value="李四"/>
    <property name="age" value="16"/>
</bean>
  • 扫描项目中的所有组件,使用<context:component-scan>标签标注扫描路径。
  • 只要标注了@Reposity、@Service、@Controller、@Component注解的类都会被扫描到,并注入到ioc容器。
<context:component-scan base-package="com.rm"></context:component-scan>
  1. 使用ClassPathXmlApplicationContext获取一个ioc容器,再通过getBean方法获取容器中的bean
  • 使用的多态知识,父类引用指向子类对象。
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean.xml");
Person person = (Person) applicationContext.getBean("person");

1.2 使用注解

1.注入bean,为bean设置值,扫描所有组件。

  • 新建一个Myconfig类,添加@configuration注解,表明该类是个配置类。该类也将注入到ioc容器中。
  • @bean注解标在一个方法上面,该方法返回值类型就是bean的类型,方法名就是bean的id。
  • @ComponentScan注解用来扫描所有包,可以根据需求指定哪些组件需要扫描,哪些组件需要排除。
@Configuration
@ComponentScan("com.rm")
public class Myconfig{
    @bean
    public Person person(){
      return new Person("张三",15);
    }
}
  1. 获取bean
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(Myconfig.class);
Person person = (Person) applicationContext.getBean("person");

2. IOC

IOC和AOP是Spring两大核心思想。

  • IOC的本质是将对象的创建,赋值,初始化,调用.....交给Spring统一管理。
  • AOP的本质是切面编程,允许程序在运行期间获取每个类的方法,并对这些方法进行一些增强处理。

2.1 组件注册(将对象交给ioc容器)

Spring应用启动时,会将所有需要交给IOC容器管理的bean统一注册给IOC。IOC容器来统一管理这些bean,需要使用bean时。可以直接从容器中获取。(默认是单实例bean,只存在一个(id和type唯一))。
bean在容器中有很多属性,比如id,类型(属于什么class),生命周期等。

  • 对于单实例bean来说,通类型的bean可以存在多个(实现类,继承等)。但是id只能存在一个。
  • 获取bean时,根据id和类型来唯一确定一个bean。如果存在相同的,则报错。
  • bean经过创建---赋值---初始化一系列生命周期后注入到ioc容器中。

2.1.1 @Configuration

该注解用来代替原来的beans.xml配置文件,声明该类是一个配置类。同时将该配置类注入到容器。该配置类bean的id是类名首字母小写。类型是class类型。

  • @ComponentScan注解用来扫描组件包。替代原来的<context:component-scan>标签。
  • @bean注解用来在ioc容器中注入bean。返回值类型就是bean类型。方法名就是bean的id(默认)。
@Configuration
@ComponentScan("com.rm")
public class Myconfig{
    @bean
    public Person person(){
      return new Person("张三",15);
    }
}

2.1.2 @ComponentScan

该注解通过扫描文件夹,批量将文件夹内的组件注入到ioc容器中。可以自定义条件只注入哪些组件或者排除哪些组件。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Repeatable(ComponentScans.class)
public @interface ComponentScan {
	@AliasFor("basePackages")
	String[] value() default {};
	@AliasFor("value")
	String[] basePackages() default {};
}

excludeFilters排除组件规则,值是一个@filter注解集合

例:排除所有含Controller和Service注解的组件

@ComponentScan(value="cn.rm",@excludeFilters={@filter(type=FilterType.ANNOTATION,classes={Controller.class,Service.class})})

或  type默认就是FilterType.ANNOTATION
@ComponentScan(basePackages="cn.rm",@excludeFilters={@filter(classes={Controller.class,Service.class})})

includeFilters只注入组件规则,值是一个@filter注解集合

例:只注入含Reposity注解的组件。主要关闭默认组件注入规则。

@ComponentScan(value="cn.rm",@includeFilters={@filter(type=FilterType.ANNOTATION,classes={Reposity.class})},useDefaultFilters=false)

或  type默认就是FilterType.ANNOTATION
@ComponentScan(basePackages="cn.rm",@includeFilters={@filter(classes={Reposity.class})},useDefaultFilters=false)

自定义FilterType过滤规则

FilterType.ANNOTATION 按照注解进行过滤

@ComponentScan(value="cn.rm",@excludeFilters={@filter(type=FilterType.ANNOTATION,classes={Controller.class})})

FilterType.ASSIGNABLE_TYPE 按照给定的类型进行过滤

  • UserService.class类型都会被过滤,包括子类或实现类。
@ComponentScan(value="cn.rm",@excludeFilters={@filter(type=FilterType.ANNOTATION,classes={UserService.class})})

2.1.3 @Scope

该注解用来表示注入到ioc的bean的作用域是什么,默认是SCOPE_SINGLETON (单实例)的。ioc容器中的bean默认都是单实例的。无论获取多少次,取到的bean都是同一个。

SCOPE_PROTOTYPE  多实例 (ioc容器创建时不会生成实例。``获取该实例时``,才会创建。每次获取都会创建一个新的。)
SCOPE_SINGLETON  单实例 (ioc容器创建的时候就会生成该实例并放在容器内)
SCOPE_REQUEST    web项目用到(同一次请求创建一个实例)
SCOPE_SESSION    同一个session创建一个实例

2.1.4 @Lazy

该注解只针对单实例bean有效。

  • 默认:单实例默认在容器创建的时候创建对象。
  • 使用@Lazy:容器启动不创建对象,第一次使用(获取)bean时创建对象,并实例化(为属性赋值)。
@Lazy
@Scope("singleton")
@Bean
public User user() {
    return new User();
}

2.1.5 @Conditional(重要)

该注解作用:按照条件给容器中注册bean。spring源码大量使用。

  • 配置在方法上,满足条件该bean才会被注册。
  • 配置在类上,满足条件,该配置类内的所有bean都会被注册。

测试:存在Bill GatesLinus两个对象。

  • 判断当前系统是Windows时注入Bill Gates,判断当前系统是Linux时注入Linus

MainConfig

@Configuration
public class MainConfig {
    //参数是匹配规则,返回true说明匹配,注入该bean。
    @Conditional({WindowsConditional.class})
    @Bean("bill")
    public User user1(){
      return new User("Bill Gates");
    }

    @Conditional({LinuxConditional.class})
    @Bean("linus")
    public User user2(){
        return new User("Linus");
    }
}

WindowsConditional

public class WindowsConditional implements Condition {

    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        Environment environment = context.getEnvironment();
        String property = environment.getProperty("os.name");
        if (property.contains("Windows")){
            return true;
        }
        return false;
    }
}

LinuxConditional

public class LinuxConditional implements Condition {
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        Environment environment = context.getEnvironment();
        String property = environment.getProperty("os.name");
        if (property.contains("linux")){
            return true;
        }
        return false;
    }
}

测试类

MyTest

public class MyTest {
    public static void main(String[] args) {
        //如果完全使用配置类方式,就只能通过AnnotationConfig上下文来获取容器,通过配置类的class对象加载
        ApplicationContext context = new AnnotationConfigApplicationContext(MainConfig.class);
        //查看容器中注册的所有组件
        String[] names = context.getBeanDefinitionNames();
        for (String name : names) {
            System.out.println(name);
        }
    }
}

2.1.6 @Import(重要)

源码中也大量使用该注解

  • 通过@import注解快速注入一个组件。(调用无参构造器创建一个对象)
  • 组件id默认全类名,组件类型即class类型。
@Configuration
@Import(Red.class)
public class RedConfig {
}
------------------------------
public class Red {
  //默认存在无参构造器
}
------------------------------
@Test
public void Test1(){
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(RedConfig.class);
    String[] names = context.getBeanDefinitionNames();
    for (String name : names) {
        //打印所有组件的名字,即id
        System.out.println(name);
    }
    context.close();
}
------------------------------
结果
redConfig
cn.rm.pojo.Red

2.1.6.1 ImportSelector接口

编写实现类实现该接口,重写selectImports方法。方法的返回值(全路径类名)即是要注入到ioc容器的bean。可以是多个。注入到ioc容器的bean的id即是全路径名。类型即是该class类。

使用

  • 编写一个实现类实现该接口,重写方法,返回需要导入的组件全类名
public class MyImportSelector implements ImportSelector {
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[]{"cn.rm.pojo.User"};
    }
}
  • 将该实现类注入到组件
@Configuration
@Import(MyImportSelector.class)
public class MainConfig {
}
--------------------------------
//组件id:cn.rm.pojo.User

2.1.6.2 ImportBeanDefinitionRegistrar接口(重要)

public interface ImportBeanDefinitionRegistrar {
  public void registerBeanDefinitions(
	AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry);
}

使用该接口 方法的两个参数:

  • AnnotationMetadata:当前类的注解信息
  • BeanDefinitionRegistry:BeanDefinition注册类
    • 调用BeanDefinition.registerBeanDefinition方法将需要注册的bean根据条件手工注入。

使用该接口

public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {

    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        //判断容器中是否有bean的id为mainConfig的组件,有就执行下一步
        boolean b = registry.containsBeanDefinition("mainConfig");
        if (b){
            //指定bean的定义信息(bean类型,bean作用域等...)
            BeanDefinition beanDefinition = new RootBeanDefinition(User.class);
            //指定bean的id和类型并注入到容器
            registry.registerBeanDefinition("user1",beanDefinition);
        }
    }
}

将该实现类注入到ioc容器

  • 此时容器是存在mainConfig的组件,所以判断生效。注入id为user1,类型为User.class的组件。
@Configuration
@Import(MyImportBeanDefinitionRegistrar.class)
public class MainConfig {
}
--------------------------------------------------
//user1

2.1.7 FactoryBean接口

通过创建一个bean工厂,在这个工厂里面生成需要的bean。

public class UserFactoryBean implements FactoryBean {
    //生产bean
    public Object getObject(){
        return new User();
    }
    //bean类型
    public Class<?> getObjectType() {
        return User.class;
    }
    //true:单例。false:多例
    public boolean isSingleton() {
        return true;
    }
}
  • 将这个工厂bean注入到容器中。
@Configuration
public class MyConfig(){
  @bean
  public UserFactoryBean userFactoryBean(){
    return new UserFactoryBean();
  }
}
  • 查看容器中所有的bean。
ApplicationContext context = new AnnotationConfigApplicationContext(MainConfig.class);
//查看容器中注册的所有组件
String[] names = context.getBeanDefinitionNames();

//mainConfig
//userFactoryBean
  • 查看id为userFactoryBean的组件class类型。实例类型是工厂生产的bean类型
ApplicationContext context = new AnnotationConfigApplicationContext(MainConfig.class);
Object user = context.getBean("userFactoryBean");
System.out.println("==="+user.getClass());
----------------------------------------------------------------------------------------
//===class cn.rm.pojo.User

如果不想获取生产的bean。想要获取工厂bean本身。

Object userFactoryBean = context.getBean("&userFactoryBean");
System.out.println("---"+userFactoryBean.getClass());
----------------------------------------------------------------
//---class cn.rm.pojo.UserFactoryBean

2.2 bean的生命周期

bean的生命周期由ioc容器来管理:bean创建----赋值----初始化前----初始化----初始化后----销毁前----销毁的过程

  1. bean创建
  • 单实例:在容器启动的时候(new AnnotationConfigApplicationContext())调用无参构造器创建对象。
  • 多实例和懒加载实例:在每次获取时创建对象。

2.初始化bean

  • 对象创建完成,并赋值后,调用初始化方法。
  1. 销毁bean
  • 单实例和懒加载单实例:容器关闭的时候调用销毁方法
  • 多实例:容器不会管理这个bean,只是你需要的时候给你创建,容器不会调用销毁方法。

2.2.1 bean的初始化和销毁

我们可以自定义初始化和销毁方法,容器在bean进行到当前生命周期的时候会自动调用自定义的初始化或销毁过程。

新建一个类,自定义初始化和销毁方法

public class Car {
    public Car() {
        System.out.println("car...construct...");
    }
    public void init(){
        System.out.println("car....init...");
    }
    public void destroy(){
        System.out.println("car...destroy...");
    }
}

定义一个配置类,注入Car组件,通过@Bean注解指定初始化和销毁方法

@Configuration
public class MyCarConfig {
    @Bean(name = "car1",initMethod = "init",destroyMethod = "destroy")
    public Car car(){
        return new Car();
    }
}

测试

2.2.1.1 指定初始化和销毁方法

1.使用@bean注入组件时

  • 指定initMethod 和destroyMethod 确定bean中哪些方法是初始化方法和销毁方法。
public void init(){
    System.out.println("car....init...");
}
public void destroy(){
    System.out.println("car...destroy...");
}
---------------------------------------------
使用@bean注入组件时,指定initMethod 和destroyMethod 确定bean中哪些方法是初始化方法和销毁方法。
@Bean(name = "car",initMethod = "init",destroyMethod = "destroy")
public Car car(){
    return new Car();
}

2.通过实现接口,重写接口方法。

  • 实现InitializingBean接口,重写afterPropertiesSet方法,该方法在bean创建好并赋值之后执行。(定义初始化逻辑)
  • 实现DisposableBean接口,重写destroy方法,该方法在单实例bean销毁后执行。

3.使用JSR250规范定义的两个注解

  • 注解标注在bean所在类的方法上。
  • @PostConstruct:该注解标注的方法在bean创建完成并赋值之后调用。来执行初始化方法。
  • @PreDestroy:在容器销毁之前调用该注解标注的方法,用来通知清理工作。
@PostConstruct
public void init(){
    System.out.println("car....init...");
}
@PreDestroy
public void destroy(){
    System.out.println("car...preDestroy...");
}
---------------------------------------------
@Bean
public Car car(){
    return new Car();
}

2.2.2 后置处理器BeanPostProcessor

在bean初始化前后进行一些工作。

实现BeanPostProcessor接口

  • postProcessBeforeInitialization:在bean初始化之前进行一些工作。
  • postProcessAfterInitialization:在bean初始化之后进行一些工作。
@Component
public class MyBeanPostProcess implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("后置处理器处理前--->"+beanName+bean);
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("后置处理器处理后--->"+beanName+"---"+bean);
        return bean;
    }
}
---------------------------------------------
// 先调用构造器创建对象,然后赋值(见下一节原理),然后执行初始化前方法,初始化,初始化后方法。容器关闭的时候调用销毁方法
car...construct...
后置处理器处理前--->car---cn.rm.pojo.Car@4d339552
car....init...
后置处理器处理后--->car---cn.rm.pojo.Car@4d339552

2.2.2.1 后置处理器BeanPostProcessor原理

//1.加载配置文件,创建ioc容器
 AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig.class);
---------------------------------------------------------------------------------------------------------
//2.刷新容器
refresh();
---------------------------------------------------------------------------------------------------------
//3.实例化所有剩余的(非惰性初始化)单例(创建bean)
finishBeanFactoryInitialization(beanFactory);
//3.1实例化前
beanFactory.preInstantiateSingletons();
//3.2判断容器中是否存在该bean
getBean(beanName);
return doGetBean(name, null, null, false);
//3.3创建该bean的实例。
sharedInstance = getSingleton(beanName, () -> {...}
//3.4返回在给定名称下注册的(原始)单例对象,如果尚未注册,则创建并注册一个新对象。 @return 注册的单例对象
singletonObject = singletonFactory.getObject();
---------------------------------------------------------------------------------------------------------
//3.5 首次加载,不存在该bean对象,于是创建bean
return createBean(beanName, mbd, args);
Object beanInstance = doCreateBean(beanName, mbdToUse, args);
---------------------------------------------------------------------------------------------------------
//4.给bean赋值
populateBean(beanName, mbd, instanceWrapper);
---------------------------------------------------------------------------------------------------------
//5.初始化bean
exposedObject = initializeBean(beanName, exposedObject, mbd);
---------------------------------------------------------------------------------------------------------

初始化bean方法(initializeBean)体内容

if (mbd == null || !mbd.isSynthetic()) {
  //执行初始化之前方法
  wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
}
try {
  //执行初始化方法,如@PostConstruct标注的方法。或@Bean表明的初始化方法。
  invokeInitMethods(beanName, wrappedBean, mbd);
}
if (mbd == null || !mbd.isSynthetic()) {
  //执行初始化之后方法
  wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
}

执行初始化前方法(applyBeanPostProcessorsBeforeInitialization)方法体

  • 遍历获取所有bean后置处理器(只要实现了BeanPostProcessor接口都是),挨个执行postProcessBeforeInitialization方法
  • 一旦返回null,跳出循环。
@Override
public Object applyBeanPostProcessorsBeforeInitialization(Object existingBean, String beanName)
		throws BeansException {
	Object result = existingBean;
	for (BeanPostProcessor beanProcessor : getBeanPostProcessors()) {
		Object current = beanProcessor.postProcessBeforeInitialization(result, beanName);
		if (current == null) {
			return result;
		}
		result = current;
	}
	return result;
}

2.2.2.2 Spring底层使用后置处理器BeanPostProcessor

例:ApplicationContextAware接口

该接口功能:可以在组件中注入ioc容器。功能是ApplicationContextAwareProcessor提供的。

class ApplicationContextAwareProcessor implements BeanPostProcessor {
	private final ConfigurableApplicationContext applicationContext;
	/**
         * 通过给定的ioc容器创建一个ApplicationContextAware后置处理器 
	 * Create a new ApplicationContextAwareProcessor for the given context.
	 */
	public ApplicationContextAwareProcessor(ConfigurableApplicationContext applicationContext) {
                //将ioc容器赋值给该类的属性applicationContext,方便其他方法调用。
		this.applicationContext = applicationContext;
		this.embeddedValueResolver = new EmbeddedValueResolver(applicationContext.getBeanFactory());
	}

        //
	@Override
	@Nullable
	public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
		if (!(bean instanceof EnvironmentAware || bean instanceof ApplicationContextAware)){
			return bean;
		}
	        else {
		    invokeAwareInterfaces(bean);
		}
	    return bean;
	}

	private void invokeAwareInterfaces(Object bean) {
		if (bean instanceof ApplicationContextAware) {
			((ApplicationContextAware) bean).setApplicationContext(this.applicationContext);
		}
	}
}
@Component
public class MyTestApp implements ApplicationContextAware {
    //想要在该类中其他方法使用ioc容器,只需要将setApplicationContext方法的参数保存在applicationContext属性即可。
    private ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext=applicationContext;
        String[] names = applicationContext.getBeanDefinitionNames();
        for (String name : names) {
            System.out.println("=------>"+name);
        }
    }
}

2.2.3 Bean的属性赋值

2.2.3.1 @Value

  • 基本数值
  • SPEL表达式 #{}
@Value("张三")
private String name;

@Value("#{20-2}")
private Integer age; 

2.2.3.2 @PropertySource

使用该注解读取外部配置文件的key/value,保存到运行的环境变量。加载完外部的配置文件后使用${}取值

person.name=张三
@PropertySource("classpath:/person.properties")
@Configuration
public class MyConfig{
  @Bean
  public Person person(){
    return new Person();
  }
}
--------------------------------------------------
public class Person{
  @Value("${person.name}")
  private String name;
}

2.2.4 Bean的自动装配

Spring利用依赖注入(DI),完成对IOC容器中各个组件的依赖关系赋值。
AutowiredAnnotationBeanPostProcessor:后置处理器,解析完成自动装配功能。

2.2.4.1 @AutoWired等

  • 默认有限按照组件类型从容器中找该class类型的组件。
  • 如果只有一个:使用该组件,将找到的组件赋值给该属性。
  • 如果有多个:使用属性名作为id进行查找。如果查到一个使用该组件。查到多个,报错。
  • 如果容器中不存在,查不到该注解时不想让报错。@AutoWired(required=false)。找不到就赋值null。
public Class MyServiceImpl{
  @AutoWired
  private MyDao myDao;
}

@Qualifer注解

  • 使用该注解的value值作为id去ioc容器查找。而不是用默认的属性名去查找。
public Class MyServiceImpl{
  @AutoWired
  @Qualifer("dao111")
  private MyDao myDao;
}

2.2.4.2 @Resource等

  • @Resource(JSR250规范)默认按照属性名作为组件id去查找。可以通过@Resource(name="")指定组件id名。
  • @Inject(JSR330规范)需要导入javax.inject的包。功能和 @AutoWired一样。
  • 这两个是java规范的注解
public Class MyServiceImpl{
  @Resource
  private MyDao myDao;
}

#### 2.2.4.3 方法、构造器自动装配

> 自动装配注解标注在方法上。

- 标注在方法上,Spring容器创建该对象时,就会调用该方法,完成该方法参数的赋值。
- 方法使用的``参数``,``自定义类型``从ioc容器中获取。

```java
private Car car;

@AutoWired
public void setCar(Car car){
  this.car =car;
}

自动装配标注在构造器上

  • 默认注入到ioc容器中的组件,调用的都是无参构造器创建对象。之后再进行初始化及赋值操作。
  • 构造器要用的组件,都是从容器中获取的。
//只存在有参构造时生效。存在无参构造时,使用的是无参构造创建的对象。
@AutoWired
public Boss(Car car){
  this.car=car;
}

标注在参数上

  • 也是从容器中获取

2.2.4.4 Aware自动装配原理

自定义组件想要使用Spring容器底层的一些组件(如:ApplicationCopntext、BeanFactory等)

  • 需要自定义组件所属的类实现xxxAware接口。在创建该对象时,会调用接口规定的方法,注入底层组件。

继承Aware接口的接口

自定义组件响应获取ioc容器,实现ApplicationContextAware接口。

  • 在创建Car对象时,会调用setApplicationContext方法,获取到ioc容器。
  • 将ioc容器赋值给该类的属性,其他方法即可使用ioc容器。
public class Car implements ApplicationContextAware {

    private ApplicationContext applicationContext;

    public Car() {
        System.out.println("car...construct...");
    }

  
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext=applicationContext;
    }

     public void printBeanNames(){
        String[] names = applicationContext.getBeanDefinitionNames();
        for (String name : names) {
            System.out.println(name);
        }
    }
}

3. AOP

什么是AOP

  • 面向切面编程(方面),利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
  • 通俗描述:不通过修改源代码方式,在主干功能里面添加新功能
  • AOP底层使用的是动态代理模式。

有两种情况的动态代理。

  1. 需要增强的类有接口,使用JDK动态代理。
  • 创建接口实现类代理对象,增强类中的方法。

  1. 没有接口,使用cglib动态代理。
  • 创建子类的代理对象,增强类中的方法。

3.1 AOP中的一些术语

连接点

  • 类里面哪些方法可以被增强,这些方法称为连接点。

切入点

  • 实际被真正增强的方法,叫做切入点。
  • 比如上面四个方法都能被增强,这四个方法都称为连接点。但实际只有add()方法被增强了,那add()方法被称为切入点。

通知(增强)

  • 实际增强的逻辑部分称为通知(增强)。
  • 通知有多种类型。前置通知、后置通知、环绕通知、异常通知、最终通知(类似于try..catch中的finally)。

切面

  • 切面是一个动作,把通知应用到切入点的过程称为切面。

切入点表达式

  • 作用:用来确定对哪个类里面的哪个方法进行增强。
  • execution([权限修饰符][返回类型][类全路径][方法名][参数列表])

对UserServiceImpl中的add方法进行增强

  • 返回类型可以不写,修饰权限符*表示所有权限符。(..)表示参数列表。
execution(* com.rm.service.UserServiceImpl.add(..))

对UserServiceImpl中的所有方法进行增强

execution(* com.rm.service.UserServiceImpl.*(..))

对com.rm.service包中的所有类的所有方法进行增强

execution(* com.rm.service.*.*(..))

3.2 相同切入点抽取(重要)

  • 将相同的切入点通过@Pointcut抽取。
  • 再使用该切入点,加上方法名即可。
@Pointcut("execution(* cn.rm.service.UserServiceImpl.*(..))")
public void pointDemo(){
}

@Before("pointDemo()")
public void before() {
    System.out.println("前置通知====");
}

3.3 使用@Aspect注解实现aop

创建接口和实现类

实际业务方法

public class DivServiceImpl implements DivService {
    public int div(int i,int j) {
        System.out.println("方法被执行了.....");
        return i/j;
    }
}

创建增强类

对业务方法进行增强

  • 可以获取方法名称,参数名,返回值。异常信息。
@Aspect
public class DivPointCut {
    //相同切入点抽取
    @Pointcut("execution(* cn.rm.service.DivServiceImpl.*(..))")
    public void pointDemo(){

    }
    @Before("pointDemo()")
    public void before(JoinPoint point) {
        String methodName = point.getSignature().getName();
        List<Object> list = Arrays.asList(point.getArgs());
        System.out.println("前置通知执行,方法名:"+methodName+"参数列表:"+list);
    }
    @After("pointDemo()")
    public void after(JoinPoint point) {
        String methodName = point.getSignature().getName();
        List<Object> list = Arrays.asList(point.getArgs());
        System.out.println("后置通知执行,方法名:"+methodName+"参数列表:"+list);
    }
    @AfterReturning(value = "pointDemo()",returning="result")
    public void afterReturning(JoinPoint point,Object result) {
        String methodName = point.getSignature().getName();
        List<Object> list = Arrays.asList(point.getArgs());
        System.out.println("返回通知执行,方法名:"+methodName+"参数列表:"+list+"返回值:"+result);
    }
    @Around(value = "pointDemo()")
    //若方法返回值是void。afterReturning获取到的result也是null。因为先执行的环绕通知。
    public Object around(ProceedingJoinPoint point) throws Throwable {
        System.out.println("环绕执行前====");
        Object o = point.proceed();
        System.out.println("环绕执行后====");
        System.out.println("方法执行结果"+o);
        return o;
    }
    @AfterThrowing(value = "pointDemo()",throwing = "exception")
    public void afterThrowing(JoinPoint point,Exception exception)  {
        System.out.println(point.getSignature().getName()+"方法遇到异常执行====异常信息:"+exception.getMessage());
    }
}

注入ioc容器

@Configuration
@EnableAspectJAutoProxy
public class MyConfig {

    @Bean
    public DivService divService(){
        return new DivServiceImpl();
    }
    @Bean
    public DivPointCut divPointCut(){
        return new DivPointCut();
    }
}

测试(正常情况)

public class MyTest {
public static void main(String[] args) {
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class);
    DivService bean = (DivService) context.getBean("divService");
    bean.div(2,1);
    context.close();
}
---------------------------------------------------------------------------------------------------------------
环绕执行前====
前置通知执行,方法名:div参数列表:[2, 1]
方法被执行了.....
环绕执行后====
方法执行结果2
后置通知执行,方法名:div参数列表:[2, 1]
返回通知执行,方法名:div参数列表:[2, 1]返回值:2

测试(异常)

public class MyTest {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class);
        DivService bean = (DivService) context.getBean("divService");
        bean.div(2,0);
        context.close();
    }
}
---------------------------------------------------------------------------------------------------------------
环绕执行前====
前置通知执行,方法名:div参数列表:[2, 0]
方法被执行了.....
后置通知执行,方法名:div参数列表:[2, 0]
div方法遇到异常执行====异常信息:/ by zero

总结:
使用AOP分三步

  • 将业务逻辑和切面类注入到容器,告诉spring哪个是切面类(@Aspect)
  • 在切面类上的每个方法上标注注解通知,告诉spring何时运行(切入点表达式)
  • 开启基于注解的AOP模式(@EnableAspectJAutoProxy)

3.4 增强优先级

  • 如果有两个类都对一个类中的一个切入点进行增强,可以通过@Order注解设置优先级。
  • 数值越小,优先级越高。
@Aspect
@Order(1)
public class AnnPointCut1 {
}
@Aspect
@Order(2)
public class AnnPointCut2 {
}

3.5 AOP原理

。。。。。。。。。。。。。

4. 事务

4.1 事务特性

ACID原则

  • 原子性:一个事务中的多个语句要么都成功,要么都失败。
  • 一致性:事务执行前后,数据总量不变。
  • 隔离性:多个角色操作同一个记录。多个事务直接相互隔离,不会产生影响。
  • 持久性:事务一经提交,持久化到数据库。

4.2 事务操作

声明式事务(使用),编程式事务(不推荐)。

声明式事务:基于注解(使用),基于配置文件(不推荐)。

  • 在spring中进行声明式事务管理,底层使用的就是AOP。

使用事务需要导入数据库相关依赖

  • 声明式事务使用的还是数据库的事务。所以操作的数据库引擎需要支持事务才能用(INNODB)。

@Transactional注解

  • 该注解标注的方法表示该方法是一个事务方法。
  • 在SpringBoot中只需要加该注解即可实现事务功能。
  • 只要是RuntimeExceptionRuntimeException下面的子类抛出的异常 @Transactional都可以回滚的。
  • 如果需要支持回滚Exception异常可以用@Transactional(rollbackFor = Exception.class)

注解失效场景

  • 注解标注的方法不是public修饰的
  • 异常被try...catch捕获了,没有抛出。
  • 该方法所在的类没有注入到ioc容器。

业务逻辑层

public class userService{
  @AutoWired
  private UserMapper userMapper

  @Transactional
  public void  insertUser(User user){
      return userMapper.insertUser(user);
  }
}

4.3 事务原理

。。。。。。。。。

posted @ 2022-01-07 15:02  初夏那片海  阅读(63)  评论(0)    收藏  举报