Java从坚持到精通-Spring

目录

1.控制反转的概念

2.依赖注入

3.Spring的第一个入门程序

4.启用日志框架Log4j2

5.bean的作用域(单例和多例)

6.简单工厂模式(非23种设计模式)

7.工厂方法模式(23种设计模式之一)

8.BeanFactory和FactoryBean的区别

9.Bean的生命周期(五步法)

10.Bean的生命周期(七步法)

11.Bean的生命周期(十步法)

12.Bean的循环依赖问题

13.Bean循环依赖的源码分析

14.声明Bean的注解

15.负责注入的注解

16.注解方式声明配置类

17.代理模式

1.静态代理

2.动态代理

1.JDK动态代理

2.CGLIB动态代理

3.Javassist动态代理

18.面向切面编程的七大术语

19.切点表达式

20.AOP的注解式开发

21.通知的顺序

22.事务的传播行为

23.只读事务

24.事务的全注解式开发

25.Spring对JUnit4的支持


1.控制反转的概念

控制反转,也就是IoC(Inversion of Control),是反转两件事:
1.不在程序中采用硬编码方式来new对象
2.不在程序中采用硬编码方式来维护对象之间的关系

2.依赖注入

依赖注入又叫DI(Dependdency Injection),是对象A和对象B之间的关系靠注入的手段来维护,包括两种常见的方式:
1.set注入
2.构造方法注入

3.Spring的第一个入门程序

1.在pom文件中添加spring坐标,如:

<!--Spring基础依赖-->
<dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      version>5.3.27</version>
</dependency>

2.在类路径下添加spring.xml核心配置文件,里面通过bean标签声明对象,如:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="user" class="com.duolaimi.spring.bean.User">
        <property name="id" value="1"/>
        <property name="name" value="张三"/>
        <property name="age" value="18"/>
    </bean>

</beans>

3.编写测试类,获取Spring容器,然后再根据bean的id获取对象:

public class SpringTest {

    @Test
    public void test(){
        // 第一步:获取Spring容器对象
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");

        // 第二步:根据bean的id从Spring容器中获取这个对象
        User user = applicationContext.getBean("user", User.class);
        System.out.println(user);
    }

}

4.启用日志框架Log4j2

1.引入依赖

<!--log4j2的依赖-->
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-core</artifactId>
    <version>2.19.0</version>
</dependency>
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-slf4j2-impl</artifactId>
    <version>2.19.0</version>
</dependency>

2.在类路径下新建log4j2.xml文件,内容如下:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <loggers>
        <!--
            level指定日志级别,从低到高的优先级:
            ALL < TRACE < DEBUG < INFO < WARN < ERROR < FATAL < OFF
        -->
        <root level="DEBUG">
            <appender-ref ref="springlog"/>
        </root>
    </loggers>

    <appenders>
        <!--输出日志信息到控制台-->
        <console name="springlog" target="SYSTEM_OUT">
            <!--控制日志输出的格式-->
            <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss sss} [%t] %-3level %logger{1024} - %msg%n"/>
        </console>
    </appenders>
</configuration>

即可开启日志信息,如果自己的代码想要输出日志信息,可以使用如下代码:

@Test
public void testLog(){
    Logger logger = LoggerFactory.getLogger(SpringTest.class);
    logger.info("输出了");
}

5.bean的作用域(单例和多例)

可以在bean标签的scope属性设置单例或是多例

singleton:spring容器一创建就创建对象,所有对象都是同一个实例

prototype:spring容器创建时不会创建对象,new对象的时候才会创建对象,每次创建对象都是新的对象。

6.简单工厂模式(非23种设计模式)

简单工厂模式,是工厂方法模式的一种特殊实现,又被称为:静态工厂方法模式。

简单工厂模式解决的问题:
客户端程序不需要关心对象的创建细节,需要哪个对象时,只需要向工厂索要即可,初步实现了责任的分离。客户端只负责“消费”,工厂负责“生产”,生产和消费分离。

简单工厂模式中的角色:抽象产品角色、具体产品角色、工厂类角色。

比如传入什么参数就创建什么对象:

7.工厂方法模式(23种设计模式之一)

工厂方法模式可以解决简单工厂模式当中的OCP问题。

怎么解决的?一个工厂对应生成一种产品。这样工厂就不是全能类、上帝类了。另外,也符合OCP原则。

工厂方法模式中的角色:抽象产品角色、具体产品角色、抽象工厂角色、具体工厂角色

缺点:每次增加一个产品时,都需要增加一个具体类和对象实现工厂,使得系统中类的个数成倍增加。

8.BeanFactory和FactoryBean的区别

他们是两个完全不一样的对象

BeanFactory:Spring IoC容器的顶级对象,BeanFactory被翻译为“Bean工厂",在Spring的IoC容器中,“Bean工厂”负责创建Bean对象。BeanFactory是工厂。

FactoryBean:它是一个Bean,是一个能够辅助Spring实例化其他Bean对象的一个Bean。

9.Bean的生命周期(五步法)

Bean生命周期的管理,可以参考Spring的源码:AbstractAutowireCapableBeanFactory类的doCreateBean()方法。

Bean生命周期可以粗略的划分为五大步:
1.实例化Bean(调用无参数构造方法)
2.Bean属性赋值(调用set方法)
3.初始化Bean(会调用Bean的init方法,这个init方法需要自己写,自己配)
4.使用Bean
5.销毁Bean(会调用Beand的destroy方法,这个destroy方法需要自己写,自己配)

10.Bean的生命周期(七步法)

在原有初始化Bean的前后额外添加两个处理:

1.实例化Bean(调用无参数构造方法)
2.Bean属性赋值(调用set方法)
3.执行“Bean后处理器”的befeore方法
4.初始化Bean(会调用Bean的init方法,这个init方法需要自己写,自己配)
5.执行“Bean后处理器”的after方法
6.使用Bean
7.销毁Bean(会调用Beand的destroy方法,这个destroy方法需要自己写,自己配)

Bean后处理器:实现BeanPostProcessor即可,需要重写里面的Before和After的相关方法。

11.Bean的生命周期(十步法)

在原有执行“Bean后处理器”的before方法前后加入了方法,在使用Bean后或者销毁Bean前加了方法。

添加的这三个点位的特点:都是在检查你这个Bean是否实现了某些特定接口,如果实现了这些接口,则Spring容器会调用这个接口中的方法。

Aware相关接口:BeanNameAware、BeanClassLoaderAware、BeanFactoryAware

1.实例化Bean(调用无参数构造方法)
2.Bean属性赋值(调用set方法)
3.检查Bean是否实现了Aware相关接口,并设置相关依赖
4.执行“Bean后处理器”的befeore方法
5.检查Bean是否实现了InitializingBean接口,并调用接口方法
6.初始化Bean(会调用Bean的init方法,这个init方法需要自己写,自己配)
7.执行“Bean后处理器”的after方法
8.使用Bean
9.检查Bean是否实现了DisposableBean接口,并调用接口方法
10.销毁Bean(会调用Bean的destroy方法,这个destroy方法需要自己写,自己配)

12.Bean的循环依赖问题

什么是循环依赖:A对象中有B属性,B对象中有A属性。

在singleton+setter模式下,为什么循环依赖不会出现问题,Spring是如何应付的?

主要的原因是,在这种模式下Spring对Bean的管理主要分为清晰的两个阶段:

        第一个阶段:在Spring容器加载的时候,实例化Bean,只要其中任意一个Bean实例化之后,马上进行“曝光”【不等属性赋值就曝光】

        第二个阶段:Bean曝光之后,再进行属性的赋值(调用set方法)

核心解决方案是:实例化对象和对象的属性赋值分为两个阶段来完成。

注意:只有在scope是singleton的情况下,Bean才会采取提前“曝光”的措施。

当两个bean的scope都是prototype的时候,才会出现异常。其中一个是singleton的时候,就不会出现异常。

如果是构造注入,也是无法解决循环依赖问题的。

13.Bean循环依赖的源码分析

DefaultSingletonBeanRegistry类中有三个比较重要的缓存:
private final Map<String,0bject> singleton0bjects(一级缓存)
private final Map<String,0bject> earlySingleton0bjects(二级缓存)
private final Map<String, 0bjectFactory<?>> singletonFactories(三级缓存)

一级缓存存储的是:单例Bean对象。完整的单例Bean对象,也就是说这个缓存中的Bean对象的属性都已经赋值了,是一个完整的Bean对象。

二级缓存存储的是:早期的单例Bean对象,这个缓存中的单例Bean对象的属性没有赋值。只是一个早期的实例对象。

三级缓存存储的是:单例工厂对象。这个里面存储了大量的“工厂对象”,每一个单例Bean对象都会对应一个单例工厂对象。这个集合中存储的是,创建该单例对象时对应的那个单例工厂对象。

每次创建对象时会在三级缓存中先创建工厂对象,然后通过工厂对象创建早期Bean对象到二级缓存,并删除三级缓存中的工厂对象,然后找的话先从一级缓存中找,找不到再从二级缓存中找,再找不到就去三级缓存中创建对象工厂。

14.声明Bean的注解

负责声明Bean的注解,常见的包括四个:

@Componant
@Controller
@Service
@Repository

 查看源码发现,后面三个注解全部是@Componant注解的别名,只是用于区分,增强程序可读性。

15.负责注入的注解

@Value:只能注入简单类型,可以用在属性上、set方法上、构造方法的形参上注入简单类型。
@Autowired:单独用时,默认是byType,也就是根据类型自动装配非简单类型。
@Qualifier:和@Autowired联合使用,指定需要注入的名称
@Resource:是JDK扩展包中的注解,是标准注解。默认根据名称装配byName,未指定name时,使用属性名作为name,通过name找不到的话会自动启用通过类型byType装配。

16.注解方式声明配置类

使用@Configuration声明该类是配置类,用来替代原先的xml配置文件

17.代理模式

1.静态代理

主要是通过构造方法中传入代理类对象,然后通过该代理类对象去调用方法实现。代码如下:

public interface OrderService {

    void generate();

    void modify();

    void detail();
}

public class OrderServiceImpl implements OrderService {
    @Override
    public void generate() {
        System.out.println("实现类生产");
    }

    @Override
    public void modify() {
        System.out.println("实现类修改");
    }

    @Override
    public void detail() {
        System.out.println("实现类详情");
    }
}

public class OrderServiceProxy implements OrderService {

    private OrderService orderService;

    public OrderServiceProxy(OrderService orderService) {
        this.orderService = orderService;
    }

    @Override
    public void generate() {
        System.out.println("增强代码");
        orderService.generate();
    }

    @Override
    public void modify() {
        System.out.println("增强代码");
        orderService.modify();
    }

    @Override
    public void detail() {
        System.out.println("增强代码");
        orderService.detail();
    }
}

@Test
public void testProxy(){
    OrderService orderService = new OrderServiceImpl();
    OrderServiceProxy proxy = new OrderServiceProxy(orderService);
    proxy.generate();
    proxy.modify();
    proxy.detail();
}

优点:解决了OCP问题,采用代理模式的has a,可以降低耦合度。

缺点:类爆炸,每个接口都需要对应代理类,这样类会急剧膨胀,不好维护。

2.动态代理

在程序运行阶段,在内存中动态生成代理类,被称为动态代理,目的是为了减少代理类的数量,解决代码复用问题。常见的动态代理技术包括:JDK动态代理、CGLIB动态代理、Javassist动态代理技术。

1.JDK动态代理

只能代理接口

示例代码如下:

    @Test
    public void testJDKProxy(){
        // 创建代理对象
        OrderService orderService = new OrderServiceImpl();

        /*
        1.newProxyInstance:翻译为:新建代理对象
        也就是说,通过调用这个方法可以创建代理对象。
        本质上,这个Proxy.newProxyInstance()方法的执行,做了两件事情:
            一:在内存中动态生成了一个代理类的字节码class
            二:new对象了。通过内存中生成的代理类这个代码,实例化了代理对象。
        2.关于newProxyInstance()方法的三个重要的参数,每一个什么含义,有什么用?
            第一个参数:ClassLoader loader
                类加载器。在内存当中生成的字节码也是class文件,要执行也得先加载到内存当中。加载类就需要类加载器,所以这里需要指定类加载器。
                并且JDK要求,目标类的类加载器必须和代理类的类加载器使用同一个
            第二个参数:Class<?>[] interfaces
                代理类和目标类要实现同一个接口或同一些接口
                在内存中生成代理类的时候,这个代理类是需要你告诉它实现哪些接口的
            第三个参数:InvocationHandler h
                InvocationHandler被翻译为:调用处理器,是一个接口,其中编写的就是增强代码
         */
        OrderService proxyInstance = (OrderService)Proxy.newProxyInstance(orderService.getClass().getClassLoader(), orderService.getClass().getInterfaces(), new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("增强1");
                Object invoke = method.invoke(orderService, args);
                System.out.println("增强2");
                return invoke;
            }
        });

        proxyInstance.generate();
    }

2.CGLIB动态代理

CGLIB(Code Generation Library)是一个开源项目。是一个强大的,高性能,高质量的Code生成类库,它可以在运行期扩展Java类与实现Java接口。它既可以代理接口,又可以代理类,底层是通过继承的方式实现的。性能比JDK动态代理好(底层有一个小而快的字节码处理框架ASM)

3.Javassist动态代理

Javassist是一个开源的分析、编辑和创建Java字节码的类库。通过使用Javassist对字节码操作为JBoss实现动态”AOP“框架。

18.面向切面编程的七大术语

连接点Joinpoint:在程序的整个执行流程中,可以织入切面的位置。方法的执行前后,异常抛出之后等位置。

切点Pointcut:在程序执行流程中,真正织入切面的方法。(一个切点对应多个连接点)

通知Advice:通知又叫增强,就是具体你要织入的代码。
通知包括:前置通知、后置通知、环绕通知、异常通知、最终通知

切面Aspect:切点+通知就是切面

织入Weaving:把通知应用到目标对象上的过程

代理对象Proxy:一个目标对象被织入通知后产生的新对象

目标对象Target:被织入通知的对象

19.切点表达式

切点表达式用来定义通知(Advice)往哪些方法上切入。

切入点表达式语法格式为:
execution([访问控制权限修饰符] 返回值类型 [全限定类名] 方法名 (形式参数列表) [异常])

访问权限控制符:

  • 可选项
  • 没写,就是4个权限都包括
  • 写public就表示只包括公开的方法

返回值类型:

  • 必填项
  • *表示返回值类型任意

全限定类名:

  • 可选项
  • 两个点“..” 代表当前包以及子包下的所有类
  • 省略时表示所有的类

方法名:

  • 必填项
  • *表示所有的方法
  • set*表示所有的set方法

形式参数列表:

  • 必填项
  • ()表示没有参数的方法
  • (..)参数类型和个数随意的方法
  • (*)只有一个参数的方法
  • (*,String) 第一个参数类型随意,第二个参数是String的

异常:

  • 可选项
  • 省略时表示任意异常类型

20.AOP的注解式开发

在配置类上加入@EnableAspectJAutoProxy说明开启切面功能,在切面类上添加@Aspect说明这是一个切面类,并在里面写切面。

@Configuration
@ComponentScan("com.duolaimi.spring")
@EnableAspectJAutoProxy
public class SpringConfig {

}


@Component
@Aspect
public class LogAspect {

    @Before("execution(* com.duolaimi.spring.service..*(..))")
    public void logStart(){
        System.out.println("日志开始记录");
    }

    @Around("execution(* com.duolaimi.spring.service..*(..))")
    public void aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("前环绕");
        joinPoint.proceed();
        System.out.println("后环绕");
    }

}

21.通知的顺序

可以使用@Order注解来指定通知的顺序,数字越小优先级越高。

22.事务的传播行为

在service类中有a()方法和b()方法,a()方法上有事务,b()方法上也有事务,当a()方法执行过程中调用了b()方法,事务是如何传递的?合并到一个事务里?还是开启一个新的事务?这就是事务的传播行为。

一共有七种传播行为:

代码中使用

@Transactional(propagation = Propagation.REQUIRED)

来指定传播行为。

默认的事务传播行为是REQUIRED。

23.只读事务

使用@Transactional(readOnly = true)将当前事务设置为只读事务,在该事务中只允许select语句执行,增删改语句均不可执行。该特性的作用是:启动spring的优化策略,提高select查询效率。如果该事务中没有增删改语句,建议设置为只读事务。

24.事务的全注解式开发

需要在配置类上加上@EnableTransactionManagement注解说明开启事务,并通过@Bean注解返回事务管理器对象。

@Configuration
@ComponentScan("com.duolaimi.spring")
@EnableAspectJAutoProxy
@EnableTransactionManagement
public class SpringConfig {

    @Bean
    public DruidDataSource getDataSource(){
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName("");
        dataSource.setUrl("");
        dataSource.setUsername("");
        dataSource.setPassword("");
        return dataSource;
    }

    @Bean
    public JdbcTemplate getJdbcTemplate(DataSource dataSource){
        JdbcTemplate jdbcTemplate = new JdbcTemplate();
        jdbcTemplate.setDataSource(dataSource);
        return jdbcTemplate;
    }

    @Bean
    public DataSourceTransactionManager getDataSourceTransactionManager(DataSource dataSource){
        DataSourceTransactionManager txManager = new DataSourceTransactionManager();
        txManager.setDataSource(dataSource);
        return txManager;
    }

}

25.Spring对JUnit4的支持

在pom文件中添加spring-test依赖,再添加junit4版本的依赖,在测试类上添加上@RunWith和@ContextConfiguration注解指定配置文件的路径,这样就可以直接通过@Autowired等注入方式直接注入对象。

  • 25
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值