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、官网
2、实现步骤
- 创建Maven工程,打包方式jar
- 导入依赖【spring-context】
- 写自己代码【UserDao】
- spring配置文件
- 测试代码
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 测试效果
测试效果 |
---|
![]() |
四、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、实现步骤
- 自定义一个类,实现FactoryBean接口
- 重写方法,在方法中去指定规则
- 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 实现步骤
- 准备工作
- GoodsService接口和GoodsServiceImpl实现类
- 日志类
- 导入依赖【aspects】
- spring配置文件
- 配置GoodsServiceImpl的Bean 和 日志类的Bean
- 配置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一模一样
- @Repository : 通过用于标记在Dao层实现类上
- @Service : 通过用于标记在Service层实现类上
- @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 ==》构建完成 ==》随工厂关闭销毁