spring介绍

1.解耦

耦合就是指代码之间的依赖性。解耦就是尽可能的降低代码之间的依赖性。
class1 = new Class2(),那么class1就和class2耦合了,如果class2没有的话,那么class1就会出现编译错误(ERROR,而不是异常Exception),所以可以用映射文件的办法,来解耦。
减少耦合性,就是少用new

2.解耦常用的一种方法是:利用映射文件来进行创建新对象。

配置文件中,后面要写全类限定名。而且写的是实现类。
image.png
映射法需要用到配置文件。以后不再有各种各样复杂的配置,而是通过配置文件来进行修改。只使用字符串,而不使用真实的类名。
image.png

3.javaBean和properties对象的创建,尤其是spring Resource,见下图。

4.引入工厂模式,所需要的对象直接从工厂里面拿。这是ioc核心。

ioc全名叫做反转控制,为什么这么叫?因为如果有类使用工厂模式,相当于把他自己创建新对象的权利交给了BeanFactory,那么就称为把权利反转给BeanFactory,由BeanFactory进行控制。
比如后图2.
如图所示,Client把自己new一个对象的权利交给beanFactory。

  1. /**
  2. * 仿照 BeanFactory 创建一个自己的
  3. */
  4. public class MyFactory {
  5. private static Properties prop;
  6. private static Map<String, Object> map;
  7. static {
  8. try {
  9. prop = new Properties();
  10. map = new HashMap<>();
  11. InputStream is = MyFactory.class.getClassLoader().getResourceAsStream("application.properties");
  12. prop.load(is);
  13. Enumeration<Object> keys = prop.keys();
  14. while(keys.hasMoreElements()) {
  15. String key = keys.nextElement().toString();
  16. Object o = Class.forName(prop.getProperty(key)).newInstance();
  17. map.put(key,o);
  18. }
  19. } catch (Exception e) {
  20. e.printStackTrace();
  21. }
  22. }
  23. public Object getBean(String name) {
  24. return map.get(name);
  25. }
  26. }

image.png

5.总结一句话:ioc(控制反转)作用就是为了解耦。

二、spring项目的创建

1.把bean交给spring管理。怎么交?

在resource下创建bean.xml文件。然后引入约束,进行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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
      http://www.springframework.org/schema/beans/spring-beans.xsd">
    <!--第一步配置xml约束和 beans约束-->
    <!--第二步,配置对象映射文件  id和class-->
    <bean id="AccountDao" class="com.itheima.dao.impl.AccountDaoImpl"></bean>
    <bean id="AccountService"class="com.itheima.service.impl.AccountServiceImpl"></bean>
</beans>

image.png

2.第二步:获取容器对象,得到配置文件中的内容。怎么获取?

用ApplicationContext对象。
image.png
ApplicationContext是一个接口,常用的实现类有三种。
1.)AnnontationConfigApplicationContext
2.)FileSystemXmlApplicationContext
3.)ClassPathXmlApplicationContext
image.png

BeanFactory和ApplicationContext:一个延迟加载创建多例对象。另一个立即加载产生单例对象

3.核心容器接口有很多,包括BeanFactory和ApplicationContext,怎么确定选择哪一个呢?
image.png

三、依赖注入:普通无参注入,有参注入(此又分构造注入、传值注入)

1.普通无参构造方法注入

image.png

2.有参方法注入,分两种(构造注入,传值注入)

下图是构造注入

image.png
image.png

还有一种是传值注入
1.被注入的类中要写set方法
2.配置文件中使用property
image.pngimage.png

四、几种表达式
${ } **是jsp中 EL标签的最常用表达
image.png
OGNL标签是直接使用
下图:s:property是struts的标签。
value后面的值是 OGNL表达式。
注意OGNL如果取类怎么取?取类中的静态方法呢?加@
image.png
单例,多例
scope作用范围
init-method
destroy-mothod

复杂类型的注入list,map等
分模块开发

第二天
使用注解方式的步骤
在service中写注解(比如@Component等)—>在配置文件中写context**
1.注解
1)Component,Service,Repository注解:
image.png
image.png
2)Autowired,Qualifier,Resource,Value
image.png
使用set方法是怎样的?(类中写set,配置中写property)
image.pngimage.pngimage.png
Autowired+private行中的
accountDao = Autowired+Qualifier=resource
image.pngimage.png
3)Scope,PreDestory,PostConstruct
image.png

303.ApplicationContext是一个接口,常用的实现类有三种。(了解)
1.)AnnontationConfigApplicationContext
2.)FileSystemXmlApplicationContext
3.)ClassPathXmlApplicationContext

304.根据context中的id获取对象(了解)
// 从容器里取值的时候有两种方法,一种强制转换,一种是在第二个参数位置,显示声明
// Student student1 = context.getBean(“student”,Student.class);
Student student = (Student) context.getBean(“student2”);

305. bean中属性的含义:id,class,name,scope(了解)
id:相当于对象名
class:对象所对应的全限定名
name:相当于别名,而且可以取多个,可以通过 逗号, 分号; 或 空格分开
scope:singleton(单例) prototype(多例)

306. 有参构造方法有三种方式(了解)
第一种:通过属性名



第二种:通过属性类型(不常用)



第三种:通过索引




307.设置别名 (了解)

通过import导入其他的配置文件
指定要扫描的包
bean属性autowire:byName:通过名字去查找,自动注入;byType:通过class去查。

p命名空间和c命名空间(p:property;c:constructor):
<bean id="student2" class="demo.pojo.Student" c:name="李四" 
      c:wife="gg" p:name="张三" p:wife="ss"/>


308.丰富类型赋值(了解)**
空是 数组是 map是
list是 配置是value set是

    <!-- String-->
    <property name="wife">
        <null/>
    </property>
    <!-- 配置-->
    <property name="info">
        <props>
            <prop key="driver">demo.pojo.Student</prop>
        </props>
    </property>
    <!-- 数组注入-->
    <property name="books">
        <array>
            <value>红楼梦</value>
        </array>
    </property>
    <!-- List注入-->
    <property name="bobbys">
        <list>
            <value>打代码</value>
        </list>
    </property>
    <!-- map注入-->
    <property name="card">
        <map>
            <entry key="身份证" value="1222"/>
        </map>
    </property>
    <!-- Set-->
    <property name="games">
        <set>
            <value>LOL</value>
        </set>
    </property>

上面都是一些xml文件的配置
怎么配置bean,如果有很多配置文件怎么导入,set,map,property这些特殊类型怎么赋值,有参构造方法怎么赋值,怎么扫描包,怎么设置别名,怎么取值
但是以后最常用的还是注解方式

309.**以上各种问题怎么用注解解决?(了解)**
@Scope(“singleton”) property对应的是 @Value(“kuangshen”) public String name;
@ComponentScan(basePackages = {“aop.anno”})
@Component @Repository @Service @Controller
@Autowire是通过byType的,如果有很多的话(比如一个接口有多个实现类),还要通过@Qualifier再通过名字找一次
@Resource是先通过byName去找,如果找不到,会自动通过byType再找一次。
@Nullable字段标记了这个注解,说明这个字段可以是null
下面进入AOP的学习

310.静态代理。

类如下:Service接口(add del等方法) ServiceImpl(Service的实现类。真实类)
ServiceProxy类(代理类)实现了Service接口,接受实现类作为参数

public class ServiceProxy implements Service {
    private Service service;
    public void log(String msg) { System.out.println("方法名是:"+msg); }
    public void setService(Service service) { this.service = service; }
    public void del() { log("del");  service.del(); }
    public void add() { log("add");  service.add(); }
}

具体流程:代理类实现了真实类实现的接口。 代理类接受真实类作为参数。
代理类对接口里的方法进行加工(比如运行前打印下日志。或者对参数进行一些变动)
具体看下面(Service是接口。 ServiceImpl是真实类。 ServiceProxy是代理类)

public interface Service {
    void del();
    void add();
    void update();
    void query();
}
public class ServiceImpl implements Service {
    public void del() {
        System.out.println("del执行了。");
    }
    public void add() {
        System.out.println("add执行了。");
    }
    public void update() {
        System.out.println("update执行了。");
    }
    public void query() {
        System.out.println("query执行了。");
    }
}
public class ServiceProxy implements Service {
    private Service service;
    //这里对具体的方法进行日志输出,然后再调用真实方法
    public void log(String msg) {
        System.out.println("方法名是:" + msg);
    }
    //接受真实类作为参数
    public void setService(Service service) {
        this.service = service;
    }
    public void del() {
        log("del");
        service.del();
    }
    public void add() {
        log("add");
        service.add();
    }
    public void update() {
        log("update");
        service.update();
    }
    public void query() {
        log("query");
        service.query();
    }
}
public class Client {
    public static void main(String[] args) {
          //先创建真实角色
        ServiceImpl impl = new ServiceImpl();
          //创建代理角色
        ServiceProxy proxy = new ServiceProxy();
          //把真实角色给代理角色
        proxy.setService(impl);
          //通过代理角色调用方法
        proxy.add();
    }
}

311.动态代理:即代理类不是我们写的,而是动态生成的

动态代理分成两大类:
基于接口—JDK动态代理(创建实现类代理对象,增强类的方法)
基于类:CGLIB(创建子类的代理对象,增强类的方法)

jdk动态代理

首先是一个程序:调用处理程序。 所属:代理类的实例。
作用,接受真实角色(setTarget),创建代理类实例(代理角色)(return new Proxy.new),
处理代理角色接管的真实角色的方法,并且可以加上代理角色自己的方法。

/**
 * 1.ClassLoad为什么用的是这个类的ClassLoad?JDKProxy是什么?
 * 2.上一个用的是MyInvocationHandler的ClassLoad
 * MyInvocationHandler是 调用处理程序 (实现了InvocationHandler接口)
 * 3.invoke方法什么时候被调用的?怎么被调用的?整个动态代理的实现流程是什么?
 * Proxy.newnewProxyInstance(ClassLoad,行为接口的class,调用处理程序) ->
 * 给第3参数传真实角色 —>第3参数调用invoke()实现增强。
 */
public class JDKProxy {
    public static void main(String[] args) {
        Class<UserDAO>[] interfaces = new Class[]{UserDAO.class};
        //        先生成代理类,通过代理类执行方法
        //        三个参数:当前类的ClassLoader; 要被代理的接口(要字节码形式的); 调用处理程序;
        UserDAO userDAO = new UserDAOImpl();
        UserDAO proxy = (UserDAO) Proxy.newProxyInstance(JDKProxy.class.getClassLoader(), interfaces, new MyInvoHanlder(userDAO));
        int add = proxy.add(1, 2);
        proxy.update("d");
        System.out.println(add);
    }
}

class MyInvoHanlder implements InvocationHandler {
    private Object obj;

    //    调用 调用处理程序 的时候必须给定 真实角色(就是接口的实现类)
    public MyInvoHanlder(Object obj) {
        this.obj = obj;
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //方法执行之前:方法的名字是 ‘add’。  给方法传递的参数是:‘[1, 2]’
        System.out.println("方法执行之前:方法的名字是 ‘" + method.getName() + "’。  给方法传递的参数是:‘" + Arrays.toString(args) + "’");
        Object returnValue = method.invoke(obj, args);  //returnValue=null
        System.out.println("方法执行之后。。" + returnValue);
        return returnValue;
    }
}

说明:AOP是属于代理模式的,他们都是面向切面编程。
代理模式是嵌入到每个方法内部的。(比如一个UserService接口里有add,del,update方法,那么这些方法里面都需要添加一个打印语句。就可以通过代理模式实现。代理角色自己有一个log方法,并且给每个真实方法里加上这个log方法。)

基于类:CGLIB

//这里是动态代理机制:即代理类不是我们写的,而是动态生成的
//这是 代理实例的  调用处理程序(是一个程序,属于代理实例)
public class MyInvocationHandler implements InvocationHandler {
    //    被代理的接口
    private Object target;

    public void setRent(Rent rent) {
        this.target = rent;
    }

    //    生成代理类
    public Object getProxy() {
        return Proxy.newProxyInstance(this.getClass().getClassLoader(),
                target.getClass().getInterfaces(), this);
    }

    //    处理代理实例,并返回结果
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//        动态代理的本质,就是使用反射机制实现
        log(method.getName());
        Object result = method.invoke(target, args);
        return result;
    }

    public void log(String msg) {
        System.out.println("方法" + msg + "执行了");
    }
}

312.AOP重点名词辨析

被增强类的所有方法都是连接点,其中真正被增强的方法称为切入点 增强类的增强方法被称为通知。 通知根据时期分为多种:前置通知、后期通知、返回通知、环绕通知、异常通知 切面:是动作,或者说一个过程。没有具体意义 **代理:生成的代理对象

1、连接点(JointPoint) 类里面哪些方法可以被增强,这些方法称为连接点
2、切入点(PointCut) 实际被真正增强的方法,称为切入点
3、通知(Advice,增强方法):
(1)实际增强的逻辑部分称为通知(增强)
(2)通知有多钟类型:前置通知(Before) 后置通知(AfterReturning也叫返回通知)
环绕通知(Around) 异常通知(AfterThrowing) 最终通知
4、切面(Aspect) 是动作,把通知应用到切入点的过程
5、代理(Proxy):目标对象应用通知之后创建的对象

313.AOP应用实例

纯注解版:

使用aop的流程

  1. 创建需要被增强的类(比如User)
  2. 创建增强类(比如UserProxy)
  3. 创建配置文件,开启注解扫描(比如在ProxyConfig上加上@ComponentScan)
  4. 配置注解,确定前置后置,环绕等(在UserProxy类里添加 @Before、@After方法)

增强类和被增强类 都加Component,归spring管理
给增强类增加 Aspect —- 生成代理类(比如给UserProxy加上@Aspect)
开启Aspect自动生成代理对象(比如给ProxyConfig加上@EnableAspectJAutoProxy)

  1. 配置不同的通知类型 后面的值是切入点表达式:表示出切入点的位置(就是对那个类的那个方法进行增强)

    @Component
    public class User {
     public void addUser() {
         System.out.println("add.........");
         // int i = 5/0;
     }
    }
    
    /**
    * around在before之前,在after之后
    *
    * after是防止执行完,return之前执行,afterReturning是执行return之后才调用
    * 所以后者早于前者
    *
    * 如果出现异常,after执行,afterReturning和around后面都不执行
    */
    @Component
    @Aspect
    public class UserProxy {
     //相同切入点抽取
     @Pointcut("execution(* aop2.*.*(..))")
     public void pointDemo() {    }
    
     // execution(权限修饰符、返回类型[可省略]、全类名.方法名(参数列表))
     @Before("execution(* aop2.User.addUser(..))")
     public void before() {
         System.out.println("before.......");
     }
    
     @After("execution(* *.*.*(..))")
     public void after() {
         System.out.println("after........");
     }
    
     @AfterReturning("execution(* aop2.*.*(..))")
     public void afterReturning() {
         System.out.println("afterReturning.....");
     }
    
     @Around("pointDemo()")
     public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
         System.out.println("around之前.....");
         proceedingJoinPoint.proceed();
         System.out.println("around之后.......");
     }
    
     @AfterThrowing("pointDemo()")
     public void afterThrowing() {
         System.out.println("afterThrowing........");
     }
    }
    
    @Component
    @EnableAspectJAutoProxy
    public class AopConfig {
    }
    
    public class TestAop {
     public static void main(String[] args) {
         ApplicationContext context = new AnnotationConfigApplicationContext("aop2");
         User user = context.getBean("user", User.class);
         user.addUser();
     }
    }
    

    没开 5/0的运行结果: after在return之前。 afterReturning在return之后。所以 afterReturning肯定在return之前。around两个之前。 before之前,after之前

    image.png

开始5/0之后:before、after不受影响。 around和afterReturning都不执行

image.png

也可以不用注解实现,用纯xml实现

也分两种办法

增强类实现通知,然后xml只配置切入点和通知的ref
//method:要执行的目标对象的方法   args:参数   target:目标对象
public class BeforeLog implements MethodBeforeAdvice {
    public void before(Method method, Object[] args, Object target) throws Throwable {
        System.out.println(target.getClass().getName() + "的" + method.getName() + "被执行了");
    }
}
public class AfterLog implements AfterReturningAdvice {
    //    returnValue:返回值
    public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
        System.out.println("执行了" + method.getName() + "方法,返回结果为:" + returnValue);
    }
}

xml配置

<bean id="beforeLog" class="aop.model.BeforeLog"/>
<bean id="afterLog" class="aop.model.AfterLog"/>

    <!--配置aop-->
<aop:config>
        <!--切入点:expression:表达式,execution(要执行的位置)-->
    <aop:pointcut id="pointcut" expression="execution(* aop.service.UserServiceImpl.*(..))"/>
    <aop:pointcut id="pointcut1" expression="execution(* aop.service.UserServiceImpl.*(..))"/>
        <!--执行环绕通知-->
    <aop:advisor advice-ref="beforeLog" pointcut-ref="pointcut"/>
    <aop:advisor advice-ref="afterLog" pointcut-ref="pointcut1"/>
</aop:config>


第二种方法
增强类不实现增强方法

<!--配置aop增强-->
<aop:config>
  <!--切入点-->
  <aop:pointcut id="p" expression="execution(* aop.anno.*.*(..))"/>
  <!--配置切面-->
  <aop:aspect ref="recordProxy">
    <!--增强作用在具体的方法上-->
    <aop:after method="after" pointcut-ref="p"/>
  </aop:aspect>
</aop:config>

注意:切面的ref必须是recordProxy(增强类)
method必须是切面的ref联系的类里面的方法。 pointcut-ref连接的是切入点。(切入点表达式说明了切点所在的位置。)

注意:无论是@Aspect还是都是作用在增强类上的。@Before,@Pointcut也都是作用在增强类内部的方法上。