快速入门Spring

Spring

实现类与类之间的解耦合:便于类与类之间的管理

1.轻量:运行时占用资源少,运行效率高,不依赖其他jar

2.解耦合:spring提供了ioc控制反转 实现了由容器管理对象,对象的依赖关系,原来在程序代码中实现对象的创建,现在由容器完成,实现对象之间的解耦合

3.aop的支持

4.可以集成各种优秀的框架(mybatis)

核心

ioc

​ DI(作为ioc的技术实现):依赖注入 只需要对象的名字,就可以使用对象

​ spring底层使用的反射来创建对象

AOP

如果说 IoC 是 Spring 的核心,那么面向切面编程就是 Spring 最为重要的功能之一了,在数据库事务中切面编程被广泛使用。

AOP 即 Aspect Oriented Program 面向切面编程

首先,在面向切面编程的思想里面,把功能分为核心业务功能,和周边功能。

  • 所谓的核心业务,比如登陆,增加数据,删除数据都叫核心业务
  • 所谓的周边功能,比如性能统计,日志,事务管理等等

周边功能在 Spring 的面向切面编程AOP思想里,即被定义为切面

在面向切面编程AOP的思想里面,核心业务功能和切面功能分别独立进行开发,然后把切面功能和核心业务功能 “编织” 在一起,这就叫AOP

AOP 的目的

AOP能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码降低模块间的耦合度,并有利于未来的可拓展性和可维护性

AOP 当中的概念
  • 切入点(Pointcut)
    在哪些类,哪些方法上切入(where
  • 通知(Advice)
    在方法执行的什么实际(**when:**方法前/方法后/方法前后)做什么(**what:**增强的功能)
  • 切面(Aspect)
    切面 = 切入点 + 通知,通俗点就是:在什么时机,什么地方,做什么增强!
  • 织入(Weaving)
    把切面加入到对象,并创建出代理对象的过程。(由 Spring 来完成)
一个例子

为了更好的说明 AOP 的概念,我们来举一个实际中的例子来说明:

image-20210809102610825

在上面的例子中,包租婆的核心业务就是签合同,收房租,那么这就够了,灰色框起来的部分都是重复且边缘的事,交给中介商就好了,这就是 AOP 的一个思想:让关注点代码与业务代码分离

加载方法

ApplicationContext:表示spring的容器,它是一个接口,有很多实现:
1. ClassPathXmlApplicationContext() -> 类路径扫描xml来创建容器
2. FileSystemXmlApplicationContext() -> 根据文件系统路径来读取xml创建容器
3. AnnotationConfigApplicationContext -> 根据注解配置类来创建容器
4. webXmlApplicationContext() -> web项目

基于配置文件

标准的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="deom" class="xmlSpring.service.impl.DeomImpl"/>
    										<!--原型模式  勤快加载  每调用一次初始化一次-->
    <bean id="deom1" class="xmlSpring.service.impl.DeomImpl" scope="prototype"/>
    <!--会发现使用xml加载外部类相对灵活-->
    <bean id="date" class="java.util.Date"/>
</beans>

基于xml需要一个xml配置文件,里面配置需要托管的bean

//通过类路径加载xml文件
ApplicationContext ac=new ClassPathXmlApplicationContext("beans.xml");
Deom d= (Deom) ac.getBean("deom");
d.printHello();
DI注入
  1. 通过property注入(bean中必须存在set方法 但是可以不存在属性名)

    ApplicationContext ac=new ClassPathXmlApplicationContext("beans.xml");
    Student student = (Student) ac.getBean("student");
    System.out.println(student);
    
    <bean id="student" class="xmlSpring.bean.Student">
        <!--        设置值的方法      -->
        <property name="age" value="12"/>
        <!--        设置属性的方法     -->
        <property name="school" ref="school"/>
        <property name="name" value="马道宇"/>
    </bean>
    
    <bean id="school" class="xmlSpring.bean.School">
        <property name="name" value="湖南工学院"/>
        <property name="age" value="100"/>
    </bean>
    
  2. 通过构造方法注入

    Student student1= (Student) ac.getBean("student1");
    System.out.println(student1);
    
    <bean id="student1" class="xmlSpring.bean.Student">
        <!--可以使用name,index或者不声明  index需要按有参构造的顺序指定    不声明不能调整位置-->
        <constructor-arg name="age" value="1"/>
        <constructor-arg name="name" value="asd"/>
        <constructor-arg name="school" ref="school"/>
    </bean>
    
    <bean id="school" class="xmlSpring.bean.School">
            <property name="name" value="湖南工学院"/>
            <property name="age" value="100"/>
     </bean>
    
  3. 自动注入(只能给引用自动注入,不能给基本字段注入 如:String int)

    1.byName

        <bean id="student2" class="xmlSpring.bean.Student" autowire="byName">
            <!--        设置值的方法      -->
            <property name="age" value="12"/>
            <property name="name" value="马道宇"/>
        </bean>
    
        <bean id="school" class="xmlSpring.bean.School">
            <property name="name" value="湖南工学院"/>
            <property name="age" value="100"/>
        </bean>
    

    ​ student中有一个字段名为school的属性 autowrite通过byname 字段名自动注入配置类中名为school的字段

    1. ByType

      <bean id="student3" class="xmlSpring.bean.Student" autowire="byType">
          <!--        设置值的方法      -->
          <property name="age" value="12"/>
          <!--        设置属性的方法     -->
          <!--        <property name="school" ref="school"/>-->
          <property name="name" value="马道宇"/>
      </bean>
      
      <bean id="school" class="xmlSpring.bean.School">
          <property name="name" value="湖南工学院"/>
          <property name="age" value="100"/>
      </bean>
      

      student中有一个字段为school的属性 autowrite通过byType 查找同类型的school自动注入

多配置文件

项目过大配置文件会很大,所以要分开写

import关键字

<!--    <import resource="classpath:spring/spring-School.xml"/>-->
<!--    <import resource="classpath:spring/spring-Student.xml"/>-->
<!-- 在使用通配符时,注意主配置文件名防止和通配文件冲突,
	配置文件上层一定要用一个文件夹    否则查找不到   
-->
    <import resource="classpath:spring/spring-*.xml"/>

基于注解

声明bean的注解(类上)

value属性:默认为类名首字母小写

@Component 组件,没有明确的角色

@Service 在业务逻辑层使用(service层)

@Repository 在数据访问层使用(dao层)

@Controller 在展现层使用,控制器的声明(C
注入bean的注解

​ ps:给user对象自动注入一个对象
​ @Autowired
​ user u

有两个个实现类时,初始化接口有2种办法
	 * 1.在实现类上加@Primary
	 * 2.在变量初始化时加@Qualifier("想要的实现类")

	 * 3.在变量使用resource(name=要实现的类)

@Autowired:由Spring提供    默认byType

@Inject:由JSR-330提供

@Resource:由JSR-250提供   先使用byName原则   再使用byType

都可以注解在set方法和属性上,推荐注解在属性上(一目了然,少写代码)。
java配置类相关注解

1.纯java

@Configuration 声明当前类为配置类,相当于xml形式的Spring配置(必须有,可以是空类)一般放置实现类,提供main方法使用//把该类单做ioc容器,该过程为依赖注入(di)//@Configuration//扫描包//@ComponentScan(basePackages = {"Bean"})public class App {	public static void main(String[] args) {		// 注解解析器的功能 tomcat loadClass(); class-> 读取		ApplicationContext ctx = new AnnotationConfigApplicationContext(App.class);		System.out.println("容器创建");		Hello h = (Hello) ctx.getBean("h");		h.sayHello();		}	}@Bean 注解在方法上,声明当前方法的返回值为一个bean,替代xml中的方式(方法上)@Configuration 声明当前类为配置类,其中内部组合了@Component注解,表明这个类是一个bean(类上)@ComponentScan 用于对Component进行扫描,相当于xml中的(类上)@WishlyConfiguration 为@Configuration与@ComponentScan的组合注解,可以替代这两个注解

2.配置文件

<!--包扫描器--><context:component-scan base-package="noteSpring;xmlSpring"/>
ApplicationContext ac=new ClassPathXmlApplicationContext("noteSpring/applicationContext.xml");TestNote student = (TestNote) ac.getBean("testNote");

读取配置文件

使用$符

public class TestNote {    @Value("${name}")    private String name;//    @Autowired    private School s;}
<context:property-placeholder location="app.properties" file-encoding="utf8"/>

AOP(aspectj依赖)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aKpOA5s0-1644158766294)(C:\Users\fox11\AppData\Roaming\Typora\typora-user-images\image-20201205110940321.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qr0LJWXi-1644158766306)(C:\Users\fox11\AppData\Roaming\Typora\typora-user-images\image-20201205110903242.png)]

执行顺序

​ 构造函数(初始化容器时)–>
​ @PostConstruct(初始化容器时,构造函数一完成就执行)–>
​ @Around(执行方法和前置增强之前 .proceed()方法之前的代码)–>
​ @Before(前置增强)–>
​ 类的方法–>
​ @AfterThrowing||@AfterReturning(有异常或者没异常)–> 出现异常停止
​ After()–>
​ @Around(.proceed()方法之后的代码,方法中出现异常则不运行)

代码实现
Aspect类

@Aspect(添加到类上面 表示这是一个aspect的类 还要托管到spring 依赖为 aspectj)

@org.aspectj.lang.annotation.Aspect@Componentpublic class Aspect {    /**     * 前置通知注释     * 要求     * 1.公共方法 public     * 2.方法没有返回值     * 3.方法名称自定义     * 属性value 是切入点表达式     * 特点:     * 1.在目标方法之前先执行     * 2.不会改变目标方法的执行结果     * 3.不会影响目标方法的执行     * 参数:     * JoinPoint:业务方法,要加入切面功能的业务方法     * 作用: 可以在通知方法中获取方法执行时的信息,例如方法名,方法实参     * 这个参数是由框架赋予的不行是第一个位置的参数     *     * @param jp     */    @Before("execution(public * AOP..*.add*(..))")    public void before(JoinPoint jp) {        System.out.println("=========前置通知开始============");        for (Object o : jp.getArgs()) {            System.out.println(o);        }        System.out.println("=========前置通知结束============");    }    /****     * 后置通知     * 要求     * 1.公共方法 public     * 2.方法没有返回值     * 3.方法名自定义     * 4.方法有参数的,推荐是Object     * 属性     * 1.value 切入点表达式     * 2.returning 自义定变量,表示目标方法的返回值     *              自义定名称必须和通知方法的形参名一样     * 特点     * 1.在目标方法之后执行     * 2.能获取目标方法的返回值     * 3.可以修改这个返回值     * @param res     */    @AfterReturning(value = "execution(public * AOP..*.add*(..))", returning = "res")    public void AfterReturning(JoinPoint jp, Object res) {        //值传递    所有不改变原参数的值        System.out.println("=========后置通知开始==========");        System.out.println(res);        System.out.println("方法信息" + jp.getSignature());        System.out.println("=========后置通知结束==========");    }    /**     * 环绕通知     * 属性     * value 切入点表达式     * 特点     * 1.他是功能最强的通知     * 2.在目标方法的前和后都能增强功能     * 3.控制目标方法是否被调用     * 4.修改原来的目标方法的执行结果,影响最后的调用结果 ****     * 返回值     * Object     * 它等同于jdk动态代理     * ProceedingJoinPoint 等同于Method     *     * 一般用来做事务处理     *     * @param pjp     */    @Around(value = "execution(public * AOP..*.add*(..))")    public Object Around(ProceedingJoinPoint pjp) throws Throwable {        Object result = null;        System.out.println("=========环绕通知开始==========");        //接口需要继承JoinPoint  获取信息        if (12 == Integer.valueOf(pjp.getArgs()[0].toString())) {            result = pjp.proceed();        }        System.out.println("方法信息" + pjp.getSignature());        System.out.println("=========环绕通知结束==========");        //环绕通知牛逼的地方,可以改变结果        if (result != null) {            result = 55;        }        return result;    }    /**     * 要求     * 1.public     * 2.没有返回值     * 3.方法名自定义     * 4.方法有一个Exception,如果还有就是  jp     * 属性     * 1.value  切入点表达式     * 2.throwing  抛出异常的对象  名字和传入参数名一样     * @param jp     * @param ex     */    @AfterThrowing(value = "execution(public * AOP..*.add*(..))", throwing = "ex")    public void AfterReturning(JoinPoint jp, Exception ex) {        //值传递    所有不改变原参数的值        System.out.println("=========异常通知开始==========");        ex.printStackTrace();        System.out.println("方法信息" + jp.getSignature());        System.out.println("=========异常通知结束==========");    }    /**     * 要求     * 1.public     * 2.没有返回值     * 3.方法名自定义     * 属性     * 1.value     * 特点:     * 总是被执行     * 可以做资源关闭     * @param jp     */    @After(value = "mypt()")    public void After(JoinPoint jp) {        //值传递    所有不改变原参数的值        System.out.println("=========最终通知开始==========");        System.out.println(jp.getSignature());        System.out.println("=========最终通知结束==========");    }    /**     * 定义和管理切入点,如果你的项目中有多个切入点表达式是重复的     * 属性     * value 切点表达式     * 特点     * 在其他通知中可以使用这个方法名   代替切入点表达式     */    @Pointcut(value = "execution(public * AOP..*.add*(..))")    public void mypt() {    }}
注解类的实现类
@Configuration@ComponentScan(basePackages = "AOP")//声明使用了aop@EnableAspectJAutoProxypublic class app {    public static void main(String[] args) {        ApplicationContext ac=new AnnotationConfigApplicationContext(app.class);        System.out.println(ac.getApplicationName());        add add = (AOP.add) ac.getBean(AOP.add.class);        add.add(12);    }}
配置文件的实现方式
    <bean id="add" class="AOP.impl.addImpl"/><!--    声明切面-->    <bean id="as" class="AOP.Aspect"/><!--    声明自动代理-->    <aop:aspectj-autoproxy />

在spring中

如果类是有上级接口的则自动使用jdk代理

com.sun.proxy.$Proxy17

如果没有则自动使用cglib代理

AOP.impl.addImpl$$EnhancerBySpringCGLIB$$4d366cd

强制使用cglib动态代理

注解方式

@EnableAspectJAutoProxy(proxyTargetClass = true)

配置文件方式

<aop:aspectj-autoproxy proxy-target-class="true"/>

动态增强两种实现方式

jdk动态代理和cglib动态代理。两种方法同时存在,各有优劣。

jdk动态代理是由Java内部的反射机制来实现的

cglib动态代理底层则是借助asm来实现的

总的来说,反射机制在生成类的过程中比较高效,而asm在生成类之后的相关执行过程中比较高效(可以通过将asm生成的类进行缓存,这样解决asm生成类过程低效问题)。还有一点必须注意:jdk动态代理的应用前提,必须是目标类基于统一的接口。如果没有上述前提,jdk动态代理不能应用。由此可以看出,jdk动态代理有一定的局限性,cglib这种第三方类库实现的动态代理应用更加广泛,且在效率上更有优势。。

(创建代理对象,调用方法时自动回调接口方法)

jdk
public class App implements InvocationHandler{			private Object a;			public Object createCroxy(Object a) {			this.a=a;			//第一个参数  目标类的类加载器			//第二个参数  目标类接口(模板) 因为proxy要根据这个模板来生成新的代理对象			//第三个参数  表示激活被调用方法,自动回调,InvocationHandler实例中的  invoke			return Proxy.newProxyInstance(a.getClass().getClassLoader(),					a.getClass().getInterfaces(), this);					}				@Override		/**		 * 参数		 * 代理对象   连接点     被调用方法的参数		 */		public Object invoke(Object proxy, Method m, Object[] arg) throws Throwable {			//先调用目标类对象			Object o=m.invoke(a, arg);						if(m.getName().startsWith("add")) { 				//相当于execution(* com.LightseaBlue.Spring..*.add*(..))				System.out.println("");			}			return o;		}			}

test

UserDaoimpl ud=new UserDao();ud.addUser();	App a=new App();UserDaoimpl ud1=(UserDaoimpl) a.createCroxy(ud);ud1.addUser();ud1.sayName();
cglib

(需要引入第三方jar包 cglib-nodep)

	public class cglib implements MethodInterceptor{		private Object o;		public Object createProxy(Object o) {			this.o=o;			Enhancer e=new Enhancer();			//设置父类也就是接口类			e.setSuperclass(o.getClass());			//设置回调			e.setCallback(this);			return e.create();		}	@Override	/**	 * 代理对象   类的方法   方法参数	 */	public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {		Object x=method.invoke(o, args);		System.out.println("后置增强");		return x;	}}

Test

UserDaoimpl ud=new UserDao();cglib c=new cglib();UserDaoimpl ud1=(UserDaoimpl) c.createProxy(ud);ud1.addUser();

Spring事务

1.事务隔离级别:读未提交 读已提交 可重复读 串行化

2.超时时间

3.事务传播行为

required supports required-new(一定是新建自己的事务,如果存在当前事务挂起,先执行自己的的事务)

img

使用

@EnableTransactionManagement 开启事务放在启动类,其实默认开启

例:(一般是放在service层上)

@Override@Transactional(rollbackFor = Exception.class)public boolean upDateUserStu(Integer uId, Integer uStu) throws Exception {    UpdateWrapper<TableUser> tableUserUpdateWrapper = new UpdateWrapper<>();    tableUserUpdateWrapper.lambda().eq(TableUser::getUId, uId).set(TableUser::getUStu, uStu);    boolean update = this.update(tableUserUpdateWrapper);    boolean updateAudioNameByUid = tableAudioNameService.updateAudioNameStuByUid(uId, uStu);    boolean b = update && updateAudioNameByUid;    if(!b){        throw new Exception("回滚。。");    }    return true;}

小知识点

创建时机

​ 单例模式 在容器创建时创建好对象
​ 勤快加载,单例模式

别名机制
1. 在注释中添加value值     ps:server(value="s")2. @Bean	public server zz(){		return new server();	}   适用于无法修改源代码的外部包

有个实现类时,初始化接口有2种办法

  1. 在实现类上加@Primary(设置类优先)
  2. 在初始化时加@Qualifier(“想要的实现类”)
单例或多例 ?

默认单例

什么时候创建的?

勤快加载 -> 懒加载???

创建容器时初始化
默认情况 勤快 设置懒加载在bean上添加@lazy(true)开启 开启后 获取获取对象时初始化

多例模式也是获取对象时初始化 设置@scope()

    1. singleton 单例模式,  全局有且仅有一个实例
  1. prototype 原型模式 每次获取Bean的时候会有一个新的实例
  2. request request 表示该针对每一次HTTP请求都会产生一个新的bean,
  3. session  session 作用域表示该针对每一次HTTP请求都会产生一个新的bean,同时该bean仅在当前HTTP session内有效
  4. global session 作用域类似于标准的HTTP Session作用域,
调用无参构造方法

调用无参构造方法,必须提供无参构造方法

  1. 是否有更多的生命周期回调方案??? init destroy
    @PostConstruct:在构造方法和init方法(如果有的话)之间得到调用,且只会执行一次。
    @PreDestory:注解的方法在destory()方法调用后得到执行。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值