Spring(个人)学习笔记

Spring基本介绍

Spring快速入门

代码

beans.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">

    <!--id:java对象在Spring容器中的id 通过id获取对象-->
    <bean class="com.cxz.spring.bean.Monster" id="monster01">
        <property name="monsterId" value="100"/>
        <property name="name" value="牛魔王"/>
        <property name="skill" value="芭蕉扇"/>
    </bean>

</beans>

测试:

public class SpringBeanTest {

    @Test
    public void getMonster(){

        // 创建容器:
        ClassPathXmlApplicationContext ioc =
                new ClassPathXmlApplicationContext("beans.xml");

        Monster monster01 = ioc.getBean("monster01", Monster.class);
        System.out.println("monster01 = "+monster01);

    }
}

Spring容器结构

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MPQ4A7dl-1689565399191)(C:\Users\cxz\Desktop\笔记图片\spring-ioc1.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rH1Gw35I-1689565399191)(C:\Users\cxz\Desktop\笔记图片\spring-ioc2.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QtapFq8C-1689565399192)(C:\Users\cxz\Desktop\笔记图片\spring-ioc03.png)]

Spring管理Bean-IOC

基于XML配置bean

通过类型获取bean

说明:在配置文件中不用指定id,获取bean时直接使用class获取

<!--通过类型获取bean-->
<bean class="com.cxz.spring.bean.Monster">
    <property name="monsterId" value="100"/>
    <property name="name" value="牛魔王"/>
    <property name="skill" value="芭蕉扇"/>
</bean
/**
 * 通过类型获取bean
 */
@Test
public void getBeanByType(){
    ApplicationContext ioc =
            new ClassPathXmlApplicationContext("beans.xml");

    Monster bean = ioc.getBean(Monster.class);
    System.out.println("bean ="+bean);
}

注意:

1、ioc容器中同一个类的bean只能有一个!

2、在一个线程中只需要一个对象情况下使用!

属性赋值的底层实现:

属性的赋值是通过属性的对应的Setter方法 实现的

name --> setName()

通过指定构造器配置bean
<!--指定构造器实现配置bean
    通过全参构造器设置属性
-->
<bean id="monster03" class="com.cxz.spring.bean.Monster">
    <constructor-arg value="200" index="0"/>
    <constructor-arg value="白骨精" index="1"/>
    <constructor-arg value="吸血" index="2"/>
</bean>
<!--其他形式-->
<bean id="monster04" class="com.cxz.spring.bean.Monster">
    <constructor-arg value="200" name="monsterId"/>
    <constructor-arg value="白骨精"name="name"/>
    <constructor-arg value="吸血" name="skill"/>
</bean>

......
通过p名称空间来配置bean
<!--P 名称空间配置bean-->
<bean id="monster06" class="com.cxz.spring.bean.Monster"
    p:monsterId="400"
    p:name="红孩儿"
    p:skill="吐火"
/>
使用ref来配置bean
/**
 * DAO对象
 */
public class MemberDAOImpl {

    public MemberDAOImpl() {
        System.out.println("MemberDAOImpl 构造器被执行...");
    }

    public void add() {
        System.out.println("MemberDAOImpl add()方法执行");
    }
}
/**
 * service对象
 */
public class MemberServiceImpl {

    private MemberDAOImpl memberDAO;

    public MemberDAOImpl getMemberDAO() {
        return memberDAO;
    }

    public void setMemberDAO(MemberDAOImpl memberDAO) {
        this.memberDAO = memberDAO;
    }

    public void add(){
        System.out.println("Service 中的add方法执行");
        memberDAO.add();
    }

}

xml配置:

<!--ref配置bean-->
<bean class="com.cxz.spring.dao.MemberDAOImpl" id="memberDAO"/>

<bean class="com.cxz.spring.service.MemberServiceImpl" id="service">
    <property name="memberDAO" ref="memberDAO"/>
</bean>

测试

@Test
public void setBeanByRef(){
    ApplicationContext ioc =
            new ClassPathXmlApplicationContext("beans.xml");

    MemberServiceImpl service = ioc.getBean("service", MemberServiceImpl.class);

    service.add();
注入集合/数组类型

bean:

public class Master {

    private String name;
    private List<Monster> monsterList;
    private Map<String, Monster> monsterMap;
    private Set<Monster> monsterSet;
    private String[] monsterName;
    private Properties pros;
}

List:

<property name="monsterList">
    <list>
        <!--引用注入:-->
        <ref bean="monster03"/>
        <ref bean="monster04"/>
        <!--内部Bean: -->
        <bean class="com.cxz.spring.bean.Monster">
            <property name="name" value="老鼠精"/>
            <property name="monsterId" value="122"/>
            <property name="skill" value="咬人"/>
        </bean>
    </list>
</property>

map:

<!--map属性赋值-->
<property name="monsterMap">
    <map>
        <entry>
            <key>
                <value>monster_03</value>
            </key>
            <ref bean="monster03"/>
        </entry>
        <entry>
            <key>
                <value>monster_04</value>
            </key>
            <ref bean="monster04"/>
        </entry>
    </map>
</property>

set:

<!--set属性赋值-->
<property name="monsterSet">
    <set>
        <ref bean="monster04"/>
        <ref bean="monster03"/>
        <bean class="com.cxz.spring.bean.Monster">
            <property name="monsterId" value="11111"/>
            <property name="name" value="金角"/>
            <property name="skill" value="吐水"/>
        </bean>
    </set>
</property>

Array:

<!--Array-->
<property name="monsterName">
    <array>
        <!--使用 的是String的数组-->
        <value>牛魔王</value>
        <value>白龙马</value>
        <value>小妖怪</value>
    </array>
</property>

properties:

<!--Properties-->
<property name="pros">
    <props>
        <prop key="username">root</prop>
        <prop key="pwd">123456</prop>
    </props>
</property>
使用utillist进行配置

实现数据的复用

<!--定义util:list   实现数据复用-->
<util:list id="myBookList">
    <value>红楼梦</value>
    <value>西游记</value>
</util:list>

<bean class="com.cxz.spring.bean.BookStore" id="bookStore">
    <property name="bookList" ref="myBookList"/>
</bean>
级联属性赋值

spring的ioc属性可以直接给对象的属性的属性赋值

<bean class="com.cxz.spring.bean.Dept" id="dept"/>

<bean class="com.cxz.spring.bean.Emp" id="emp">
    <property name="name" value="jack"/>
    <property name="dept" ref="dept"/>
    <!--给dept的name属性赋值  级联属性赋值-->
    <property name="dept.name" value="开发部门"/>
</bean>
通过BeanFactory获取Bean

实现FactoryBean接口并重写方法:

public class MyFactoryBean implements FactoryBean<Monster> {

    // 配置时,指定获取对象的key
    private String key;
    private Map<String,Monster> monster_map;
    {
        //完成初始化:
        monster_map = new HashMap<>();
        monster_map.put("monster03",new Monster(300,"牛魔王","芭蕉扇"));
        monster_map.put("monster04",new Monster(400,"fox","xxx"));

    }

    public void setKey(String key) {
        this.key = key;
    }

    @Override
    public Monster getObject() throws Exception {
        return monster_map.get(key);
    }

    @Override
    public Class<?> getObjectType() {
        return Monster.class;
    }

    @Override
    public boolean isSingleton() {
        return true;
    }
}

配置:

<!--
	class 指定使用的FactoryBean
    key   是MyFactoryBean的属性key
    value Factory中的某个对象的id 
-->
<bean id="my_monster05" class="com.cxz.spring.factory.MyFactoryBean">
    <property name="key" value="monster04"/>
</bean>
Bean配置信息的重用

配置:可以实现Bean对象的属性和某一已经配置的bean属性一致 使用 parent

<bean id="monster10" class="com.cxz.spring.bean.Monster">
    <property name="monsterId" value="10"/>
    <property name="name" value="蜈蚣精"/>
    <property name="skill" value="蜇人"/>
</bean>

<!--属性值和monster10的一样-->
<bean id="monster11" class="com.cxz.spring.bean.Monster" parent="monster10"/>

专门用来作为被继承的bean:abstract="true"

<!--abstract=true 表示给bean是用于被继承的 给bean不能被实例化-->
<bean id="monster12" class="com.cxz.spring.bean.Monster" abstract="true">
    <property name="monsterId" value="120"/>
    <property name="name" value="蜈蚣"/>
    <property name="skill" value="蜇人"/>
</bean>

<bean id="monster13" class="com.cxz.spring.bean.Monster" parent="monster12"/>
关于Bean的创建顺序:

顺序:ioc 容器先创建好所有的bean,然后再进行bean的属性的引入

<bean class="com.cxz.spring.service.MemberServiceImpl" id="service">
    <property name="memberDAO" ref="memberDAO"/>
</bean>
<bean class="com.cxz.spring.dao.MemberDAOImpl" id="memberDAO"/>

执行结果:

1、MemberServiceImpl()执行~~~
2、MemberDAOImpl 构造器被执行...
3、setMemberDAO 执行~~
bean对象的单例和多例

说明:

单例:只要ioc容器没有被销毁,ioc一创建,所有的bean都被创建了,整个过程都是用同一个bean对象 scope=“singleton”

多例:每次获取bean时都是新建的bean(调用bean对象的构造器方法)。

scope=“prototype”

默认单例

单例+懒加载:只有在使用getBean()时才会创建bean

<bean class="com.cxz.spring.bean.Cat" id="cat" scope="singleton" lazy-init="true">
    <property name="name" value="mimi"/>
    <property name="id" value="111"/>
</bean>

多例:bean的创建时间为ioc.getBean(“xxx”)时创建

<bean class="com.cxz.spring.bean.Cat" id="cat" scope="prototype">
    <property name="name" value="mimi"/>
    <property name="id" value="111"/>
</bean>
Bean的生命周期

bean:

public class House {

    private String name;

    public House() {
        System.out.println("House() 构造器~~~");
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        System.out.println("House setName()..."+name);
        this.name = name;
    }

    //程序员指定的方法:
    public void init() {
        System.out.println("House init()..");
    }
    
	//程序员指定的方法:
    public void destroy() {
        System.out.println("House destroy()..");
    }
}

配置中指定初始化方法和销毁时方法:

init 执行时机:setter执行后
destroy 执行时机:容器关闭时
<bean class="com.cxz.spring.bean.House" id="house"
      init-method="init"
      destroy-method="destroy">

    <property name="name" value="大房子"/>

</bean>

测试:

@Test
public void beanLife(){
    ApplicationContext ioc =
            new ClassPathXmlApplicationContext("beans.xml");
    House house = ioc.getBean("house", House.class);

    //关闭ioc
    ((ConfigurableApplicationContext)ioc).close();
}

执行结果:

1、House() 构造器~~~
2、House setName()...大房子
3、House init()..
4、House destroy()..
配置bean的后置处理器

执行时机:后置处理器在bean的初始化方法前初始化方法调用后被调用

原理:使用了AOP

作用:对ioc中所有对象进行统一处理,如 日志处理/权限验证

创建后置处理器:实现BeanPostProcessor

public class MyBeanPostProcessor implements BeanPostProcessor {


    /**
     * 时机 : 在bean的init 方法调用之前
     * @param bean 传入的在ioc中创建的bean
     * @param beanName bean的 id
     * @return Object 对传入的bean进行修改处理
     * @throws BeansException
     */
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName)
            throws BeansException {
        System.out.println("postProcessBeforeInitialization调用  bean = "+bean +
                "  beanName= "+beanName);
        // 实例: 对多个对象进行编程
        if (bean instanceof House){
            ((House)bean).setName("上海豪宅");
        }
        return bean;
    }

    /**
     * 时机 :在init方法 调用之后调用
     * @param bean 传入的在ioc中创建的bean
     * @param beanName bean的 id
     * @return
     * @throws BeansException
     */
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName)
            throws BeansException {

        System.out.println("postProcessAfterInitialization调用  bean = "+bean +
                "  beanName= "+beanName);
        return null;
    }
}

配置:

<bean class="com.cxz.spring.bean.House" id="house02"
      init-method="init"
      destroy-method="destroy">
    <property name="name" value="窝棚"/>
</bean>

<!--配置后置处理器-->
<!--配置后置处理器后,后置处理器就会总用于该容器的对象-->
<bean class="com.cxz.spring.bean.MyBeanPostProcessor" id="beanPostProcessor"/>
通过属性文件给Bean注入值

在classpath下新建文件:

monsterId =1001
name = tom
skill = Hello

配置:

<!--指定文件-->
<context:property-placeholder location="classpath:my.properties"/>

<!--配置-->
<bean class="com.cxz.spring.bean.Monster" id="monster1111">
    <!--通过属性文件注入值 使用 : ${ 属性名 }-->
    <property name="monsterId" value="${monsterId}"/>
    <property name="name" value="${name}"/>
    <property name="skill" value="${skill}"/>
</bean>

属性文件中文处理:

使用工具将中文转Unicode编码

monsterId =1001
name = \u4e4c\u9f9f\u7cbe
skill = \u7f29\u8116\u5b50\u000d\u000a
自动装配Bean

ByType 根据类型自动装配

关系:OrderAction -> OrderService -> OrderDao

配置:

<bean class="com.cxz.spring.dao.OrderDao" id="orderDao"/>

<!--自动装配:
    不能有两个相同类型的对象
    没有属性的对象没必要装配-->
<bean autowire="byType" class="com.cxz.spring.service.OrderService"
      id="orderService" />

<bean autowire="byType" class="com.cxz.spring.web.OrderAction" id="orderAction"/>

ByName 根据名称进行装配

配置:

<bean class="com.cxz.spring.dao.OrderDao" id="orderDao"/>

<!--自动装配
    通过名字完成自动装配
    orderService是根据setter方法:根据setOrderDao()的OrderDao,找id为的orderDao的对象
	进行装配
    -->
<bean autowire="byName" class="com.cxz.spring.service.OrderService"
      id="orderService" />

<bean autowire="byName" class="com.cxz.spring.web.OrderAction" id="orderAction"/>

基于注解配置bean

注解快速入门

使用注解标注:

com.cxz.spring.component包下的类:

@Controller
public class UserAction {
}
@Component
public class UserComponent {
}
@Repository
public class UserDao {
}

配置文件中指定包,将包下的类带注解的类实例化并放入ioc容器中:

<!--配置容器要扫描的包
    1、对指定的包下类扫描,并创建对象到容器中
    2、spring容器初始化时就会扫描该包下的所有 有注解@Controller..的类。
      将类实例化并放入ioc容器中
-->
<context:component-scan base-package="com.cxz.spring.component"/>
注意事项:

1、base-package=" " 扫描的是指定包的下类及子包下的类

2、某个包下的某个类不想被扫描,可以直接不加注解。

​ 或者是 resource-pattern="User*.class"表示只扫描User打头的类

3、排除某个包下的类,通过注解的方式:

​ 排除所有有@Service注解的类

<!--排除某个包下的某种类型的类
    type="annotation" 表示基于注解
-->
<context:component-scan base-package="com.cxz.spring.component">
    <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Service"/>
</context:component-scan>

4、指定自动扫描哪些注解类,表示只扫描指定的注解的类。

​ 要先把默认机制关掉:use-default-filters="false"

<!--按照自己的规则扫描某些注解的类
    use-default-filters="false" 表示不适用默认的扫描机制
-->
<context:component-scan base-package="com.cxz.spring.component" use-default-filters="false">
    <context:include-filter type="annotation" expression="org.springframework.stereotype.Service"/>
</context:component-scan>

5、标记注解后,类名首字母小写作为id的默认值:

@Service
public class UserService {
}

id = UserService

手动实现Spring的注解配置

ComponentScan注解

/**
 *自定义 ComponentScan 注解
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ComponentScan {

    String value() default "";
}

Config配置类:

/**
 * 类似于 beans.xml
 */
@ComponentScan(value = "com.cxz.spring.component")
public class HspSpringConfig {
}

容器:

/**
 * 类似Spring的 原生 容器
 */
public class HspSpringApplicationContext {

    private Class configClass;

    //ioc 存放通过反射创建的对象
    private final ConcurrentHashMap<String,Object> ioc=
            new ConcurrentHashMap<>();

    public HspSpringApplicationContext(Class configClass) {
        this.configClass = configClass;

        ComponentScan componentScan =
                (ComponentScan)this.configClass.getDeclaredAnnotation(ComponentScan.class);
        // 要扫描的包的路径
        String path = componentScan.value();

        path = path.replace(".","/");
        //path = com/cxz/spring/component

        // 获取扫描包下的所有资源 .class
        // 1.得到类的加载器:
        ClassLoader classLoader = HspSpringApplicationContext.class.getClassLoader();
        // 2、通过类的加载器 获取到要扫描包资源的URL
        URL resource = classLoader.getResource(path);
        //file:/D:/Java/HSP_SSM_CODES/spring/out/production/spring/com/cxz/spring/component

        //3、对要加载的资源路径下的文件进行遍历:
        File file = new File(resource.getFile());
        if (file.isDirectory()){
            File[] files = file.listFiles();
            for (File f : files) {

                String fileAbsolutePath = f.getAbsolutePath();
                /**
                 * fileAbsolutePath = D:\Java\HSP_SSM_CODES\spring\out\production\spring\com\cxz\spring\component\UserAction.class*/
                //过滤 只处理Class文件:
                if (fileAbsolutePath.endsWith(".class")) {

                    // 路径更改形式为 com.cxz.spring.component.UserService
                    // 1、获取类名
                    String className =
                            fileAbsolutePath.substring(fileAbsolutePath.lastIndexOf("\\") + 1, fileAbsolutePath.indexOf(".class"));
                    // 2、获取类的全路径 :com.cxz.spring.component + 类.class
                    String classFullName = path.replace("/", ".") + "." + className;
                    /**
                     * classFullName = com.cxz.spring.component.UserAction
                     */
                    // 3、判断该类是否要被注入到容器中 看该类有没有@Commponent/ @Service
                    try {
                        // 得到该类的Class对象
                        Class<?> aClass = classLoader.loadClass(classFullName);
                        if (aClass.isAnnotationPresent(Component.class) ||
                            aClass.isAnnotationPresent(Controller.class)||
                            aClass.isAnnotationPresent(Service.class)   ||
                            aClass.isAnnotationPresent(Repository.class)){

                        // 可以反射对象 并放入到容器中:
                            Class<?> clazz = Class.forName(classFullName);
                            Object instance = clazz.newInstance();
                            //放入容器 类名首字母小写
                            ioc.put(StringUtils.uncapitalize(className), instance);

                        }

                    } catch (Exception e) {
                        e.printStackTrace();
                    }


                }
            }//for
        }

    }
    // 编写方法返回对象
    public Object getBean(String name){
        return ioc.get(name);
    }
}
自动装配@AutoWired

@AutoWired的装配原理:

步骤1 : 在 IOC 容器中查找待装配的组件的类型,如果有唯一的 bean 匹配,则使用该 bean 装 配

步骤2 : 如待装配的类型对应的 bean 在 IOC 容器中有多个,则使用待装配的属性的属性名作 为 id 值再进行查找, 找到就装配,找不到就抛异常

自动装配@Resource

有name属性和type属性

如果不指定属性,直接使用@Resource?

先按照 byName 匹配,再按照 byType 匹配。

泛型依赖注入

BaseDao

public abstract class BaseDao<T> {

    public abstract void save();
}

BaseService

public class BaseService<T> {

    @Autowired
    private BaseDao<T> baseDao;

    public void save(){

        baseDao.save();
    }
}

说明:

凡是 继承 了BaseService的XXXService的子类,和继承了BaseDao的类XXXDao的子类

XXXService 不再需要注入 XXXDao了,可以提供Spring的泛型依赖注入。

AOP

动态代理

定义接口

public interface Vehicle {
    public void run();
}

该接口的两个实现类:

public class Car implements Vehicle{
    @Override
    public void run() {
        System.out.println("汽车跑起来!!");
    }
}
public class Sheep implements Vehicle{
    @Override
    public void run() {
        System.out.println("轮船在水上运行");
    }
}

该接口的代理类

/**
 *  返回一个代理对象
 */
public class VehicleProxyProvider {

    // 表示真正要执行的对象
    private Vehicle target_vehicle;


    public VehicleProxyProvider(Vehicle target_vehicle) {
        this.target_vehicle = target_vehicle;
    }


    public Vehicle getProxy(){

        //得到一个类加载器
        ClassLoader classLoader = target_vehicle.getClass().getClassLoader();

        // 得到要被代理的对象的接口信息,底层是通过接口来完成
        Class<?>[] interfaces = target_vehicle.getClass().getInterfaces();

        // 创建InvocationHandler 对象
        // 使用匿名对象
        /**
         * Object proxy, 代理对象
         * Method method, 通过代理对象调用方法时的方法  代理对象.run()
         * Object[] args : 代理对象.run(xxx)
         * 返回:代理对象.run()
         */
        InvocationHandler invocationHandler = new InvocationHandler() {

            @Override
            public Object invoke(Object proxy, Method method, Object[] args)
                    throws Throwable {
                System.out.println("交通工具运行···");
                Object result = method.invoke(target_vehicle, args);
                System.out.println("交通工具停止···");
                return result;
            }
        };

        /**
         * newProxyInstance() : 返回一个代理对象
         * public static Object newProxyInstance(ClassLoader loader,
         *                                           Class<?>[] interfaces,
         *                                           InvocationHandler h) {
         */
        Vehicle proxy =
                (Vehicle) Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);

        return proxy;
    }
}

测试使用:

@Test
public void testProxy2(){

    Vehicle vehicle = new Sheep();
    // 创建代理类对象 并传入要代理的实际对象
    VehicleProxyProviderTest01 vehicleProxyProviderTest01 =
            new VehicleProxyProviderTest01(vehicle);

    // 实际执行对象
    Vehicle proxy = vehicleProxyProviderTest01.getProxy();
    proxy.run();

}

AOP快速实现

配置文件中配置:

<context:component-scan base-package="com.cxz.spring.aop.aspectj"/>
<!--开启基于注解的AOP功能-->
<aop:aspectj-autoproxy/>

接口

public interface SmartAnimalable {

    float getSum(float i,float j);
    
    float getSub(float i,float j);
    
}

接口实现类

@Component
public class SmartDog implements SmartAnimalable {

    @Override
    public float getSum(float i, float j) {

        float result = i + j;
        System.out.println("方法内部打印的result = "+result);
        return result;
    }

    @Override
    public float getSub(float i, float j) {
        float result = i - j;
        System.out.println("方法内部打印的result = "+result);
        return result;
    }
}

切面类

/**
 * 切面类
 * @Aspect : 切面类 自动引入底层的动态代理+反射机制+动态绑定
 */
@Aspect
@Component
public class SmartAnimalAspect {

    // 1 切入到SmartDog-getSum()前执行
    // 2 execution(访问修饰符 返回类型 全类名.方法名(形参列表))
    // 3 joinPoint:在底层执行时 由AspectJ切面框架 会给该切入方法传入JoinPoint对象,
    //   通过该对象,程序员可以获取到相关信息
    @Before(value ="execution(public float com.cxz.spring.aop.aspectj.SmartDog.getSum(float,float))" )
    public void showBeginLog(JoinPoint joinPoint) {
        // 通过JoinPoint可以获得方法签名:
        Signature signature = joinPoint.getSignature();
        System.out.println("AspectJ切入类的 before 日志--方法名:" + signature.getName()
                + "--方法开始--参数:" + Arrays.asList(joinPoint.getArgs()));
    }

    // 返回通知
    @AfterReturning(value = "execution(public float com.cxz.spring.aop.aspectj.SmartDog.getSum(float,float))")
    public void showSuccessEnd(JoinPoint joinPoint) {
        Signature signature = joinPoint.getSignature();
        System.out.println("AspectJ切入类的 after 日志--方法名:" + signature.getName()
                + "--方法正常结束");
    }

    // 异常通知
    @AfterThrowing(value = "execution(public float com.cxz.spring.aop.aspectj.SmartDog.getSum(float,float))")
    public void showExceptionLog(JoinPoint joinPoint) {
        Signature signature = joinPoint.getSignature();
        System.out.println("AspectJ切入类的 执行异常 日志--方法名:" + signature.getName());
    }

    // 最终通知
    @After(value = "execution(public float com.cxz.spring.aop.aspectj.SmartDog.getSum(float,float))")
    public void showFinallyEndLog(JoinPoint joinPoint) {
        Signature signature = joinPoint.getSignature();
        System.out.println("AspectJ切入类的 最终通知 日志--方法名:" + signature.getName());
    }
}

切入表达式

@Before(value = "execution(public void com.cxz.spring.aop.aspectj.Phone.work())")

任意修饰类型及返回类型:

@Before(value = "execution(* com.cxz.spring.aop.aspectj.Phone.work())")

work方法的任意形参列表的方法

@Before(value = "execution(public void com.cxz.spring.aop.aspectj.Phone.work(..))")

某接口下的方法

切入表达式也可以指向接口的方法,会指向实现了该接口的类生效

@Before(value = "execution(public void com.cxz.spring.aop.aspectj.USBInterface.work())")

注意事项

注意:切入表达式也可以对没有实现接口的类进行切入

@Component
public class Car {

    public void run(){
        System.out.println("car running");
    }
}
@Before(value = "execution(public void com.cxz.spring.aop.aspectj.Car.run())")
public void ok(JoinPoint joinPoint){
    Signature signature = joinPoint.getSignature();
    System.out.println("切面ok 执行:"+signature.getName());
}
@Test
public void test(){
    ApplicationContext ioc =
            new ClassPathXmlApplicationContext("beans08.xml");
    //此时car是代理对象
    Car car = ioc.getBean(Car.class, "car");

    car.run();
}

说明:

1、这是基于CGlib的动态代理,之前的是基于JDK的动态代理

2、这里的car也是代理对象,不过可以认为是Car类的子类(动态生成的)

CGlib动态代理和JDK动态代理

说明:

1.jdk动态代理是面向接口的,只能增强实现类中接口中存在的方法。

cglib是面向父类的,可以增强父类的所有方法。

2.jdk动态代理得到的对象是jdk代理对象实例(是基于接口的代理对象),CGlib得到的对象是被代理对象的子类

对比:

1、JDK动态代理得到的是 基于接口的JDK代理的代理对象:class com.sun.proxy.$Proxy22

UsbInterface phoneProxy =  (UsbInterface) ioc.getBean("phone");
UsbInterface cameraProxy =  (UsbInterface) ioc.getBean("camera");

2、CGlib动态代理是面向父类的方法增强,得到的代理对象是被代理类的子类

class com.cxz.spring.aop.aspectj.Car E n h a n c e r B y S p r i n g C G L I B EnhancerBySpringCGLIB EnhancerBySpringCGLIB5f8de81e

Car car = ioc.getBean(Car.class, "car");

把目标方法结果返回给返回通知

在**@AfterReturning** 增加属性 returning = “res”,同时在切入方法中增加 Object res

// 返回通知
@AfterReturning(value = "execution(public float com.cxz.spring.aop.aspectj.SmartDog.getSum(float,float))",returning = "res")
public void showSuccessEnd(JoinPoint joinPoint, Object res) {
    Signature signature = joinPoint.getSignature();
    System.out.println("AspectJ切入类的 after 日志--方法名:" + signature.getName()
            + "--方法正常结束");
}

异常信息返回异常通知

// 异常通知
@AfterThrowing(value = "execution(public float com.cxz.spring.aop.aspectj.SmartDog.getSum(float,float))",throwing = "throwable")
public void showExceptionLog(JoinPoint joinPoint,Throwable throwable) {
    Signature signature = joinPoint.getSignature();
    System.out.println("AspectJ切入类的 执行异常 日志--方法名:" + signature.getName()+"," +
            "异常信息为 = "+throwable);
}

切入表达式重用

定义切入点表达式

// 定义切入点:
@Pointcut(value = "execution(public float com.cxz.spring.aop.aspectj.SmartDog.getSum(float,float))")
public void myPointCut(){
}

使用:

@Before(value ="myPointCut()")

切面类执行顺序

使用@Order(value = xx)标注切面类,value值越小,优先级越高

@Aspect
@Component
@Order(value = 2)
public class SmartAnimalAspect02 { ... }

切面类中方法的执行顺序:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uENu0DKj-1689565399192)(C:\Users\cxz\Desktop\笔记图片\aop顺序.png)]

基于XML配置的AOP

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

    <!--切面类的bean-->
    <bean class="com.cxz.spring.aop.xml.SmartAnimalAspect" id="smartAnimalAspect"/>

    <bean class="com.cxz.spring.aop.xml.SmartDog" id="smartDog"/>

    <!--配置切面类-->
    <aop:config>

        <!--配置切入点表达式-->
        <aop:pointcut id="myPointCut" expression="execution(public float com.cxz.spring.aop.xml.SmartDog.getSum(float,float))"/>

        <!--指定切面对象-->
        <aop:aspect ref="smartAnimalAspect" order="1">
            <!--前置通知-->
            <aop:before method="showBeginLog" pointcut-ref="myPointCut"/>
            <!--返回通知-->
            <aop:after-returning method="showSuccessEnd" pointcut-ref="myPointCut" returning="res"/>
            <!--异常通知-->
            <aop:after-throwing method="showExceptionLog" pointcut-ref="myPointCut" throwing="throwable"/>
            <!--最终通知-->
            <aop:after method="showFinallyEndLog" pointcut-ref="myPointCut"/>
        </aop:aspect>

    </aop:config>

</beans>

Spring底层实现

整体架构

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-o4AxOCro-1689565399193)(C:\Users\cxz\Pictures\Screenshots\spring0.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RvkCTM61-1689565399193)(C:\Users\cxz\Desktop\笔记图片\image-20230713100158547.png)]

整体思路:

1、new HspSpringApplicationContext() 后会先执行**beanDefinitionByScan()**扫描,

​ 扫描所有有**@Component**注解的类:

​ 如果是有后置处理器类,就将后置处理器对象加入 beanPostProcessorList中,

​ 其他的带有**@Component的类,将其value拿出来当做该类的beanName**,

​ 再取出带有**@Scope**的类,同时把它的value拿出来当做scope,同时把beanName和scope

​ 封装成一个BeanDefinition对象,并放入beanDefinitionMap中。

2、之后会执行HspSpringApplicationContext() 构造器的其他行。取得BeanDefinitionMap

​ 中的所有BeanDefinition对象(根据beanName)。再获取BeanDefinitionMap中所有的beanName,

​ 根据这个beanName拿出Map中的BeanDefinition对象,并判断它是否是单例的,如果是,就调用

createBean(), 通过反射机制创建一个Object, 并放入singletonObjects中。

3、在使用Spring容器时,创建好Spring容器后,通过getBean() 方法,先判断beanDefinitionMap

​ 里面有没有BeanDefinition对象,如果有:

​ 判断是否是单例的?如果是单例的,直接从singletonObjects中取出。

​ 如果是多例的,直接调用createBean() 方法创建一个新的Bean。

4、在createBean() 时,先判断里面有没有**@AutoWired**?有的话,就从Map中调用getBean()

​ 并组装到该bean对象中。接着执行初始化方法,并在该初始化方法前后执行后置处理器的before

​ 和after方法。同时要在after中运用动态代理机制生成该Bean的代理类对象

JDBCTemplates

配置数据源DataSource

jdbc.properties

jdbc.user=root
jdbc.pwd=123456
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/spring?serverTimezone=UTC

配置数据源

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

    <context:property-placeholder location="classpath:jdbc.properties"/>
    <!--配置数据源-->
    <bean class="com.mchange.v2.c3p0.ComboPooledDataSource" id="dataSource">
        <property name="user" value="${jdbc.user}"/>
        <property name="password" value="${jdbc.pwd}"/>
        <property name="driverClass" value="${jdbc.driver}"/>
        <property name="jdbcUrl" value="${jdbc.url}"/>
    </bean>
</beans>

应用

配置JdbcTemplate对象
<!--配置JdbcTemplate对象-->
<bean class="org.springframework.jdbc.core.JdbcTemplate" id="jdbcTemplate">
    <property name="dataSource" ref="dataSource"/>
</bean>
添加数据

方式一

@Test
public void test001() throws SQLException {

    ApplicationContext ioc
            = new ClassPathXmlApplicationContext("JdbcTemplates_ioc.xml");

    JdbcTemplate jdbcTemplate = ioc.getBean(JdbcTemplate.class);
    // 添加
    String sql = "insert into monster values(400,'红孩儿','红缨枪')";
    jdbcTemplate.execute(sql);
    System.out.println("ok~");
}

方式二

@Test
public void test001() throws SQLException {

    ApplicationContext ioc
            = new ClassPathXmlApplicationContext("JdbcTemplates_ioc.xml");

    JdbcTemplate jdbcTemplate = ioc.getBean(JdbcTemplate.class);
    String sql = "insert into monster values(?,?,?)";
    jdbcTemplate.update(sql,500,"牛魔王","大斧头");

    System.out.println("ok~");
}
修改数据
@Test
public void test001() throws SQLException {

    ApplicationContext ioc
            = new ClassPathXmlApplicationContext("JdbcTemplates_ioc.xml");

    JdbcTemplate jdbcTemplate = ioc.getBean(JdbcTemplate.class);
    String sql = "update monster set skill = ? where id = ?";
    jdbcTemplate.update(sql,"大力拳头",100);

    System.out.println("ok~");
}
批量处理
@Test
public void test001() throws SQLException {

    ApplicationContext ioc
            = new ClassPathXmlApplicationContext("JdbcTemplates_ioc.xml");

    JdbcTemplate jdbcTemplate = ioc.getBean(JdbcTemplate.class);
    String sql = "insert into monster values(?,?,?)";
    List<Object[]> bathArgs = new ArrayList<>();
    bathArgs.add(new Object[]{600,"老鼠精","吃粮食"});
    bathArgs.add(new Object[]{700,"老鹰精","吃老鼠"});
    
    int[] ints = jdbcTemplate.batchUpdate(sql, bathArgs);
    
    System.out.println("ok~");
}
查询后封装成对象
@Test
public void test001() throws SQLException {

    ApplicationContext ioc
            = new ClassPathXmlApplicationContext("JdbcTemplates_ioc.xml");
    JdbcTemplate jdbcTemplate = ioc.getBean(JdbcTemplate.class);
	// 注意:使用AS把id指定为Monster类的属性monsterId
    String sql = "select id as monsterId, name, skill from monster where id=100";

    RowMapper<Monster> rowMapper = new BeanPropertyRowMapper<>(Monster.class);

    Monster monster = jdbcTemplate.queryForObject(sql, rowMapper);
    System.out.println(monster);
    System.out.println("ok~");
}
查询后封装成对象集合
@Test
public void test001() throws SQLException {

    ApplicationContext ioc
            = new ClassPathXmlApplicationContext("JdbcTemplates_ioc.xml");
    JdbcTemplate jdbcTemplate = ioc.getBean(JdbcTemplate.class);

    String sql = "select id as monsterId, name, skill from monster where id>=?";
    RowMapper<Monster> rowMapper = new BeanPropertyRowMapper<>(Monster.class);
    List<Monster> monsterList = jdbcTemplate.query(sql, rowMapper, 100);
    for (Monster monster : monsterList) {
        System.out.println(monster);
    }
    System.out.println("ok~");
}
查询单行行列
@Test
public void test001() throws SQLException {

    ApplicationContext ioc
            = new ClassPathXmlApplicationContext("JdbcTemplates_ioc.xml");
    JdbcTemplate jdbcTemplate = ioc.getBean(JdbcTemplate.class);

    String sql = "select name from monster where id = 100";
    String name = jdbcTemplate.queryForObject(sql, String.class);
    System.out.println(name);

    System.out.println("ok~");
}
具名参数

配置:

<bean class="org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate" id="template">
    <constructor-arg name="dataSource" ref="dataSource"/>
</bean>
@Test
public void test001() throws SQLException {

    ApplicationContext ioc
            = new ClassPathXmlApplicationContext("JdbcTemplates_ioc.xml");
    NamedParameterJdbcTemplate namedParameterJdbcTemplate =
            ioc.getBean(NamedParameterJdbcTemplate.class);

    String sql = "insert into monster values(:id,:name,:skill)";
    Map<String, Object> map = new HashMap<>();
    // 给map传参数
    map.put("id",900);
    map.put("name","猪八戒");
    map.put("skill","铁耙子");
    int update = namedParameterJdbcTemplate.update(sql, map);
    System.out.println("ok~");
}
sqlparametersource

封装具名参数为对象

@Test
public void test001() throws SQLException {

    ApplicationContext ioc
            = new ClassPathXmlApplicationContext("JdbcTemplates_ioc.xml");
    NamedParameterJdbcTemplate namedParameterJdbcTemplate =
            ioc.getBean(NamedParameterJdbcTemplate.class);

    String sql = "insert into monster values(:monsterId,:name,:skill)";
    Monster monster = new Monster(1111, "大象精", "大鼻子");

    SqlParameterSource sqlParameterSource =
            new BeanPropertySqlParameterSource(monster);

    namedParameterJdbcTemplate.update(sql,sqlParameterSource);

    System.out.println("ok~");
}

声明式事务

事务管理

配置事务管理器

在bean.xml中

<!--配置事务管理器-->
<bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="transactionManager">
    <!--指定对哪个数据源进行配置-->
    <property name="dataSource" ref="dataSource"/>
</bean>
<!--配置基于注解的事务管理-->
<tx:annotation-driven transaction-manager="transactionManager"/>
事务管理器的使用-基于注解

使用注解 @Transactional

@Transactional
public void buyGoodsByTx(int userId, int goodsId, int amount){

    System.out.println("用户购买信息: userId = "+userId+" 购买数量 = "+amount);

    // 1 得到商品价格
    Float price = goodsDao.queryPriceById(userId);
    // 2 减少用户余额:
    goodsDao.updateBalance(userId,price*amount);
    // 3 减少库存量
    goodsDao.updateAmount(goodsId,amount);

    System.out.println("用户购买成功");
}
底层实现

@Transactional底层使用的是AOP机制,底层使用动态代理对象来调用buyGoodsByTx()方法调用前,

先调用了事务管理器的doBegin(), 再调用**buyGoodsByTx()**方法,如果执行过程中没有发生异常,就

调用事务管理器的doCommit() ,如果发生了异常,调用事务管理器的doRollBack().

事务的传播机制

REQUIRED 传播属性【默认】

说明:在同一个事务中,对所有的事务方法当做整体处理,一个失败,整体都会失败

示例:

/**
 * 两次购买商品的行为
 */
@Transactional
public void multiByGoodsByTx(){
    // REQUIRED传播属性 作为一个整体
    goodsService.buyGoodsByTx(1,1,1);
    goodsService.buyGoodsByTx2(1,1,1);

}

说明:当方法buyGoodsByTx2失败时,buyGoodsByTx方法也会回滚,由multiByGoodsByTx()事务

管理,进行同一管理。

REQUIRED_NEW 传播属性

修改事务传播属性为REQUIRES_NEW

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void buyGoodsByTx(int userId, int goodsId, int amount){

    System.out.println("用户购买信息: userId = "+userId+" 购买数量 = "+amount);

    // 1 得到商品价格
    Float price = goodsDao.queryPriceById(userId);
    // 2 减少用户余额:
    goodsDao.updateBalance(userId,price*amount);
    // 3 减少库存量
    goodsDao.updateAmount(goodsId,amount);

    System.out.println("用户购买成功");
}



@Transactional(propagation = Propagation.REQUIRES_NEW)
public void buyGoodsByTx2(int userId, int goodsId, int amount){

    System.out.println("用户购买信息: userId = "+userId+" 购买数量 = "+amount);

    // 1 得到商品价格
    Float price = goodsDao.queryPriceById2(userId);
    // 2 减少用户余额:
    goodsDao.updateBalance2(userId,price*amount);
    // 3 减少库存量
    goodsDao.updateAmount2(goodsId,amount);

    System.out.println("用户购买成功");
}

说明:如果方法buyGoodsByTx2()失败了,不会做造成前面的buyGoodsByTx()的执行。他们是单独的。

注意:如果是前一个事务出现问题,则会影响第二事务的执行!

事务隔离级别

/**
 * 默认:声明式事务的隔离级别 可重复读
 * 读已提交
 */
@Transactional(isolation = Isolation.READ_COMMITTED)
public void buyGoodsByTxISOLATION(){
    Float price = goodsDao.queryPriceById(1);
    System.out.println("第一次查询价格 = "+price);

    Float price2 = goodsDao.queryPriceById(1);
    System.out.println("第二次查询价格 = "+price2);
}

事务超时回滚

超过时间timeout ,事务就会发生回滚

@Transactional(timeout = 2)
public void buyGoodsByTxTimeOut(int userId, int goodsId, int amount) {

    System.out.println("用户购买信息: userId = "+userId+" 购买数量 = "+amount);


    // 1 得到商品价格
    Float price = goodsDao.queryPriceById(userId);
    // 2 减少用户余额:
    goodsDao.updateBalance(userId,price*amount);
    // 模拟超时
    System.out.println("超时开始!!");
    try {
        Thread.sleep(3000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println("超时结束!!");
    // 3 减少库存量
    goodsDao.updateAmount(goodsId,amount);

    System.out.println("用户购买成功");

}

ByGoodsByTx()事务

管理,进行同一管理。

REQUIRED_NEW 传播属性

修改事务传播属性为REQUIRES_NEW

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void buyGoodsByTx(int userId, int goodsId, int amount){

    System.out.println("用户购买信息: userId = "+userId+" 购买数量 = "+amount);

    // 1 得到商品价格
    Float price = goodsDao.queryPriceById(userId);
    // 2 减少用户余额:
    goodsDao.updateBalance(userId,price*amount);
    // 3 减少库存量
    goodsDao.updateAmount(goodsId,amount);

    System.out.println("用户购买成功");
}



@Transactional(propagation = Propagation.REQUIRES_NEW)
public void buyGoodsByTx2(int userId, int goodsId, int amount){

    System.out.println("用户购买信息: userId = "+userId+" 购买数量 = "+amount);

    // 1 得到商品价格
    Float price = goodsDao.queryPriceById2(userId);
    // 2 减少用户余额:
    goodsDao.updateBalance2(userId,price*amount);
    // 3 减少库存量
    goodsDao.updateAmount2(goodsId,amount);

    System.out.println("用户购买成功");
}

说明:如果方法buyGoodsByTx2()失败了,不会做造成前面的buyGoodsByTx()的执行。他们是单独的。

注意:如果是前一个事务出现问题,则会影响第二事务的执行!

事务隔离级别

/**
 * 默认:声明式事务的隔离级别 可重复读
 * 读已提交
 */
@Transactional(isolation = Isolation.READ_COMMITTED)
public void buyGoodsByTxISOLATION(){
    Float price = goodsDao.queryPriceById(1);
    System.out.println("第一次查询价格 = "+price);

    Float price2 = goodsDao.queryPriceById(1);
    System.out.println("第二次查询价格 = "+price2);
}

事务超时回滚

超过时间timeout ,事务就会发生回滚

@Transactional(timeout = 2)
public void buyGoodsByTxTimeOut(int userId, int goodsId, int amount) {

    System.out.println("用户购买信息: userId = "+userId+" 购买数量 = "+amount);


    // 1 得到商品价格
    Float price = goodsDao.queryPriceById(userId);
    // 2 减少用户余额:
    goodsDao.updateBalance(userId,price*amount);
    // 模拟超时
    System.out.println("超时开始!!");
    try {
        Thread.sleep(3000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println("超时结束!!");
    // 3 减少库存量
    goodsDao.updateAmount(goodsId,amount);

    System.out.println("用户购买成功");

}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值