Spring 学习

# 一、原先代码存在的问题

1、Service层中会用到Dao层的对象?

public class UserService {
	UserDao userDao = new UserDaoImpl();
}

Service层与Dao层是强耦合关系。

没有Dao的实现,程序在编译期间就报错了。

2、JDBC开发效率低

学习了Mybatis

Spring 自身提供了持久层的ORM实现。 JDBCTemplate

3、侵入性强,移植性差(例如:DAO实现的更换,从Connection到SqlSession)。

Spring 轻量级、低侵入

二、自定义Bean工厂

参考视频

三、快速入门Spring

1、官网

https://spring.io/

https://spring.io/projects/spring-framework#learn

2、实现步骤

  1. 创建Maven工程,打包方式jar
  2. 导入依赖【spring-context】
  3. 写自己代码【UserDao】
  4. spring配置文件
  5. 测试代码

3、目的

体会IOC思想

4、具体实现代码

4.1 依赖

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

4.2 UserDao

  • 接口
public interface UserDao {

    void save();
}
  • 实现
public class UserDaoImpl implements UserDao {
    @Override
    public void save() {
        System.out.println("保存用户");
    }
}

4.3 spring配置文件

  • applicationContext.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">

    <!-- 配置组件 -->
    <bean id="userDao" class="com.java2007.dao.impl.UserDaoImpl"></bean>

</beans>

4.4 测试代码

public class Spring01Test {

    	public static void main(String[] args) {

        ApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserDao userDao = (UserDao) ioc.getBean("userDao");
        userDao.save();

    }

}

4.5 测试效果

测试效果
image-20210224103936175

四、Spring架构图

Spring架构图

四、IOC

1、概述

控制反转:Inversion of Control

​ 把创建和管理对象的权利交给Spring容器来完成。

依赖注入【DI】:Dependency Injection。是IOC的一种表现形式

2、作用

降低程序之间的耦合性

​ 类与类之间的耦合、方法之间的耦合

3、如何实现降低耦合性

灵活应用IOC和DI

五、依赖注入

Dependency Injection。是IOC的一种表现形式

1、setter注入

通过setter方法为UserService组件注入UserDao实例【UserService和UserDao必须在同一个容器中】

setter注入也是开发中使用的最多的【自己写的代码】

1.1 具体实现

1.2.1 创建一个类,编写属性,提供setter方法

  • User
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {

    private Integer id;
    private String username;
    private String password;
    private Date birthday;
    private int gender;
    private Double money;

}

1.2.2 Spring配置文件【applicationContext-setter.xml】

<!-- setter注入 -->
<bean id="user" class="com.java2007.pojo.User">
    <property name="id" value="1"/>
    <property name="username">
        <value><![CDATA[<SuperMan>]]></value>
    </property>
    <property name="password" value="123abc"/>
    <property name="birthday" ref="now"/>
    <property name="gender" value="1"/>
    <property name="money" value="1200.20"/>
</bean>

<bean id="now" class="java.util.Date"/>

2、构造器注入【会用】

通过构造器的方式创建对象,并注入到容器中

2.1 具体实现

  • 创建一个类,提供相应的成员变量与构造器
public class Car {

    private Integer id;
    private String brandName;
    private Double price;

    private User user;

    public Car(Integer id, String brandName, Double price) {
        this.id = id;
        this.brandName = brandName;
        this.price = price;
    }

    public Car(Integer id, String brandName, Double price, User user) {
        this.id = id;
        this.brandName = brandName;
        this.price = price;
        this.user = user;
    }

    @Override
    public String toString() {
        return "Car{" +
                "id=" + id +
                ", brandName='" + brandName + '\'' +
                ", price=" + price +
                ", user=" + user +
                '}';
    }
}


2.1.2 Spring配置文件【applicationContext-constructor.xml】

<bean class="com.java2007.pojo.Car" id="car">
    <!-- name : 构造器中形参的名字 -->
    <constructor-arg name="id" value="10"></constructor-arg>
    <constructor-arg name="brandName" value="BMW"></constructor-arg>
    <constructor-arg name="price" value="500000"></constructor-arg>
</bean>

<bean class="com.java2007.pojo.Car" id="car1">
    <constructor-arg name="id" value="20"></constructor-arg>
    <constructor-arg name="brandName" value="Ford"></constructor-arg>
    <constructor-arg name="price" value="100000"></constructor-arg>
    <constructor-arg name="user" ref="user"></constructor-arg>
</bean>

<!-- import : 表示导入其他的配置文件 -->
<import resource="applicationContext-setter.xml"></import>

3、自动注入【配置文件形式,了解】

配置文件方式:了解

后期的注解方式:掌握

3.1 具体实现

  • Java类
public class Car {

    private Integer id;
    private String brandName;
    private Double price;

    private User user;

    public void setUser(User user) {
        this.user = user;
    }

    public Car(Integer id, String brandName, Double price) {
        this.id = id;
        this.brandName = brandName;
        this.price = price;
    }

    public Car(Integer id, String brandName, Double price, User user) {
        this.id = id;
        this.brandName = brandName;
        this.price = price;
        this.user = user;
    }

    @Override
    public String toString() {
        return "Car{" +
                "id=" + id +
                ", brandName='" + brandName + '\'' +
                ", price=" + price +
                ", user=" + user +
                '}';
    }
}


  • Spring配置文件
<!--
    autowire : 自动注入【底层是通过setter注入】
        byType : 根据类型自动注入
            会在当前Spring容器中根据类型去匹配,如果能够匹配到有且只有一个Bean实例,那么就自动注入
        byName : 根据beanName自动注入。 (bean的ID)
-->
<bean class="com.java2007.pojo.Car" id="car2" autowire="byName">
    <constructor-arg name="id" value="20"></constructor-arg>
    <constructor-arg name="brandName" value="Ford"></constructor-arg>
    <constructor-arg name="price" value="100000"></constructor-arg>
    <!--<constructor-arg name="user" ref="user2"></constructor-arg>-->
</bean>

<bean id="user2" class="com.java2007.pojo.User">
    <property name="id" value="11"/>
    <property name="username" value="Jack"/>
</bean>

<bean id="user" class="com.java2007.pojo.User">
    <property name="id" value="12"/>
    <property name="username" value="Mary"/>
</bean>

4、集合属性注入【了解】

通过跟setter注入一起使用

  • Java类
@Data
public class Demo {

    private String[] hobbies;
    private List<Integer> ids;
    private Set<User> users;
    private Set<String> sets;
    private Map<String, Object> maps;
    private Properties pros;

}

  • Spring配置文件【applicationContext-demo.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">

    <bean id="demo" class="com.java2007.pojo.Demo">
        <property name="hobbies">
            <array>
                <value>Java</value>
                <value>Game</value>
                <value>Sleep</value>
            </array>
        </property>
        <property name="ids">
            <list>
                <value>1</value>
                <value>21</value>
                <value>15</value>
            </list>
        </property>
        <property name="users">
            <set value-type="com.java2007.pojo.User">
                <ref bean="user1"/>
                <bean class="com.java2007.pojo.User">
                    <property name="username" value="jack"/>
                </bean>
            </set>
        </property>
        <property name="sets">
            <set>
                <value>AAA</value>
                <!-- 会自动去从 -->
                <value>AAA</value>
            </set>
        </property>
        <property name="maps">
            <map>
                <entry key="AA" value="aa"></entry>
                <entry key="BB" value="bb"></entry>
            </map>
        </property>
        <property name="pros">
            <props>
                <prop key="jdbc.driver">com.jdbc.mysql.Driver</prop>
                <prop key="jdbc.username">root</prop>
            </props>
        </property>
    </bean>
    
    <bean id="user1" class="com.java2007.pojo.User">
        <property name="username" value="mary"/>
    </bean>

</beans>

六、Bean配置的细节问题

1、细节

<!-- 配置组件 -->
<!--
    bean : spring容器中一个组件标签
        id : bean的唯一标识
        class : 创建的Bean实例所对应的全类名
        scope : bean的作用范围
            singleton: 单例,默认值
                默认情况下,bean是单例的。容器被创建后,Bean实例就被创建了。getBean方法只是从容器中获取Bean
            prototype: 原型(多例)。
                从容器中获取一次,就创建一个对象
            request: 在web环境下,每次请求创建一个新的实例
            session: 在web环境下,每次会话创建一个新的实例
            global-session: 在web环境下,全局会话创建一个新的实例【所有的会话都共享】
         init-method : 该属性是容器初始化bean时调用的方法
         destroy-method : 该属性是容器销毁bean时调用的方法
    生命周期:
        singleton : 随着容器创建而创建,随着容器的销毁而销毁。
			
        prototype : 使用时,由容器创建; 销毁由JVM来控制。
-->
<bean id="userDao" class="com.java2007.dao.impl.UserDaoImpl"
      scope="prototype" init-method="aa" destroy-method="bb"></bean>

2、生命周期阶段

单例bean:singleton

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

多例bean:prototype

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

七、FactoryBean

工厂bean:就是用来创建Bean的工厂

有些对象不能通过构造器直接反射创建,如 Connection,SqlSessionFactory等等。

这个时候,我们就可以自定义Bean工厂来完成对象的创建

1、实现步骤

  1. 自定义一个类,实现FactoryBean接口
  2. 重写方法,在方法中去指定规则
  3. spring配置文件

2、具体实现

2.1 自定义一个类,实现FactoryBean接口

2.2 重写方法,在方法中去指定规则

package com.java2007.factory;

import org.springframework.beans.factory.FactoryBean;

import java.sql.Connection;
import java.sql.DriverManager;

/**
 * 1.spring解析配置文件,得到com.java2007.factory.MyConnectionBeanFactory。
 * 2.获取MyConnectionBeanFactory信息【主要是获取实现的接口列表】
 * 3.直接反射创建对象MyConnectionBeanFactory。
 *      3.1 判断当前接口列表中是否有FactoryBean接口
 *          有:就调用 getObject() 得到一个实例,放入Spring容器中
 *          没有:把对象放入容器
 *
 * @author ghy
 * @version 1.0
 */
public class MyConnectionBeanFactory implements FactoryBean<Connection> {

    /**
     * Bean工厂要创建的实例对象
     * @return
     * @throws Exception
     */
    @Override
    public Connection getObject() throws Exception {
        Class.forName("com.mysql.jdbc.Driver");
        return DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "root");
    }

    /**
     * 实例对象的具体Class类型
     * @return
     */
    @Override
    public Class<?> getObjectType() {
        return Connection.class;
    }

    /**
     * 实例对象是否是单例
     * @return
     */
    @Override
    public boolean isSingleton() {
        return false;
    }
}


2.3 spring配置文件

<!-- 因为MyConnectionBeanFactory 实现 BeanFactory接口,所以会自动调用 getObject()方法得到结果,把这个结果对象放入容器中 -->
<bean id="connection" class="com.java2007.factory.MyConnectionBeanFactory"></bean>

八、代理设计模式

1、分类

  • 静态代理【了解】
    • 我们自己先创建好代理对象,然后调用代理对象的方法完成功能
  • 动态代理
    • 由程序运行期间创建代理对象,然后我们调用代理对象的方法完成功能

2、动态代理分类

  • JDK动态代理【实现接口】
  • Cglib动态代理【可以没有接口,目标类不能被final修饰】

3、动态代理具体实现

3.1 Jdk动态代理

  • 接口
public interface Zufang {

    void zf();

}

  • 目标类
public class FangDong implements Zufang {

    @Override
    public void zf() {
        System.out.println("签合同");
        System.out.println("收钱");
    }
}


  • 创建代理对象,调用方法
public class JdkProxy {

    public static void main(String[] args) {
        //要求: 目标对象所在的类必须实现接口
        //1.创建目标对象
        FangDong target = new FangDong();
        //2.创建代理对象
        Zufang proxy = (Zufang) Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println("发广告");
                        System.out.println("带客看房");
                        Object result = method.invoke(target, args);  //target.zf()
                        System.out.println("家电坏了,我是中介来维修");
                        return result;
                    }
                }
        );
        //3.代理对象代理目标对象去实现租房功能
        proxy.zf();

    }
}

3.2 Cglib动态代理

  • 目标类
//目标类有无实现接口,都可以
public class FangDong implements Zufang {

    @Override
    public void zf() {
        System.out.println("签合同");
        System.out.println("收钱");
    }
}

  • 创建代理对象,调用方法
public class CglibProxy {

    public static void main(String[] args) {

        FangDong target = new FangDong();

        //如何创建代理对象
        FangDong proxy = (FangDong) Enhancer.create(target.getClass(), new MethodInterceptor() {
            @Override
            public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
                System.out.println("发布广告啦....");
                System.out.println("带租客看房啦....");
                Object result = method.invoke(target, args);
                System.out.println("出问题,维修啦...");
                return result;
            }
        });

        proxy.zf();

    }
}

九、AOP

1、概述

  • Aspect Oriented Programming:面向切面编程,也是一种编程思想

  • 底层是动态代理【在不修改源代码的情况下,对目标方法进行功能增强】

  • Spring会根据用户的配置自动筛选代理方式

2、术语

[1] 连接点(Joinpoint):程序执行的某个特定位置:如类某个方法调用前、调用后、方法抛出异常后等。连接点由两个信息确定:方法表示的程序执行点;相对点表示的方位。

----类中可以被增强的方法 ==> 就是连接点(Joinpoint)

[2] 切点(pointcut):每个类都拥有多个连接点:例如 类中所有方法实际上都是连接点,即连接点是程序类中客观存在的事务。AOP 通过切点定位到特定的连接点。类比:连接点相当于数据库中的记录,切点相当于查询条件。切点和连接点不是一对一的关系,一个切点匹配多个连接点,切点通过 org.springframework.aop.Pointcut 接口进行描述,它使用类和方法作为连接点的查询条件。(你想切入到指定目标类的哪些方法上面)
譬如: 你只想让其中的几个,在调用这几个方法之前,之后或者抛出异常时干点什么,那么就用切点来定义这几个方法,让切点来筛选连接点,选中哪几个你想要的方法。

----类中真正被增强的方法 ==> 就是切点
----譬如:一个类有增删改查四个方法,都可以被增强,但是实际上只增强了增跟删方法,那么增跟删就被称为切点

[3] 通知(Advice): (又称增强,切点中增强功能的代码)切面必须要完成的工作 (需求:日志,事务,验证等)

----譬如现在要给方法增加日志功能,那么这个功能就被称为叫 通知/增强

[4] 切面(Aspect): 横切关注点(跨越应用程序多个模块的功能)被模块化的特殊对象 (通知和切入点的结合)

----通知/增强 + 切点 = 切面

[5] 目标(Target): 被通知的对象 (被追踪的目标类,也就是真正的业务逻辑)

----要增强的类的对象

[6] 织入(Weaving)
--- 把 增强 应用到 目标的过程

[7] 代理(Proxy): 向目标对象应用通知之后创建的对象

----一个类被织入增强后,产生的结果就是一个代理(代理类)

3、以案例来快速入门AOP编程

3.1 案例

要为GoodsService的方法进行日志记录

3.2 实现步骤

  1. 准备工作
    1. GoodsService接口和GoodsServiceImpl实现类
    2. 日志类
  2. 导入依赖【aspects】
  3. spring配置文件
    1. 配置GoodsServiceImpl的Bean 和 日志类的Bean
    2. 配置AOP

3.3 具体实现

3.3.1 准备工作

  • GoodService接口与实现类
package com.java2007.service;

import com.java2007.pojo.Goods;

import java.util.List;

/**
 * @author ghy
 * @version 1.0
 */
public interface GoodsService {

    void save();

    int delete(Integer id);

    boolean update(Goods goods);

    List<Goods> findAll();

    Goods findById(Integer id);


}


package com.java2007.service.impl;

import com.java2007.pojo.Goods;
import com.java2007.service.GoodsService;

import java.util.List;

/**
 * @author ghy
 * @version 1.0
 */
public class GoodsServiceImpl implements GoodsService {
    @Override
    public void save() {
        System.out.println("保存Goods数据成功了。。。。");
        int i=1/0;
    }

    @Override
    public int delete(Integer id) {
        System.out.println("删除Goods数据成功了。。。。");
        return 0;
    }

    @Override
    public boolean update(Goods goods) {
        return false;
    }

    @Override
    public List<Goods> findAll() {
        return null;
    }

    @Override
    public Goods findById(Integer id){
        System.out.println("根据ID查询的方法。。。。。");
        return new Goods();
    }
}


  • 日志增强类【切面】
package com.java2007.log;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.sql.ResultSet;
import java.util.Arrays;

/**
 * @author ghy
 * @version 1.0
 */
public class LoggerInfo {

    //通过LoggerInfo的class对象获取日志对象
    Logger logger = LoggerFactory.getLogger(LoggerInfo.class);

    public void aa(JoinPoint jp){
        //方法名
        String methodName = jp.getSignature().getName();
        //参数列表
        Object[] args = jp.getArgs();
        if(args != null) {
            logger.info(methodName + "方法开始了--------------->" + Arrays.asList(args));
        } else {
            logger.info(methodName + "方法开始了--------------->");
        }
    }

    public void bb(JoinPoint jp, Object result){
        logger.info(jp.getSignature().getName() + "方法正常结束了........" + result);
    }

    public void cc(Throwable ex){
        logger.info("方法出异常了........" + ex.getMessage());
    }

    public void dd(){
        logger.info("无论出不出异常,我都要执行.....");
    }


    //环绕通知
    public Object ee(ProceedingJoinPoint pjp){
        Object result = null;
        try {
            aa(pjp);
            //执行目标方法
            result = pjp.proceed();
            bb(pjp, result);
        } catch (Throwable throwable) {
            throwable.printStackTrace();
            cc(throwable);
        }
        dd();
        return result;
    }

}


  • 事务通知类【切面】
package com.java2007.log;

/**
 * @author ghy
 * @version 1.0
 */
public class TxInfo {

    public void begin(){
        System.out.println("开启事务");
    }

    public void commit(){
        System.out.println("提交事务");
    }

    public void rollback(){
        System.out.println("回滚事务");
    }

    public void release(){
        System.out.println("释放资源");
    }

}


3.3.2 导入依赖

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aspects</artifactId>
        <version>5.1.6.RELEASE</version>
    </dependency>

3.3.3 SpringAOP 具体配置

<?xml version="1.0" encoding="UTF-8"?>
<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.xsd">

    <bean id="goodsService" class="com.java2007.service.impl.GoodsServiceImpl"></bean>

    <bean id="loggerInfo" class="com.java2007.log.LoggerInfo"></bean>

    <bean id="txInfo" class="com.java2007.log.TxInfo"></bean>

    <!-- 配置AOP -->
    <aop:config>
        <!-- 配置切点 -->
        <!--
            切点表达式
                execution ( 表达式 )
                execution(* com.java2007.service.impl.*.*(..))
                    com.java2007.service.impl包下的所有的类所有的方法都是切点
         -->
        <aop:pointcut id="pt" expression="execution(* com.java2007.service.impl.*.*(..))"/>
        <!-- 配置切面 -->
        <!-- ref:引用切面类
            order:指定执行顺序,值越小,优先级越高。 默认优先级是Integer的最大值
         -->
        <aop:aspect ref="loggerInfo">
            <!-- 前置通知 -->
            <!--<aop:before method="aa" pointcut-ref="pt"></aop:before>-->
            <!-- 后置通知 -->
            <!-- returning : 目标方法的返回值 -->
            <!--<aop:after-returning method="bb" pointcut-ref="pt" returning="result"/>-->
            <!-- 异常通知 -->
            <!-- throwing : 目标方法出的异常 -->
            <!--<aop:after-throwing method="cc" pointcut-ref="pt" throwing="ex"/>-->
            <!-- 最终通知 -->
            <!--<aop:after method="dd" pointcut-ref="pt"/>-->
            
            <!-- 环绕通知 -->
            <aop:around method="ee" pointcut-ref="pt" />
        </aop:aspect>
        <aop:aspect ref="txInfo" order="2">
            <aop:before method="begin" pointcut-ref="pt"></aop:before>
            <aop:after-returning method="commit" pointcut-ref="pt"></aop:after-returning>
            <aop:after-throwing method="rollback" pointcut-ref="pt"></aop:after-throwing>
            <aop:after method="release" pointcut-ref="pt"></aop:after>
        </aop:aspect>
    </aop:config>

    <!--
        四种通知的执行顺序
            前置 ===> 后置/异常 ===> 最终
            前置:在目标方法开始之前执行
            后置:在目标方法正常结束后执行
            异常:在目标方法出异常时执行
            最终:无论目标方法是否正常执行,最终都会执行
    -->

</beans>

4、Spring是如何选择JDK动态代理和Cglib动态代理的?

  • 源代码
public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {

    //决定如何选择代理方式
	@Override
	public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
		if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
			Class<?> targetClass = config.getTargetClass();
			if (targetClass == null) {
				throw new AopConfigException("TargetSource cannot determine target class: " +
						"Either an interface or a target is required for proxy creation.");
			}
			if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
				return new JdkDynamicAopProxy(config);
			}
			return new ObjenesisCglibAopProxy(config);
		}
		else {
			return new JdkDynamicAopProxy(config);
		}
	}
}

  • config.isOptimize():是否优化,看到否的逻辑是JDK,就可以知道Spring认为CGLIB动态代理的性能更高点。。。
  • config.isProxyTargetClass():是否直接代理目标类以及任何接口
  • hasNoUserSuppliedProxyInterfaces(config):是否没有指定代理接口
  • targetClass.isInterface():确定指定的对象是否表示接口类型
  • Proxy.isProxyClass(targetClass):是否是代理类
  • 在代理对象不是接口类型或不是代理类时,指定proxyTargetClass=true后,执行CGLIB代理
  • 代理对象是接口类型或是代理类,使用JDK代理
  • proxy-target-class="true":强制使用cglib动态代理【apache dubbo : RPC框架】

十、注解开发

1、IOC相关的注解

1.1 创建Bean相关的注解

1.1.1 @Component

把当前标记的类的实例当成组件交给Spring容器管理

1.1.2 由@Compent衍生的注解

增加可读性,功能跟@Component一模一样

  1. @Repository : 通过用于标记在Dao层实现类上
  2. @Service : 通过用于标记在Service层实现类上
  3. @Controller : 通过用于标记在Controller层类上
  • 注意
    • 一般三层架构使用各自的衍生注解。其他则采用@Component
    • 注解要生效,必须被Spring扫描

1.1.3 案例

//@Component    //等同于 <bean id="userDaoImpl" class="com.java2007.dao.impl.UserDaoImpl"></bean>
//@Component(value = "userDao")  //等同于 <bean id="userDao" class="com.java2007.dao.impl.UserDaoImpl"></bean>
//@Component("userDao")  //等同于 <bean id="userDao" class="com.java2007.dao.impl.UserDaoImpl"></bean>
@Repository("userDao")
public class UserDaoImpl implements UserDao {
    @Override
    public void save() {
        System.out.println("UserDaoImpl save.....");
    }
}

  • 配置文件
<?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"
       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">

    <!--<bean id="userDao" class="com.java2007.dao.impl.UserDaoImpl"></bean>-->

    <!-- 配置组件扫描,让Spring能够识别的注解生效
        component-scan : 组件扫描
            base-package : 要扫描的包【扫描的包及子包下能够被Spring识别的注解】
    -->
    <!--<context:component-scan base-package="com.java2007.dao.impl"/>-->
    <!-- 配置多包的方式 -->
    <!--<context:component-scan base-package="com.java2007.dao.impl, com.java2007.service.impl"/>-->
    <!-- 扫描父包 -->
    <context:component-scan base-package="com.java2007"/>

</beans>

1.2 依赖注入相关

1.2.1 @Autowired

自动注入属性

  • 特征
    • 先根据类型进行匹配注入属性
    • 如果类型相同,再把属性的名字当做Bean的ID去从容器进行筛选注入

1.2.2 @Qualifier

根据beanId到容器查找bean实例,注入的功能是交给@Autowired注解完成

@Qualifier通过跟@Autowired一起使用

@Qualifier在属性如果单独使用是无法注入成功的。但是在方法的形参上使用,是可以注入成功的,一般跟@Bean注解一起使用

1.2.3 @Resource

直接按照beanId进入注入属性

1.2.4 案例

@Service
public class UserServiceImpl implements UserService {

    @Autowired
    //@Qualifier("userDao2")   //找beanId为userDao2的实例
    //@Resource(name = "userDao2")   //根据name属性指定的bean名称进入注入
    private UserDao userDao;

    //@Autowired
    // 如果标记在setter方法上,则采用的是setter注入
    // 如果标记在成员变量上,则采用的是反射暴力注入,可以没有setter方法。虽然这种方式是有风险的,但是我们不care,一般都采用这种方式【简单】
    /*public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }*/

    @Override
    public void save() {
        System.out.println("UserServiceImpl userDao....");
        userDao.save();
    }
}


1.3 @Bean

通过方法创建Bean实例,交给Spring管理【一般用于第三方的实例对象】

1.3.1 自己通过@Bean来创建Bean

  • 创建接口与实现类
public interface RoleDao {
}


public class RoleDaoImpl implements RoleDao {
}

  • UserServiceImpl
@Service
public class UserServiceImpl implements UserService {

    private UserDao userDao;

    @Override
    public void save() {
        System.out.println("UserServiceImpl userDao....");
        userDao.save();
    }

    @Bean("rd") 
    //将当前方法的返回值作为bean注入到容器中,默认这个beanId是方法名,可以通过name属性或者value来指定beanId
    public RoleDao roleDao2222(@Qualifier("userDao") UserDao ud){
        this.userDao = ud;
        System.out.println(userDao);   //null
        return new RoleDaoImpl();
    }
}


1.3.2 案例

配置数据源

  • jdbc.properties
jdbc.driver=com.mysql.jdbc.Driver
#serverTimezone=GMT #时区
#characterEncoding=UTF8  #编码格式
jdbc.url=jdbc:mysql://localhost:3306/test?useSSL=false&serverTimezone=GMT&characterEncoding=UTF8&useUnicode=true
jdbc.username=root
jdbc.password=root

jdbc.maxWait=100000

  • 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"
       xmlns:context="http://www.springframework.org/schema/context"
       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">

    <!-- 加载外部的properties文件 -->
    <!-- context:property-placeholder 一个spring容器只写一个 -->
    <context:property-placeholder location="classpath*:properties/*.properties"/>

    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
        <property name="driverClassName" value="${jdbc.driver}"></property>
        <property name="url" value="${jdbc.url}"></property>
        <property name="username" value="${jdbc.username}"></property>
        <property name="password" value="${jdbc.password}"></property>

        <!-- 配置获取连接等待超时的时间 -->
        <property name="maxWait" value="${jdbc.maxWait}"/>

        <!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
        <property name="timeBetweenEvictionRunsMillis" value="60000"/>

        <!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
        <property name="minEvictableIdleTimeMillis" value="300000"/>

        <!-- 配置最小空闲连接数 -->
        <property name="minIdle" value="3"/>

        <!-- https://blog.csdn.net/Joker_lcg/article/details/109511251  参数详解 -->

    </bean>

</beans>

  • 注解
package com.java2007.config;

import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;

import javax.sql.DataSource;

/**
 * @author ghy
 * @version 1.0
 */
@Component
public class SpringConfig {

    @Bean
    public DataSource getDataSource() {
        DruidDataSource ds = new DruidDataSource();
        ds.setDriverClassName("com.mysql.jdbc.Driver");
        ds.setUrl("jdbc:mysql://localhost:3306/test?useSSL=false&serverTimezone=GMT&characterEncoding=UTF8&useUnicode=true");
        ds.setUsername("root");
        ds.setPassword("root");
        return ds;
    }

}


1.4 额外注解

1.4.1 @Configuration

代表当前类是一个配置类,相当于applicationContext.xml

1.4.2 @ComponentScan

@ComponentScan(basePackages = {"com.java2007"})

注解版组件扫描

相当于配置文件中的 <context:component-scan base-package="com.java2007"/>

1.4.3 @Import

@Import({BeanConfig.class})

注解版的引入其他配置类

相当于配置文件中的

1.4.4 @PropertySource 和 @PropertySources

引入外部配置文件

​ @PropertySource:引入单个

​ @PropertySources:引入多个

@PropertySource(value = "classpath:jdbc.properties") //无法使用通配符

相当于配置文件中 <context:property-placeholder location="classpath:jdbc.properties"/>

1.4.5 @Value

获取配置文件中的值,赋值给@Value标记的属性

@Value("${jdbc.driver}") //@Value: 配置文件中取key为jdbc.driver的值

2、AOP相关的注解

  • 日志增加类【切面类】
package com.java2007.entity;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

/**
 * @author ghy
 * @version 1.0
 */
//@Component
@Aspect
public class LogInfo {

    @Pointcut("execution(* com.java2007.service.impl.*.*(..))")
    public void pt(){}

    //@Before(value = "pt()")
    public void before(){
        System.out.println("方法开始了");
    }

    //@AfterReturning(value = "pt()")
    public void afterReturning(){
        System.out.println("方法正常结束了");
    }

    //@AfterThrowing(value = "pt()", throwing = "ex")
    public void afterThrowing(Exception ex){
        System.out.println("方法出异常了" + ex.getMessage());
    }

    //@After(value = "pt()")
    public void after(){
        System.out.println("无论如何,我都要执行");
    }

    @Around(value = "pt()")
    public Object around(ProceedingJoinPoint pjp){
        Object result = null;

        try {
            String methodName = pjp.getSignature().getName();
            System.out.println(pjp.getSignature().getName() + "方法开始了" + pjp.getArgs());
            result = pjp.proceed();
            System.out.println(methodName + "方法正常结束了");  //提交事务
            //System.out.println("无论如何,我都要执行");   //释放资源
        } catch (Throwable ex){
            System.out.println("方法出异常了" + ex.getMessage());   //回滚事务
            //System.out.println("无论如何,我都要执行");   //释放资源
        }
        System.out.println("无论如何,我都要执行");   //释放资源
        return result;
    }

    //AOP注解,会存在一个小问题。 最终通知会在后置或异常通知执行之前执行。【如果是事务控制,就会出现失败的问题】
    //但是spring控制事务有一个声明式事务。已经解决了这个问题

}


  • 要生效,必须加一个配置

@EnableAspectJAutoProxy //开启 Aspect 注解生效, 相当于配置文件中的 <aop:aspectj-autoproxy />

十一、Spring后置处理器

可以在初始化Bean的前后对Bean进行操作

  • 具体代码
@Component
public class MyBeanPostProcessor implements BeanPostProcessor {
    /**
     * 初始化Bean之前被调用
     */
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("----init方法之前执行了....");
        return bean;
    }

    /**
     *
     * 初始化Bean之后被调用
     */
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("====init方法之后执行了....");
        return bean;
    }

}

  • Bean的生命周期

随工厂启动创建 ==》 构造方法 ==》 set方法(注入值) ==》Bean后置处理器的postProcessBeforeInitialization ==》 init(初始化) ==》 Bean后置处理器的postProcessAfterInitialization ==》构建完成 ==》随工厂关闭销毁

posted @ 2021-07-21 13:10  牛奶配苦瓜  阅读(38)  评论(0编辑  收藏  举报