Spring 整体学习(IOC控制翻转 DI依赖注入 AOP 面向切面编程(动态代理) 配置注入 注解注入 )

Spring

一. Spring的IOC与DI

​ IOC(Invertion Of Control)是控制翻转,是一种编程的思想,就是以前在代码中我们需要通过 new 的方式来使用其他的对象,这种方式导致的结果就是类与类之间耦合度过高,耦合度高导致到迭代成本极高;通过控制的翻转的方式,就是我们项目中用到所有的对象,都通用交个一个容器来管理(在spring中我们叫做 IOC容器);
反转了依赖关系的满足方式,由之前的自己创建依赖对象,变为由工厂推送。(变主动为被动,即反转)
解决了具有依赖关系的组件之间的强耦合,使得项目形态更加稳健

​ DI(Dependency Injection)是依赖注入,它是对IOC编程思想的具体实现。在Spring创建对象的同时,为其属性赋值,称之为依赖注入。

1.1 依赖注入

依赖注入主要分为 属性注入(set方法注入)构造器注入

第一步,创建一个配置文件 application-context.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">
    
    <!-- SSDDisk和MachineDisk实现了共同的 Disk 接口 -->
    <!-- 当容器启动的时候会创建一个名为 ssdDisk 的对象,然后纳入到IOC容器中 -->
    <bean id="ssdDisk" class="org.example.ioc.impl.SSDDisk"></bean>
    <bean id="machineDisk" class="org.example.ioc.impl.MachineDisk"></bean>
    
    <!-- primary=true 表示以该对象为主,当从容器中根据类型来获取的时候,拿到是这个对象 -->
  	<!-- 可以避免 NoUniqueBeanDefinitionException 异常的发生 -->
    <bean id="computer" class="org.example.ioc.Computer" primary="true">
        <!-- property 会找到 Computer类的 setDisk方法来将属性设置到对象中 -->
        <property name="disk" ref="ssdDisk"></property>
        <property name="name" value="联想电脑"></property>
    </bean>
    
    <!-- 通过构造器的方式来实现注入 -->
    <bean id="computerTwo" class="org.example.ioc.Computer">
        <constructor-arg name="disk" ref="ssdDisk"></constructor-arg>
        <constructor-arg name="name" value="dell"></constructor-arg>
    </bean>
</beans>

第二步,创建容器,并获取IOC容器中的内容:

IOC容器中的所有bean都会提前创建好,而不是调用的时候再去创建

public class IocTest {
    public static void main(String[] args) throws InterruptedException {
        // 创建一个 IOC 容器
        ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("META-INF/application-context.xml");
        // 获取到IOC中的对象
		// Computer c = (Computer)ctx.getBeam("computer");
        
        // 可以根据类型来获取;因为目前对于上面的配置来说,我们有两个 Computer对象,拿到的是 
        // 加了 Primary = true 这个对象
        Computer c = ctx.getBeam(Computer.class);
    }
}
1.2 特殊类型注入

特殊类型指的是:

  1. 引用类型;
  2. List
  3. Map
  4. Set
  5. Propety
  6. 数组

例如给定如下的类:

public class Person {
    private String name;
    private Date birthday;
  	private List<Dog> dogs
    private List<String> friends;
    private Set<String> account;
    private Map<String, String> addresses;
    private Properties prop;
    private String[] array;
    // setter and getter
}
public class Dog {
  private String name;
  private Integer age;
}
<?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="birthday" class="java.util.Date"></bean>
    
 	<bean id="person" class="org.Person">
    	<property name="name" value="张三"></property>
        <property name="birthday" ref="birthday"></property>
      	<property name="dogs">
          	<!-- List对象注入 -->
          	<List>
              	<bean class="org.Dog">
                  	<property name="name" value="大黄"></property>
                  	<property name="age" value="5"></property>
              	</bean>
              	<bean class="org.Dog">
                  	<property name="name" value="小白"></property>
                  	<property name="age" value="2"></property>
              	</bean>
          	</List>
      	</property>
        <property name="friends">
            <!-- list注入 -->
        	<list>
            	<value>李四</value>
                <value>王五</value>
            </list>
        </property>
        <property name="account">
        	<!-- set注入 -->
        	<set>
            	<value>李四</value>
                <value>王五</value>
            </set>
        </property>
        <property name="addresses">
        	<!-- map注入 -->
        	<map>
            	<entry name="home-address" value="洪山"></entry>
                <entry name="home-address" value="江夏"></entry>
            </map>
        </property>
         <property name="prop">
        	<!-- map注入 -->
        	<props>
            	<prop name="usename">root</prop>
                <prop name="password">123456</prop>
            </props>
        </property>
        <!-- 数组注入可以通过 , 将值进行分隔 -->
        <!-- <property name="array" value="a,b,c"></property> -->
        <property name="array">
        	<array>
            	<value>a</value>
                <value>b</value>
                <value>c</value>
            </array>
        </property>
    </bean>
 </beans>
1.3 自动装配

在 “配置” 的方式上有一个属性 autowire="byName | byType" 两种方式:

  1. byName 是严格按照名字到容器中进行查找,就算有primary="true"也会根据名字找。
  2. byType 是根据属性的类型到容器中进行查找,如果多个会报错,除非在其中一个加上 primary=“true” 这个属性
1.4 单例和非单例模式

在 “配置” 的方式上有一个属性 scope="singleton | prototype" 两种方式创建bean:

  1. singleton 表示从容器中获取的永远是单例的。
  2. prototype表示每次从容器中获取的都是一个新的对象。

二. bean的生命周期

关于 spring中管理的bean的生命周期在spring的一个接口 BeanFactory(是spring容器的顶级接口) 的描述上有准确的描述,但是目前来说,先简单理解 spring 的生命周期:

  1. 实例化,调用构造方法。
  2. 设置属性。
  3. 初始化,调用 init-method(配置的方式) 对应的方法
  4. 创建完毕。
  5. 销毁阶段,调用 destory-method(配置的方式) 对应的方法
public class LifeCycleBean {
    
    private String name;
    
    public void setName(String name) {
        System.out.println("setName 方法被调用了...");
        this.name = name;
    } 
    
    public LifeCycleBean() {
        System.out.println("构造方法被调用了...");
    }
    
    public void init() {
         System.out.println("init 方法被调用了...");
    }
    
    public void destroy() {
        System.out.println("destroy 方法被调用了...");
    }
}
<?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">
    
    <!-- 
		对应的 org.LifeCycleBean 输入的结果:
            1. 构造方法被调用了...
			2. setName 方法被调用了...
			3. init 方法被调用了...
			4. destroy 方法被调用了... 
	-->
    <bean id="lifeCycleBean" class="org.LifeCycleBean" init-method="init" 
          destroy-method="destroy">
    	<property name="name" value="XXX"></property>
    </bean>
 </beans>

单例bean:singleton

随工厂启动创建 ==》 构造方法 ==》 set方法(注入值) ==》 init(初始化) ==》 构建完成 ==》随工厂关闭销毁

多例bean:prototype

被使用时创建 ==》 构造方法 ==》 set方法(注入值) ==》 init(初始化) ==》 构建完成 ==》JVM垃圾回收销毁

三. 注入的注解形式开发

spring提供两套创建 IOC 容器的方式:

  1. 配置的方式,这是历史遗留问题。
  2. 注解的方式,这也是未来的主流的方式。(注解创建成员对象的方式是通过构造器)

在三阶段,我们都是混合使用;在四阶段(包括以后工作) 都是适应注解的方式。

@ComponentScan(basePackages = "com.qf")
public class IocTestWithAnnotation {
    public static void main(String[] args) {       
      	/**
      	 * 通过注解的方式来创建 IOC容器,传递给 AnnotationConfigApplicationContext 的构造方法的
      	 * 类会被实例化并纳入到IOC容器中。然后通过它头顶 @ComponentScan(basePackages = "com.qf") 递归
      	 *  扫描 com.qf 下所有的类和接口,然后将他们头顶上带 @Service @Repository @Component 标注的类
      	 *  按照spring-bean的生命的方式来创建后纳入到IOC容器中。
      	 */
        ApplicationContext ctx = new AnnotationConfigApplicationContext(
            IocTestWithAnnotation.class);
        
        UserService userService = ctx.getBean(UserService.class);

        userService.add();
        userService.delete(45);
        
    }
}

此处注解相当于配置文件中的

@Repository: 这个注解放到 DAO 的实现类上。

@Sercvice: 这个注解放到 Serviece 的实现类上。`

@Component: 性质不明确的时候,就加上该注解。

此处注解相当于配置文件中的autowire="byType | byName "

@Autowired:首先根据类型查找,如果多个,就看谁的头顶的有 @Primary,就注入谁;如果没有没有@Primary 注解,就按照名字来进行注入,如果名字都没有区分就报异常。

@Qualifier: 它的出现就是为了和 @Autowire 搭配使用的,就是指定一个名字,在注入的时候根据类型的时候同时判断名字来实现注入,无视名字和@Primary注解,只根据@Qualifier来创建。

@Resource:首先根据名字来实现注入,如果命名没有相同,就根据类型查找,如果类型有多个,就看谁的大头顶有 @Primary 注解,名字优先于@Primary。

此处的注解相当于init-method | destroy-method配置

@PostConstruct: 对应着 init-method 这个配置的一个注解,是spring bean的生命周期的一个注解。在Spring中碰到方法名或者注解中带有 Post, 表示在XXXX之后。

@PreDestroy: 对应着destroy-method这个配置的一个注解,销毁的时候执行的方法。在Spring中碰到方法名或者注解中带有 Pre, 表示在XXXX之前。

四. AOP

AOP(Aspect Oriented Programming): 面向切面的编程。说白就是可以在方法执行的某个时机做一些其他的工作;他是对应 OOP 的一种增强。AOP底层的实现都是通过动态代理(JDK原生方式和CGLIB的方式)来实现的,在spring框架中,提供了两种实现的方式:

  1. spring原生的方式。(了解即可)
  2. 通过AspectJ框架的方式。

面试: OOP和AOP的区别?

答:OOP面向对象编程,只能通过纵向的添加的代码的方式来实现增强的功能;AOP可以实现在不改变原有方法的代码基础之上实现对方法的横向扩展。

连接点:类中方方法。

切入点:就是我们所关注的方法。

通知:就是当我们所关注的切入点,在发生某个行为的时候,出发某个调用。

4.1 AOP开发术语
  • 连接点(Joinpoint):连接点是程序类中客观存在的方法,可被Spring拦截并切入内容。
  • 切入点(Pointcut):被切入连接点。
  • 通知、增强(Advice):可以为切入点添加额外功能,分为:前置通知、后置通知、异常通知、环绕通知等。
  • 目标对象(Target):代理的目标对象
  • 织入(Weaving):把通知应用到具体的类,进而创建新的代理类的过程。
  • 代理(Proxy):被AOP织入通知后,产生的结果类。
  • 切面(Aspect):由切点和通知组成,将横切逻辑织入切面所指定的连接点中。
4.2 通配切入点

根据表达式通配切入点

<!--匹配参数-->
<aop:pointcut id="myPointCut" expression="execution(* *(com.qf.aaron.aop.basic.User))" />
<!--匹配方法名(无参)-->
<aop:pointcut id="myPointCut" expression="execution(* save())" />
<!--匹配方法名(任意参数)-->
<aop:pointcut id="myPointCut" expression="execution(* save(..))" />
<!--匹配返回值类型-->
<aop:pointcut id="myPointCut" expression="execution(com.qf.aaron.aop.basic.User *(..))" />
<!--匹配类名-->
<aop:pointcut id="myPointCut" expression="execution(* com.qf.aaron.aop.basic.UserServiceImpl.*(..))" />
<!--匹配包名-->
<aop:pointcut id="myPointCut" expression="execution(* com.qf.aaron.aop.basic.*.*(..))" />
<!--匹配包名、以及子包名-->
<aop:pointcut id="myPointCut" expression="execution(* com.qf.aaron.aop..*.*(..))" />
4.3 AspectJ对于AOP的实现

第一步,创建一个接口,然后定义一个实现类。

public interface StudentService {

    void deleteStudent(Integer id);

    void add();
}
public class StudentServiceImpl implements StudentService {
    public void deleteStudent(Integer id) {
        System.out.println("删除学生:" + id);
    }

    @Override
    public void add() {
        System.out.println("add");
    }
}

第二步,创建一个通知类,类的头顶必须要有 @Aspect 这个注解

@Aspect
public class AopAdvisor {

    /**
     *  “* org.example.aop.service.impl.*.*(..)” 第一个 * org.example 表示人任意的返回值
     *     org.example.aop.service.impl.*.*  org.example.aop.service.impl下所有的类的所有的方法。
     *     (..) 表示接受任意的参数
     * @Before() 表示在执行这些方法之前。
     */
    @Before("execution(* org.example.aop.service.impl.StudentServiceImpl.deleteStudent(..))")
    public void before(JoinPoint jp) {
        System.out.println("UserService下的方法调用之前被执行了...");
        // 获取方法的签名
        MethodSignature signature = (MethodSignature)jp.getSignature();
		//获取方法的对象
        Method method = signature.getMethod();

        System.out.println("方法执行之前打印方法名: " + method.getName());
    }
    
    // 是方法无论正确获取不正确都会执行
    @After("execution(* org.example.aop.service.impl.StudentServiceImpl.add*(..))")
    public void after(JoinPoint jp) {
         System.out.println("方法无论是否抛出异常都会执行");
    }
    
    
    // 方法正确执行了,才会执行该方法
    @AfterReturning("execution(* org.example.aop.service.impl.StudentServiceImpl.add*(..))")
    public void after(JoinPoint jp) {
         System.out.println("方法正确执行了,才会执行该方法");
    }
    
    
     /**
      * 1. 只有当关注的方法抛出 NullPointerException 这个异常方法才会执行;
      * 2. 要保证 throwing的值和方法中异常的形参名要一致。
      * 3. 如果想让该方法处理所有的异常,要使用 Exception
      */
     @AfterThrowing(value = "execution(* org.example.service.impl.UserServiceImpl.add*(..))", throwing = "npe")
     public void catchSpecifyException(JoinPoint jp, NullPointerException npe) {
         System.out.println("方法抛出了 NPE 异常");
    }
    
    /**
     * 表示环绕通知。
     */ 
    @Around("execution(* org.example.service.impl.UserServiceImpl.add*(..))")
    public void around(ProceedingJoinPoint pjp) throws Exception{

      	Object[] args = pjp.getArgs();  // 表示获取方法执行的所有的参数
      	//获取方法的签名
        MethodSignature signature = (MethodSignature)pjp.getSignature();
        //获得当前方法对象
        Method method = signature.getMethod();
        //通过方法获得字节码对象,然后通过字节码对象来获取简单类名
        System.out.println(method.getDeclaringClass().getSimpleName());
      
        Object obj = pjp.proceed(); // 执行目标对象的方法
        
        // 做其他
        return obj;
    }
}

第三步,在配置文件中进行配置

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

	<!-- 被代理的类必须要纳入到IOC容器中 -->
    <bean id="studentService" class="org.example.aop.service.impl.StudentServiceImpl"></bean>

    <bean id="advisor" class="org.example.aop.AopAdvisor"></bean>

    <!-- 表示开启 aspectj
        可以通过 proxy-target-class="true" 来控制使用 CGLIB 的方式来实现代理,false表示使用原生JDK的方式来实现动态代理;
        如果被代理的类没有接口,那么尽管指定使用jdk的代理方式,但是也还是会使用CGLIB的方式来实现动态代理。
    -->
    <aop:aspectj-autoproxy proxy-target-class="false"></aop:aspectj-autoproxy>
</beans>

五. spring与mybatis的整合

第一步,导入依赖:

<dependencies>
     <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.3.3</version>
    </dependency>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.13.2</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
      <version>3.5.7</version>
    </dependency>
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>8.0.20</version>
    </dependency>
    <dependency>
      <groupId>com.github.pagehelper</groupId>
      <artifactId>pagehelper</artifactId>
      <version>5.1.9</version>
    </dependency>
    <dependency>
      <groupId>log4j</groupId>
      <artifactId>log4j</artifactId>
      <version>1.2.17</version>
    </dependency>
    <!-- 是mybatis和spring整合的中间包 -->
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis-spring</artifactId>
      <version>2.0.6</version>
    </dependency>

    <!-- 连接池,功能和 druid 是一样的 -->
    <dependency>
      <groupId>com.zaxxer</groupId>
      <artifactId>HikariCP</artifactId>
      <version>2.6.1</version>
    </dependency>

    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-test</artifactId>
      <version>5.3.3</version>
      <scope>test</scope>
    </dependency>

    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-orm</artifactId>
      <version>5.3.3</version>
    </dependency>
</dependencies>

第二步,配置

<?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:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
                           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
                           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">


    <!-- 当通过配置文件的方式来创建容器的时候,会根据 base-package="org.example" 进行递归扫描: @Service @Component, 纳入到IOC容器中 -->
    <context:component-scan base-package="org.example"></context:component-scan>

    <!-- 配置 HikariCP 的连接池 -->
    <bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource">
        <property name="username" value="root"></property>
        <property name="password" value="123456"></property>
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"></property>
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/qf?serverTimezone=Asia/Shanghai"></property>
        <!-- 连接池中最大的连接的数量 -->
        <property name="maximumPoolSize" value="20"></property>
        <!-- 测试连接是否有效的 sql 语句 -->
        <property name="connectionTestQuery" value="select 1"></property>
    </bean>

    <!--
        之前在单独使用 MyBatis的时候,我们自己通过 mybatis-config.xml 核心配置文件来自己创建一个 SqlSessionFactory;
        但是在和Spring整合的时候,将该项工作交给 Spring容器了。
     -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!-- 配置数据源 -->
        <property name="dataSource" ref="dataSource"></property>
        <!--
            "classpath:" 是spring提供的一个用来定位文件路径的缩写;
            classpath:org/example/**/*.xml 扫描 org/example 下所有的后代包的 xml文件
         -->
        <property name="mapperLocations" value="classpath:org/example/**/*.xml"></property>

        <!-- 设置分页插件 -->
        <property name="plugins">
            <array>
                <bean class="com.github.pagehelper.PageInterceptor">
                    <!-- 分页插件的配置 -->
                    <property name="properties">
                        <props>
                            <!-- 设置方言 -->
                            <prop key="helperDialect">mysql</prop>
                            <!-- 设置分页合理化 -->
                            <prop key="reasonable">true</prop>
                        </props>
                    </property>
                </bean>
            </array>
        </property>
        <!-- 配置日志打印的 -->
        <property name="configuration">
            <bean class="org.apache.ibatis.session.Configuration">
                <property name="logImpl" value="org.apache.ibatis.logging.stdout.StdOutImpl"></property>
            </bean>
        </property>
    </bean>

    <!-- 将所有的 Mapper纳入到容器中
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
    -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <!-- 因为获取 Mapper需要需要通过 SqlSession, 这里我们直接提供工厂就可以了  -->
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property>

        <!-- 这是找那些 XXMapper 的基础包;只找接口; 需要和 annotationClass 配置使用;表示只找头顶有 @Mapper 注解的接口。-->
        <property name="basePackage" value="org.example"></property>
        <property name="annotationClass" value="org.apache.ibatis.annotations.Mapper"></property>
    </bean>
</beans>

六. spring的单元测试

第一步,引入依赖:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>5.3.3</version>
    <scope>test</scope>
</dependency>

第二部,测试类的编写

@RunWith(SpringJUnit4ClassRunner.class)  // 用于spring测试
@ContextConfiguration("classpath:META-INF/application-context.xml")
public class UserServiceTest {

    @Autowired
    private IUserService userServiceImpl;

    @Test
    public void getPageData() {
        List<User> list = userServiceImpl.getPageData(2, 10);
        list.forEach(u -> System.out.println(u.getEmail() + "#" + u.getName()));
    }

    @Test
    public void validateTx() {
        userServiceImpl.validateTx();
    }
}
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值