Java学习

本文详细介绍了Spring框架的核心特性,包括IOC(控制反转)和DI(依赖注入),以及如何通过Bean容器管理类的实例化。同时,讲解了面向切面编程(AOP)的概念,如动态代理。注解方面,讨论了内置注解的用途和元注解的含义。文章还深入探讨了Spring Bean的生命周期,包括作用域、初始化和销毁方法。最后,提到了Spring AOP的装配和使用,以及如何通过注解实现精准拦截。
摘要由CSDN通过智能技术生成

一、Spring框架: 

        IOC控制反转 ——> 降低耦合度

                1、私有属性保存依赖对象,且通过构造函数传入。

                2、Spring控制new过程:Spring启动时先实例化依赖,再实例化所需的类。

        Bean 是 Bean容器(Spring IOC容器) 生成的对象。

        Bean 容器:管理类的实例化、依赖的实例化、依赖的传入

        DI:依赖传入 ——》传递服务给客户端

spring IoC

在给类内成员上直接写@AutoWired的时候, IDE提示尽量不要给成员直接注入, 查了一番资料后找到了一个靠谱的说法

1. 必要的依赖使用构造方法注入

2. 可选的依赖使用set方法注入

3. 尽量不使用字段注入

1. 降低类之间的耦合度

不使用IoC,类的创建形成多对多依赖关系,耦合度高。IoC相当于一个中间层,IoC与类之间是一对多依赖,将多对多关系变为一对多关系,减少系统整体耦合度

2. 实例复用

不使用IoC,获取一个类的实例时,每次都要实例化,增加空间和时间。增加了IoC,IoC对实例提供了多种生命周期,可以提高实例的复用。

3. 更方便根据不同环境创建不同实例

方便程序在不同环境下运行

另外,IoC跟依赖注入很像,貌似前者是设计原则,后者是设计模式的一种,不知道这种理解对不对

        AOP面向切面编程——〉 动态代理    

二、注解 Annotation

        注解可以理解为标签!!!

        本质是 @interface 修饰的类

总共7个内置注解:

3个作用于代码的注解- @Override、@Deprecated、@SuppressWarnings (java.lang)

  • @Override - 检查该方法是否是重写方法。如果发现其父类,或者是引用的接口中并没有该方法时,会报编译错误。
  • @Deprecated - 标记过时方法。如果使用该方法,会报编译警告。
  • @SuppressWarnings - 指示编译器去忽略注解中声明的警告。

4个用于注解其他注解,也称为元注解- @Retention、@Documented、@Target、@Inherited

  • @Retention - 标识这个注解怎么保存,是只在代码中,还是编入class文件中,或者是在运行时可以通过反射访问。(用来指定Annotation的RetentionPolicy属性)
  • @Documented - 标记这些注解是否包含在用户文档中。
  • @Target - 标记这个注解应该是哪种 Java 成员。(用来指定Annotation的ElementType属性)
  • @Inherited - 标记这个注解是继承于哪个注解类(默认 注解并没有继承于任何子类)

ElementType.java

package java.lang.annotation;
public enum ElementType {
    TYPE,               /* 类、接口(包括注释类型)或枚举声明  */

    FIELD,              /* 字段声明(包括枚举常量)  */

    METHOD,             /* 方法声明  */

    PARAMETER,          /* 参数声明  */

    CONSTRUCTOR,        /* 构造方法声明  */

    LOCAL_VARIABLE,     /* 局部变量声明  */

    ANNOTATION_TYPE,    /* 注释类型声明  */

    PACKAGE             /* 包声明  */
}

RetentionPolicy.java

package java.lang.annotation;
public enum RetentionPolicy {
    SOURCE,            /* Annotation信息仅存在于编译器处理期间,编译器处理完之后就没有该Annotation信息了  */

    CLASS,             /* 编译器将Annotation存储于类对应的.class文件中。默认行为  */

    RUNTIME            /* 编译器将Annotation存储于class文件中,并且可由JVM读入 */
}

一个Annotation对应唯一个RetentionPolicy,可对应多个ElementType.

Annotation通用定义:

@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation1 {
}


@Component  ——〉 识别为 Spring Bean类

@Repository  ——〉 标注一个DAO组件类

@Service  ——〉 标注业务逻辑组件类

@Controller ——〉标注控制器组件类

@Autowired ——〉随拿随用,组件类对象不需要初始化(自动装配)

@Resource ——〉@Autowired的子集,注解偏基础设施类(中间件)

@SLF4J ——〉日志接口

代码结构:

@Common ——〉通用配置

@Controller

@Service ——〉具体业务逻辑服务层

@Manager ——〉通用业务逻辑层

@wrapper ——〉 外调层、HSF(RPC)等。

三、Spring IOC容器

Bean

什么是javaBean?  简单的说:就是普通Java类,只是满足一定的特征

(1)JavaBean 类必须有一个没有参数的构造函数;此构造函数在使用《jsp:useBean》实例化JavaBean 类时调用,若JavaBean 类内没有任何构造函数,则系统会自动生成一个没有任何参数的构造函数。
(2)JavaBean 内的属性都应该定义为私有的。这样可以保证数据的完整性和封装性。
(3)属性值可以通过setXxx 和getXxxx 来操作。需要注意的是,变量的第一个字母是小写的,但方法名内的第一个字母必须大写,此为必须遵守的约定。
javaBean在MVC设计模型中是model,又称模型层,在一般的程序中,我们称它为数据层,就是用来设置数据的属性和一些行为。

  • 每个<bean ...>都有一个id标识,相当于Bean的唯一ID;
  • 在userServiceBean中,通过<property name="..." ref="..." />注入了另一个Bean;
  • Bean的顺序不重要,Spring根据依赖关系会自动正确初始化。

<?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
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="userService" class="com.itranswarp.learnjava.service.UserService">
        <property name="mailService" ref="mailService" />
    </bean>

    <bean id="mailService" class="com.itranswarp.learnjava.service.MailService" />
</beans>

把上述XML配置文件用Java代码写出来,就像这样:

UserService userService = new UserService();
MailService mailService = new MailService();
userService.setMailService(mailService);

Bean自动装配

最后一步,我们需要创建一个Spring的IoC容器实例,然后加载配置文件,让Spring容器为我们创建并装配好配置文件中指定的所有Bean,这只需要一行代码

ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");

接下来,我们就可以从Spring容器中“取出”装配好的Bean然后使用它:

// 获取Bean:
UserService userService = context.getBean(UserService.class);
// 正常调用:
User user = userService.login("bob@example.com", "password");

BeanFactory和ApplicationContext的区别在于,BeanFactory的实现是按需创建,即第一次获取Bean时才创建这个Bean,而ApplicationContext会一次性创建所有的Bean

实际上,ApplicationContext接口是从BeanFactory接口继承而来的,并且,ApplicationContext提供了一些额外的功能,包括国际化支持、事件和通知机制等。通常情况下,我们总是使用ApplicationContext,很少会考虑使用BeanFactory。

Annotation配置Bean

使用Spring的IoC容器,实际上就是通过类似XML这样的配置文件,把我们自己的Bean的依赖关系描述出来,然后让容器来创建并装配Bean。一旦容器初始化完毕,我们就直接从容器中获取Bean使用它们。

使用XML配置的优点是所有的Bean都能一目了然地列出来,并通过配置注入能直观地看到每个Bean的依赖。它的缺点是写起来非常繁琐,每增加一个组件,就必须把新的Bean配置到XML中。

我们可以使用Annotation配置,可以完全不需要XML,让Spring自动扫描Bean并组装它们。

@Component
public class UserService {
    @Autowired
    MailService mailService;

    ...
}

这个@Component注解就相当于定义了一个Bean,它有一个可选的名称,默认是mailService,即小写开头的类名

使用@Autowired就相当于把指定类型的Bean注入到指定的字段中。和XML配置相比,@Autowired大幅简化了注入,因为它不但可以写在set()方法上,还可以直接写在字段上,甚至可以写在构造方法中:

@Component
public class UserService {
    MailService mailService;

    public UserService(@Autowired MailService mailService) {
        this.mailService = mailService;
    }
    ...
}

编写一个AppConfig类启动容器:


@ComponentScan
public class AppConfig {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
        UserService userService = context.getBean(UserService.class);
        User user = userService.login("bob@example.com", "password");
        System.out.println(user.getName());
    }
}

AppConfig标注了@Configuration,表示它是一个配置类

使用的实现类是AnnotationConfigApplicationContext,必须传入一个标注了@Configuration的类名。

此外,AppConfig还标注了@ComponentScan,它告诉容器,自动搜索当前类所在的包以及子包,把所有标注为@Component的Bean自动创建出来,并根据@Autowired进行装配。

整个工程结构如下:

使用Annotation配合自动扫描能大幅简化Spring的配置,我们只需要保证:

  • 每个Bean被标注为@Component并正确使用@Autowired注入;
  • 配置类被标注为@Configuration和@ComponentScan;
  • 所有Bean均在指定包以及子包内。

使用@ComponentScan非常方便,但是,我们也要特别注意包的层次结构。通常来说,启动配置AppConfig位于自定义的顶层包(例如com.itranswarp.learnjava),其他Bean按类别放入子包。

 @Scope — Bean多个实例

对于Spring容器来说,当我们把一个Bean标记为@Component后,它就会自动为我们创建一个单例(Singleton),即容器初始化时创建Bean,容器关闭前销毁Bean。在容器运行期间,我们调用getBean(Class)获取到的Bean总是同一个实例。

还有一种Bean,我们每次调用getBean(Class),容器都返回一个新的实例,这种Bean称为Prototype(原型),它的生命周期显然和Singleton不同。声明一个Prototype的Bean时,需要添加一个额外的@Scope注解:

@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) // @Scope("prototype")
public class MailSession {
    ...
}

Spring支持的几种bean的作用域

当定义一个bean在Spring里,我们还能给这个bean声明一个作用域。它可以通过bean的scope属性来定义。Spring框架支持以下五种bean的作用域:

作用域

描述

singleton

单例模式,在spring IoC容器仅存在一个Bean实例,默认值

prototype

原型模式,每次从容器中获取Bean时,都返回一个新的实例,即每次调用getBean()时,相当于执行newXxxBean()

request

每次HTTP请求都会创建一个新的Bean,该作用域仅在基于web的Spring ApplicationContext环境下有效

session

同一个HTTP Session共享一个Bean,不同Session使用不同的Bean,该作用域仅在基于web的Spring ApplicationContext环境下有效

global-session

同一个全局的HTTP Session中共享一个Bean,一般用于Portlet应用环境,该作用域仅在基于web的Spring ApplicationContext环境下有

注入List

有些时候,我们会有一系列接口相同,不同实现类的Bean。例如,注册用户时,我们要对email、password和name这3个变量进行验证。为了便于扩展,我们先定义验证接口:

public interface Validator {
    void validate(String email, String password, String name);
}

通过一个Validators作为入口:

@Component
public class Validators {
    @Autowired
    List<Validator> validators;

    public void validate(String email, String password, String name) {
        for (var validator : this.validators) {
            validator.validate(email, password, name);
        }
    }
}

注意到Validators被注入了一个List<Validator>,Spring会自动把所有类型为Validator的Bean装配为一个List注入进来,这样一来,我们每新增一个Validator类型,就自动被Spring装配到Validators中了,非常方便。

因为Spring是通过扫描classpath获取到所有的Bean,而List是有序的,要指定List中Bean的顺序,可以加上@Order注解。

可选注入

默认情况下,当我们标记了一个@Autowired后,Spring如果没有找到对应类型的Bean,它会抛出NoSuchBeanDefinitionException异常。

可以给@Autowired增加一个required = false的参数:

@Component
public class MailService {
    @Autowired(required = false)
    ZoneId zoneId = ZoneId.systemDefault();
    ...
}

这个参数告诉Spring容器,如果找到一个类型为ZoneId的Bean,就注入,如果找不到,就忽略。

创建第三方Bean

如果一个Bean不在我们自己的package管理之内,例如ZoneId,如何创建它?

答案是我们自己@Configuration类中编写一个Java方法创建并返回它,注意给方法标记一个@Bean注解

@Configuration
@ComponentScan
public class AppConfig {
    // 创建一个Bean:
    @Bean
    ZoneId createZoneId() {
        return ZoneId.of("Z");
    }
}

Spring对标记为@Bean的方法只调用一次,因此返回的Bean仍然是单例。

初始化和销毁

在Bean的初始化和清理方法上标记@PostConstruct@PreDestroy

@Component
public class MailService {
    @Autowired(required = false)
    ZoneId zoneId = ZoneId.systemDefault();

    @PostConstruct
    public void init() {
        System.out.println("Init mail service with zoneId = " + this.zoneId);
    }

    @PreDestroy
    public void shutdown() {
        System.out.println("Shutdown mail service");
    }
}

使用别名

默认情况下,对一种类型的Bean,容器只创建一个实例。但有些时候,我们需要对一种类型的Bean创建多个实例。例如,同时连接多个数据库,就必须创建多个DataSource实例。

可以用@Bean("name")指定别名,也可以用@Bean+@Qualifier("name")指定别名。

Resource使用 -@Value注入资源路径

在Java程序中,我们经常会读取配置文件、资源文件等。使用Spring容器时,我们也可以把“文件”注入进来,方便程序读取。

    @Value("classpath:/logo.txt")    //资源路径
    private Resource resource;

但使用classpath是最简单的方式。上述工程结构如下:

注入配置 — 读取配置文件 @PropertySource

Spring容器提供了一个简单的@PropertySource来自动读取配置文件。我们只需要在@Configuration配置类上再添加一个注解:

@Configuration
@ComponentScan
@PropertySource("app.properties") // 表示读取classpath的app.properties
public class AppConfig {
    @Value("${app.zone:Z}")
    String zoneId;

    @Bean
    ZoneId createZoneId() {
        return ZoneId.of(zoneId);
    }
}

注意注入的字符串语法,它的格式如下:

  • "${app.zone}"表示读取key为app.zone的value,如果key不存在,启动将报错;
  • "${app.zone:Z}"表示读取key为app.zone的value,但如果key不存在,就使用默认值Z

这样一来,我们就可以根据app.zone的配置来创建ZoneId

还可以把注入的注解写到方法参数中:

@Bean
ZoneId createZoneId(@Value("${app.zone:Z}") String zoneId) {
    return ZoneId.of(zoneId);
}

#{bean.property}形式注入时,Spring容器自动把指定Bean的指定属性值注入。


四、Spring AOP

1、装配AOP

Sprint AOP相当于Python中的装饰器,增强函数功能,同时抽象出复用的函数。

Aspect:切面,即一个横跨多个核心逻辑的功能,或者称之为系统关注点;
Joinpoint:连接点,即定义在应用程序流程的何处插入切面的执行;
Pointcut:切入点,即一组连接点的集合;
Advice:增强,指特定连接点上执行的动作;
Introduction:引介,指为一个已有的Java对象动态地增加新的接口;
Weaving:织入,指将切面整合到程序的执行流程中;
Interceptor:拦截器,是一种实现增强的方式;
Target Object:目标对象,即真正执行业务的核心逻辑对象;
AOP Proxy:AOP代理,是客户端持有的增强后的对象引用。

1、通过Maven引入Spring对AOP的支持:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>${spring.version}</version>
</dependency>

依赖会自动引入AspectJ框架,使用AspectJ实现AOP比较方便

2、定义切面和执行方法

@Aspect
@Component
public class LoggingAspect {
    // 在执行UserService的【每个方法】前执行:
    @Before("execution(public * com.itranswarp.learnjava.service.UserService.*(..))")
    public void doAccessCheck() {
        System.err.println("[Before] do access check...");
    }

    // 在执行MailService的【每个方法】前后执行:
    @Around("execution(public * com.itranswarp.learnjava.service.MailService.*(..))")
    public Object doLogging(ProceedingJoinPoint pjp) throws Throwable {
        System.err.println("[Around] start " + pjp.getSignature());
        Object retVal = pjp.proceed();
        System.err.println("[Around] done " + pjp.getSignature());
        return retVal;
    }
}

拦截器:@Around注解,它和@Before不同,@Around可以决定是否执行目标方法。

3、然后,需要给@Configuration加上一个@EnableAspectJAutoProxy注解:

@Configuration
@ComponentScan
@EnableAspectJAutoProxy
public class AppConfig {
    ...
}

Spring的IoC容器看到这个注解,就会自动查找带有@Aspect的Bean,然后根据每个方法的@Before@Around等注解把AOP注入到特定的Bean中。

其实AOP的原理非常简单。我们以LoggingAspect.doAccessCheck()为例,要把它注入到UserService的每个public方法中,最简单的方法是编写一个子类,并持有原始实例的引用。

public UserServiceAopProxy extends UserService {
    private UserService target;
    private LoggingAspect aspect;  //切面

    public UserServiceAopProxy(UserService target, LoggingAspect aspect) {
        this.target = target;
        this.aspect = aspect;
    }

    public User login(String email, String password) {
        // 先执行Aspect的代码:
        aspect.doAccessCheck();
        // 再执行UserService的逻辑:
        return target.login(email, password);
    }

    public User register(String email, String password, String name) {
        aspect.doAccessCheck();
        return target.register(email, password, name);
    }

    ...
}

这些都是Spring容器启动时为我们自动创建的注入了Aspect的子类,它取代了原始的UserService(原始的UserService实例作为内部变量隐藏在UserServiceAopProxy中)

 Spring对接口类型使用JDK动态代理,对普通类使用CGLIB创建子类。如果一个Bean的class是final,Spring将无法为其创建子类。

可见,虽然Spring容器内部实现AOP的逻辑比较复杂(需要使用AspectJ解析注解,并通过CGLIB实现代理类),但我们使用AOP非常简单,一共需要三步:

  1. 定义执行方法,并在方法上通过AspectJ的注解告诉Spring应该在何处调用此方法;
  2. 标记@Component@Aspect
  3. @Configuration类上标注@EnableAspectJAutoProxy

拦截器类型

  • @Before:这种拦截器先执行拦截代码,再执行目标代码。如果拦截器抛异常,那么目标代码就不执行了;

  • @After:这种拦截器先执行目标代码,再执行拦截器代码。无论目标代码是否抛异常,拦截器代码都会执行;

  • @AfterReturning:和@After不同的是,只有当目标代码正常返回时,才执行拦截器代码;

  • @AfterThrowing:和@After不同的是,只有当目标代码抛出了异常时,才执行拦截器代码;

  • @Around:能完全控制目标代码是否执行,并可以在执行前后、抛异常后执行任意拦截代码,可以说是包含了上面所有功能。

2、使用注解装配AOP

        主要实现精准拦截目标方法!

@Around("execution(public * update*(..))")
public Object doLogging(ProceedingJoinPoint pjp) throws Throwable {
    // 对update开头的方法切换数据源:
    String old = setCurrentDataSource("master");
    Object retVal = pjp.proceed();
    restoreCurrentDataSource(old);
    return retVal;
}

我们在使用AOP时,要注意到虽然Spring容器可以把指定的方法通过AOP规则装配到指定的Bean的指定方法前后,但是,如果自动装配时,因为不恰当的范围,容易导致意想不到的结果,即很多不需要AOP代理的Bean也被自动代理了,并且,后续新增的Bean,如果不清楚现有的AOP装配规则,容易被强迫装配。

如果我们自己写的Bean希望在一个数据库事务中被调用,就标注上@Transactional

@Component
public class UserService {
    // 有事务:
    @Transactional
    public User createUser(String name) {
        ...
    }

    // 无事务:
    public boolean isValidName(String name) {
        ...
    }

    // 有事务:
    @Transactional
    public void updateUser(User user) {
        ...
    }
}

或者直接在class级别注解,表示“所有public方法都被安排了”:

@Component
@Transactional
public class UserService {
    ...
}

例子:

@Target(METHOD)
@Retention(RUNTIME)
public @interface MetricTime {
    String value();   //注解的method名
}

需要被监控的关键方法上标注该注解:

@Component
public class UserService {
    // 监控register()方法性能:
    @MetricTime("register")
    public User register(String email, String password, String name) {
        ...
    }
    ...
}

定义MetricAspect

@Aspect
@Component
public class MetricAspect {
    @Around("@annotation(metricTime)")
    public Object metric(ProceedingJoinPoint joinPoint, MetricTime metricTime) throws Throwable {
        String name = metricTime.value();  //获取被监控的方法名
        long start = System.currentTimeMillis();
        try {
            return joinPoint.proceed(); //连接点的执行,即register方法执行
        } finally {
            long t = System.currentTimeMillis() - start;
            // 写入日志或发送至JMX:
            System.err.println("[Metrics] " + name + ": " + t + "ms");
        }
    }
}

注意metric()方法标注了@Around("@annotation(metricTime)"),它的意思是,符合条件的目标方法是带有@MetricTime注解的方法,因为metric()方法参数类型是MetricTime(注意参数名是metricTime不是MetricTime),我们通过它获取性能监控的名称。

有了@MetricTime注解,再配合MetricAspect,任何Bean,只要方法标注了@MetricTime注解,就可以自动实现性能监控。

Welcome, Bob!
[Metrics] register: 16ms

五、Spring Bean的生命周期


 

中间件:

        MetaQ (RocketMQ) :  pull

        Notify: push

缓存:

       TDDL - 改造Mysql

分布式缓存:

        Tair:(K-V 缓存) MDB - 基于内存的KV缓存

                                        LDB - 基于SSD的KV持久化缓存

                                        RDB - 基于内存的KV持久化缓存

                

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值