spring快照版本是最新的版本,未发布。需要用到
下面这个不需要配置仓库,直接写在依赖中就行
引入spring相关依赖
- "1.0" encoding="UTF-8"?>
- <project xmlns="http://maven.apache.org/POM/4.0.0"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
- <modelVersion>4.0.0modelVersion>
-
- <groupId>com.gringroupId>
- <artifactId>spring-002-firstartifactId>
- <version>1.0-SNAPSHOTversion>
-
- <packaging>jarpackaging>
-
- <properties>
- <maven.compiler.source>17maven.compiler.source>
- <maven.compiler.target>17maven.compiler.target>
- <project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
- properties>
-
- <dependencies>
- <dependency>
- <groupId>org.springframeworkgroupId>
- <artifactId>spring-contextartifactId>
- <version>6.0.10version>
- dependency>
- <dependency>
- <groupId>junitgroupId>
- <artifactId>junitartifactId>
- <version>4.13.2version>
- <scope>testscope>
- dependency>
-
- dependencies>
- project>
疑惑:
1.类加载路径
类路径classpath指的是编译后路径即:
本项目[或模块目录]/target/[项目或模块名]/WEB-INF/classes
原项目中java目录下的文件和recource目录下的文件页都被打包到了此类路径下。
如下图: java目录中的文件和recource目录中的文件经过编译后,都会放到WEB-INF/classes目录下:
把test写成private后,外界调用不了这个测试方法。
一个小细节:发现构造方法,不是在getBean()时创建的。
证明如下:
上去就报错了
第一步:引入依赖
- <dependency>
- <groupId>org.apache.logging.log4jgroupId>
- <artifactId>log4j-coreartifactId>
- <version>2.19.0version>
- dependency>
- <dependency>
- <groupId>org.apache.logging.log4jgroupId>
- <artifactId>log4j-slf4j2-implartifactId>
- <version>2.19.0version>
- dependency>
第二步:在类的根路径下提供log4j2.xml配置文件(文件名固定为:log4j2.xml,文件必须放到类根路径下。)
- "1.0" encoding="UTF-8"?>
-
- <configuration>
-
- <loggers>
-
- <root level="DEBUG">
- <appender-ref ref="spring6log"/>
- root>
- loggers>
-
- <appenders>
-
- <console name="spring6log" target="SYSTEM_OUT">
-
- <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss SSS} [%t] %-3level %logger{1024} - %msg%n"/>
- console>
- appenders>
-
- configuration>
第三步:使用日志框架
- Logger logger = LoggerFactory.getLogger(FirstSpringTest.class);
- logger.info("我是一条日志消息");
在配置文件中,已经指定了 debug日志级别, 很有意思。当打印日志时,如果低于这个级别,则不会打印出来。
除此之外,你可以在配置文件中去指定这个级别。
1.set注入。
配合property标签,
注意 name是 本类的set方法的名字:去掉set,首字母小写
ref 是所要引用 的其他类。
总之,通过 本类的set方法对所引用的类进行注入。
‘
2.构造注入
利用, name属性可被 索引,类型推断 替代。
3.内部bean和外部
没有那么复杂,声明在外面的bean叫做 外部bean,有自己的 id 和class .
而内部bean写在
4.给简单类型注入
疑惑:为什么要给它注入。
下面是spring中认为的基本数据类型,不使用ref来引用,而是通过name属性。
但要注意 Date类型的注入,要满足它所要求的格式。
在实际开发中,不会用Date这种注入方式,应该有更好的。
经典:
注入空字符串
注入的值含有 特殊符号
第二种方案,只能用
验证xml文档
根据名字自动装配
根据类型自动装配
根据类型进行自动装配时,在有效的配置文件当中,某种类型的实例只能有一个
当加载时,会默认先加载windows中的环境变量,因此要在properties文件中的key前 加上 jdbc前缀
下面是引入步骤:
我们都知道编写数据源的时候是需要连接数据库的信息的,例如:driver url username password等信息。这些信息可以单独写到一个属性配置文件中吗,这样用户修改起来会更加的方便。当然可以。
创建实现数据源的类
引入配置文件,并可以用${}进行根据key取值
单例和多例(singleton ans protope)
自定义scope, 同一个线程有一个bean
五、GoF之工厂模式
简单工厂模式(静态 工厂方法模式)
简单工厂模式是工厂方法模式的一种特殊实现。
工厂方法模式
解决了 简单工厂模式违背 ocp的原则
抽象工厂模式
在spring配置文件中直接配置类路径,spring会自动调用该类的无参构造方法。
底层还是new() 创建一个对象
3.通过工厂方法模式
4.实现 接口(惊讶)
接口里可以有默认方法。
这是对第3中方式的优化:spring提供了一个抽象工厂的接口,我们只要去实现这个接口,就创建出了具体的工厂 ,我们通过这个具体的工厂来创建bean.
备注:我们这个具体的工厂 也是一个bean,因为它是通过构造方法创建出来的,是一个特殊的bean,可以去创建普通的bean
通过Factory工厂bean 主要是对普通bean进行加工处理
beanFactory和FactoryBean 的区别
注入自定义Date
1.创建bean
2.属性赋值(即注入)
3.初始化bean ,init方法自己写,自己配,方法名随意
第五步,销毁bean,自己写,自己配,方法名随意
在配置文件中指定 初始化方法和销毁方法。
必须手动关闭spring容器,就是ApplicationContext接口 实现类的close()方法,记得强转一下类型(转成实现类的类型),因为接口没有这个方法。调用close()后,会自动调用 bean指定的销毁方法
在bean初始化,加上 之前,之后这两步
bean后处理器 只作用当前作用域的所有bean
bean后处理器也需要在spring配置文件中注册
新加的3个点位,看是否实现其对应的特定接口,在这三个点位都会去检查bean是否实现了某个接口,则spring容器会调用这个接口中的方法
before之前
before之后
销毁bean之前或者说使用bean之后
点位3实现一系列接口,会有相应的方法。
多例 ,spring容器只管理一部分生命周期,当客户端得到bean后,容器就不再管理该bean
1.什么是循环依赖
A对象中有B属性。B对象中有A属性。这就是循环依赖。我依赖你,你也依赖我。
曝光
只有bean的scope为singleton 才会进行曝光 ,
多例 + 多例 +set注入
多例 +单例 + set注入
单例+构造注入
构造注入的时候需要赋值。
spring解决循环依赖的机理(精彩)
往map里放bean对象就是曝光的过程,可以在内存中找到它。
再分析下面的源码:
上图,从三级缓存中拿到工厂创建的bean对象后,会将bean对象放到二级缓存中,然后根据beanName把三级缓存中的bean对象删除。
通过反射可获得某个类的方法,然后调用它,例如可以获得set方法
- package com.itheima.a02reflectdemo1;
-
- import java.lang.reflect.InvocationTargetException;
- import java.lang.reflect.Method;
-
- public class ReflectDemo6 {
- public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
- //1.获取字节码文件对象
- Class clazz = Class.forName("com.itheima.a02reflectdemo1.Student");
-
- //2.获取一个对象
- //需要用这个对象去调用方法
- Student s = new Student();
-
- //3.获取一个指定的方法
- //参数一:方法名
- //参数二:参数列表,如果没有可以不写
- Method eatMethod = clazz.getMethod("eat",String.class);
-
- //运行
- //参数一:表示方法的调用对象
- //参数二:方法在运行时需要的实际参数
- //注意点:如果方法有返回值,那么需要接收invoke的结果
- //如果方法没有返回值,则不需要接收
- String result = (String) eatMethod.invoke(s, "重庆小面");
- System.out.println(result);
-
- }
- }
-
-
-
- public class Student {
- private String name;
- private int age;
- public String gender;
- public String address;
-
-
- public Student() {
-
- }
-
- public Student(String name) {
- this.name = name;
- }
-
- private Student(String name, int age) {
- this.name = name;
- this.age = age;
- }
-
- /**
- * 获取
- * @return name
- */
- public String getName() {
- return name;
- }
-
- /**
- * 设置
- * @param name
- */
- public void setName(String name) {
- this.name = name;
- }
-
- /**
- * 获取
- * @return age
- */
- public int getAge() {
- return age;
- }
-
- /**
- * 设置
- * @param age
- */
- public void setAge(int age) {
- this.age = age;
- }
-
- public String toString() {
- return "Student{name = " + name + ", age = " + age + "}";
- }
-
- private void study(){
- System.out.println("学生在学习");
- }
-
- private void sleep(){
- System.out.println("学生在睡觉");
- }
-
- public String eat(String something){
- System.out.println("学生在吃" + something);
- return "学生已经吃完了,非常happy";
- }
- }
com.grin :代表开发
com.myspringframework :代表框架
为什么要向下转型,因为Element的方法更丰富
如果value不用默认值的话,用户使用注解后必须在里面写 相应的值
value的注意点:
1.将要扫描的包用 逗号 隔开
2.指定父包
- 用逗号隔开
-
- "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">
- <context:component-scan base-package="com.powernode.spring6.bean,com.powernode.spring6.bean2"/>
- beans>
- 指定父包
-
- "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">
- <context:component-scan base-package="com.powernode.spring6"/>
- beans>
1.第一种解决方案
2.第二种解决方案
适用范围是 简单类型
- 回顾一下,spring的简单类型有哪些?
-
- public class BeanUtils{
-
- //.......
-
- /**
- * Check if the given type represents a "simple" property: a simple value
- * type or an array of simple value types.
- *
See {@link #isSimpleValueType(Class)} for the definition of simple
- * value type.
- *
Used to determine properties to check for a "simple" dependency-check.
- * @param type the type to check
- * @return whether the given type represents a "simple" property
- * @see org.springframework.beans.factory.support.RootBeanDefinition#DEPENDENCY_CHECK_SIMPLE
- * @see org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#checkDependencies
- * @see #isSimpleValueType(Class)
- */
- public static boolean isSimpleProperty(Class> type) {
- Assert.notNull(type, "'type' must not be null");
- return isSimpleValueType(type) || (type.isArray() && isSimpleValueType(type.getComponentType()));
- }
-
- /**
- * Check if the given type represents a "simple" value type: a primitive or
- * primitive wrapper, an enum, a String or other CharSequence, a Number, a
- * Date, a Temporal, a URI, a URL, a Locale, or a Class.
- *
{@code Void} and {@code void} are not considered simple value types.
- * @param type the type to check
- * @return whether the given type represents a "simple" value type
- * @see #isSimpleProperty(Class)
- */
- public static boolean isSimpleValueType(Class> type) {
- return (Void.class != type && void.class != type &&
- (ClassUtils.isPrimitiveOrWrapper(type) ||
- Enum.class.isAssignableFrom(type) ||
- CharSequence.class.isAssignableFrom(type) ||
- Number.class.isAssignableFrom(type) ||
- Date.class.isAssignableFrom(type) ||
- Temporal.class.isAssignableFrom(type) ||
- URI.class == type ||
- URL.class == type ||
- Locale.class == type ||
- Class.class == type));
- }
-
- //........
- }
@Autowired 能够标注的位置
@Autowired 根据类型进行装配。
回顾一下原来在进行类型自动装配时,会出现错误的情况。
接口newClazz有两个实现类,分别是Clazz 和Clazz2
类Student中,有类型为newClazz的属性。
当我把Clazz和Clazz2注册一下类型,此时让Student类根据类型装配就会报错,因为此时有多个类型,即有两个实现类,不知道选哪一个。
1.当使用@Component @Controller @Service @Respository 注解 时,还需要配置xml文件来进行包扫描后,会将带有这些注解的类创建对象,放到集合中。
此时仅仅完成了创建对象,并未进行注入
1.作用
①当一个对象需要收到保护时,可以考虑使用代理模式保护对象
②需要进行功能增强时,可以找一个代理进行增强。
③A对象无法与B对象直接进行交互时,也可以使用代理模式来解决。
代理模式有三大对象
第一个角色:目标对象(演员)
第二个角色:代理对象(替身演员)
第三个角色:目标对象和代理对象的公共接口(演员和替身演员需要有相同的动作)
代理模式的实现方式
静态代理
第一种方案:直接在源代码中进行更改。
缺点:违背了OCP原则;代码没有进行复用
第二种方案中 写一个类继承目标对象,在目标对象中重写父类方法。
优点:遵守OCP原则
缺点:继承关系的耦合度太高
第三种方案 让A类作为B类的属性,为了进一步降低耦合,再将A类所实现的接口作为属性的类型。
优点:降低了耦合度
缺点:当有多个目标对象时,需要创建多个代理对象,类会急剧递增。
静态代理总结
动态代理
概述
jdk动态代理
创建目标对象,然后 创建代理对象,然后 调用代理对象的代理方法
-
- public class Client {
- public static void main(String[] args) {
- // 创建目标对象
- OrderService target = new OrderServiceImpl();
- // 创建代理对象
- OrderService orderServiceProxy = (OrderService) Proxy.newProxyInstance(target.getClass().getClassLoader(),
- target.getClass().getInterfaces(),
- new TimerInvocationHandler(target));
- // 调用代理对象的代理方法
- orderServiceProxy.detail();
- orderServiceProxy.modify();
- orderServiceProxy.generate();
- }
- }
-
创建 实现 InvocationHandler接口 的实现类。
原因:因为在 创建代理对象时,需要用到该接口的实现类。
重写的invoke方法 的三个参数
invoke方法执行过程中,使用method可以调用目标对象的目标方法, 在调用目标对象的目标方法前后,可以加上一段代码,用来添加你想要的增强的功能。
注意invoke方法的返回值,如果 代理对象调用方法后需要返回值,那么invoke()方法必须目标对象的目标方法的返回值返回。(也就是invoke方法参数中method执行后的返回值)
-
- public class TimerInvocationHandler implements InvocationHandler {
- // 目标对象
- private Object target;
-
- // 通过构造方法来传目标对象
- public TimerInvocationHandler(Object target) {
- this.target = target;
- }
-
- @Override
- public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
- // 目标执行之前增强。
- long begin = System.currentTimeMillis();
- // 调用目标对象的目标方法
- Object retValue = method.invoke(target, args);
- // 目标执行之后增强。
- long end = System.currentTimeMillis();
- System.out.println("耗时"+(end - begin)+"毫秒");
- // 一定要记得返回哦。
- return retValue;
- }
- }
Spring的AOP使用的动态代理是:JDK动态代理 + CGLIB动态代理技术。Spring在这两种动态代理中灵活切换,如果是代理接口,会默认使用JDK动态代理,如果要代理某个类,这个类没有实现接口,就会切换使用CGLIB。当然,你也可以强制通过一些配置让Spring只使用CGLIB。
切面是在业务流程中,与业务逻辑不挂钩的通用代码。
AOP:将与业务逻辑无关的通用的非业务逻辑代码,能够单独的提取出来,形成一个切面,然后把横向的切面应用到 业务逻辑当中的过程。
专业叫法:交叉业务 :在业务流程中,与业务逻辑不挂钩的通用代码。
连接点,切点,通知,切面, 织入(weaving),代理对象,目标对象
切面:切点+通知 (切点是一个方法)
spring底层用的 jdk自带的动态代理和cglib库的动态代理 来实现 AOP.
jdk自带的只能代理拥有接口的目标对象。
而cglib通过继承,既能代理 没有接口的目标对象,也能代理有 接口的目标对象。
(注意:可以在配置文件里进行修改)
@Aspect这个注解很关键
- "1.0" encoding="UTF-8"?>
- <project xmlns="http://maven.apache.org/POM/4.0.0"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
- <modelVersion>4.0.0modelVersion>
-
- <groupId>com.gringroupId>
- <artifactId>spring-007-aop-aspectartifactId>
- <version>1.0-SNAPSHOTversion>
-
- <properties>
- <maven.compiler.source>17maven.compiler.source>
- <maven.compiler.target>17maven.compiler.target>
- <project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
- properties>
-
- <dependencies>
-
- <dependency>
- <groupId>org.springframeworkgroupId>
- <artifactId>spring-contextartifactId>
- <version>6.0.10version>
- dependency>
-
- <dependency>
- <groupId>junitgroupId>
- <artifactId>junitartifactId>
- <version>4.13.2version>
- <scope>testscope>
- dependency>
-
- <dependency>
- <groupId>org.springframeworkgroupId>
- <artifactId>spring-aopartifactId>
- <version>6.0.10version>
- dependency>
- <dependency>
- <groupId>org.springframeworkgroupId>
- <artifactId>spring-aspectsartifactId>
- <version>6.0.10version>
- dependency>
- dependencies>
-
-
- project>
对spring的动态代理产生了误解。
理由如下:有两个类,logAspect切面类和UserService 类
我在logAspect类上加了@Before(切点表达式)前置通知,但是当我调用logAspect类的增强方法时,目标方法并没有执行。
但其实是我理解错了,应该获取 目标对象: UserService的bean,然后调用目标对象的方法,此时切面类所设置的通知才会生效
我对 环绕通知 的实际使用感兴趣。
原来是通过ProceedingJoint
通知类型:
通过注解Order(数字) 可以解决。
我们知道,业务流程当中不一定只有一个切面,可能有的切面控制事务,有的记录日志,有的进行安全控制,如果多个切面的话,顺序如何控制:可以使用@Order注解来标识切面类,为@Order注解的value指定一个整数型的数字,数字越小,优先级越高
其他类 也可以引用 某个类定义的通用切点
通过@Pointcut注解
通过配置相关注解的类来进行 组件扫描 和 启用aspectj的自动代理机制
测试一下
当有多条DML语句,这时需要开启事务,提交事务 ,或异常时捕捉事务。 可以看出关于事务处理的步骤是很固定的,此时可以利用AOP,复用事务处理的重复代码。
定义的通用切点 在使用时可以用 || 运算符隔开。
忘记:@Resource注解
编程式事务:手动去写。 (麻烦)
声明式事务:①基于注解方式(important) ②基于xml文件配置
1.由于使用的JdbcTemplate 因此为了事务管理,需要引入DataSourceTransactionManager.
配置事物管理器时,其中数据源 是它的属性。
2.如果想用注解,就要在xml文件中 启动 事务注解驱动器。
事务传播行为:
SUPPORT :原先有事务,就以事务的方式执行,没有就以非事务的方式执行
mandatory:adj 强制性
MANDATORY 有就加入,没有就抛异常
REQUEIRES_NEW 不管有没有,开启一个新的
NOT_SUPPORTED:不管有还是没有,无论如何都要开启一个新的,不存在事务的嵌套,原先的事务被挂起
事务隔离级别
基于xml实现的配置可以进行模糊匹配(不熟练)
参考资料: