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("用户购买成功");
}